As a final component to the Caffeine tool set, Inprise includes yet another naming service, called the URL-based Naming Service. This service is an alternative to the Directory or Naming Services that allows for the locating of objects in the enterprise using standard URLs. Before we dive into the particulars of the URL-based Naming Service, we need to cover two new CORBA technologies, both of which are utilized by the URL- based Naming Service.
First up is Interoperable Object Reference (IOR), which is a text string that uniquely identifies an object. As you may remember from our coverage of the class
org.omg.CORBA.ORB , there are two operations called string_to_object() , and its converse object_to_string() , that allow for converting back and forth between an object and a string representation of that object. The “stringified” representation of an object used by these two operations is called an IOR and is simply a long stream of letters and numbers that uniquely identifies the target object. Here’s an example:
IOR:000000000000001c49444c3a42616e6b2f4163636f756e744d616e616765723a31 2e300000000001000000000000005800010000000000103139322e3136382e3130312e 31313800044f00000000003800504d43000000000000001c49444c3a42616e6b2f4163
636f756e744d616e616765723a312e30000000000c42616e6b4d616e616657200 The next technology utilized by the URL-based Naming Service is an enhanced Web server called gatekeeper. The gatekeeper application ships with the Visibroker tool set and has the ability to offer both IIOP tunneling and support for the URL-based Naming Service. Use of the gatekeeper to support IIOP tunneling was covered in Chapter 24, “A CORBA Server,” and Chapter 25, “A CORBA Client.” In terms of supporting the URL- based Naming Service, the gatekeeper functions in conjunction with a file containing an object’s IOR to serve that file when requested.
Note Even though the gatekeeper ships with the Inprise ORB, it is not required for use with the URL-based Naming Service. Any Web server that supports the HTTP-PUT protocol can be substituted.
As an example, consider the following scenario:
1. Object X is instantiated.
2. Object X registers itself with the URL-based Naming Service.
3. The URL-based Naming Service determines object X’s IOR using the object_to_string() operation in the ORB class.
4. The URL-based Naming Service writes out object X’s IOR to a file called X.ior.
5. Object Y binds to the URL-based Naming Service and requests the object located in X.ior.
6. The URL-based Naming Service reads in X.ior, uses the data in conjunction with the string_to_object() operation to obtain a reference to object X, and returns the reference to object Y.
To facilitate these steps, the URL-based Naming Service exposes the interfaces and exceptions contained in the URLNaming module. This module, contained in Listing 27.10, provides all the needed operations to successfully register an object and locate an already registered object.
LISTING 27.10 THE URLNaming MODULE
module URLNaming {
exception InvalidURL{};
exception CommFailure{};
exception ReqFailure{};
exception AlreadyExists{};
interface Resolver {
Object locate(in string url_s)
raises (InvalidURL, CommFailure, ReqFailure);
void force_register_url(in string url_s, in Object obj)
raises (InvalidURL, CommFailure, ReqFailure);
void register_url(in string url_s, in Object obj) raises (InvalidURL, CommFailure, ReqFailure, AlreadyExists);
};
};
As you examine the URLNaming module, first focus on the register_url() and force_register_url() operations in the Resolver interface. Both of these operations accept two parameters, a String object and an org.omg.CORBA.Object object, and attempt to write out the object’s IOR to the URL specified by the string object.
The operations differ in function in that register_url() raises an AlreadyExists exception if a file already exists at the URL identified by the string object. If the
force_register_url() operation encounters a file located at the location pointed to by the string object, it is overwritten.
The next operation you want to focus on is locate(). The locate() operation accepts, in the form of a string object, a URL pointing to an IOR file. Finally, the locate() operation turns that URL into an object reference using the
string_to_object() operation in the ORB class and then returns that reference.
To complete our coverage of the URL-based Naming Service, we develop an application
that uses the service to both register and locate objects. In this initial example, we work with a traditional application that uses IDL to define the object interface. As a final example for this chapter, we use both the URL-based Naming Service and the java2iiop application to create an IDL-free application that uses the URL-based Naming Service.
The first component of this application is the IDL definition for the remote object. This definition, contained in Listing 27.11, defines a simple interface that exports a single attribute. In the final example, we’ll incorporate more complex interfaces.
LISTING 27.11 AN OBJECT THAT IMPLEMENTS THE URLNamingInterfaceI INTERFACE WILL BE LOCATED USING THE URL-BASED NAMING SERVICE interface URLNamingInterfaceI {
attribute string sData;
};
Complementing the URLNamingInterfaceI interface is the URLNamingInterface class, which implements the interface (see Listing 27.12).
LISTING 27.12 A REFERENCE IMPLEMENTATION OF THE URLNamingInterfaceI INTERFACE
public class URLNamingInterface extends _URLNamingInterfaceIImplBase {
private String _sData;
public URLNamingInterface(String name) { super(name);
}
public URLNamingInterface() { super();
}
public void sData(String sData) { _sData = sData;
}
public String sData() { return _sData;
} }
Now that we’ve developed an interface that can be served by the URL-based Naming Service, we’re ready to code client and server applications that exploit the functionality of the URL-based Naming Service. Starting with the server, Listing 27.13 contains the code for the URLNamingServer class.
LISTING 27.13 THE URLNamingServer CLASS USES THE URL-BASED NAMING SERVICE TO REGISTER OBJECTS
import org.omg.CORBA.*;
import com.visigenic.vbroker.URLNaming.*;
public class URLNamingServer {
public static ORB _orb = null;
public static BOA _boa = null;
public URLNamingServer() {
// Create a new URLNamingInterfaceI object URLNamingInterfaceI interfaceObject = new URLNamingInterface();
interfaceObject.sData("The URL-based Naming Service rules!");
// register the object with the ORB _boa.obj_is_ready(interfaceObject);
// obtain a reference to the Resolver instance try{ Resolver resolver =
ResolverHelper.narrow(
_orb.resolve_initial_references("URLNamingResolver"));
resolver.force_register_url(
"http://localhost:15000/interfaceObject.ior",
interfaceObject);
}
catch( Exception e ) {}
}
public static void main(String[] args)) { _orb = ORB.init();
_boa = _orb.BOA_init();
URLNamingServer server = new URLNamingServer();
_boa.impl_is_ready();
} }
As you study this example, focus on the code contained in the try-catch block. In the first line, we obtain a reference to the Resolver singleton. We then use the force_register_url() operation to bind an instance of the
URLNamingInterface class to the URL
http://localhost:15000/interfaceObject.ior . This URL points to a Web server (in this case, the gatekeeper) running on the same machine as the server at port 15000. If you decide to run the server and Web server on different machines, or the Web server on an alternate port, the URL will need to be modified. The
force_register_url() operation is used instead of the register_url()
operation simply due to the fact that as you play with the application, you may want to run the server over and over.
Now that the server has been developed, you’ll need a client that obtains a reference to the object registered by the server. The client code contained in Listing 27.14 does this.
LISTING 27.14 THE URLNamingClient CLASS USES THE URL-BASED NAMING SERVICE TO LOCATE OBJECTS
import org.omg.CORBA.*;
import com.visigenic.vbroker.URLNaming.*;
public class URLNamingClient {
public URLNamingClient() throws Exception { // bind to the ORB
ORB orb = ORB.init();
// obtain a reference to the Resolver object Resolver resolver = ResolverHelper.narrow(
orb.resolve_initial_references("URLNamingResolver"));
// ask the Resolver object to locate // the InterfaceObjectI instance
org.omg.CORBA.Object obj = resolver.locate(
"http://localhost:15000/interfaceObject.ior");
// narrow the URLNamingInterfaceI instance URLNamingInterfaceI interfaceObject = URLNamingInterfaceIHelper.narrow(obj);
// print out the object, and its contents
System.out.println("got object: "+interfaceObject);
System.out.println("data is: "+interfaceObject.sData());
}
public static void main(String[] args)) {
try{ URLNamingClient client = new URLNamingClient(); } catch( Exception e ) {}
} }
As you may be able to guess from the discussion of the Resolver interface, the operation that actually locates the object we’re looking for is the locate() operation.
This operation, invoked on a Resolver instance, is passed the URL we used when registering the object in the server code, and it returns a reference in the form of an org.omg.CORBA.Object instance. Once we have the Object object, we use the narrow() operation present in the URLNamingInterfaceIHelper class to narrow the Object object into a URLNamingInterfaceI object.
With a solid understanding of the URL-based Naming Service under your belt, you’re now ready to get the code up and running. First off, you need to compile the IDL and Java source files. Start the ORB by entering
osagent
and start the gatekeeper by entering gatekeeper
Both commands are executed at any command prompt. Now, launch the server by typing java URLNamingServer
and, finally, launch the client by typing
java URLNamingClient
When the client executes, you should see output similar to this:
D:\Development\JAVA\dou\027>java URLNamingClient
got object: [UnboundStubDelegate,ior=struct IOR{string type_id="
IDL:URLNamingInterfaceI:1.0";sequence<TaggedProfile> profiles={
struct TaggedProfile{unsigned long tag=0;sequence<octet>
profile_data
={80 bytes:
(0)(1)(0)(0)(0)(0)(0)(16)[1][9][2][.][1][6][8][.][1][0]
[1][.][1][1][8](0)(4)(171)(0)(0)(0)(0)(0)[0](0)[P][M][C](0)(0)(0)
(1)(0)(0)(0)(28)[I][D][L][:][U][R][L][N][a][m][i][n][g][I][n][t][e][r]
[f][a][c][e][I][:][1][.][0](0)(0)(0)(0)(1)(148)(204)(0)(175)};}};}]
data is: The URL-based Naming Service rules!
PUTTING IT ALL TOGETHER
Well, the past few pages really have crammed in a lot of material. You’ve learned how to develop IDL-free CORBA code, how to develop IDL from Java interfaces, and how to locate objects using the URL-based Naming Service. As this chapter concludes, we develop a distributed address book application that contains no IDL and locates objects using the URL-based Naming Service.
The address book application developed in this chapter centers around a class called AddressBook that contains a collection of entries in the form of Address objects. In addition, a class called AddressBookServer manages, at the server, the lifecycle of an AddressBook object. The client object, called AddressBookApplet , is actually a Java applet that stores its address data at the server; upon login, it uses the URL-based Naming Service to locate the address book and load it. When the user exits the applet, the AddressBook object, along with all its entries, is stored at the server.
Starting with the server, the interface AddressBookServerI , shown in Listing 27.15, defines the functionality needed from the server. In this application, the server is charged with AddressBook lifecycle management due to the fact that if the objects existed at the client, they would disappear when the user quit his browser. Because all objects are created and remain at the server, they will exist as long as the server does. If you chose to do so, you could have the server serialize all objects upon quitting to allow for the reestablishing of the object state when the server is restarted.
LISTING 27.15 THE AddressBookServerI INTERFACE DEFINES OPERATIONS NEEDED TO MANAGE THE LIFECYCLE OF AN AddressBookI OBJECT
interface AddressBookServerI extends org.omg.CORBA.Object { public AddressBookI obtainNewAddressBook();
public void saveAddressBook(String sBookName,
AddressBookI addressBook);
}
An implementation of the AddressBookI interface is provided in Listing 27.16. As you examine the code, pay attention to the saveAddressBook() method, because it interacts with the URL-based Naming Service to register an AddressBookI object.
Once the object is registered with the URL-based Naming Service, a client object can easily obtain a reference to it.
LISTING 27.16 AN IMPLEMENTATION OF THE AddressBookServerI INTERFACE
import org.omg.CORBA.*;
import com.visigenic.vbroker.URLNaming.*;
/**
* Implementation of the AddressBookServerI interface
*/
public class AddressBookServer extends _AddressBookServerIImplBase {
private static ORB _orb = null;
private static BOA _boa = null;
private static Resolver _resolver = null;
public AddressBookServer() { super("AddressBookServer");
} /**
* Invoked when a client object desires a new
* AddressBookI instance.
*/
public AddressBookI obtainNewAddressBook() {
AddressBookI addressBook = new AddressBook(_boa);
_boa.obj_is_ready(addressBook);
return addressBook;
} /**
* Invoked when a client object wants to have an AddressBookI
* instance saved through use of the URL-based Naming Service.
*/
public void saveAddressBook(String sBookName,
AddressBookI addressBook) {
try{ // create the url, change the word "localhost" to the
// target IP-address if
// you are running on a different
// machine from the server or if the web server // is not running on port 15000
StringBuffer sbURL =
new StringBuffer("http://localhost:15000/");
sbURL.append(sBookName);
sbURL.append(".ior");
// force the registration
_resolver.force_register_url(sbURL.toString(),
addressBook);
}
catch( Exception e ) {}
}
public static void main(String[] args)) { _orb = ORB.init();
_boa = _orb.BOA_init();
// obtain a reference to the Resolver instance try{ _resolver = ResolverHelper.narrow(
_orb.resolve_initial_references("URLNamingResolver"));
// create a AddressBookServer instance
AddressBookServer server = new AddressBookServer();
_boa.obj_is_ready(server);
// register the AddressBookServer instance _resolver.force_register_url(
"http://localhost:15000/addressBook.ior", server);
_boa.impl_is_ready();
}
catch( Exception e ) {}
} }
Now that we’ve defined the entities charged with managing the lifecycle of an
AddressBook instance, it’s time to define the AddressBook object itself. To begin, Listing 27.17 contains the code for the AddressBookI interface. The interface defines operations for adding, removing, and obtaining all entries in the book.
LISTING 27.17 THE AddressBookI INTERFACE DEFINES THE OPERATIONS NEEDED TO MANAGE AN ADDRESS BOOK
/**
* Interface defining functionality present in an
* address book
*/
public interface AddressBookI extends org.omg.CORBA.Object { public AddressI[] obtainAddresses(();
public void addAddress(String sFirstName, String sLastName, String sEmailAddress);
public void removeAddress(AddressI address);
}
Moving from the AddressBookI interface to its implementation, Listing 27.18 contains the AddressBook class. Note how the class uses a Vector object to internally store Address objects and only converts it to an array when the entries are asked for.
LISTING 27.18 THE AddressBook CLASS IMPLEMENTS THE AddressBookI INTERFACE
import java.util.*;
import org.omg.CORBA.BOA;
/**
* Basic implementation of the AddressBookI interface
*/
public class AddressBook extends _AddressBookIImplBase { private Vector _addresses;
private BOA _boa;
public AddressBook(BOA boa) { super();
_boa = boa;
_addresses = new Vector();
}
public AddressI[] obtainAddresses(() {
AddressI[] returnValue == new Address[_addresses.size()];
_addresses.copyInto(returnValue);
return returnValue;
}
public void addAddress(String sFirstName, String sLastName,
String sEmailAddress) { AddressI address = new Address(sFirstName, sLastName, sEmailAddress);
_boa.obj_is_ready(address);
_addresses.addElement(address);
}
public void removeAddress(AddressI address) { _addresses.removeElement(address);
} }
The final two pieces of the server code are the AddressI interface (shown in Listing 27.19) and its implementation (shown in Listing 27.20). These entities are both rather simple; they define access to first and last names, along with email addresses.
LISTING 27.19 THE AddressI INTERFACE DEFINES THE OPERATIONS NEEDED TO MANAGE A UNIQUE ADDRESS
/**
* Interface defining basic Address data
*/
public interface AddressI extends org.omg.CORBA.Object { public String getFirstName();
public String getLastName();
public String getEmailAddress();
public void setFirstName(String sFirstName);
public void setLastName(String sLastName);
public void setEmailAddress(String sEmailAddress);
}
LISTING 27.20 IMPLEMENTATION OF THE AddressI INTERFACE
/**
* Basic implementation of the AddressI interface
*/
public final class Address extends _AddressIImplBase { private String _sFirstName = "";
private String _sLastName = "";
private String _sEmailAddress = "";
public Address(String sFirstName, String sLastName,
String sEmailAddress) { _sFirstName = sFirstName;
_sLastName = sLastName;
_sEmailAddress = sEmailAddress;
}
public Address() { }
public String getFirstName() { return _sFirstName;
}
public String getLastName() { return _sLastName;
}
public String getEmailAddress() { return _sEmailAddress;
}
public void setFirstName(String sFirstName) { _sFirstName = sFirstName;
}
public void setLastName(String sLastName) { _sLastName = sLastName;
}
public void setEmailAddress(String sEmailAddress) { _sEmailAddress = sEmailAddress;
} }
With the server fully coded, we can now begin work on the client. As was stated before, the client in this environment is written as a Java applet. The applet consists of the AddressBookApplet class, contained in Listing 27.21, and two additional classes that we’ll cover momentarily.
The AddressBookApplet class functions by first asking a user to log into the system using a username. The class then attempts to bind to an AddressBook instance using a URL formed from the user name. If the bind fails, the login fails; otherwise, the entries are displayed onscreen. In addition to logging into an existing account, the user is also presented with the opportunity to create a new address book. Finally, when the user quits the address book, it’s saved at the server.
LISTING 27.21 AddressBookApplet BINDS TO AN AddressBook INSTANCE
import org.omg.CORBA.*;
import com.visigenic.vbroker.URLNaming.*;
import java.applet.*;
import java.awt.*;
/**
* Client applet
*/
public class AddressBookApplet extends Applet { private String _sLoginID;
private Resolver _resolver = null;
private ORB _orb = null;
private AddressBookI _addressBook = null;
private AddressBookServerI _addressBookServer = null;
public void init() { bindToServices();
showLoginPanel();
} /**
* Displays the login panel */
private void showLoginPanel() { removeAll();
LoginPanel loginPanel = new LoginPanel(this);
setLayout(new GridLayout(1,1));
add(loginPanel);
doLayout();
validate();
} /**
* Binds to all services, including ORB */
private void bindToServices() {
// obtain a reference to the ORB _orb = ORB.init();
// obtain a reference to the Resolver instance try{ _resolver = ResolverHelper.narrow(
_orb.resolve_initial_references("URLNamingResolver"));
// locate the AddressBookServerI instance,
// change the url if you are running on a different
// machine from the server or if the web server // is not running on port 15000
org.omg.CORBA.Object returnObject = _resolver.locate(
"http://localhost:15000/addressBook.ior");
_addressBookServer =
AddressBookServerIHelper.narrow(returnObject);
}
catch( Exception e ) {}
} /**
* Creates a new Address Book for the user
*/
public void createNewAddressBook(String sName) { _sLoginID = sName;
_addressBook = _addressBookServer.obtainNewAddressBook();
AddressBookViewer viewer = new AddressBookViewer(_addressBook, this,
_addressBookServer);
removeAll();
add(viewer);
doLayout();
validate();
} /**
* Uses the URL-based Naming Service to attempt to
* bind to an existing Address Book instance.
*/
private void obtainAddressBook() throws Exception { StringBuffer sbURL = new
StringBuffer("http://localhost:15000/");
sbURL.append(_sLoginID);
sbURL.append(".ior");
org.omg.CORBA.Object returnObject = _resolver.locate(sbURL.toString());
_addressBook = AddressBookIHelper.narrow(returnObject);
}
/**
* Asks the AddressBookServerI instance to save
* the active address book.
*/
private void saveAddressBook() {
_addressBookServer.saveAddressBook(_sLoginID, _addressBook);
showLoginPanel();
} /**
* Invoked when the user desires a quit */
public void doQuit() { saveAddressBook();
} /**
* Invoked when the login panel desires a login attempt */
public boolean doLogin(String sName) { _sLoginID = sName;
try{ obtainAddressBook(); } catch( Exception e ) {
return false;
}
// login success, create the viewer AddressBookViewer viewer = new AddressBookViewer(_addressBook,
this,
_addressBookServer);
removeAll();
add(viewer);
doLayout();
validate();
return true;
} }
The first utility class used by the client is the LoginPanel class (contained in Listing 27.22), which prompts a user to log in to or create a new address book.
LISTING 27.22 THE LoginPanel CLASS FACILITATES THE LOGIN PROCESS
import java.awt.*;
import java.awt.event.*;
/**
* Facilitates the login or create new address book process
*/
public class LoginPanel extends Panel implements ActionListener { private TextField _txtLogin;
private Button _btnNew;
private Button _btnLogin;
private AddressBookApplet _applet;
public LoginPanel(AddressBookApplet applet) { _applet = applet;
setLayout(new BorderLayout(10,10));
add(new Label("Welcome To The Address Book Server"), BorderLayout.NORTH);
Panel p = new Panel();
p.setLayout(new GridLayout(1,3));
p.add(new Label("Login ID"));
p.add(_txtLogin = new TextField(15));
p.add(_btnLogin = new Button("Login"));
Panel p2 = new Panel();
p2.setLayout(new GridLayout(5,1));
p2.add(p);
p2.add(_btnNew = new Button("New Address Book"));
add(p2, BorderLayout.CENTER);
_btnNew.addActionListener(this);
_btnLogin.addActionListener(this);
}
public void actionPerformed(ActionEvent ae) { Object target = ae.getSource();
if(target == _btnLogin) doLogin();
else doNew();
}
private void doLogin() {
_applet.doLogin(_txtLogin.getText().trim());
}
private void doNew() {
_applet.createNewAddressBook(_txtLogin.getText().trim());
} }
Finally, the AddressBookViewer class, contained in Listing 27.23, provides access to the contents of the AddressBook object, and it also allows for the adding of entries.
LISTING 27.23 THE AddressBookViewer CLASS INTERACTS WITH AN AddressBookI OBJECT TO DISPLAY AND MODIFY ADDRESSES
import java.awt.*;
import java.awt.event.*;
/**
* Displays the contents of the address book, and allows
* for the addition of new addresses.
*/
public class AddressBookViewer extends Panel implements ActionListener {
private AddressI[] __addresses;