![]() |
Source: Webster's Revised Unabridged Dictionary (1913) [web1913]
|
The Web has evolved from a static hypermedia system with limited expressiveness into a collection of services provided at different levels and is increasingly being perceived as critical to enterprise computing architectures. Development of Collaborataion Environments are amongst the most promising fields of new Web-applications built from these existing services using support infrastructures. First generation Collaborative systems were represented by custom Java based collaboratory server technologies such as NCSA Habanero or JavaSoft JSDA, with the former providing for sharing of state and the latter providing a Shared Framework at the data level. Both these approaches were socket based, thus allowing for realtime communication. However they share the disadvantage of being low-level mechanisms, and requiring the provision of a messaging protocol as well as the message transport. What would really obviate this is a direct object-to-object communication mechanism that would allow objects to call others' methods directly The recent onset of distributed object and/or component technologies opens new and interesting avenues for standards based collaboratory infrastructures.
However, selecting a specific direction in the exploding field of distributed object and component technologies is far from being an easy task, illustrated very well both by the myraid options and the uncertainty of overall direction in the field. The questions the we pose ourseleves is whether the Web could serve as an infrastructure for both developing and implementing business applications in a, possibly globall, distributed and collaborative business environment. In the approach to answer the question, we adopt the integrative methodology i.e. we setup a multiple-standards based framework in which the best assets of various approaches complement each other. Java Distributed Collaborative Environment as the name suggests is a Distributed Collaborative environment, implemented using both CORBA and RMI. In the sections that follow I intend to explore and contrast the rationale driving these two approaches, and the overall design of the system. In the end we take examples, which are technology demonstrations,and see how various collaboration issues are dealt with.
Definition Of Collaboration
``Collaboration is a process to reach goals that cannot be achieved acting singly (or, at a minimum, cannot be reached efficiently). As a process, collaboration is a means to an end, not an end in itself. The desired end is more comprehensive and appropriate services that improve outcomes.''
Simple GuideLines For Collaboration - The first principle:
Identification Of
![]() | Actions that should be exclusively under the preview of the user
![]() Actions that would progate state changes to users working on copies of a paritcular tool in an established collabrative environment.
| |
![]() | Hitherto, design and development of software tools have focussed on single-user control.
![]() Independent vendor solutions to address the Collaboration problem, have resulted in inter-operability problems of the software tool.
| |
![]() | Collaboration API's need to provide a developer with
|
![]() | Habanero from NCSA
![]() JSDT (Java Shared Data Toolkit) from Javasoft
| |
![]() | Properties (public state information readable and/or writable by a program)
![]() Methods (Invocable at run-time by a program)
| ![]() Events (notifications to a program in response to state changes in the object)
Component Framework:
| A component Framework is the design of a set of API's that allow software developers to define a software components that can be dynamically combined together to create an application. A component Framework consists of two major elements :components and containers. Components encapsulate semantically meaningful application functions. Components differ from other types of reusable software modules in that they can be modified at dsign time as binary executables instead of a modification at the source code level. Components can have a visual appearance such as a button and can be non-visual such as a data feed monitoring component. Containers are used to hold an assembly or related components. Containers provide the shared context for components to be arranged and interact with one another. Containers also offer common access to system-level services for a components' embedded components viz. process threads and memory resources. Containers are sometimes reffered to as forms, pages frames or shells, Containers can also be used as components i.e. a container can be used as a component inside another container. Event-based protocols are used to establish relationships between a component and its container. Since it's a "component architecture" for Java, Beans can be used in graphical programming environments, like Borland's JBuilder, or IBM's VisualAge for Java. This means that someone can use a graphical tool to connect a lot of beans together and make an application, without actually writing any Java code -- in fact, without doing any programming at all. Graphical development environments let you configure components by specifying aspects of their visual appearance (like the color or label of a button) in addition to the interactions between components (what happens when you click on a button or select a menu item). One important aspect of Java Beans is that components don't have to be visible. This sounds like a minor distinction, but it's very important: the invisible parts of an application are the parts that do the work. So, for example, in addition to manipulating graphical widgets, like checkboxes and menus, Beans allows you to develop and manipulate components that do database access, perform computations, and so on. You can build entire applications by connecting pre-built components, without writing any code. A "bean" is just a Java class with additional descriptive information. The descriptive information is similar to the concept of an OLE type library, though a bean is usually self-describing. Any Java class with public methods can be considered to be a bean, but a bean typically has properties and events as well as methods. Introspection Because of Java's late binding features, a Java .class file contains the class's symbol information and method signatures, and can be scanned by a development tool to gather information about the bean. This is commonly referred to as "introspection" and is usally done by applying heuristics to the names of public methods in a Java class. For those who are queasy about the idea of enforced naming conventions, JavaBeans provides an alternate approach. Explicit information about a class can be provided using the BeanInfo class. The programmer sets individual properties, events, methods using a Bean Info class and several descriptor class types (viz. Property Descriptor, for specifying properties or the Method Descriptor for specifying methods). To some extent, naming conventions do come into play here as well, as when defing the a BeanInfo class. When an RAD Tool wants to find out about a JavaBean, it asks with the Introspector class by name, and if the matching BeanInfo is found the tool uses the names of the properties, events and methods defined inside that pre-packages class. If not the default is to use the reflection process to investigate what methods exist inside a particular JavaBean class. Design Patterns The Beans specification refers to these heuristics of introspection and reflection as "design patterns".
![]() Java Beans
Property
The property metaphor in Java esentially standardizes what is common practice both in Java and other object-oriented languages.
Properties are set of methods that follow special naming conventions. In the case of read/write properties, the convention is that if the property name is XYZ, then the calss the methods setXYZ and getXYZ respectively. The return type of the getter method must match the single argument to the setter method. Read-only or write-only properties have only one of these methods.
|
![]() | In particular a client can create a thread and have it make a remote operation call, rather than making that remote call directly. In the same framework, a multi-threaded client can receive incoming operation requests (for example, a server call-back) to its objects without having to poll for communication events. ![]() In case of Servers too, remote calls can be made without blocking the server. This can also be done within the code that implements an operation or attribute so that some complex algorithm may be parallelized.
| |
|
![]() |
![]() | ImageFilter.java It maps the filter.idl to the corresponding Java Interface.
![]() ImageFilterHelper.java :: This provides the bind method, which is used by clients to locate objects of the ImageFilter class. This also contains mappings for IDL out parameters. (Java natively supports only in parameters).
| ![]() ImageFilterHolder.java :: Support for out and inout parameter passing modes requires the use of additional "holder" classes. These classes are available for all of the basic IDL datatypes in the org.omg.CORBA package and are generated for all named user defined types except those defined by typedefs.
| ![]() ImageFilterOperations.java :: This has a list of all the operations that the ImageFilter performs.
| ![]() _ImageFilterImplBase.java :: Class which Java implementations extend for actual Object implementations.
| ![]() _sk_ImageFilter.java :: This implements the CORBA Server-side skeleton for
ImageFilter. It unmarshalls the arguments for the ImageFilter Object. In addition this is what is reponsible for the convergence of CORBA and Java Object models.
| ![]() _st_ImageFilter.java :: This implements the client-side stub for the ImageFilter Object. This is the class which provides for marshalling of functions.
| ![]() _tie_ImageFilter.java
| |
![]() | Initializing the ORB
![]() Initialize the BOA
| ![]() Create a new instance of the ObjectImpl
| ![]() Export the newly created object to the ORB
| ![]() Wait for incoming requests
| // CoordinatorServer.java class FilterServer { static public void main(String[] args) { try { // Initialize the ORB. org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(); // Initialize the BOA. org.omg.CORBA.BOA boa = orb.BOA_init(); // Create the Convex Filter object. ConvexFilterImpl _convex = new ConvexFilterImpl("Convex"); // Export the newly created object. boa.obj_is_ready(_convex); System.out.println("Exported both " + _convex); // Ready to service requests. System.out.println("Scheduler waiting for requests"); boa.impl_is_ready(); } catch(org.omg.CORBA.SystemException e) { System.err.println(e); } } } |
|
![]() |
![]() | The remote interface must be public. Otherwise, a client will get an error when attempting to load a remote object that implements the remote interface.
![]() The remote interface extends the interface java.rmi.Remote.
| ![]() Each method must declare java.rmi.RemoteException in its throws clause, in addition to any application-specific exceptions.
| ![]() Any remote object passed as an argument or return value (either directly or embedded within a local object) must be declared as the remote interface, not the implementation class.
| |
![]() | Specify the remote interface(s) being implemented, in this case the RMIFilter Interface.
![]() Define the constructor for the remote object.
| ![]() Provide implementations for the methods that can be invoked remotely. A class can define methods not specified in the remote interface, but those methods can only be invoked within the virtual machine running the service and cannot be invoked remotely.
| ![]() Create and install a security manager.
| ![]() Create one or more instances of a remote object.
| ![]() Register at least one of the remote objects with the RMI remote object registry, for bootstrapping purposes.
| The implementation class specifies the remote interface(s) it is implementing. Optionally, it can indicate the remote server that it is extending, which in this example is java.rmi.server.UnicastRemoteObject. As can be seen there is a call to the super() method. The super method call invokes the no-arg constructor of java.rmi.server.UnicastRemoteObject which "exports" the remote object by listening for incoming calls to the remote object on an anonymous port. The constructor must throw java.rmi.RemoteException, because RMI's attempt to export a remote object during construction might fail if communication resources are not available. For bootstrapping, the RMI system provides a URL-based registry that allows a bind to a URL of the form rmi://host/objectname to the remote object, where objectname is a simple string name. Once the remote object is registered on the server, clients can look up the object by name, obtain a remote object reference, and then remotely invoke methods on the object. The code which implements this operation is Naming.rebind("rmi://tarkovsky.npac.syr.edu:6000/rmiFilter", h);, assuming ofcourse that the rmiregistry is started on port 6000 on tarkovsky.npac.syr.edu. import java.rmi.*; import java.rmi.server.*; import java.net.*; public class RMIFilterImpl extends UnicastRemoteObject implements RMIFilter { public RMIFilterImpl() throws RemoteException { super(); } public int[] filter(int[] npixels, int nimgw, int nimgh) throws RemoteException { System.out.println("Filterng begins on the server-side...."); int pixel[] = new int[nimgw * nimgh]; int wpixel[] = new int[nimgw]; for (int j=0; j < nimgh ; j++) { for (int i=0; i < nimgw ; i++) { int a = 10 - i%10 + i; a += nimgw*j; if (a < nimgw * nimgh) wpixel[i] = npixel[a]; else wpixel[i] = npixel[nimgw*nimgh -1]; } System.arraycopy(wpixel,0, pixel, j*nimgw, nimgw); } return pixel; } public static void main(String args[]) { try { RMIFilterImpl h = new RMIFilterImpl(); Naming.rebind("rmi://tarkovsky.npac.syr.edu:6000/rmiFilter", h); System.out.println("RMIFilter Server ready."); } catch (RemoteException re) { System.out.println("Exception in RMIFilterImpl.main: " + re); } catch (MalformedURLException e) { System.out.println("MalformedURLException in RMIFilterImpl.main: " + e); } } } |
![]() | A distributed-approach towards Collaborative Environments.
![]() Support for IIOP- and RMI- based Transport Protocols.
| ![]() A Distributed Event and Exception Heirarchy.
| ![]() Based on the CORBA Naming Service.
| |
![]() | It starts with the createBahn(String PartyName,
String _applicationServerName) throws jdceBahnException
function which would return a true in the event that a new dataBahn Object has been instantiated or a jdceBahnException is raised to signify the prior existence of the desired Bahn. It is the Scheduler's job to signal the appropriate notification to the Clients and perform appropriate house-keeping to reflect new instances of dataBahn's.
![]() The Client now has the option to decide wether he wishes to join an existing Bahn (party) or initiate the existence of a new one. In the latter case Step[1] is repeated as mentioned earlier. This operation also raises the jdceBahnException to signify the non-existence of the dataBahn. Once the process is over, the Client gets a handle to the dataBahn Object by invoking jdce.byteways.dataBahn getDataBahnHandle(String PartyName, String _applicationServerName) throws jdceBahnException .
| ![]() Once Steps I and II are over and done with, the Client is in a Distributed Collaboration mode, and can invoke operations specified in IDL definitions for the dataBahn. These operations are preceeded by public int register(String _clientName, clientProxy _clientObjRef) throws jdceBahnException, jdceClientException , the registration process is a pre-requisite to switching to the Collaboration mode. Once the registration process is over and done with the DataBahn serves as a conduit for messages and the Client could invoke operations such as public boolean broadcastMessage(jdceMessage _message), public boolean whisperMessage(jdceMessage _message,
String _clientName) throws jdceClientException, jdceBahnException etc.
| |
package jdce.scheduler; import jdce.util.exception.*; public interface RMISessionScheduler extends java.rmi.Remote { String[] listApplications() throws java.rmi.RemoteException, jdceRMIBahnException; String[] listBahns(String _applicationServerName) throws java.rmi.RemoteException, jdceRMIBahnException; boolean createBahn(String PartyName ,String _applicationServerName) throws java.rmi.RemoteException, jdceRMIBahnException; boolean joinBahn(String PartyName ,String _applicationServerName) throws java.rmi.RemoteException, jdceRMIBahnException; jdce.byteways.RMIDataBahn getDataBahnHandle(String PartyName,String _applicationServerName) throws java.rmi.RemoteException, jdceRMIBahnException; boolean destroyBahn( String PartyName ,String _applicationServerName) throws java.rmi.RemoteException, jdceRMIBahnException; } |
module scheduler { interface sessionScheduler { typedef sequence |
It should be noted that the RMIDataBahn and the CORBA-based DataBahn supports the same set of operations. The DataBahn also provides operations for expelling a certain client, listing the number/information of members registered to the DataBahn. The exceptions thrown in both the RMI/CORBA case are similar but for the additional RemoteException thrown in the RMI mode. The RMIDataBahn Interface detialing all these operation are given below, the IDL definition for the DataBahn has been omiited here for the sake of clarity. The complete IDL defintion would nevertheless be available in the appendix.
package jdce.byteways; import jdce.util.message.*; import jdce.util.exception.*; public interface RMIDataBahn extends java.rmi.Remote { String getApplicationServerName() throws java.rmi.RemoteException; int numberOfMembers() throws java.rmi.RemoteException; String[] getClientNames() throws java.rmi.RemoteException; boolean isEmpty() throws java.rmi.RemoteException; int register (String ClientName, jdce.client.RMIClientProxy clientObjRef) throws java.rmi.RemoteException, jdceRMIBahnException, jdceRMIClientException; boolean expelClient(String _sender, String _expelee) throws java.rmi.RemoteException, jdceRMIBahnException, jdceRMIClientException; boolean deregister(String ClientName) throws java.rmi.RemoteException,jdceRMIClientException; boolean broadcast(String Message) throws java.rmi.RemoteException; boolean broadcastMessage(jdce.util.message.jdceMessage Message) throws java.rmi.RemoteException, jdceRMIBahnException; boolean inform(jdce.util.message.jdceMessage Message) throws java.rmi.RemoteException, jdceRMIBahnException; boolean whisper(String Message, String clientName) throws java.rmi.RemoteException, jdceRMIClientException, jdceRMIBahnException; boolean whisperMessage(jdce.util.message.jdceMessage Message, String clientName) throws java.rmi.RemoteException, jdceRMIClientException, jdceRMIBahnException; }
module java { module lang { extensible struct Object; }; }; module jdce { module util { module exception { exception jdceBahnException { long type; }; exception jdceClientException { long type; }; }; }; }; IDL Definition |
|
try { thisClient._chatBahn.register(thisClient.clientName, (jdce.client.clientProxy)control); System.out.println("Registration Succeeded"); } catch (jdceBahnException e) { System.out.println(e.typeToString(e.getType())); System.out.println("Bahn Exception"); } catch (jdceClientException e){ System.out.println(e.typeToString(e.getType())); System.out.println("Client Exception"); }If a Client exception has occured. The following exception types can occur:
![]() | NAME_IN_USE - a client with a similar name exists.
![]() ALREADY_BOUND - The client & the obj Reference have been registered.
| ![]() CLIENT_NOT_REGISTERED - The registration process failed.
| ![]() NOT_BOUND : The client has not been bound to the Session Scheduler, Collaboration mode not yet activated for Client.
| ![]() INVALID_OPERATION: Client not authorized for the particular operation.
| ![]() UNKNOWN : Reason unknow to the JDCE Environment.
| |
![]() | BAHN_CREATION_FAILED : The Bahn was not created successfully.
![]() BAHN_EXISTS : The Bahn exists, i.e. there is Bahn for a given application under the same name.
| ![]() BAHN_JOIN_FAILED : Join to the dataBahn failed.
| ![]() NO_SUCH_BAHN: Reference to a non-existant dataBahn.
| ![]() PERMISSION_DENIED: One of the operations on the dataBahn (expel, invite etc ) not completed due to insufficient permissions for the client which invoked the operation.
| ![]() UNKNOWN : Reason unknow to the JDCE Environment.
| |
module jdce { module util { module event { extensible struct jdceEvent { long type; string clientName; }; }; }; }; IDL Definition |
|
![]() | CLIENT_JOIN - a client has joined the dataBahn.
![]() CLIENT_LEAVE - a client has left the dataBahn.
| ![]() CLIENT_EXPEL - a client has been expelled by the creator of the bahn.
| |
![]() | java.lang.Object
|
module java { module lang { extensible struct Object; }; }; module jdce { module util { module message { extensible struct jdceMessage { string sender; string applicationType; string messageType; string protocolType; ::java::lang::Object content; }; }; }; }; IDL Definition. |
The Message Object Implementation consists of the following methods, which conform to the JavaBeans Design Patterns.
|
jdce.util.message.jdceMessage _mess = new jdce.util.message.jdceMessage(); _mess.setSender(clientName); _mess.setApplicationType("Chat"); _mess.setProtocol("IIOP"); _mess.setBahnName(partyName+"Chat"+"Bahn"); try { for (int i=0; i < 15; i++) { _mess.setContent((Object)typeField.getText() + " Inform" +i); _chatBahn.inform(_mess); } } catch (jdceBahnException e) {}
![]() | The RMIWorker Thread contains references to the corresponding RMIClient.
![]() The CorbaWorker contains references to the CorbaClient.
| |
![]() | class java.lang.Thread (implements java.lang.Runnable)
|
![]() | A CORBA-compliant Java Object Request Broker.
![]() The CORBA Naming service.
| ![]() The JDK 1.1.X Virtual Machine.
| |
![]() | Starting the Active Object Server, the default port it binds to is 14000 (ports less than 1024 require administrative priveleges).
% osagent ![]() Starting the Gatekeeper which acts as a HTTP-tunneler for Callbacks through firewalls, the default port it runs on is 15000
| % gatekeeper ![]() Starting up the RMIregistry on some port, (ports less than 1024 require administrative priveleges) say 7000. The RMI registry is a simple server-side bootstrap name server that allows remote clients to get a reference to a remote object. It is typically
used only to locate the first remote object an application needs to talk to. That object in turn will provide application specific support for finding
other objects.
| % rmiregistry 7000 For security reasons, an application can bind or unbind only in the registry running on the same host. This prevents a client from removing or overwriting any of the entries in a server's remote registry. A lookup, however, can be done from any host. This requires every Client to start-up the rmiregistry on its host machine. In the case that the client and server are on the same machine, the rmiregistry needn't be started for the client. It is necessary to specify the port number only if a server creates a registry on a port other than the default 1099. ![]() Starting the Naming Service, with the nameContext Root as JDCE. To use the Name Service, at least one Naming Factory must be started. The Factory object lives within a server process, and is used to create NamingContext objects. When the default Factory server is started, it creates no NamingContexts. When it is asked to create NamingContexts, all such NamingContext objects created by a given Factory are located within the same process as that Factory.
| %java -DORBservices=CosNaming -DSVCnameroot=JDCE -DJDKrenameBug com.visigenic.vbroker.services.CosNaming.ExtFactory JDCE namingLog The first step to creating a namespace is starting at least one Factory server. Here we start up a single Factory server process. The Factory will be given the logical name "JDCE". In addition, the name of a log file is specified (in this case, the locally created "namingLog" file will be used). This log will contain information allowing the Name Service to be shut down and then reDOWNLOADd upon restart to its state prior to shutdown. Here we have used the idea of connecting Contexts that live within distinct Name Servers, the rootContext in this case is JDCE. Now that a Context has been created, we can "publish" an Object's reference within that Context. Anyone can access this published object if they have both:
![]() Starting the main Object Server, which starts up both the RMI/CORBA sessionScheduler.The ORBservices System Property tells the ORB you wish your program to use the Name Service. The SVCnameroot parameter tells the ORB which root naming context should be returned by resolve_initial_references.. Now that a Context has been created, we can "publish" an Object's reference within that Context. Anyone can access this published object if they have both:
a reference to the NamingContext in which it is published.
the name within that NamingContext with which the Object's reference was originally bound.
| %java -DORBservices=CosNaming -DSVCnameroot=JDCE jdce.impl.corba.sessionServer |
![]() | The HTML file, for the JDCE CORBA-client applet.
When using Communicator, you have two choices on how to program your applets. You may use the ORB embedded in Communicator (which matches VisiBroker for Java 2.5), or you may download the VisiBroker for Java 3.0 ORB on top of the existing version in Communicator.
You should download the VisiBroker for Java 3.0 ORB if you are using features new to VisiBroker 3.0 such as interceptors, event
handlers, or SSL.
![]() The HTML file, for the JDCE RMI-client applet.
| |
![]() | Getting a handle to the session Scheduler
![]() Getting a reference to the DataBahn
| ![]() Publishing the client Reference
| |
![]() | Obtaining the Initial Naming Context: The client obtains a reference to the initial naming context by invoking the org.omg.CORBA.ORB's resolve_initial_references.
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(this, null); org.omg.CORBA.Object nameServiceObj = orb.resolve_initial_references("NameService"); org.omg.CosNaming.NamingContext nameService = org.omg.CosNaming.NamingContextHelper.narrow(nameServiceObj); ![]() Creating the Composite name: Since we are looking for Scheduler, we need to first create the compound name "Collaboration", "Scheduler".
| NameComponent[] collabName = { new NameComponent("Collaboration", "Scheduler")}; ![]() Locate the Object with this Name: Inoke resolve on the NamingContext to obtain the object reference associated with the name created in the earlier step. Since the Name Service returns generic CORBA objects, it is necessary to narrow the generic object to a more derived class.
| jdce.scheduler.sessionScheduler _chatSession= jdce.scheduler.sessionSchedulerHelper.narrow(nameService.resolve(collabName)); |
private void getSessionSchedulerHandle() { try { /* Initialize the ORB.*/ org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init(this, null); /* Get a reference to the Naming Service */ org.omg.CORBA.Object nameServiceObj = orb.resolve_initial_references("NameService"); /* Check to see if the operation involving getting a reference to the NameService was successful */ if (nameServiceObj == null) { System.out.println("Name Service Object = null"); return; } org.omg.CosNaming.NamingContext nameService = org.omg.CosNaming.NamingContextHelper.narrow(nameServiceObj); if (nameService == null) { System.out.println("nameService = null"); return; } NameComponent[] collabName = { new NameComponent("Collaboration", "Scheduler")}; _chatSession= jdce.scheduler.sessionSchedulerHelper.narrow(nameService.resolve(collabName)); } catch(Exception e) { System.out.println("Exception: " + e); } }
![]() | ChatUser.java
![]() ClientControlImpl.java
| RMI-based Collaboration In this case the files involved are ![]() RMIChatUser.java
| ![]() RMIClientControlImpl.java
| |