Day 4 Building a CORBA Application

来源:互联网 发布:经典爱情电影知乎 编辑:程序博客网 时间:2024/04/28 08:04


Teach Yourself CORBA In 14 Days

Previous chapterNext chapterContents 


Day 4
Building a CORBA Application

  • Building a CORBA Server
    • Defining the Server Interfaces
    • Choosing an Implementation Approach
    • Using the IDL Compiler
    • Implementing the Server Interfaces
    • Compiling and Running the Server
  • Building a CORBA Client
    • Implementing the Client
    • Compiling and Running the Client
  • Summary
  • Q&A
  • Workshop
    • Quiz
    • Exercises

 


Up to this point, you've spent most of your time learning about CORBA's Interface Definition Language (IDL). You saw that IDL is used to define the interfaces of CORBA objects; you've even created some IDL interfaces of your own. Now it's time to put your IDL knowledge to use by not only defining object interfaces, but also by implementing those interfaces to build a CORBA server. This chapter will walk you through that process, from defining object interfaces to running the server. The outline of the process is this:

1. Define the server's interfaces using IDL.

2
. Choose an implementation approach for the server's interfaces. (You'll see that CORBA provides two such approaches: inheritance and delegation.)

3
. Use the IDL compiler to generate client stubs and server skeletons for the server interfaces. (For now, you'll only be concerned with the server skeletons.)

4
. Implement the server interfaces.

5
. Compile the server application.

6
. Run the server application. (With any luck, everything will fall into place!)

Your next task will be to build a client that uses the services you implemented in the first part of this chapter. Conceptually speaking, the process of building the client is much simpler; you only need to decide what you want the client to do, include the appropriate client stubs for the types of server objects you want to use, and implement the client functionality. Then you'll be ready to compile and run the client.

Building a CORBA Server

The first step in building a CORBA application is usually to implement the server functionality. The reason for this is that while a server can be tested (at least in a limited way) without a client, it is generally much more difficult to test a client without a working server. There are exceptions to this, of course, but typically you'll need to at least define the server interfaces before implementing the client; server and client functionality can then be developed in parallel. For the sake of simplicity, in this book you'll implement server functionality first, followed by client functionality.

On a high level, to build the server you'll need to first define the server interfaces (which define the capabilities that will be made available by the server and how those capabilities are accessed), implement those interfaces, and finally compile and run the server. There are a few issues you'll encounter along the way, which will be discussed in this section.

Defining the Server Interfaces

This is where you begin to apply the knowledge you assimilated on Day 3, "Mastering the Interface Definition Language (IDL)." There you learned much about IDL but had no real chance to apply that knowledge. Now you can use that knowledge to transform a clean slate into a system design.

A Few Words About System Design

Obviously, in order to build a system, one must first have an idea of what that system is supposed to accomplish. Before you're ready to write a single line of IDL, you must first have a notion of what you're trying to achieve. Although the subject of system design is far beyond the scope of this book, on Day 5, "Designing the System: A Crash Course in Object-Oriented Analysis and Design," you'll be exposed to some of the basic concepts of designing a system and mapping a system design into IDL. In this chapter, the work will be done for you; in the real world, this is seldom the case.

The Stock Market Server Example

At this point, it's preferable to examine a simple example to help you focus on the process of implementing a CORBA server. A complex example would likely bog you down in various implementation details and is best avoided until later.

Consider a stock market-related example: A service is desired that, when given a stock symbol, will return the value of that stock at that particular time. As an added convenience, the service will also return a list of all known stock symbols upon request.

A cursory analysis of this scenario suggests that a StockServer interface could be defined that provides two services (methods): getStockValue() and getStockSymbols(). getStockValue() should take a StockSymbol as a parameter and return a floating-point result (float will probably do). getStockSymbols() need not take any arguments and should return a list of StockSymbol objects.

 


Note:During the process of determining StockServer system capability, the StockSymbol class was inadvertently produced. This spontaneous generation of classes, often a by-product of object-oriented analysis, can lead to a better understanding of how a particular system works.

Listing 4.1. StockMarket.idl.

 1: // StockMarket.idl   2:    3: // The StockMarket module consists of definitions useful   4: // for building stock market-related applications.   5: module StockMarket {   6:    7:     // The StockSymbol type is used for symbols (names)   8:     // representing stocks.   9:     typedef string StockSymbol;  10:   11:     // A StockSymbolList is simply a sequence of  12:     // StockSymbols.  13:     typedef sequence<StockSymbol> StockSymbolList;  14:   15:     // The StockServer interface is the interface for a  16:     // server which provides stock market information.  17:     // (See the comments on the individual methods for  18:     // more information.)  19:     interface StockServer {  20:   21:         // getStockValue() returns the current value for  22:         // the given StockSymbol. If the given StockSymbol  23:         // is unknown, the results are undefined (this  24:         // would be a good place to raise an exception).  25:         float getStockValue(in StockSymbol symbol);  26:   27:         // getStockSymbols() returns a sequence of all  28:         // StockSymbols known by this StockServer.  29:         StockSymbolList getStockSymbols();  30:     };

31: };


Mapping this particular design to IDL is a clear-cut process; the final result, StockMaret.idl, appears in Listing 4.1. First, because it's good practice to group together related interfaces and types into IDL modules, start by including all the definitions in a module called StockMarket:

module StockMarket {  

Next, consider the use of the StockSymbol class. For the purposes of this example, it really doesn't require any functionality over and above the string type. Thus, you can either substitute the string type anywhere that the StockSymbol type would have been used, or you can use typedef to define StockSymbol as a string. In a complex system, using specific data types such as StockSymbol makes the system design easier to comprehend. To reinforce this practice, use typedef to define StockSymbol as a string:

   typedef string StockSymbol;  

You're now ready to define the interface for the StockServer object:

interface StockServer {  

The first method in StockServer is a method that takes a StockSymbol as input and returns a float. Expressing this in IDL is uncomplicated:

float getStockValue(in StockSymbol symbol);  

 


Note:It's conceivable that a client could call getStockValue() with an invalid StockSymbol name. To handle this, getStockValue() could (and probably should) raise an exception when an invalid name is passed to it. For the sake of simplicity, though, this example does not make use of exceptions.

The other StockServer method takes no arguments and returns a list of StockSymbols. Recall that IDL offers two constructs to represent lists: the sequence and the array. Because the size of the list is unknown in this case, it might be advantageous to use a sequence. However, a method cannot return a sequence directly; you'll first need to typedef a sequence of StockSymbols to use with this method. For the sake of convenience, add the typedef immediately following the typedef of the StockSymbol type:

typedef sequence<StockSymbol> StockSymbolList;  

You're now ready to add the getStockSymbols() method to the StockServer interface. This method is described in IDL as follows:

StockSymbolList getStockSymbols();  

That's all the IDL you need for this example. Armed with the StockMarket.idl file, you're now ready for the next step: deciding how you'd like to implement these IDL definitions.

Choosing an Implementation Approach

Before actually implementing the server functionality, you'll first need to decide on an implementation approach to use. CORBA supports two mechanisms for implementation of IDL interfaces. Developers familiar with object-oriented concepts might recognize these mechanisms, or at least their names. These include the inheritance mechanism, in which a class implements an interface by inheriting from that interface class, and the delegation mechanism, in which the methods of the interface class call the methods of the implementing class (delegating to those methods). These concepts are illustrated in Figures 4.1 and 4.2. Figure 4.2 also illustrates that a tie class can inherit from any class--or from no class--in contrast to the inheritance approach, in which the implementation class must inherit from the interface class that it implements.

New Term: Implementation by inheritance consists of a base class that defines the interfaces of a particular object and a separate class, inheriting from this base class, which provides the actual implementations of these interfaces.

Implementation by delegation consists of a class that defines the interfaces for an object and then delegates their implementations to another class or classes. The primary difference between the inheritance and delegation approaches is that in delegation, the implementation classes need not derive from any class in particular.

A tie class, or simply a tie, is the class to which implementations are delegated in the delegation approach. Thus, the approach is often referred to as the tie mechanism or tying.

Most IDL compilers accept command-line arguments to determine which implementation approach to generate code for. Therefore, before you use the IDL compiler to generate code from your IDL definitions, you'll want to determine the approach you want to use. Consult your IDL compiler's documentation to determine which command-line arguments, if any, the IDL compiler expects.

Figure 4.1. Implementation by inheritance.

Figure 4.2. Implementation by delegation.


How to Choose an Implmentation Approach

One question you might be asking by now is how to choose an implementation approach. In many cases, this is probably a matter of taste. However, there are certain cases that work well with a particular approach. For example, recall that in the inheritance approach, the implementation class derives from a class provided by the IDL compiler. If an application makes use of legacy code to implement an interface, it might not be practical to change the classes in that legacy code to inherit from a class generated by the IDL compiler. Therefore, for such an application it would make more sense to use the delegation approach; existing classes can readily be transformed into tie classes.

 


Warning: After you've chosen an implementation approach and have written a great deal of code, be prepared to stick with that approach for that server. Although it's possible to change from one implementation approach to another, this is a very tedious process if a lot of code has already been written. This issue doesn't present itself very often, but you should be aware of it.

For the purposes of this example, either implementation approach will do. The example will use the delegation approach; implementing the server using inheritance will be left as an exercise.

Note that you can usually mix and match the implementation and delegation approaches within a single server application. Although you'll use only one approach per interface, you could choose different approaches for different interfaces in the system. For example, if you had decided that the inheritance approach was the best match for your needs, but you had a few legacy classes that mandated the use of the tie approach, you could use that approach for those classes while using the inheritance approach for the remainder.

Using the IDL Compiler

Now that you have defined your system's object interfaces in IDL and have decided on an implementation approach, you're ready to compile the IDL file (or files, in a more complex system).

 


Note:The method and command-line arguments for invoking the IDL compiler vary across platforms and products. Consult your product documentation for specific instructions on using your IDL compiler.

Recall that this example will use the delegation approach--also called the tie mechanism--so be sure to consult your IDL compiler documentation for the appropriate command-line arguments (if any are required) to generate the proper files and source code. For example, the command to invoke the IDL compiler included with Sun's Java IDL product is this:

idltojava -fno-cpp -fclient -fserver StockMarket.idl  

In this case, the IDL compiler is named idltojava. The -fno-cpp switch instructs the IDL compiler to not invoke the C preprocessor before compiling the file. The -fclient and -fserver switches instruct the IDL compiler to generate client stubs and server skeletons, respectively. For now, you could get by without the -fclient switch because you'll only be implementing the server, but because you'll want the client stubs later, it will save time to generate them now.

The command to invoke the IDL compiler included in Visigenic's VisiBroker/C++ for Windows 95 is as follows:

orbeline -h h StockMarket.idl  

Here, the IDL compiler, named orbeline, generates client stubs and server skeletons for the StockMarket.idl file. The -c cpp switch instructs the compiler to use the .cpp filename extension for C++ source files; similarly, -h h tells the compiler to use the .h extension for header files. You can, of course, substitute your favorite filename extensions in place of these.

 


Tip: As with any utility run from the command line, before you can run the IDL compiler, you might have to set the PATH variable in your system's environment to include the directory where the IDL compiler resides. Generally, your CORBA product's documentation will tell you how to set the PATH variable to include the proper directories.

Client Stubs and Server Skeletons

When the IDL compiler is invoked, it generates code that conforms to the language mapping used by that particular product. The IDL compiler will generate a number of files--some of them helper classes, some of them client stub classes, and some of them server skeleton classes.

 


Note:Recall from Day 2 that client stubs for an interface are pieces of code compiled with client applications that use that interface. These stubs do nothing more than tell the client's ORB to marshal and unmarshal outgoing and incoming parameters. Similarly, server skeletons are snippets of code that create the server framework. These skeletons pass incoming parameters to the implementation code--written by you, the developer--and pass outgoing parameters back to the client.

The names of the files generated by the IDL compiler are dependent on the language mapping used and sometimes on command-line arguments passed to the IDL compiler. (For example, some IDL compilers accept switches that specify prefixes and suffixes to be added to the class names.) The contents of these files will remain the same, for the most part, regardless of the IDL compiler used (assuming the products conform to the standard language mappings). For example, the output of the IDL compiler in IONA's Orbix/C++ will be roughly the same as the output of Visigenic's VisiBroker/C++ IDL compiler. Similarly, the corresponding Java products will output nearly the same source code.

 


Note:Strictly speaking, the term "IDL compiler" is a misnomer. Whereas a compiler generally converts source code to object code, the IDL compiler is more of a translator: It converts IDL source code to C++ source code, or Java source code, and so on. The generated code, along with the implementations that you provide, are then compiled by the C++ compiler, or Java compiler, and so on.

Implementing the Server Interfaces

After you have successfully used the IDL compiler to generate server skeletons and client stubs for your application, you are ready to implement the server interfaces. The IDL compiler generates a number of files; for each IDL interface, the compiler will generate a source file and header file for the client stub and a source file and header file for the server skeleton, resulting in four files per interface. (This is for an IDL compiler targeting C++; an IDL compiler targeting Java will, of course, not generate header files.) Additionally, the IDL compiler can create separate directories for IDL modules; it can also create additional files for helper classes. Also, most IDL compilers allow you to specify the suffix to use for client stubs and server skeletons. For example, the client stub files can be named StockMarket_c.h and StockMarket_c.cpp, or StockMarket_st.h and StockMarket_st.cpp. Refer to your IDL compiler's documentation to determine what files it produces, what filenames it uses, and how to change the default filename suffixes.

To keep the example as simple as possible, Java is used as the implementation language. Java was chosen for this example because of its relative simplicity, particularly when developing CORBA applications. Of all the languages commonly used for CORBA application development, Java probably gets in the way of the developer the least, making it the best suited for an introductory example. Most of the remainder of this book will use C++ for example code, with the exception of the Java-specific Chapters 13 and 14.

Using Server Skeletons

The server skeleton, as you have learned, provides a framework upon which to build the server implementation. In the case of C++, a server skeleton is a set of classes that provides pure virtual methods (methods with no implementations) or methods that delegate to methods in another class (the tie class discussed previously). You, the developer, provide the implementation for these methods. In the case of Java, a server skeleton combines a set of helper classes with an interface, for which you, again, provide the implementation.

Assuming you use Sun's Java IDL compiler to produce the server skeletons for your application, you will see that the compiler produced a directory called StockMarket (corresponding to the name of the IDL module defined in StockMarket.idl). Within this directory are a number of files containing client stub and server skeleton definitions:

StockSymbolHelper.java  StockSymbolListHolder.java  StockSymbolListHelper.java  StockServer.java  StockServerHolder.java  StockServerHelper.java  _StockServerStub.java  _StockServerImplBase.java  

At this point, your only concern is with the server skeleton portion of these files. In particular, note that the Java interface describing the StockServer services is contained in the StockServer.java file. Its contents appear in Listing 4.2. The StockServerImplBase.java file contains a helper class from which you'll derive your server implementation class; you need not concern yourself with its contents because it provides functionality that works under the hood.

Listing 4.2. StockServer.java.

 1: /*   2:  * File: ./StockMarket/StockServer.java   3:  * From: StockMarket.idl   4:  * Date: Mon Jul 21 16:12:26 1997   5:  *   By: D:/BIN/DEVEL/JAVA/JAVAIDL/BIN/IDLTOJ~1.EXE JavaIDL   6:  *   Thu Feb 27 11:22:49 1997   7:  */   8:    9: package StockMarket;  10: public interface StockServer  11:     extends org.omg.CORBA.Object {  12:     float getStockValue(String symbol)  13: ;  14:     String[] getStockSymbols()  15: ;

16: }

Examining Listing 4.2, you see that the StockServer interface is placed in the StockMarket package. Furthermore, you can see that this interface extends the org.omg.CORBA.Object interface. All CORBA object interfaces extend this interface, but you need not concern yourself with this interface's contents either. Finally, you can see that the StockServer interface contains two methods that correspond to the IDL methods in StockServer.idl. Note, however, that the IDL types have been mapped to their Java counterparts: StockSymbol, which was typedef'ed as an IDL string, maps to a Java String; StockSymbolList, which was a sequence of StockSymbols, is mapped to a Java array of Strings. The IDL float, not surprisingly, is mapped to a Java float.

Writing the Implementation

The implementation for the StockServer is straightforward. This section walks you through the implementation (the example uses the class name StockServerImpl, but you can name the implementation class anything you want) line by line and explains what is happening at every step of the way.

Listing 4.3. StockServerImpl.java.

  1: // StockServerImpl.java    2:     3: package StockMarket;    4:     5: import java.util.Vector;    6:     7: import org.omg.CORBA.ORB;    8: import org.omg.CosNaming.NameComponent;    9: import org.omg.CosNaming.NamingContext;   10: import org.omg.CosNaming.NamingContextHelper;   11:    12: // StockServerImpl implements the StockServer IDL interface.   13: public class StockServerImpl extends _StockServerImplBase implements   14:         StockServer {   15:    16:     // Stock symbols and their respective values.   17:     private Vector myStockSymbols;   18:     private Vector myStockValues;   19:    20:     // Characters from which StockSymbol names are built.   21:     private static char ourCharacters[] = { `A', `B', `C', `D', `E', `F',   22:             `G', `H', `I', `J', `K', `L', `M', `N', `O', `P', `Q', `R',   23:             `S', `T', `U', `V', `W', `X', `Y', `Z' };   24:    25:     // Path name for StockServer objects.   26:     private static String ourPathName = "StockServer";   27:    28:     // Create a new StockServerImpl.   29:     public StockServerImpl() {   30:    31:         myStockSymbols = new Vector();   32:         myStockValues = new Vector();   33:    34:         // Initialize the symbols and values with some random values.   35:         for (int i = 0; i < 10; i++) {   36:    37:             // Generate a string of four random characters.   38:             StringBuffer stockSymbol = new StringBuffer("    ");   39:             for (int j = 0; j < 4; j++) {   40:    41:                 stockSymbol.setCharAt(j, ourCharacters[(int)(Math.random()   42:                         * 26f)]);   43:             }   44:    45:             myStockSymbols.addElement(stockSymbol.toString());   46:    47:             // Give the stock a value between 0 and 100. In this example,   48:             // the stock will retain this value for the duration of the   49:             // application.   50:             myStockValues.addElement(new Float(Math.random() * 100f));   51:         }   52:    53:         // Print out the stock symbols generated above.   54:         System.out.println("Generated stock symbols:");   55:         for (int i = 0; i < 10; i++) {   56:             System.out.println("  " + myStockSymbols.elementAt(i) + " " +   57:                     myStockValues.elementAt(i));   58:         }   59:         System.out.println();   60:     }   61:    62:     // Return the current value for the given StockSymbol.   63:     public float getStockValue(String symbol) {   64:    65:         // Try to find the given symbol.   66:         int stockIndex = myStockSymbols.indexOf(symbol);   67:         if (stockIndex != -1) {   68:    69:             // Symbol found; return its value.   70:             return ((Float)myStockValues.elementAt(stockIndex)).   71:                     floatValue();   72:         } else {   73:    74:             // Symbol was not found.   75:             return 0f;   76:         }   77:     }   78:    79:     // Return a sequence of all StockSymbols known by this StockServer.   80:     public String[] getStockSymbols() {   81:    82:         String[] symbols = new String[myStockSymbols.size()];   83:         myStockSymbols.copyInto(symbols);   84:    85:         return symbols;   86:     }   87:    88:     // Create and initialize a StockServer object.   89:     public static void main(String args[]) {   90:    91:         try {   92:    93:             // Initialize the ORB.   94:             ORB orb = ORB.init(args, null);   95:    96:             // Create a StockServerImpl object and register it with the   97:             // ORB.   98:             StockServerImpl stockServer = new StockServerImpl();   99:             orb.connect(stockServer);  100:   101:             // Get the root naming context.  102:             org.omg.CORBA.Object obj = orb.  103:                     resolve_initial_references("NameService");  104:             NamingContext namingContext = NamingContextHelper.narrow(obj);  105:   106:             // Bind the StockServer object reference in the naming  107:             // context.  108:             NameComponent nameComponent = new NameComponent(ourPathName,  109:                     "");  110:             NameComponent path[] = { nameComponent };  111:             namingContext.rebind(path, stockServer);  112:   113:             // Wait for invocations from clients.  114:             java.lang.Object waitOnMe = new java.lang.Object();  115:             synchronized (waitOnMe) {  116:                 waitOnMe.wait();  117:             }  118:         } catch (Exception ex) {  119:             System.err.println("Couldn't bind StockServer: " + ex.  120:                     getMessage());  121:         }  122:     }

123: }


The entire listing for StockServerImpl.java appears in Listing 4.3; the remainder of this section will walk you through the file step by step, so that you can see the details of what's going on.

package StockMarket;  

Because the StockServer interface is part of the StockMarket module, the IDL compiler places the Java class and interface definitions into the StockMarket package. For convenience, StockServerImpl is placed into this package as well. (If you're not familiar with Java or with packages, you can safely ignore this bit of code for now.)

import java.util.Vector;  

StockServerImpl will make use of the Vector class. This import should look familiar to Java developers already. If you're not familiar with Java, the import statement behaves much like the #include preprocessor directive in C++; the java.util.Vector class is a container class that behaves as a growable array of elements.

import org.omg.CORBA.ORB;  import org.omg.CosNaming.NameComponent;  import org.omg.CosNaming.NamingContext;  import org.omg.CosNaming.NamingContextHelper;  

The classes being imported here are commonly used in CORBA applications. The first, of course, is the class that provides the ORB functionality; the other classes are related to the CORBA Naming Service, which you'll explore further on Day 12.

// StockServerImpl implements the StockServer IDL interface.  public class StockServerImpl extends _StockServerImplBase implements          StockServer {  

Given the StockServer IDL interface, the IDL compiler generates a class called StockServerImplBase and an interface called StockServer. To implement the StockServer IDL interface, your StockServerImpl class must extend _StockServerImplBase and implement StockServer, which is exactly what is declared here:

// Stock symbols and their respective values.  private Vector myStockSymbols;  private Vector myStockValues;  

StockServerImpl uses Vectors to store the stock symbols and their values.

// Characters from which StockSymbol names are built.  private static char ourCharacters[] = { `A', `B', `C', `D', `E', `F',  `G', `H', `I', `J', `K', `L', `M', `N', `O', `P', `Q', `R',  `S', `T', `U', `V', `W', `X', `Y', `Z' };  

The ourCharacters array contains the set of characters from which stock symbols are built.

// Path name for StockServer objects.  private static String ourPathName = "StockServer";  

The ourPathName variable stores the pathname by which this StockServer object can be located in the Naming Service. This can be any name, but for the purposes of this example, StockServer works well.

// Create a new StockServerImpl.  public StockServerImpl() {  myStockSymbols = new Vector();  myStockValues = new Vector();  

Although constructors aren't a part of an IDL interface, the class implementing that interface will still have constructors so that the server can create the implementation objects. StockServerImpl has only a default constructor, but like any other class, a class that implements an IDL interface can have any number of constructors.

This part of the constructor creates Vectors to hold the stock symbols and their respective values.

// Initialize the symbols and values with some random values.  for (int i = 0; i < 10; i++) {  

Rather arbitrarily, the StockServerImpl creates ten stock symbols.

// Generate a string of four random characters.  StringBuffer stockSymbol = new StringBuffer("    ");  for (int j = 0; j < 4; j++) {      stockSymbol.setCharAt(j, ourCharacters[(int)(Math.random()              * 26f)]);  }  myStockSymbols.addElement(stockSymbol.toString());  

For each stock symbol, the StockServerImpl creates a string of four random characters (chosen from the preceding ourCharacters array). The four-character length, like the number of symbols, was chosen arbitrarily. For the sake of simplicity, no checks are made for duplicate strings.

// Give the stock a value between 0 and 100. In this example,  // the stock will retain this value for the duration of the  / application.  myStockValues.addElement(new Float(Math.random() * 100f));  }  

Here, a random value between 0 and 100 is given to each stock symbol. In this example, the stock will retain the assigned value for as long as the StockServerImpl runs.

// Print out the stock symbols generated above.  System.out.println("Generated stock symbols:");  for (int i = 0; i < 10; i++) {      System.out.println("  " + myStockSymbols.elementAt(i) + " " +             myStockValues.elementAt(i));  }   System.out.println();  }  

Finally, the constructor prints out the stock symbols and their values.

// Return the current value for the given StockSymbol.  public float getStockValue(String symbol) {       // Try to find the given symbol.       int stockIndex = myStockSymbols.indexOf(symbol);       if (stockIndex != -1) {          // Symbol found; return its value.          return ((Float)myStockValues.elementAt(stockIndex)).                  floatValue();       } else {           // Symbol was not found.           return 0f;        }  }  

The getStockValue() method takes a String, attempts to find a match in the myStockSymbols data member, and returns the value for the stock symbol (if found). If the stock symbol is not found, a zero value is returned.

 


Note:Naturally, the getStockValue() method is an excellent candidate to raise an exception--if an invalid stock symbol were passed to the method, it could raise (for example) an InvalidStockSymbolException rather than return zero, as it currently does. This is an exercise at the end of the chapter.
// Return a sequence of all StockSymbols known by this StockServer.  public String[] getStockSymbols() {       String[] symbols = new String[myStockSymbols.size()];       myStockSymbols.copyInto(symbols);       return symbols;  }  

The getStockSymbols() method simply creates an array of Strings, copies the stock symbols (contained in myStockSymbols) into the array, and returns the array.

// Create and initialize a StockServer object.  public static void main(String args[]) {  

The main() method in StockServerImpl creates a StockServerImpl object, binds that object to a naming context, and then waits for clients to call methods on that object.

try {  

Because the methods that main() will later call might throw exceptions, those calls are wrapped in a try ... catch block.

// Initialize the ORB.  ORB orb = ORB.init(args, null);  

Before doing anything with the ORB, the server application must first initialize the ORB.

// Create a StockServerImpl object and register it with the  // ORB.  StockServerImpl stockServer = new StockServerImpl();  orb.connect(stockServer);  

Here a new StockServerImpl object is created and registered with the ORB.

// Get the root naming context.  org.omg.CORBA.Object obj = orb.          resolve_initial_references("NameService");  NamingContext namingContext = NamingContextHelper.narrow(obj);  

Now for a little black magic. The CORBA Naming Service is a service that allows CORBA objects to register by name and subsequently be located, using that name, by other CORBA objects. As mentioned before, use of the Naming Service will be described in detail on Day 12, but the sample client application in this chapter will still need to be able to locate a server; therefore the Naming Service is introduced here, though somewhat prematurely. Consequently, in this chapter, don't worry if you don't understand all the details of the Naming Service or how it is used.

In order for clients to connect to the StockServerImpl, they must have some way of locating the service on the network. One way to accomplish this is through the CORBA Naming Service. Here, a NamingContext object is located by resolving a reference to an object named NameService.

// Bind the StockServer object reference in the naming  // context.  NameComponent nameComponent = new NameComponent(ourPathName,          "");  NameComponent path[] = { nameComponent };  namingContext.rebind(path, stockServer);  

Now the NamingContext object is asked to bind the StockServerImpl object to the pathname defined earlier (StockServer). Clients can now query the Naming Service for an object by this name; the Naming Service will return a reference to this StockServerImpl object.

// Wait for invocations from clients.  java.lang.Object waitOnMe = new java.lang.Object();  synchronized (waitOnMe) {      waitOnMe.wait();  }  

Because the StockServerImpl object is now registered with the Naming Service, the only thing left to do is to wait for clients to invoke methods on the object. Because the actual handling of these method invocations occurs in a separate thread, the main() thread simply needs to wait indefinitely.

        } catch (Exception ex) {              System.err.println("Couldn't bind StockServer: " + ex.                      getMessage());          }      }  }  

If any exceptions are thrown by any of the methods called, they are caught and handled here.

Compiling and Running the Server

Now you're ready to compile and run the server. Compiling the server application is simple. If you're using an integrated development environment, use that tool's "build" command (or equivalent) to build the application. If you're using the JDK from the command line, change directories to the directory where StockMarket.idl is located (there should also be a directory called StockMarket contained in this directory). Then issue the command

javac StockMarket/StockServerImpl.java  

(You might have to substitute the appropriate directory separator for your platform in the preceding command.) This will compile the server implementation and all the source files it depends on.

 


Tip: Before compiling the server, make sure that your CLASSPATH contains the appropriate directory or file for the CORBA classes. For Sun's JavaIDL package, the file (directory where JavaIDL is installed) /lib/classes.zip will appear in the CLASSPATH. Consult your CORBA product's documentation to determine your CLASSPATH setting.

Assuming that the server application compiled correctly, you're about ready to run the server. Before you do that, though, you need to run the Name Server. (Recall that the client application uses the CORBA Naming Service to locate the server application; the Name Server provides the mechanism that makes this possible.)

The exact method for running the Name Server varies from product to product, but the end result is the same. For Sun's JavaIDL, simply running nameserv will bring up the Name Server.

When the Name Server is running, you're ready to run the server. You can invoke the server with the command

java StockMarket.StockServerImpl  

For the sake of simplicity, you'll want to run the Name Server and your server application on the same machine for now. If everything works correctly, you will see output similar to Listing 4.4. The stock symbols and their values will, of course, vary, but you will see output resembling Listing 4.4 without any exception messages following the output.

Listing 4.4. Sample StockServer output.

 1: Generated stock symbols:   2:   PTLF 72.00064   3:   SWPK 37.671585   4:   CHHL 78.37782   5:   JTUX 75.715645   6:   HUPB 41.85024   7:   OHQR 14.932466   8:   YOEX 64.3376   9:   UIBP 75.80115  10:   SIPR 91.13683

11: XSTD 16.010124


If you got this far, congratulations! You have successfully designed, implemented, and deployed a CORBA server application. After reveling in your success, feel free to terminate the application because you won't have a client to connect to it until the end of this chapter. Alternatively, you can leave the server running to save yourself the trouble of restarting it later (or just to impress and amaze your friends).

Building a CORBA Client

In the first half of this chapter, you were left hanging with a server that couldn't do much because there were no clients to connect to it. Now you'll remedy that unfortunate situation by implementing a client that will utilize the services provided by the server you built. Because you've already written and compiled the IDL interfaces (and implemented them, for that matter), implementing the client will be a much simpler process. Additionally, clients are often (though not always) simpler than servers by nature, so they are easier to implement in that regard as well.

Implementing the Client

As mentioned already, implementing the client is a straightforward process. There are only a handful of concepts involved: how to use client stubs in the client implementation, how to locate a server object, and how to use the interfaces of a server object after it has been located.

Using Client Stubs

When you compiled StockServer.idl, the IDL compiler generated client stubs as well as server skeletons. Because client stubs aren't used for server implementations, you ignored the stubs for the time being. Now, it's time to use them. If you're curious, open the _StockServerStub.java file and have a look at it. You'll see a fair amount of cryptic code along with two familiar methods:

public float getStockValue(String symbol) {      ...  }  public String[] getStockSymbols() {      ...  }  

The implementations for these methods, as discussed before, marshal the parameters through the ORB to the remote object and then marshal the return value back to the client. (This is what all that cryptic-looking code is doing.)

You really needn't concern yourself with the contents of _StockServerStub.java; all you need to know is that this file contains the client stub for the StockServer interface. The Java compiler is smart enough to compile this file automatically, but if you were implementing a client in C++, you'd have to be sure to link the client stub object with the rest of the client application.

The other thing to know about the client stub is that it specifies the actual interfaces for the server object. In other words, you can see in the preceding example that the getStockValue() method takes a Java String as a parameter and returns a Java float. Similarly, getStockSymbols() takes no parameters and returns a Java array of Strings.

Locating a Server Object

Just as a server application is practically useless if it cannot make its location known, a client application cannot do useful work if it cannot locate services to use. This is where the CORBA Naming Service steps in again. After a server registers itself with the Name Server, clients can locate that server object through the Name Server, bind to that server object, and subsequently call methods on the server object. Again, do not be concerned if there are details of the Naming Service which escape you, as it will be discussed in greater detail on Day 12.

In the StockMarketClient, binding to the server object takes place in the connect() method, as shown in Listing 4.5. This method first binds to the Name Server by looking for an object with the name NameService. Upon successfully locating a Name Server, the client proceeds to bind to an object with the name StockServer, which incidentally is the same name you registered the StockServerImpl under (actually, the names must be the same if the example is to work successfully). After this object is bound, the client is ready to do some work.

Listing 4.5. Binding to the StockServer server.

 1: // Connect to the StockServer.   2: protected void connect() {   3:    4:     try {   5:    6:         // Get the root naming context.   7:         org.omg.CORBA.Object obj = ourORB.   8:                 resolve_initial_references("NameService");   9:         NamingContext namingContext = NamingContextHelper.narrow(obj);  10:   11:         // Attempt to locate a StockServer object in the naming context.  12:         NameComponent nameComponent = new NameComponent("StockServer",  13:                 "");  14:         NameComponent path[] = { nameComponent };  15:         myStockServer = StockServerHelper.narrow(namingContext.  16:                 resolve(path));  17:     } catch (Exception ex) {  18:         System.err.println("Couldn't resolve StockServer: " + ex);  19:         myStockServer = null;  20:         return;  21:     }  22:   23:     System.out.println("Succesfully bound to a StockServer.");

24: }

Using Server Object Interfaces

Listing 4.6 shows an example of how the server object interfaces are used, after the client has bound the server object. As you might expect, the client simply calls the server methods as it sees fit. Again, you can refer to the client stub (_StockServerStub.java) or better yet, to the StockServer interface in StockServer.idl to see the method signatures for the StockServer. The usage of these methods is clear-cut, as illustrated in Listing 4.6.

Listing 4.6. Using the StockServer services.

 1: // Do some cool things with the StockServer.   2: protected void doSomething() {   3:    4:     try {   5:    6:         // Get the valid stock symbols from the StockServer.   7:         String[] stockSymbols = myStockServer.getStockSymbols();   8:    9:         // Display the stock symbols and their values.  10:         for (int i = 0; i < stockSymbols.length; i++) {  11:             System.out.println(stockSymbols[i] + " " +  12:                     myStockServer.getStockValue(stockSymbols[i]));  13:         }  14:     } catch (org.omg.CORBA.SystemException ex) {  15:         System.err.println("Fatal error: " + ex);  16:     }

17: }

In Listing 4.6, the StockServer is first asked, through a call to getStockSymbols(), for a list of all stock symbols recognized by the server. The client then iterates through the list of stock symbols and queries the server, using getStockValue(), for the value of each stock. Each stock symbol and its respective value are printed to standard output.

Compiling and Running the Client

The entire listing for StockMarketClient.java appears in Listing 4.7. Note that most of the work for the client is done in the connect() and doSomething() methods, which you've already looked at.

Listing 4.7. StockMarketClient.java.

 1: // StockMarketClient.java   2:    3: package StockMarket;   4:    5: import org.omg.CORBA.ORB;   6: import org.omg.CosNaming.NameComponent;   7: import org.omg.CosNaming.NamingContext;   8: import org.omg.CosNaming.NamingContextHelper;   9:   10: // StockMarketClient is a simple client of a StockServer.  11: public class StockMarketClient {  12:   13:     // Create a new StockMarketClient.  14:     StockMarketClient() {  15:   16:     }  17:   18:     // Run the StockMarketClient.  19:     public void run() {  20:   21:         connect();  22:   23:         if (myStockServer != null) {  24:             doSomething();  25:         }  26:     }  27:   28:     // Connect to the StockServer.  29:     protected void connect() {  30:   31:         try {  32:   33:             // Get the root naming context.  34:             org.omg.CORBA.Object obj = ourORB.  35:                     resolve_initial_references("NameService");  36:             NamingContext namingContext = NamingContextHelper.narrow(obj);  37:   38:             // Attempt to locate a StockServer object in the naming  39:             // context.  40:             NameComponent nameComponent = new NameComponent("StockServer",  41:                     "");  42:             NameComponent path[] = { nameComponent };  43:             myStockServer = StockServerHelper.narrow(namingContext.  44:                     resolve(path));  45:         } catch (Exception ex) {  46:             System.err.println("Couldn't resolve StockServer: " + ex);  47:             myStockServer = null;  48:             return;  49:         }  50:   51:         System.out.println("Succesfully bound to a StockServer.");  52:     }  53:   54:     // Do some cool things with the StockServer.  55:     protected void doSomething() {  56:   57:         try {  58:   59:             // Get the valid stock symbols from the StockServer.  60:             String[] stockSymbols = myStockServer.getStockSymbols();  61:   62:             // Display the stock symbols and their values.  63:             for (int i = 0; i < stockSymbols.length; i++) {  64:                 System.out.println(stockSymbols[i] + " " +  65:                         myStockServer.getStockValue(stockSymbols[i]));  66:             }  67:         } catch (org.omg.CORBA.SystemException ex) {  68:             System.err.println("Fatal error: " + ex);  69:         }  70:     }  71:   72:     // Start up a StockMarketClient.  73:     public static void main(String args[]) {  74:   75:         // Initialize the ORB.  76:         ourORB = ORB.init(args, null);  77:   78:         StockMarketClient stockClient = new StockMarketClient();  79:   80:         stockClient.run();  81:   82:         // This simply waits forever so that the DOS window doesn't  83:         // disappear (for developers using Windows IDEs).  84:         while (true)  85:             ;  86:     }  87:   88:     // My ORB.  89:     public static ORB ourORB;  90:   91:     // My StockServer.  92:     private StockServer myStockServer;

93: }

Compiling the client application is an uncomplicated process. Like the server, the client can be compiled simply with the command

javac StockMarket/StockMarketClient.java  

Again, ensure you are in the proper directory when compiling the client. You should be in the same directory as the one in which you compiled the server.

Assuming the client application compiled successfully, you're just about ready to run the application. First, start the Name Service and the StockServer application (as you did previously), if they aren't running already. Then to execute the client application, type the command

java StockMarket.StockMarketClient  

If the client runs successfully, you will see output similar to Listing 4.8.

Listing 4.8. StockMarketClient output.

 1: Succesfully bound to a StockServer.   2: PTLF 72.00064   3: SWPK 37.671585   4: CHHL 78.37782   5: JTUX 75.715645   6: HUPB 41.85024   7: OHQR 14.932466   8: YOEX 64.3376   9: UIBP 75.80115  10: SIPR 91.13683

11: XSTD 16.010124

The stock symbols and their values will appear exactly as they appear in the server output.

Summary

In this chapter you started out by defining the interfaces for a CORBA server, using IDL. You then implemented the server using the inheritance approach (as opposed to the delegation approach) and learned a little bit about using the CORBA Naming Service along the way. You then created a simple client application that used the services provided by the StockServer application. In the process, you learned how to use the Naming Service to locate the StockServer object and how the client stubs fit into the client application. Finally, you ran the CORBA server and client together, creating what might be your very first distributed CORBA application. Congratulations! This is no small feat.

What Comes Next?

This chapter concludes the first section of this book, which has dealt with basic CORBA architecture and methodology. In the next section of the book, spanning Days 5 through 9, you'll design and build a larger, more complex CORBA application, starting with some basic functionality and adding more capabilities to the application in subsequent chapters. You'll apply the same techniques you've already learned; the only difference will be that you'll deal with more complex IDL and thus more complex servers and clients. You currently have the basic knowledge required to build an entire CORBA application; the next days will give you the opportunity to practice applying that knowledge to a more sophisticated system.

Q&A

Q I'm a C++ programmer, and I'm not sure I understand all the Java syntax.

A
For the most part, Java syntax is very similar to C++, but Java introduces some constructs of its own. Don't be overly concerned if you don't understand certain aspects of the language, as long as you can grasp the concepts of what the code is doing.

Q If I implement my server interfaces using the inheritance approach and later rewrite the server to use the delegation approach, will I have to rewrite my client(s) as well?


A
Regardless of the approach used to implement the server, the client code remains the same. Therefore, should you ever need to rewrite a server to use a different approach, rest assured that no changes will have to be made to the clients.

Q How can the classes that implement IDL interfaces have constructors when IDL doesn't specify any?

A
IDL only specifies public interfaces--that is, methods that can be used by other objects anywhere else on the network. However, the class that implements an IDL interface can also provide additional methods, although such methods won't be visible anywhere outside the object's process space. Such methods can be useful within the server application, though; constructors are an example of this. (Server objects need to be created somehow.) So feel free to include additional methods (public, protected, and private) in your server implementations if it makes sense to do so.

Workshop

The following section will help you test your comprehension of the material presented in this chapter and put what you've learned into practice. You'll find the answers to the quiz and exercises in Appendix A.

Quiz

1. What is the purpose of server skeletons and client stubs?

2
. Why does the server need to register the implementation object with the CORBA Naming Service?

3
. Why do the client and server need to catch exceptions, especially when none are raised by the IDL operations you defined?

Exercises

1. It was pointed out in the StockMarket example that it would be a good idea to raise an exception in the getStockValue() method if an invalid StockSymbol was passed in. Modify StockMarket.idl so that the method can raise an InvalidStockSymbolException. (You'll also need to add a definition for this exception.)

2. In the StockMarket example, an implementation was provided that used the delegation approach. Implement the StockServer to use the inheritance approach. (Extra credit: Also include the exception-raising mechanism from the first exercise.)

 


Previous chapterNext chapterContents

Macmillan Computer Publishing USA 

© Copyright, Macmillan Computer Publishing. All rights reserved.