Programmer’s Manual – Shared Applications

Access Grid Toolkit Documentation

 

Argonne National Lab and University of Chicago
ag-info@mcs.anl.gov

15/01/2004

 

Abstract

This manual provides a guide for developers who are interested in creating shared applications for the Access Grid. It gives an overview of the shared application infrastructure currently available in the toolkit and step-by-step instructions on how to build an application. In addition, this document describes how to package and install an application to make it available in Venue Clients.  The Access Grid Team highly encourages developers to build shared applications and this manual provides necessary information to successfully implement the task.

 

1. Introduction

A shared application is a piece of software that enhances collaboration, where two or more people are allowed to view, modify, and add information simultaneously.  The Access Grid Toolkit supports parallel collaborative work electronically with several shared applications included in the software, for instance, the Shared Browser and the Shared Presentation viewer. But more important, the Access Grid Toolkit can be extended to include shared plug-in applications created by any developer.

The information contained in this manual gives developers guidelines on how to create shared applications for the Access Grid. It outlines the overall system architecture as well as detailed instructions on how to develop, package, and install an application. Furthermore, developers will find the Application Monitor helpful when debugging shared applications. The last section of this manual explains how to use the monitor.

 

2. System Overview

In order to support shared applications, an Application Factory is present in the Venue. When an application session is started, the Application Factory creates an Event Channel used for sending messages between Application Clients. It also starts a Web service for the application object in the Venue, the Application Service, used for client registration and storage of necessary state information. The Event Channel and the Application Service enable the Venue to provide a mechanism for discovery, coherence, and synchronization among application clients. Figure 1 shows an overview of the system.

Figure 1 Shared Application - System Overview

3. Shared Application Client

The Access Grid Toolkit provides developers with a Shared Application Client, implemented in the SharedAppClient class, which should be used as a client-side object when creating shared applications. The class interface includes methods for all necessary client operations and by creating a SharedAppClient instance your application can invoke methods that will access the Event Channel and the Application Service. The architecture is described in Figure 2.

 

Figure 2  Shared Application Client - Architecture

 

 

 

SharedAppClient(name)

Create a shared application client. Use the client as an interface to the application service and event channel provided by the application session.

 

Parameters

Name             The name of the application. Log files will by default be named <appName>.log.

 

 

Join(applicationURL, clientProfile)

Create a connection to the Application Service at a specified URL. The connection gives access to the Event Channel used for data communication among application clients connected to the service.

 

Parameters

applicationURL        Location of application service (https://localhost:8000/100).

clientProfile               Your client profile.

           

Shutdown()

Close all connections to the Application Service and Event Channel.

 

InitLogging(debugFlag = None, logFile = None)

Initialize the logging mechanism.

 

Parameters

debugFlag     If set to 1, messages will be printed to file and command window.

logFile            Name of the log file. Defaults to name specified in constructor.

 

Returns

logFile            Log file to use for debug and error messages.

 

RegisterEventCallback(eventType, callback)

Register callback for event. Several callbacks can be registered for each event.

       

Parameters

eventType     Event to listen for

callback         Method invoked when receiving event of type eventType

       

SendEvent(eventType, data)

Post an event to all applications connected to the application service.

 

Parameters

eventType     Event to send.

data                Information associated with the event of type eventType.

 

SetParticipantStatus(status)

Set status associated with this client.

 

Parameters

status              Status string.

 

SetParticipantProfile(profile)

Set profile associated with this client.

 

Parameters

Profile             Your profile of type ClientProfile.

 

SetData(dataKey, dataValue)

Add data to application service.

       

Note: If data with the same dataKey is already present in the application service, the old dataValue will be overwritten.

 

Parameters

dataKey         Unique id for this data.

dataValue      The actual data.

 

dataValue GetData(dataKey)

Get data from application service.

 

Parameters

dataKey         Unique id of data.

 

[dataKeys] GetDataKeys()

Return a list of keys for data present in application service.

 

Returns

[dataKeys]     List of unique data IDs.

 

publicID GetPublicId()

Return this client’s unique identifier.

 

Returns

publicID         Unique identifier

 

[participantDescription] GetParticipants()

Get participants of this application session.

 

Note: A participant is a client that joined this session with the clientProfile parameter set.

 

Returns

[participantDescription]       List of ParticipantDescriptions.

 

[publicId] GetComponents()

Get components of this application session.

 

Note: Every client that joined this session is considered a component.

 

Returns

[publicId]        List of public IDs.

 

uniqueID GetApplicationID()

Get unique ID of application service as represented in the Venue.

 

Returns

uniqueId         Unique identifier for this service.    

 

url GetVenueURL()

Get URL to Venue where this application session is running.

 

Returns

url        Venue location (for example; https://localhost:8000/Venues/default)

 

{state} GetApplicationState()

Get application state information.

 

Returns

{State}            Dictionary containing application state.

 

Note: Keys for state dictionary are name, description, id, mimeType, uri, and data.

 

 

4. Writing a Shared Application – Shared Browser

 

A shared application typically includes information that represents the state of the application session. The Application Service, created in the Venue when a session starts, should be used for storing the state data. Each client joining the session will then be able to load the data and perform necessary updates. Also, to synchronize participants during the application session, clients should send events to notify any changes they make to the state.

 

To explain how to build a shared application, we use the Shared Browser, currently available in the Access Grid software, as a reference example throughout this section. In the Shared Browser case, the state data is represented by the current URL. If a participant joins the session, the Shared  Browser retrieves the URL from the service and browses to the right Web page. If the participant changes the URL, the Shared Browser sends an event message, containing the new URL, to all participants and also changes the URL stored in the service to the new Web location. When other clients receive the event, they navigate their browser to the same URL and, hence, browse the Web together and maintain a synchronized view of the shared state.

 

The following code includes the main implementation of the Shared Browser.  The application is built using Python with its user interface developed in wxPython. Please note that the user interface class is not included in the example code. Following sections will provide a walk-through of the Shared Browser code below.

 

    from AccessGrid.SharedAppClient import SharedAppClient

    from AccessGrid.Platform.Config import UserConfig

    from AccessGrid.ClientProfile import ClientProfile

 

1.          class SharedBrowser( wxApp ):

2.          """

3.          The SharedBrowser combines a SharedApplication and a WebBrowser

4.          to provide shared web browsing to venue participants.

5.          """

6.          def OnInit(self):

7.              return 1

 

8.          def OnExit(self):

9.              '''

10.          Shut down shared browser.

11.          '''

12.          self.sharedAppClient.Shutdown()

13.          os._exit(1)

 

14.      def __init__( self, appServiceUrl, debugMode = 0, logFile = None):

15.          '''

16.          Creates the shared application client, used for

17.          application service interaction, and opens a web browser

18.          for UI display.

19.          '''

20.          wxApp.__init__(self, False)

       

21.          # Create shared application client.

22.          self.sharedAppClient = SharedAppClient("SharedBrowser")

23.          self.log = self.sharedAppClient.InitLogging(debugMode,logFile)

 

24.          # Get client profile.

25.          try:

26.              userConf = userConfig.instance().GetConfigDir()

27.              cpFile = os.path.join(userConf, "profile")     

28.              clientProfile = ClientProfile(cpFile)                                                                     

29.          except:

30.              log.exception("Failed to load profile.")

31.              clientProfile = None

 

 

32.          # Join the application session.

33.          self.sharedAppClient.Join(appServiceUrl, clientProfile)

 

34.          # Register browse event callback.

35.          self.sharedAppClient.RegisterEventCallback("browse",

36.                                                     self.BrowseCallback)

 

37.          # Create Browser Window

38.          self.frame = wxFrame(None, -1, "Browser")

39.          self.browser = WebBrowser(self.frame, -1, self.log, self.frame)

 

40.          # Add callback for local browsing

41.          self.browser.add_navigation_callback(self.IBrowseCallback)

 

42.          # Browse to the current url, if exists

43.          currentUrl = self.sharedAppClient.GetData("url")

 

44.          if len(currentUrl) > 0:

45.              self.browser.navigate(currentUrl)

46.              self.sharedAppClient.SetParticipantStatus(currentUrl)

 

47.          self.frame.Show(1)

48.          self.SetTopWindow(self.frame)

 

49.      def BrowseCallback(self, event):

50.          '''

51.               Callback invoked when incoming browse events arrive.  Events

52.               can include this component's browse events, so these need to

53.               be filtered out.

54.               '''

 

55.               # Determine if the sender of the event is this component.

56.               (senderId, url) = event.data

 

57.               if senderId == self.sharedAppClient.GetPublicId():

58.                   self.log.debug("Ignoring"+url+"from myself ")

 

59.               else:

60.                   self.log.debug("Browse to "+ url)

61.                   self.browser.navigate(url)

62.                   self.sharedAppClient.SetParticipantStatus(url)

 

63.      def IBrowseCallback(self, data):

64.          '''

65.          Callback invoked when local browse events occur.

66.          '''

 

67.          # Send out the event, including our public ID in the message.

68.          publicId = self.sharedAppClient.GetPublicId()

69.          message = (publicId, data)

70.          self.sharedAppClient.SendEvent("browse", message)

 

71.          # Store the URL in the application service in the venue.

72.          self.sharedAppClient.SetData("url", data)

 

 

4.1. Creating a SharedAppClient Object

When you develop a shared application, you should create a SharedAppClient object to use for communication with the Application Service that is running in the Venue and the Event Channel. The application name, “SharedBrowser” in this example, is the name that identifies your application, and log files will by default be named SharedBrowser.log. To initialize logging, call the InitLogging method that returns a handle to the log file.

 

21.       # Create shared application client

22.       self.sharedAppClient = SharedAppClient("SharedBrowser")

23.       self.log = self.sharedAppClient.InitLogging(debugMode,logFile)

4.2. Joining the Application Service

The next step is to join the application session in the Venue by using the Join method in the SharedAppClient. The appServiceURL parameter should be provided as a command line option to the application and will get loaded automatically when the application is opened in the Venue Client (read section 5 for information about packaging). The ClientProfile argument is used to identify you in the application. For example, your name will be displayed in the Application Monitor once you have joined a session (see Section 8).

 

32.    # Join the application session.

33.    self.sharedAppClient.Join(appServiceUrl, clientProfile)

You have now established a connection to the service and are allowed to use the event channel and access service data.

4.3. Sending Events

Application clients are synchronized by exchanging event messages containing reference data. To send an event message to other application clients, you should use the SendEvent method. 

When a participant of the Shared Browser navigates to a new URL, a tuple is sent as event data (publicId, data) associated with the “browse” event type. The publicId is the unique ID of the sender of this event, and data is the URL you wish to browse to. Of course, when writing an application, you can decide the type of the data to send. It does not have to be a tuple.

 

67.    # Send out the event, including our public ID in the message.

68.    publicId = self.sharedAppClient.GetPublicId()

69.    message = (publicId, data)

70.    self.sharedAppClient.SendEvent("browse", message)

4.4. Receiving Events

In order to receive events, your application should register event callbacks; methods that will be invoked when an event occurs. Callbacks are registered with the RegisterEventCallback method of the SharedAppClient. The method should have one parameter, the event that includes the received message.

The Shared Browser registers only one callback, the BrowseCallback, which is invoked when a “browse” event type is received. The browse event message includes two pieces of data, senderId and url. The senderId is the unique ID of the application client that sent the message, and the url is the address to which we should navigate our browser. 

34.        # Register browse event callback

35.        self.sharedAppClient.RegisterEventCallback("browse",

36.                                                 self.BrowseCallback)

 

 

 

49.     def BrowseCallback(self, event):

50.        '''

51.        Callback invoked when incoming browse events arrive.  Events

52.        can include this component's browse events, so these need to

53.        be filtered out.

54.        '''

 

55.        # Determine if the sender of the event is this client.

56.        (senderId, url) = event.data

       

57.        if senderId == self.sharedAppClient.GetPublicId():

58.            self.log.debug("Ignoring"+url+"from myself ")

 

59.        else:

60.            self.log.debug("Browse to "+ url)

61.            self.browser.navigate(url)

62.            self.sharedAppClient.SetParticipantStatus(url)

 

4.5. Adding Data to Service

In order to synchronize clients, state information should be stored in the Application Service. State values can be set by using the SetData method of the SharedAppClient. Each piece of information is distinguished by the data key, in this example url,” and the actual data is contained in the data value, data. 

 

71.        # Store the URL in the application service in the venue

72.        self.sharedAppClient.SetData("url", data)

4.6. Retrieving Data from Service

Application clients will join a session at different times and have to load current state of the session in order to participate. When your application needs to load current application state, you should use the GetData method of the SharedAppClient. For the Shared Browser, the GetData method with key set to url will return the current url.

 

42.        # Browse to the current url, if exists

43.        currentUrl = self.sharedAppClient.GetData("url")

 

44.        if len(currentUrl) > 0:

45.            self.browser.navigate(currentUrl)

 

4.7. Setting the Client Status and Profile

All clients joining the application session will automatically, when calling the Join method, submit their client profile to the service. The profile is used for monitoring purposes (see Application Monitor in Section 8). Use the SetParticipantProfile method to change your profile.

After joining an application session, a participant will be assigned status “connected.” If you want additional status values to be set, call the SetParticipantStatus method in the SharedAppClient. In order to keep track of which Web page each participant is currently viewing, the Shared Browser uses the URL as the status parameter.

 

66.        self.browser.navigate(url)

62.        self.sharedAppClient.SetParticipantStatus(url)

 

4.8. Disconnecting from Service

It is critical that you exit the application session properly. When closing down your client, call Shutdown to disconnect from the service.

 

8.     def OnExit(self):

9.        '''

10.        Shut down shared browser.

11.        '''

12.        self.sharedAppClient.Shutdown()

13.        os._exit(1)

 

5. Packaging the Application

To install the application and make it available in Venue Clients, you must make sure that your program is packaged in a format recognized by the Access Grid software. The easiest way is to create necessary files and store them in a zip archive, but several different formats are supported (described in Section 6).

5.1 Application Description Files

In addition to program files containing the application code, your package should include a <applicationName>.app file describing the application.

Example - The SharedBrowser.app file

 

[application]

name = Shared Browser

mimetype = application/x-ag-shared-browser

extension = sharedbrowser

files = SharedBrowser.py

 

[commands]

Open = %(python)s SharedBrowser.py  -a %(appUrl)s

 

The Application Section – [application]

 

name

Represents the application in the Venue.  This name will be added to the list of available applications under Venue – Start Application Session in the Venue Client.

mimetype

Type associated with this application, recommended mime type is application/x-ag-<name>.

extension    

File extension associated with the mime type, recommended extension is <name>.

files

Program files containing the application code.

 

The Command Section – [commands]

The commands listed in this section will show up in the pop-up menu that appears when you right click the application session in the Venue Client.  Selecting Open, from the menu option in this example, would execute the command %python SharedBrowser.py -a <appUrl>. The appUrl describes the location of the application service created in the venue and is automatically substituted by the Access Grid Toolkit during runtime.

Note: The Open menu option is always the first item of the pop-up menu, illustrated in Figure 3.  Therefore, make sure to include an Open command in the application description file.  Delete, Open Monitor…, and Properties are also permanent application options; however, no command should be specified for these options in the application description file. Their execution is already specified within the toolkit.

Figure 3 Menu Options for a Shared Application Session

.

6. Installing the Application Using AGPM

Use the Access Grid Package Manager (AGPM) to register the application with your user environment. Once registered, the program will be available in the Venue Client under Venue – Start Application Session. The AGPM offers several ways of locating the application:

            agpm –f   <applicationName>.app

            agpm –d   directory containing the <applicationName>.app

            agpm –z  zip archive containing the <applicationName>.app

            agpm –p  package archive containing the <applicationName>.app

For example, if the SharedBrowser.py and SharedBrowser.app files are saved in a zip archive in /home/<user>/SharedBrowser/SharedBrowser.zip, you can install the application by running

 

$ agpm –z /home/<user>/SharedBrowser/SharedBrowser.zip

 

For more information about different options use

$ agpm –h

 

7. Starting an Application Session in Venue

After installation is completed, your application will be represented in the Venue – Start Application Session menu of the Venue Client. Each participant will be able to select that menu option and start a session of your shared application in the Venue. To join, right click the session, and select Open.

 

8. Application Monitor

The application monitor is a piece of software that shows information about a specific application session. Displayed information includes application name and data as well as participants and their current status. Additionally, the event window prints all changes in application state; see Figure 4. 

 

 

Figure 4 Application Monitor

As a developer, you will find the Application Monitor useful for debugging, as you can view state changes of the application session in the window. To open the monitor, right click the session, and select Open Monitor in the Venue Client; see Figure 5.

 

Figure 5 Open Application Monitor

 

Note: To be displayed properly in the Application Monitor, make sure you join the application session with the client profile parameter set.

 

9. Troubleshooting

 

Problem:

My new shared had only a very few log.info, log.debug calls, yet its log file was filling up fairly quickly to quite a large size. Inspection of the log file showed it was filling up due to lots of messages from the EventClient; that made sense since the shared app had event-related stuff happening every few seconds. How to stop the EventClient logging?

 

Solution (thanks to Christoph Willing):

After initialising the application with app.Initialize(), do something like:

 

loglevels = app.GetLogLevels()

 

then, either

 

loglevels.SetLevel(Log.CRITICAL)

 

or even

 

loglevels.SetLevel(Log.CRITICAL, Log.EventClient)

 

The second version blocks all but critical and above log messages specifically from the EventClient. To allow full logging from the rest of the application, I am using:

 

app.Initialize(name) # name of shared app

loglevels = app.GetLogLevels()

loglevels.SetLevel(Log.NOTSET, name)

loglevels.SetLevel(Log.CRITICAL, Log.EventClient)

 

The levels (Log.CRITICAL etc.) are integers, with a group of preset levels available from the AccessGrid.Log module



Funding for this work has been provided by the National Science Foundation under Grant No. ANI-02-22509, the U.S. Department of Energy under Contract W-31-109-Eng 38, and Microsoft Research.