Client-Server Development
Volume Number: 14 (1998)
Issue Number: 11
Column Tag: Viewpoint
Client/Server Development based on the Apple Event Object Model
by Sascha Kratky and Christoph Reichenberger (UNI SOFTWARE PLUS)
Edited by Richard Clark
Using AEOM for the design and implementation of the VOODOO client/server system
The term "Client/Server System" has become one of most used terms in today's computing world. We use client applications practically every day to transfer incoming emails from mail servers, download files from FTP servers, or to browse HTML pages offered on web servers all over the world.
Developers have chosen the client/server architecture as a reasonable means to structure large software systems, in order to make them leaner, more efficient, and easier to maintain. The basis of every client/server system forms the protocol that the client and the server applications are using to communicate with each other. For some applications like file transfer, mail transfer or hypertext transfer, there are well-known standardized protocols (e. g. FTP, SMTP, or HTTP) which remove the burden of having to design a proprietary protocol from scratch. Other areas, such as software version control, lack standardized protocols.
This article deals with the design and implementation of a protocol for a client/server based software version control system. It starts with a discussion of the basic properties of client/server systems. It explains why we chose to design and implement the protocol for the VOODOO client/server system on top of the Apple Event Object Model. A section on the problems that the developer faces when implementing a client/server system on top of the Apple Event Object Model concludes the article.
Client/Server Systems
The term "Client/Server System" refers to a general software architecture which can be applied to a variety of different applications. Most discussions of client/server systems take for granted that we are aware of the advantages that arise from using a client/server-based system. It may be worthwhile to recall the encouragement that originally led to the widespread use of client/server systems. A main motivation for a client/server system is that it provides an elegant solution for applications that need concurrent access of many clients to the same data. This simultaneous access problem is sketched in Figure 1.
Figure 1. In a non client-server system, multiple clients may access the same resource concurrently.
In this scenario clients access a database concurrently on the file system level. The database is usually stored on a file server machine on the network and clients have to mount the database in order to access it. This results in the following shortcomings:
- All clients make concurrent changes to parts of the database. To avoid modifying parts of the database that are currently in use by another client, the clients have to implement a file system-based locking mechanism. Nevertheless, an unexpectedly interupted client process may leave the database in an unstable state. The consequences are higher chances of database corruption and the loss of valuable data.
- Every client application must have full knowledge of the external representation of the database. A change to this representation means that all client applications have to be upgraded as well.
- Propagating changes one client has made to the database to the other clients currently working on the database is difficult and unreliable. Every client would have to constantly poll the files of the database for changes made by other users in the meanwhile.
Interestingly enough, many commercial systems that claim to be truly client-server-based still follow this "file-server" approach and therefore suffer from the previously-mentioned drawbacks.
In a real client/server-system the clients do not access the database directly but get the needed data by sending requests to a server application, which is the only process that directly accesses the database (Figure 2). Instead of mounting the remote database the clients connect to a server process running on a remote machine.
Figure 2. In a client-server system, a server mediates all accesses to the shared resource.
This avoids the above-mentioned shortcomings for the following reasons:
- Only the server application makes changes to the files of the database. The server can schedule the incoming requests by the clients, such that the consistency of the data in the database is always assured. When a client process dies, the server can take care of undoing any unfinished transactions of that client.
- Only the server process has to know the external representation of the database. Clients are unaffected by changes to the representation as long as the communication protocol remains unchanged.
- The server can propagate changes from one client to the other clients, since it can keep track which client has retrieved what data and when this data has become obsolete.
- The server can postpone housekeeping tasks until idle time, i. e. no client has sent a request to the server for some time. In VOODOO Server, the server uses delta storage to store all the versions space-efficiently. The server performs the calculation of these deltas during idle time, such that check-in operations for clients solely consist of transferring the file to the server.
These advantages make the client-server architecture well suited for the implementation of a true multi-user aware version control system where users can simultaneously check-in and check-out files.
Apple Events and the Apple Event Object Model
Apple events were introduced with System 7 as a means for applications to communicate with other applications in a standard way. Applications use Apple events to request information from other applications or to return information as result to these requests. An application can send an Apple event to another application on the same computer, an application on a remote computer, and even to itself.
The Apple Event Object Model (AEOM) enables developers to structure the scripting interface of their applications in an object-oriented way. The AEOM allows both the definitions of Apple events and Apple event Classes. The Apple event Classes are templates for Apple event Objects. Each object encapsulates some data that the user can access via the object's properties. Properties are named attributes of an object. For example, an object of class window would have its name, size, and position as properties.
Apple event objects can be hierarchically structured by making some Apple event objects elements of other objects. For example, an application object can have document objects as its elements. The documents themselves have window objects as elements. The resulting hierarchy of objects is called the containment hierarchy.
Apple events act on these objects just like messages do in objects oriented programs. Related classes and events can be grouped together into suites. The most commonly used objects and events are group together in the Apple event Core Suite. The Apple events defined in the Core Suite include Create Element, Delete, Get Data and Set Data. Just as a user can apply the Cut, Copy, and Paste commands to almost any data on the screen these commands should be implemented in virtually every Apple event object.
Apple events and Apple Event Objects also form the basis for the AppleScript programming language. AppleScript scripts enable the user to write down series of Apple events in a very high-level "natural" language. Users typically write scripts to automate everyday work or to combine the functionality of different applications to achieve a desired result. [Other applications, such as UserLand's Frontier can also generate Apple events to drive your application. - ed RC]
In order to be scriptable an application must contain an Apple Event Terminology Extension resource (a resource of type AETE), which defines the containment hierarchy as well as the mapping of objects and events to the vocabulary. AETE resources allow AppleScript to support multiple "dialects" such as English and Japanese.
The attempt to fully explain the Apple Event Object Model would be beyond the scope of this article. For more information on Apple events, the AEOM and AppleScript see Inside Macintosh: Inter-Application Communications and the AppleScript Language Guide.
Why should you use Apple events and the Apple Event Object Model?
Using Apple events and the Apple Event Object Model as the basis for a protocol has a number of advantages not only for clearness and extensibility reasons but also for your development and testing process:
- Supporting the AEOM and Apple's Core Suite guarantees consistency with the scripting interface of other applications. When moving from one scriptable application to another the user will encounter already many familiar terms. So your server application is not only accessible for your clients but becomes a well-behaved citizen in the world of Open Scripting Architecture-compliant applications such as Frontier and FaceSpan.
- You can test the server application by writing AppleScripts, which send Apple events to the server. Since you can save these scripts you are able to reproduce the same tests over and over again. These test scripts can grow into a handy test suite for your server application.
- You can quickly build a GUI that accesses your server application using RAD tools like FaceSpan. Using FaceSpan you can implement an application completely in AppleScript while the FaceSpan environment offers Apple event classes for most user interface elements like Windows, Button and Lists. This makes it fairly easy to build a complete Mac-like client application with minimal effort on top of your AE-based client/server interface.
- AEOM based protocols are quite flexible when it becomes necessary to extend the protocol. You can add a property to a class or add a completely new class which supports existing events. Rarely is it necessary to define a new Apple event, since many of the existing events can also act on objects of the new class. In contrast, protocols built without the Object Model often contain a myriad of verb-noun hybrid commands such as GetPreferences, SetPreferences, GetFolder, SetFolder, etc. The only way to extend such a protocol is by adding new Apple event definitions.
- The transition from "procedural" Apple events to the AEOM can be compared with the transition from a procedural oriented language like C to an object oriented language like C++. This implies that many object oriented design methods like design patterns (1, 5) can also be applied to the design of your custom Apple Event Object Model making the design more flexible and easier to understand.
Designing your Custom AEOM
The following section guides you through the process of designing a client-server protocol based on the AEOM. We will use the protocol of our VOODOO Server application as an example here. Let us briefly explain VOODOO's functionality and data types in order to help you better understand the following description of the object hierarchy.
The VOODOO Server application is designed as an almost faceless server application, which exposes all its functionality through the Apple Event Object Model. Clients should be able to access all data stored in the server's database by sending requests to the server. Therefore, it was our goal that all elements of a VOODOO database should be modeled by means of Apple Event Objects. A VOODOO database holds collections of projects, parts and users. Users can log on to the server and open projects in the database. A project is further divided into different parts. A part represents a directory structure that is put under version control. Parts can also be shared among different projects. Figure 3 shows a VOODOO database for an example compiler project.
Figure 3. Structure of a VOODOO Server database.
The example database contains two projects: "Compiler" and "Linker". The project "Compiler" consists of the parts "Compiler" that stores all compiler specific files and a part "Toolslib" with common utility files. The part "Toolslib" is shared with the project "Linker", such that all changes made to "Toolslib" from the Compiler project are immediately visible in the project "Linker", too.
As mentioned, a part in the VOODOO database corresponds to a directory tree of the user's hard disk. The user can add files and folders to a part, fetch files from the part for modification and store a new versions to the database later. The VOODOO server takes care of storing different versions of files space efficiently using a combination of delta and compression algorithms. Each time a modification has been made to a part's item the server adds an event to the part's history. The event contains the date of the change, the user, the kind of change (e. g. added, revised, renamed, etc.), the items in the part that were changed and a comment. Besides that, the user can add a bookmark at an arbitrary point in time of the history to label milestones of the project.
It would be beyond the scope of this article to describe the full functionality of the VOODOO server. The server will form the basis for a new generation of version control tools to be released by UNI SOFTWARE PLUS in the near future but is also available for licensing to allow other companies to integrate version control functionality directly into their products. If you are interested in more details or want to download a free demo version, please visit <http://www.unisoft.co.at> or send an email to <voodoo@unisoft.co.at>.
Classes
The elements of a VOODOO database have been modeled as classes in the VOODOO suite. The inheritance hierarchy of these classes is sketched in Figure 4.
Figure 4. Inheritance hierarchy of the classes of the VOODOO AEOM.
All classes inherit from a common base class, which defines properties that are common to all the object classes. The directory tree of a part consists of files and folders. Therefore the base class of both is vItem, which represents a node in this tree. The part history contains both events and bookmarks. The common properties of these two classes have been extracted into a class entry which represents an entry in the part history.
Note that the names vItem, vFile, and vFolder were chosen to avoid name clashes with the terms item, file, folder which are the names of standard types in AppleScript.
The hierarchical relations of the objects can be seen in the containment hierarchy (Figure 5).
An object of class database forms the root of the containment hierarchy. The database contains user and project objects as elements. The recursive structure of directory trees is mirrored by the fact that the vFolder object can contain other vFolder objects.
Note that some object classes appear several times in the containment hierarchy. For example a vItem object contains all event objects that are valid for this item. This is a subset of the set of all the event objects that are valid for the whole part.
Figure 5. Containment hierarchy within the VOODOO AEOM.
Events
Once you have designed the inheritance and containment hierarchy for the classes, Apple event definitions can be added to your suite. Most of the events that you find you have to support are already defined in the Apple event Core Suite. As an example the following script shows how the Create Element event can be used to create files and folders in a part of the VOODOO database:
tell application "VOODOO Server"
tell first database
tell part "Compiler" of project "Compiler"
make new vFolder at the end of it¨
with properties {name:"FrontEnd"}
make new vFile at the end of result¨
with properties {name:"Scanner.c"}
end tell
end tell
end tell
To retrieve data from the database the Get Data and Count Elements Apple events can be used. The following script shows the use of these events.
tell application "VOODOO Server"
tell project "Compiler" of first database
- What parts does the project "Compiler" consist of?
get name of every part
tell part "Compiler"
- How many versions of the file "Scanner.c" have been stored?
count every vVersion of vFile ¨
"Scanner.c" in vFolder "FrontEnd"
end tell
end tell
end tell
When a user wants to store a new version of a file to a part, the data of this file has to be transferred to the server on the remote machine via Apple events. The following Apple events of the VOODOO Suite allow the transfer a particular version of a file to the database:
start store: announce a new store task
start store reference - the vFile to be stored to
[comment string] - comment describing new version
[unlock boolean] - unlock vFile after store is finished (default:false)
Result: reference - the newly created store task
The Apple event start store tells the server that the user wants to store data of a new version to a file. The comment parameter of the Apple event is used to initialize the comment of the event object that will be added to the part's history. The unlock parameter tells the server to unlock the file to be stored to, if the user previously locked it to prevent other users from making changes to it. The Apple event returns a reference to a store task as result, which is used later to store the actual data.
store: store data valid for the specified store task
store reference - the store task the data is valid for
data anything - the data block to be stored
The actual data of the version is transferred to the Server using one or several store Apple events (the data can be transferred in one block or in several chunks).
finish: finish the specified task
finish reference - the task to finish
When all the data has been transferred to the server the client sends a finish Apple event to the store task to signal that the transfer is complete. The following example script illustrates the use of these Apple events:
tell application "VOODOO Server"
tell first database
tell part "Compiler" of project "Compiler"
start store vFile "Scanner.c" in vFolder "FrontEnd"
set theStoreTask to result
store theStoreTask data "/* Scanner.c */"
- store additional data ...
finish theStoreTask
end tell
end tell
end tell
Using these Apple events many users can store data to the server at the same time. The server receives the versions, puts them into an incoming pool and takes care of storing these items space efficiently when there's nothing else to do.
Implementation Issues
Once you have designed your Object Model, you must implement the necessary handlers for resolving the Apple event object references and for processing the Apple events. This section gives some hints for implementing a server application and covers some implementation details that are particular for an AEOM based server application. The latter requires careful attention to identifying and maintaining multiple client/server connections.
First of all we want recommend you use an application framework that provides support for the AEOM in order to reduce your own implementation efforts. Metrowerks' PowerPlant is an example of an application framework that has extensive AEOM support. In PowerPlant, the AEOM can be implemented by adding classes to the framework using LModelObject as a base class and overriding methods to handle specific Apple events. PowerPlant already supports many of the events of the Core Suite. The exact details of implementing an AEOM in PowerPlant have been thoroughly described in (4).
Factor your Application
It is a good idea to factor your server application into domain-specific and communication-specific parts. This enables you to replace the communication layer of the server with a different protocol or to add support for other protocols. Figure 6 shows how this has been done in the VOODOO server application.
Figure 6. Structure of the VOODOO Server application.
The VOODOO Engine, which contains the code to access the data in the VOODOO database, is based on the ANSI C++ Standard Library only. The AEOM handling code is built on top of PowerPlant and calls the VOODOO engine to access the data in the VOODOO database. The very small user interface part of the VOODOO Server application also is based on PowerPlant.
Identifying Different PPC Sessions
An essential task of a server application is to keep track of all the connections of clients in order to be able to perform initializations when a new connection is established and cleanups when a connection is closed. Note that connections are not always closed explicitly by the client. The client process may crash and a server must be able to deal with this situation properly in order to ensure data integrity.
A TCP/IP based server has full control over its connections since it awaits and accepts new connections at a dedicated port. The server can easily keep track of all the connections that have been established by clients. When the client side unexpectedly closes the connection, an error is usually pending on the connection's socket on the server side.
With Apple events the situation is different. The Apple Event Manager does not explicitly inform the server application when a new session with a client is established. There is also no particular notification available when a session to a client application has been closed. Instead, the application receives all Apple events as high level events during normal event processing within the main event loop. The usual way to process an Apple event is to pass it to the Apple Event Manger function AEProcessAppleEvent, which then activates the appropriate Apple event Handler function.
However, the communication with Apple events is based on the PPC Toolbox. By using a combination of Apple Event Manger and PPC Toolbox calls, the developer can gain control over the server's session handling again. The following code examples illustrate how this can be done. The key idea is to wrap all the necessary Toolbox calls in a class PPCSession that represents a client's active connection to the server. For simplicity we use PowerPlant container classes in the following example code.
Class PPCSession
class PPCSession {
public:
PPCSession(const AppleEvent& inAppleEvent);
virtual PPCSessRefNum GetID();
static PPCSession* FindSession(PPCSessRefNum inSessID);
static void VerifySessions();
virtual Boolean IsActive();
virtual ~PPCSession();
protected:
virtual void Closed();
PPCSessRefNum mSessionID;
PPCReadPBRec mPBRec;
static TArray<PPCSession*> sActiveSessions;
};
The class PPCSession can be used as is or as a base class for more elaborate session classes. The VOODOO Server overrides this class to add information for which user has logged on in this session and which projects the user has opened.
PPCSession::PPCSession
PPCSession::PPCSession(const AppleEvent& inAppleEvent)
{
OSErr err;
Size bogusSize;
DescType bogusType;
TargetID addr;
err = ::AEGetAttributePtr(&inAppleEvent,
keyAddressAttr, typeTargetID,
&bogusType, &addr, sizeof(addr), &bogusSize);
ThrowIfOSErr_(err);
mSessionID = addr.sessionID;
mPBRec.ioResult = 0;
sActiveSessions.AddItem(this);
}
The constructor takes an Apple event as its only parameter and examines the address attribute of this Apple event. The address attribute contains the PPC session reference number of the underlying PPC session and the address of the machine on the AppleTalk network from which the Apple event was sent. The session reference number is stored in the instance variable mSessionID. The session object then inserts itself into the list of all active PPCSession objects. The instance variable mPBRec which is used by the IsActive method is initialized, too (see below).
PPCSession::GetID
PPCSessRefNum
PPCSession::GetID()
{
return mSessionID;
}
The method GetID is an accessor function that returns the session reference number of the PPC session.
PPCSession:: FindSession
PPCSession*
PPCSession::FindSession(PPCSessRefNum inSessID)
{
PPCSession* sess;
TArrayIterator<PPCSession*> iterator(sActiveSessions);
while (iterator.Next(sess)) {
if (sess->GetID() == inSessID) {
return sess;
}
}
return nil;
}
The class method FindSession searches the list of all PPCSession objects for an object with the given Session Reference Number. The search is done using a PowerPlant iterator object.
PPCSession::VerifySessions
void
PPCSession::VerifySessions()
{
PPCSession* sess;
TArrayIterator<PPCSession*> iterator(sActiveSessions);
while (iterator.Next(sess)) {
if (!sess->IsActive()) {
sess->Closed();
delete sess;
}
}
}
PPCSession:: Closed
void
PPCSession::Closed()
{
// should be overwritten!
}
VerifySessions is a class method that should be called periodically by the server application to verify that the server's connections are still intact. An ideal location for calling this method would probably be the idle handler of your application. The function iterates through the list of all PPCSession objects and deletes those objects whose underlying PPC session has been closed by the client side. Before the object is deleted, the message Closed is sent to the object. Derived classes of PPCSession can override the Closed method to perform cleanup tasks, when the connection has been unexpectedly closed. The VOODOO Server takes care of aborting any of the user's unfinished storage tasks, closing any of the user's opened projects, and logging off the user.
PPCSession::IsActive
Boolean
PPCSession::IsActive()
{
if (mPBRec.ioResult == 0) {
// test if session is still active
mPBRec.ioCompletion = nil;
mPBRec.sessRefNum = mSessionID;
mPBRec.bufferLength = 0;
mPBRec.bufferPtr = nil;
OSErr err = ::PPCReadAsync(&mPBRec);
return (err == noErr);
}
else {
// assume that session is active
// if asynchronous call has not yet finished
return (mPBRec.ioResult == 1);
}
}
The actual check whether a PPC session is still intact is done inside the method IsActive. This function tries to read a zero length block from the PPC session, so no data is actually fetched, but an error message will be returned from the call when the session is no longer intact. The actual call of PPCRead is done asynchronously. Therefore the call's parameter block can not be declared as a local variable. For simplicity we made mPBRec an instance variable of the class PPCSession. If the asynchronous PPCRead call has not yet finished, it is assumed that the session is still active.
PPCSession::~PPCSession
PPCSession::~PPCSession()
{
sActiveSessions.Remove(this);
if (mPBRec.ioResult == 1) {
// finishing asynchronous calls
PPCEndPBRec pb;
pb.ioCompletion = nil;
pb.sessRefNum = mSessionID;
::PPCEndSync(&pb);
}
}
The classes' destructor removes the PPCSession object from the list of active sessions. If an asynchronous call is still pending for the session, this call is finished using the PPCEnd toolbox call. This is necessary since the parameter block mPBRec is not longer valid after the execution of the object's destructor.
MyAppleEventHandler
pascal OSErr
MyAppleEventHandler(
const AppleEvent* inAppleEvent,
AppleEvent* outAEReply,
SInt32 inRefCon)
{
Size bogusSize;
DescType bogusType;
TargetID addr;
short eventSource;
PPCSession* sess;
err = ::AEGetAttributePtr(&inAppleEvent, keyAddressAttr,
typeTargetID,
&bogusType, &addr, sizeof(addr), &bogusSize);
if (err != noErr) return err;
sess = PPCSession::FindSession(addr.sessionID);
if (sess == nil) {
// a new session has been established
sess = new PPCSession(inAppleEvent);
}
// continue with normal Apple event processing
}
The Apple event handler of your server application is the right place to create the session objects. The function MyAppleEventHandler sketches the necessary operations. The handler first examines the Apple event's address attribute to get the session reference number. If there is no PPCSession object valid for the session reference number, a new PPC session has been established by a client.
User Interaction
A server application usually runs unattended by the user on a remote machine. An Apple event based server application can thus not make use of the user interaction calls of the Apple Event Manager. The only way for the server application to interact with the user is to return error messages. The error message should contain both a unique error number and an error message string. For a further discussion of user interaction in Apple event based server applications see (6).
Concurrency
An Apple event server application can receive events from different client applications at the same time. While the server is busy handling an Apple event from one client, Apple events of other clients are delayed. It is important for the server to be as responsive to clients as possible, otherwise the server application becomes the bottleneck of the client/server system.
With Apple events the server has the possibility to suspend the handling of an Apple event using the AESuspendTheCurrenEvent toolbox call and continue processing other events. Execution of the suspended Apple event can be resumed later using the AESuspendTheCurrentEvent. Each suspended Apple event can then be assigned an own thread of execution. See (2) for details how to thread the execution of Apple events.
Conclusion
The Apple Event Object Model is an ideal choice when designing the protocol for a client-server-based application. This article discussed some issues you should consider when designing a protocol of a client/server system based on the AEOM. It showed the advantages of using Apple events and in particular the Apple Event Object Model. Thanks to the object-oriented nature of the AEOM the data provided by the server can be designed using an object-oriented approach. However, using Apple events as the communication basis for a client/server system also leads to some problems, especially what concerns the accurate control of connections. The paper finally presented a solution to gain the necessary control by using services of the PPC Toolbox.
Bibliography and References
- E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns,. Addison-Wesley, ISBN 0-201-63361-2, 1995
- Grant Neufeld. "Threading Apple Events". MacTech Magazine 12:4 (April 1996), pp. 33-40.
- Christoph Reichenberger. "Keeping Things Straight, Orthogonally" MacTech Magazine 12:6 (June 1996), pp. 61-70.
- Jeremy Roschelle. "Powering Up Apple Events in PowerPlant". MacTech Magazine 11:6 (June 1995), pp. 33-46.
- John Schettino. "The Tao of Design". MacTech Magazine 13:4 (April 1997), pp. 30-39.
- Cal Simone. "According To Script: User Interaction in Apple Event-Driven Applications". Develop, Issue 29 (May 1997), pp. 74-80.
- Cal Simone. "The AppleScript Scorecard Guidelines". MacTech Magazine 14:2 (February 1998), pp. 16-18.
- Cal Simone. "According To Script: An External Editing Apple Event Protocol". Develop in MacTech Magazine 13:5 (April 1997), pp. 30-39.
Sascha Kratky studied computer science at the Johannes Kepler University in Linz, Austria. After graduation he joined UNI SOFTWARE PLUS where he is working on the VOODOO project. You can reach Sascha by sending email to kratky@unisoft.co.at.
Christoph Reichenberger studied computer science at the Johannes Kepler University Linz, Austria. After that he worked there as an assistant professor for seven years and finalized his work with a PhD thesis on software configuration management. Then he joined UNI SOFTWARE PLUS where he is now managing the VOODOO product line which is an offspring of his former research work. He can be reached via email at chrei@unisoft.co.at. The home page of UNI SOFTWARE PLUS can be visited at http://www.unisoft.co.at.