Callback invocations are one application programming technique which takes advantage of this OrbixWeb characteristic. A callback is an operation invocation made from a server to an object which is implemented in a client. Such invocations allow servers to send information to clients without forcing clients to explicitly request the information.
In this chapter, we will describe a common approach to implementing callbacks in an OrbixWeb application and we will illustrate this description with a small example.
// IDL interface ClientOps { ... }; interface ServerOps { ... };Our model will be based on the fact that the client application will supply an implementation of type
ClientOps
, while the server will implement ServerOps
. It is important to note that clients are not registered in the OrbixWeb Implementation Repository and therefore the server in this example will not be able to bind to the client's implementation object. Instead, our IDL definition supplies an operation which allows the client to explicitly pass an implementation object reference to the server. For example, our IDL might look like this:
// IDL interface ClientOps { void callBackToClient (in String message); }; interface ServerOps { void sendObjRef (in ClientOps objRef); };Of course, this is a rather contrived and simplistic definition; later in this chapter we will describe a more realistic example and describe the factors which must be considered when modifying this definition.
ClientOps
. This can be done using either the TIE or BOAImpl approach, as if the client were an OrbixWeb server. In our example, we will assume that the implementation is called ClientOpsImplementation
. The client
main()
method can be outlined as follows:
// Java // In file Client.java. import IE.Iona.Orbix2._CORBA; import IE.Iona.Orbix2.CORBA.SystemException; public class Client { public static void main(String args[]) { _ClientOpsRef clientImpl; // TIE approach. _ServerOpsRef serverRef; try { // Instantiate implementation and proxy. clientImpl = new _tie_ClientOps (new ClientImplementation ()); ServerRef = ServerOps._bind (); // Send object reference to server. ServerRef.sendObjRef (clientImpl); // Process possible callbacks. _CORBA.Orbix.processEvents (); } catch (SystemException se) { System.out.println( "Unexpected exception:\n" + se.toString()); return; } }The client creates an implementation object of type
ClientOpsImplementation
. It then binds to an object of type ServerOps
in the server. At this point, the client holds an implementation object for type ClientOps
and a proxy for an object of type ServerOps
, as shown in Figure 14.1.
In order to allow the server to invoke operations on the ClientOps
implementation object, the client must pass this object reference to the server. Consequently, the client now calls the operation sendObjRef()
on the ServerOps
proxy object, as shown in Figure 14.2.
Finally, the client must prepare to receive incoming operation invocations from the server. Unlike a server application, the client should not call the method
impl_is_ready()
on the _CORBA.Orbix
object. Instead, the client should use an event processing method which does not implicitly initialise the application server name. The client can safely call either the method processEvents()
or the method processNextEvent()
on the _CORBA.Orbix
object.BOA
(in package IE.Iona.Orbix2.CORBA
) and the _CORBA.Orbix
object in the client must be initialised as type BOA
(as described in Chapter 3, "OrbixWeb Configuration" of the OrbixWeb Reference Guide) if that client is to receive callbacks.
The client call to
processEvents()
will block while waiting for incoming OrbixWeb events. If the server invokes an operation on the ClientOps
object reference forwarded by the client, this invocation will be processed by processEvents()
and routed to the correct method in the client's implementation object.14.1.3 Writing a Server
The server application can be coded as a normal OrbixWeb server. In particular, an implementation class for interface ServerOps
should be defined, one or more implementation objects should be created, and the method impl_is_ready()
should be called on the _CORBA.Orbix
object.sendObjRef()
for interface ServerOps
requires special attention. This method receives an object reference from the client. When this object reference enters the server address space, a proxy for the client's ClientOps
object is created. It is this proxy which the server will use to call back to the client, and the implementation of sendObjRef()
should store the reference to the proxy for later use. ServerOps
might look like this:
// Java // In file ServerOpsImplementation.java // (TIE approach). public class ServerOpsImplementation implements _ServerOps_Operations { // Member variable to store proxy. _ClientOpsRef m_objRef; // Constructor. public ServerOpsImplementation () { clientObjRef = null; } // Operation implementation. public void sendObjRef (_ClientOpsRef objRef) { m_objRef = objRef; } }Subsequent to the creation of the proxy in the server address space, the server may invoke the operation
callBackToClient()
. For example, the server might initiate this call in response to an incoming event or after impl_is_ready()
returns. The method invocation on the ClientOps
proxy is routed to the client implementation object as shown in Figure 14.3.
The transmission of requests from server to client is possible because OrbixWeb maintains an open communications channel between client and server while both processes remain alive. The callback invocation can be sent directly to the client and does not need to be routed through an OrbixWeb daemon. Therefore, the client can process the callback event without being registered in the OrbixWeb Implementation Repository and without being given a server name.
14.2 Avoiding Deadlock in a Callback Model
By default, when an application invokes an IDL operation on an OrbixWeb object, the caller is blocked until the operation has returned. In a system where several applications have the potential to both invoke and implement operations, deadlocks may arise. sendObjRef()
. In this case, the client would be blocked on the call to sendObjRef()
when the server invokes callBackToClient()
. The callBackToClient()
call would block the server until the client reached an event processing call (in this case processEvents()
) and handled the server request. Each application would be blocked, pending the return of the other, as shown in Figure 14.4.
oneway
in the IDL definition; the second is to invoke the operation using the "deferred synchronous" approach supported by the OrbixWeb Dynamic Invocation Interface (DII).An IDL operation may be declared as
oneway
only if it has no return value, out
, or inout
parameters. A oneway operation can only raise an exception if a local error occurs before an invocation is transmitted. Consequently, the delivery semantics for a oneway request are "best-effort" only; that is, a caller can invoke a oneway request and continue processing immediately, but will not be guaranteed that the request will arrive at the server.Deadlock could have been avoided in the model described in section 14.2 by declaring either
sendObjRef()
or callBackToClient()
as a oneway operation, for example:
// IDL interface ClientOps { void callBackToClient (in String message); }; interface ServerOps { oneway void sendObjRef (in ClientOps objRef); };In this case, the client's call to
sendObjRef()
would return immediately, without waiting for the server's implementation method call to return. This would allow the client to enter the OrbixWeb event processing call, processEvents()
. At this point, the callback invocation from the server would be processed and routed to the client's implementation of callBackToClient()
. When that method invocation returns, the server would no longer block and both applications would again wait for incoming events.A similar functionality can be achieved by using the OrbixWeb DII deferred synchronous approach to invoking operations. As described in Chapter 19, "Dynamic Invocation Interface", the DII allows an application to dynamically construct a method invocation at runtime, by creating a
Request
object (type Request
is defined in package IE.Iona.Orbix2.CORBA
). The invocation can then be sent to the target object using one of a set of methods supported by the DII.
Section 19.5 describes how the methods ORB.send_deferred()
, ORB.send_oneway()
, ORB.send_multiple_requests_deferred()
, and ORB.send_multiple_requests_oneway()
can be called on the _CORBA.Orbix
object to invoke an operation without blocking the caller. If any of these methods is used, the caller can continue to process in parallel with the target implementation method. Operation results can be retrieved at a later point in the caller's processing and deadlock can be avoided as if the operation call was a oneway invocation.
14.2.2 Using Multiple Threads of Execution
An OrbixWeb application may create multiple threads of execution. In order to avoid deadlock, it may be useful to create a thread which is dedicated to handling OrbixWeb events. For example, an OrbixWeb application might instantiate an object of the following class:
// Java // In file EventProcessor.java. import IE.Iona.Orbix2._CORBA; import IE.Iona.Orbix2.CORBA.SystemException; public class EventProcessor extends Thread { public void run () { try { _CORBA.Orbix.processEvents (_CORBA.IT_INFINITE_TIMEOUT); } catch (SystemException se) { System.out.println ("Unexpected exception: " + se.toString()); } } }Invoking
run()
on an object of this type will start the execution of a thread which simply processes incoming OrbixWeb events. If another thread in this application becomes blocked while invoking an operation on a remote object, the event processing will continue in parallel. So, in our example, the remote operation may safely call back to the multi-threaded application without causing deadlock.
In the next section we will describe an example application of callbacks which uses this approach to processing OrbixWeb events in callback clients.
webchat
demonstration directory. In this application, users join a chat group by downloading an OrbixWeb callback-enabled client. Using this client, the user can send text messages to a central server. The server then forwards these messages to other clients which have joined the same group.The client provides an interface which allows each user to select a current chat group, to view messages sent to that group and to send messages to other group members. For example, if user "chris" runs the client, this user is added to the group "General" by default and initially the client interface appears as shown in Figure 14.5.
|
The option menu labelled "Groups:" allows the user to select a chat group. The user receives all messages sent to the current group and can only join one group at any given time.
|
The interface includes information about the number of users, the members of each group, the total number of messages sent through the system and the total number of messages sent to each group. A "Message Peek" option also allows each message sent through the system to be viewed. This information is available because all messages are routed through this central server.
14.3.1 The IDL Specification
The IDL specification for this application includes two interface definitions: a CallBack
interface which will be implemented by clients and a Chat
interface which will be implemented by the server. The source code for this IDL is as follows:
// IDL // In file "chat.idl". // Interface definition for callbacks from // server to client. This interface will be // implemented by clients. interface CallBack { // Operation which allows the server to forward // a chat message to a client. oneway void newMessage (in string msg); }; // Interface which allows clients to register // with central server. This interface will be // implemented by the server. interface Chat { // Join a chat group. oneway void registerClient (in CallBack objRef, in string name); // Leave a chat group. oneway void removeClient (in CallBack objRef, in string name); // Send a message to all group members. oneway void sendMessage (in string msg); };Each client will implement a single
CallBack
object. This object will allow the client to receive notification from the server when new messages are sent to the client's current chat group.The server will implement a set of
Chat
objects; one object for each available chat group. A client will invoke the operation registerClient()
on a Chat
object to join the chat group which that object supports. Similarly, a client application will call removeClient()
to leave a chat group. A client which has been registered with a chat group will call the operation sendMessage()
to send a text message to other members of the same group.
CallBackImplementation
implements the IDL interface CallBack
.
ClientGUI
initialises the client application and implements the client main()
method.
EventProcessor
supports the creation of a thread to handle incoming OrbixWeb events, such as callback invocations from the server.
CallBackImplementation
allows a server to forward a chat message to a client. The implementation of operation newMessage()
displays the incoming message in the main text area of the client user interface:
// Java // In file CallBackImplementation.java. package WebChat; ... public class CallBackImplementation extends _boaimpl_CallBack { ClientGUI m_chatDisplay; public CallBackImplementation (ClientGUI chatDisplay) throws SystemException { super (); this.m_chatDisplay = chatDisplay; } public void newMessage (String msg) { // Display new message. m_chatDisplay.m_chatText.appendText (msg + "\n"); } }The
main()
method and the constructor of class ClientGUI
implement the initial flow of control for the client application, and the code for this class can be outlined as follows:
// Java // In file ClientGUI.java. package WebChat; ... public class ClientGUI extends Frame { // OrbixWeb object reference variables. private _ChatRef m_chatRef; private _CallBackRef m_callbackObj; // Variables used for binding to server. private String m_host, m_name; // Event processor thread object. protected EventProcessor m_eventLoop; ... // Constructor. public ClientGUI (String host, String name) { ... // Initialise controls. initDisplay (); m_host = new String (host); m_name = new String (name); // Create OrbixWeb objects. if (!(initOrbixWebObjects ())) { System.exit (1); } // Register with "General" chat group. if (!(registerClient ())) { System.exit (1); } // Run thread to handle callbacks. startEventThread (); show (); } public static void main (String args[]) { String host, name; // Initialise host and name from command line // arguments (not shown). ... // Set the OrbixWeb user name. _CORBA.Orbix.set_principal (name); // Create GUI object. new ClientGUI (host, name); } ... }The static
main()
method begins by retrieving command line arguments and then instantiates an object of type ClientGUI
. The constructor for type ClientGUI
initialises the client interface by calling the method initDisplay()
, details of which will not be described here. It then invokes three methods to initialise the OrbixWeb functionality of the client: these methods are initOrbixWebObjects()
, registerClient()
and startEventThread()
.Method
initOrbixWebObjects()
instantiates a single implementation object of type CallBackImplementation
and binds to a server object which implements IDL interface Chat
:
// Java // In package WebChat, // in class ClientGUI. protected boolean initOrbixWebObjects () { // Create the OrbixWeb callback object. try { m_callbackObj = new CallBackImplementation(this); } catch (SystemException ex) { ... return false; } // Bind to "General" group Chat object. // Use object marker to specify group name. try { m_chatRef = Chat._bind ("General:WebChat", m_host); } catch(SystemException se){ ... return false; } return true; }Method
registerClient()
invokes operation registerClient()
on the server Chat
object, passing the client's CallBackImplementation
object reference as a parameter:
// Java // In package WebChat, // in class ClientGUI. protected boolean registerClient () { // Register the client with // the "General" group server object. try { m_chatRef.registerClient (m_callbackObj, m_name); m_chatRef.sendMessage ("-----> " + m_name + ": has joined group " + m_groupLabel.getText()); } catch (SystemException ex) { ... return false; } return true; }Method
startEventThread()
uses an EventProcessor
object to create a thread in which incoming OrbixWeb events will be processed, including server callback invocations:
// Java // In package WebChat, // in class ClientGUI. protected void startEventThread () { // Enter the OrbixWeb event loop // and wait for callbacks. m_eventLoop = new EventProcessor (); m_eventLoop.start (); }The definition of class
EventProcessor
is exactly as described in section 14.2.2.// Java // In package WebChat, // in class ClientGUI. // "Send" button implementation. public void clickedSendButton () { String buff; // Get message string from display. buff = m_name + ": " + m_sendEdit.getText (); try { // Send message to current chat group. synchronized (m_chatRef) { m_chatRef.sendMessage (buff); } } catch (SystemException se) { ... } ... } // "Clear" button implementation. public void clickedClearButton() { // Clear message text field (not shown). ... } // "Groups:" option menu implementation. public void selectedGroupChoice () { String newGroup = null; // If user has changed group to the // same group, then just return. if (m_groupLabel.getText().equals (m_groupChoice.getSelectedItem ())) return; try { // Remove client from current group. m_chatRef.sendMessage ("-----> " + m_name + ": has left group " + m_groupLabel.getText()); m_chatRef.removeClient (m_callbackObj, m_name); // Get new group name. newGroup = new String (m_groupChoice.getSelectedItem ()); m_groupLabel.setText (newGroup); // Bind to server object for new group. m_chatRef = Chat._bind (newGroup + ":WebChat", m_host); // Register client with new group. m_chatRef.registerClient (m_callbackObj, m_name); m_chatRef.sendMessage ("-----> " + m_name + ": has joined group " + newGroup); } catch(SystemException se) { ... } } // "Quit" button implementation. public void clickedQuitButton() { if (m_chatRef != null) { synchronized (m_chatRef) { try { // Remove client from current group. m_chatRef.sendMessage ("-----> " + m_name + ": has left WebChat"); m_chatRef.removeClient (m_callbackObj, m_name); } catch (SystemException se) { ... } m_chatRef = null; } } // Exit client. this.hide (); this.dispose (); System.exit (1); }
Chat
implementation object for each chat group. Each Chat
implementation object stores a list of CallBack
proxy objects, where each proxy is associated with a single client. In this way, each server object is aware of every client which has joined that object's chat group, and can forward incoming chat messages to those group members.The main functionality of the server is implemented in three Java classes:
ChatImplementation
implements the IDL interface Chat
. Each ChatImplementation
object implements a single chat group and maintains a linked list of clients who have joined that group.
ObjectCacheEntry
implements a single entry for a linked list of client objects. Class ChatImplementation
uses this class to store a list of CallBack
proxy objects.
ServerGUI
initialises the server application and implements the server main()
method.
ChatImplementation
allows a client to register with a server object which implements a chat group. The source code for this class is as follows:
// Java // In file ChatImplementation.java. package WebChat; ... public class ChatImplementation extends _boaimpl_Chat { // First linked list entry. ObjectCacheEntry m_objListHead; // Group name for current object. String m_group; ... ChatImplementation (String marker) throws SystemException { super (marker); // Marker is implemented as group name // in this example. m_group = new String (marker); ... } public void sendMessage (String msg) { ... // Update message count. msgCount++; // Loop through list of registered clients. _CallBackRef objRef = null; ObjectCacheEntry objEntry = m_objListHead; while (objEntry != null) { try { // Get next callback object from list. objRef = CallBack._narrow (objEntry.m_objRef); // Call back to client. objRef.newMessage (msg); } catch (SystemException se) { ... } objEntry = objEntry.m_next; } } public void registerClient (_CallBackRef objRef, String name) { ObjectCacheEntry curEntry; int index = 0; // Add message to server display to indicate // new group member (not shown). ... // Add callback object to list // for future use. if (m_objListHead == null) { m_objListHead = new ObjectCacheEntry (objRef); return; } curEntry = m_objListHead; while (curEntry.m_next != null) curEntry = curEntry.m_next; curEntry.m_next = new ObjectCacheEntry (objRef); curEntry.m_next.m_prev = curEntry; } public void removeClient (_CallBackRef objRef, String name) { ObjectCacheEntry curEntry; _CallBackRef tmpObj; int index; // Update main display (not shown). ... // Remove callback object from list. if (m_objListHead == null) { ... return; } curEntry = m_objListHead; while (curEntry != null) { try { tmpObj = CallBack._narrow (curEntry.m_objRef); if ((tmpObj._object_to_string()).equals (objRef._object_to_string())) { // Update linked list of objects // (not shown). ... break; } } catch (SystemException se) { ... } curEntry = curEntry.m_next; } } }An object of this class maintains an
ObjectCacheEntry
object as a member variable. This variable represents the head of a linked list of CallBack
proxy objects, where each object is associated with a client which has joined the current chat group. The linked list is initially empty.A client joins the
ChatImplementation
object's chat group by calling registerClient()
. The implementation of this operation adds the client's CallBack
object reference to the linked list. A client leaves a chat group by calling removeClient()
, the implementation of which removes the client's CallBack
object reference from the linked list.The operation
sendMessage()
allows a client to send a text message to all clients in the same chat group. The implementation of this operation accepts the message as a string parameter. It then cycles through the linked list of client object references, making a callback operation invocation on each, with the string value as a parameter. In this way, the server object redistributes text messages to all clients in a chat group.The class
ObjectCacheEntry
, is a simple linked list node structure which stores an object reference value. The source code for this is as follows:
// Java // In file ObjectCacheEntry.java. package WebChat; ... public class ObjectCacheEntry { public _ObjectRef m_objRef; public ObjectCacheEntry m_next; // Next. public ObjectCacheEntry m_prev; // Previous. public ObjectCacheEntry (_ObjectRef objRef) { m_objRef = objRef; } }The class
ServerGUI
implements the flow control for the server application. The source code for this class can is outlined below:
// Java // In file ServerGUI.java. package WebChat; ... public class ServerGUI extends Frame { // Group implementation objects. ChatImplementation m_chatGeneral; ChatImplementation m_chatEngineering; ChatImplementation m_chatMarcom; ChatImplementation m_chatSales; ChatImplementation m_chatProf; ChatImplementation m_chatBus; ... public ServerGUI () { ... // Initialise group implementation objects. if (!initGroupObjects ()) { System.exit (1); } // Initialise member variables. initVars (); // Initialise server display. initDisplay (); show (); } public static void main (String args[]) { mainGUI = new ServerGUI (); // Initialise the server and enter // the OrbixWeb event loop. try { _CORBA.Orbix.impl_is_ready ("WebChat", _CORBA.IT_INFINITE_TIMEOUT); } catch (SystemException se){ ... } } ... protected boolean initGroupObjects () { try { // Create one implementation object // per group - object reference marker // determines the group name. m_chatGeneral = new ChatImplementation ("General"); m_chatEngineering = new ChatImplementation ("Engineering"); m_chatMarcom = new ChatImplementation ("Marcom"); m_chatSales = new ChatImplementation ("Sales"); m_chatProf = new ChatImplementation ("Prof Services"); m_chatBus = new ChatImplementation ("BusDev"); } catch (SystemException se) { ... return false; } return true; } ... }The server
main()
method first instantiates an object of type ServerGUI
. The constructor for this object initialises the server display and invokes the method initGroupObjects()
to create a set of ChatImplementation
objects. Each ChatImplementation
object implements a single chat group, where the group name is implemented as the object marker.When the
ServerGUI
object has been created and the server implementation objects are available, the server main()
method invokes impl_is_ready()
on the _CORBA.Orbix
object and awaits incoming requests from clients.