Oracle8i Enterprise JavaBeans and CORBA Developer's Guide Release 2 (8.1.6) A81356-01 |
|
This chapter describes how to develop CORBA applications for Oracle8i. CORBA is a powerful distributed application development architecture. Although it is a powerful tool, you can still start to develop useful applications quickly, using Oracle8i CORBA. The emphasis in this chapter is practical, not conceptual. The first few sections of this chapter present the conceptual basis for CORBA application development.
This chapter is based on examples, showing you how to use Oracle8i CORBA by developing examples--from the simple to the slightly more complex. You can expand these examples into full-fledged applications for your enterprise.
This chapter covers the following topics:
This section defines some of the basic terms used in this chapter. See also Appendix D, "Abbreviations and Acronyms" for a list of common acronyms used in Java and distributed object computing.
A client is an object, an application, or an applet that makes a request of a server object. Remember that a client need not be a Java application running on a workstation or a network computer, nor an applet downloaded by a web browser. A server object can be a client of another server object. "Client" refers to a role in a requestor/server relationship, not to a physical location or a type of computer system.
In distributed object computing, marshalling refers to the process by which the ORB passes requests and data between clients and server objects.
Each CORBA ORB implements an object adapter (OA), which is the interface between the ORB and the message-passing objects. CORBA 2.0 specifies that a basic object adapter (BOA) must exist, but most of the details of its interface are left up to individual CORBA vendors. Future CORBA standards will require a vendor-neutral portable object adapter (POA). Oracle intends to support a POA in a future release.
A request is a method invocation. Other names sometimes used in its stead are method call and message.
A CORBA server object is a Java object activated by the server, typically on a first request from a client.
A session always means a database session. Although it is conceptually the same kind of session as that established when a tool such as SQL*Plus connects to Oracle, there are differences in the CORBA case, as follows:
To use CORBA with Oracle8i, you must configure the database so that the listener can recognize incoming IIOP requests, in addition to TTC requests. DBAs and system administrators should see the "Configuring CORBA and EJB in JServer" for information on setting up the database and the listener to accept incoming IIOP requests.
Note:
See Chapter 4, "Connections and Security", for more information about sessions.
This section provides a short introduction to CORBA, and should give you some idea of how you typically use CORBA in the Oracle8i server environment. Providing a complete introduction to CORBA is beyond the scope of this Guide. See the references in "For More Information" for suggested further reading. This first section gives a very high-level overview of CORBA itself.
CORBA stands for Common Object Request Broker Architecture. What is common about CORBA is that it integrates ideas from several of the original proposers. CORBA did not just follow the lead of a single large corporation, and it is very deliberately vendor neutral. The CORBA architecture specifies a software component, a broker, that mediates and directs requests to objects that are distributed across a network (or several networks), which might have been written in a different language from that of the requestor, and which might be (and in fact, usually are) running on a completely different hardware architecture from that of the requestor.
You can begin to get an idea of the tremendous advantages of CORBA from the preceding paragraph. CORBA enables your application to tie together components from various sources. Also, and unlike a typical client/server application, a CORBA application is not inherently synchronous. It is not necessarily typical that a CORBA requestor (a client) invokes a method on a server component and waits for a result. Using asynchronous method invocations, event interfaces and callbacks from server object to the client ORB, you can construct elaborate applications that link together many interacting objects and that access one or many data sources and other resources under transactional control. CORBA enables you to go beyond the bounds of the traditional client/server application in many imaginative ways.
CORBA achieves its flexibility in several ways:
CORBA provides bindings for many languages, including both non-object languages such as COBOL and C, and object-oriented languages such as Smalltalk and Java.
CORBA specifies over 12 services. Most of these are not yet implemented by CORBA ORB vendors.
The remainder of this section introduces some of the essential building blocks of an Oracle8i JServer CORBA application. These include:
The Java code examples in this chapter are available on line. You can study the complete examples (see Appendix A, "Example Code: CORBA"), compile and run them, and then modify them for your own use. All examples are installed in the $ORACLE_HOME/javavm/demo/demo.zip file.
Note:
The object request broker, or ORB, is the fundamental part of a CORBA implementation. The ORB makes it possible for a client to send messages to a server, and the server to return values to the client. The ORB handles all communication between a client and a server object.
The JServer ORB is based on code from Inprise's VisiBroker 3.4 for Java. The ORB that executes on the server side has been slightly modified from the VisiBroker code, to accommodate the different Oracle8i object location and activation model. The client-side ORB has been changed very little.
In some CORBA implementations, the application programmer and the server object developer must be aware of the details of how the ORB is activated on the client and the server, and they must include code in their objects to start up the ORBs and activate objects. The Oracle8i ORB, on the other hand, makes these details largely transparent to the application developer. As you will see from the Java code examples later in this chapter and in Appendix A, only in certain circumstances does the developer need to control the ORB directly. These occur, for example, when coding callback mechanisms, or when there is a need to register transient objects with the basic object adapter.
One of the key factors in the success of CORBA is language independence. CORBA objects written in one language can send requests to objects implemented in a different language. Objects implemented in an object-oriented language such as Java or Smalltalk can talk to objects written in C or COBOL, and the converse .
Language independence is achieved through the use of a specification meta-language that defines the interfaces that an object (or a piece of legacy code wrappered to look like an object) presents to the outside world. As in any object-oriented system, a CORBA object can have its own private data and its own private methods. The specification of the public data and methods is the interface that the object presents to the outside world.
IDL is the language that CORBA uses to specify its objects. You do not write procedural code in IDL--its only use is to specify data, methods, and exceptions.
Each CORBA vendor supplies a compiler that translates IDL specifications into language code. Oracle8i JServer uses the idl2java
compiler from Inprise (see "Miscellaneous Tools"). The idl2java
compiler translates your IDL interface specifications into Java classes, which are compiled by the Java compiler into byte codes to be loaded into the Oracle8i database for execution.
Here is an example of a short IDL file. It is the IDL for the HelloWorld
example (see "helloworld" for the complete example):
module hello { interface Hello { wstring helloWorld(); }; };
The IDL consists of a module, which contains a group of usually related object interfaces. By default, the IDL compiler uses the module name to name a directory where the IDL compiler puts the Java classes that it generates, and this maps to a Java package.
This module has only a single interface: Hello
. The Hello
interface defines a single operation: helloWorld
, which takes no parameters and returns a wstring
(a wide string, which is mapped to a Java String).
Note:
IDL data and exception types, such the |
The module and interface names must be valid Java identifiers and also valid file names for your operating system. When naming interfaces and modules, remember that both Java and CORBA objects are portable, and that some operating systems are case sensitive and some are not, so be sure to keep names distinct in your project.
You can nest modules. For example, an IDL file that specifies
module org { module omg { module CORBA { ... }; ... }; ... };
would map to the Java package hierarchy package org.omg.CORBA
.
Assume that the HelloWorld IDL is saved in a file called hello.idl
. When you run idl2java
to compile the hello
module, eight Java class files are generated and are put in a subdirectory named hello
in the same directory as the IDL file:
% idl2java hello.idl Traversing hello.idl Creating: hello/Hello.java Creating: hello/HelloHolder.java Creating: hello/HelloHelper.java Creating: hello/_st_Hello.java Creating: hello/_HelloImplBase.java Creating: hello/HelloOperations.java Creating: hello/_tie_Hello.java Creating: hello/_example_Hello.java
The ORB uses eight Java classes to invoke a remote object, pass and return parameters, and perform various other things. You can control the files generated, where they are put, and other aspects of IDL compiling (such as whether the IDL compiler generates comments in the Java files). See the complete description of the idl2java
compiler in Chapter 6, "Tools".
Each of the eight files the compiler generates is described briefly below.
Hello |
This is the interface file that specifies in Java what the interface to a Hello object looks like. In this case, the interface is: package hello; public interface Hello extends org.omg.CORBA.Object { public java.lang.String helloWorld(); }
Note that because the file is put in a
The server object developer must implement the methods in the interface. Typical of the examples in this guide is that the implementation class for an interface named |
HelloHolder |
The application uses the holder class when parameters in the interface operation are of types |
HelloHelper |
The helper classes contain methods that read and write the object to a stream, and cast the object to and from the type of the base class. For example, the helper class has a LoginServer lserver = LoginServerHelper.narrow (orb.string_to_object (loginIOR));
Note that when you get an object reference using the JNDI |
_st_Hello |
The generated files that have _st_ prefixed to the interface name are the stub files or client proxy objects. (
These classes are installed on the client that calls the remote object (the |
_HelloImplBase |
Generated source files of the form
In earlier CORBA implementations, the skeleton files were named |
HelloOperations |
The server uses these two classes for Tie implementations of server objects. See "Using the CORBA Tie Mechanism" for information about Tie classes. |
_example_Hello |
The
You can copy the example code to the directory where you will implement the |
An IDL interface body contains the following kinds of declarations:
This section gives a brief description of IDL datatypes and their mapping to Java datatypes. For more information about IDL types not covered here, see the CORBA specifications and the books cited in "For More Information".
Mapping between IDL basic types and Java primitive types is straightforward. Table 3-1 shows the mappings, as well as possible CORBA exceptions that can be raised on conversion.
The IDL character type char
is an 8-bit type, representing an ISO Latin-1 character that maps to the Java char
type, which is a 16-bit unsigned element representing a Unicode character. On parameter marshalling, if a Java char
cannot be mapped to an IDL char
, a CORBA DATA_CONVERSION exception is thrown.
The IDL string
type contains IDL chars. On conversion between Java String
, and IDL string
, a CORBA DATA_CONVERSION can be thrown. Conversions between Java strings and bounded IDL string
and wstring
can throw a CORBA MARSHALS exception if the Java String
is too large to fit in the IDL string.
Perhaps the most useful IDL constructed (aggregate) type for the Java developer is the struct. The IDL compiler converts IDL structs to Java classes. For example, the IDL specification:
module employee { struct EmployeeInfo { long empno; wstring ename; double sal; }; ...
causes the IDL compiler to generate a separate Java source file for an EmployeeInfo
class. It looks like this:
package employee; final public class EmployeeInfo { public int empno; public java.lang.String ename; public double sal; public EmployeeInfo() { } public EmployeeInfo( int empno, java.lang.String ename, double sal ) { this.empno = empno; this.ename = ename; this.sal = sal; } ...
The class contains a public constructor with parameters for each of the fields in the struct. The field values are saved in instance variables when the object is constructed. Typically, these are passed by value to CORBA objects.
The two types of ordered collections in CORBA are sequences and arrays. An IDL sequence maps to a Java array with the same name. An IDL array is a multidimensional aggregate whose size in each dimension must be established at compile time.
The ORB throws a CORBA MARSHAL exception at runtime if sequence or array bounds are exceeded when Java data is converted to sequences or arrays.
IDL also generates a holder class for a sequence. The holder class name is the sequence's mapped Java class name with Holder
appended to it.
The following IDL code shows how you can use a sequence of structs to represent information about employees within a department:
module employee { struct EmployeeInfo { long empno; wstring ename; double sal; }; typedef sequence <EmployeeInfo> employeeInfos; struct DepartmentInfo { long deptno; wstring dname; wstring loc; EmployeeInfos employees; };
The Java class code that the IDL compiler generates for the DepartmentInfo
class is:
package employee; final public class DepartmentInfo { public int deptno; public java.lang.String dname; public java.lang.String loc; public employee.EmployeeInfo[] employees; public DepartmentInfo() { } public DepartmentInfo( int deptno, java.lang.String dname, java.lang.String loc, employee.EmployeeInfo[] employees ) { this.deptno = deptno; this.dname = dname; this.loc = loc; this.employees = employees; }
Notice that the sequence employeeInfos
is generated as a Java array EmployeeInfo[]
.
Specify an array in IDL as follows:
const long ArrayBound = 12; typedef long larray[ArrayBound];
The IDL compiler generates this as:
public int[] larray;
When you use IDL constructed and aggregate types in your application, you must make sure to compile the generated .java
files and load them into the Oracle8i database when the class is a server object. You should scan the generated .java
files, and make sure that all required files are compiled and loaded. Study the Makefile
(UNIX) or the makeit.bat
batch file (Windows NT) of CORBA examples that define these types to see how the set of IDL-generated classes is compiled and loaded into the data server. A good example is "lookup".
You can create new user exception classes in IDL with the exception key word. For example:
exception SQLError { wstring message; };
The IDL can declare that operations raise user-defined exceptions. For example:
interface employee { attribute name; exception invalidID { wstring reason; }; ... wstring getEmp(long ID) raises(invalidID); }; };
Mapping between OMG CORBA system exceptions and their Java form is also quite straightforward. These mappings are shown in Table 3-2.
The Oracle8i JVM development environment offers the Inprise Caffeine tools, which enable development of pure Java distributed applications that follow the CORBA model. You can write your interface specifications in Java and use the java2iiop
tool to generate CORBA-compatible Java stubs and skeletons.
Developers can also use the java2idl
tool to code in pure Java, yet still have IDL available that can be shipped to customers who are using a CORBA server that does not support Java. This tool generates IDL from Java interface specifications. See Chapter 6, "Tools", for more information about java2iiop
and java2idl
.
This section introduces the JServer CORBA application development process. It tells you how to write a simple but useful program that runs on a client system, connects to Oracle using IIOP, and invokes a method on a CORBA server object that is activated and runs inside an Oracle8i Java VM.
This section addresses the purely mechanical aspects of the development process. See "For More Information" for references to documents on CORBA design.
The CORBA application development process has seven phases:
loadjava
tool and specifying the JAR file as its argument. Make sure to include all generated classes, such as stubs and skeletons. (Stubs are required in the server when the server object acts as a client to another CORBA object.)
The remainder of this section describes these steps in more detail, with IDL and Java code examples to illustrate the coding steps.
The first sample application asks the user for an employee number in the EMP table and returns the employee's last name and current salary, or throws an exception if there is no employee in the database with that ID number.
The application requires only a single server-side object: some code that takes an ID number and queries the database for the other information about the employee.
The interface requires three things:
The example defines an operation called query
to get the information, uses an IDL struct
to return the information, and defines an exception called SQLError
to signal that no employee was found. Here is the IDL code:
module employee { struct EmployeeInfo { wstring name; long number; double salary; }; exception SQLError { wstring message; }; interface Employee { EmployeeInfo getEmployee (in long ID) raises (SQLError); }; };
This code specifies the three things listed above: a struct named EmployeeInfo
, an operation or method named getEmployee()
, and the SQLError
exception.
Use the idl2java
compiler to compile the interface description. Because there is no use of the Tie mechanism in this example, you can invoke the compiler with the -no_tie
option. This means that two fewer classes are generated. The compiler generates the interface, helper, and holder classes for the three objects in the IDL file, as well as a stub and skeleton class for the Employee
interface. (The 12th class is the example for the interface. See "Using IDL" for more information about these classes.)
Compile the IDL as follows:
% idl2java -no_tie -no_comments employee.idl
For this example, you must implement the Employee
interface. The _example_Employee.java
file that the IDL compiler generates can provide a basis for the implementation. Here is the complete code that implements the interface:
package employeeServer; import employee.*; import java.sql.*; public class EmployeeImpl extends _EmployeeImplBase { public EmployeeImpl() { } public EmployeeInfo getEmployee (int ID) throws SQLError { try { Connection conn = new oracle.jdbc.driver.OracleDriver().defaultConnection (); PreparedStatement ps = conn.prepareStatement ("select ename, sal from emp where empno = ?"); try { ps.setInt (1, ID); ResultSet rset = ps.executeQuery (); if (!rset.next ()) throw new SQLError ("no employee with ID " + ID); return new EmployeeInfo (rset.getString (1), ID, rset.getFloat (2)); } finally { ps.close (); } } catch (SQLException e) { throw new SQLError (e.getMessage ()); } } }
This code uses the JDBC API to perform the query. Notice the use of a prepared statement to accommodate the variable in the WHERE clause of the query. See the Oracle8i JDBC Developer's Guide and Reference for more about Oracle8i JDBC. Also notice that when a JDBC SQLException
is caught, the IDL-defined SQLError
is thrown back to the client.
To access the server object you must be able to refer to it by name. In step 7 of this process, you publish the server object in the Oracle8i database. The client code looks up the published name, and activates the server object as a by-product of the look up. There are a number of other operations that go on when code such as that listed below looks up a published object. For example, the ORB on the server side is started and the client is authenticated using the environment properties supplied when the initial context object is created. See "IIOP Security".
After getting parameters such as the name of the object to look up, an IIOP service name, and some authentication information such as the database username and password, the client code performs the following four steps:
InitialContext
object with the required connect properties. See "About JNDI".
lookup()
method on the initial context, with a URL as a parameter that specifies the service name and the name of the object to be found. lookup()
returns an object reference to the Employee
CORBA server object. See "Looking Up an Object" for more information.
lookup()
method invokes the getEmployee()
method on the object in the server. This method returns an EmployeeInfo
class (derived from the IDL EmployeeInfo
struct). For simplicity, an employee ID number is hard-coded as a parameter of this method invocation.
getEmployee()
in the EmployeeInfo
class.
import employee.*; import oracle.aurora.jndi.sess_iiop.ServiceCtx; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; public class Client { public static void main (String[] args) throws Exception { String serviceURL = "sess_iiop://localhost:2481:ORCL"; String objectName = "/test/myEmployee"; // Step 1: Hashtable env = new Hashtable (); env.put (Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put (Context.SECURITY_PRINCIPAL, "SCOTT"); env.put (Context.SECURITY_CREDENTIALS, "TIGER"); env.put (Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); Context ic = new InitialContext (env); // Step 2: Employee employee = (Employee)ic.lookup (serviceURL + objectName); // Step 3 (using SCOTT's employee ID number): EmployeeInfo info = employee.getEmployee (7788); // Step 4: System.out.println (info.name + " " + info.number + " " + info.salary); } }
When the client code runs, it should print the line
SCOTT 7788 3000.0
on the client system console.
You run the client-side Java byte code compiler to compile all the Java source that you have created, including the client and server object implementation that you wrote, as well as the Java sources for the classes that were generated by the IDL compiler.
For the preceding example, you must compile the following files:
employee/Employee.java
employee/EmployeeHolder.java
employee/EmployeeInfoHolder.java
employee/EmployeeHelper.java
employee/SQLErrorHolder.java
employee/_EmployeeImplBase.java
EmployeeImpl.java
Client.java
Other generated Java files are compiled following the dependencies that the Java compiler uses.
Oracle8i JServer supports the Java JDK compiler, release 1.1.6. You might be able to use other Java compilers, such as a compiler incorporated in an IDE, but only JDK 1.1.6 is supported for this release.
CORBA server objects, such as the EmployeeImpl object created for this example, execute inside the Oracle8i database server. You must load them into the server so that they can be activated by the ORB as required. You must also load all dependent classes, such as IDL-generated Holder and Helper classes, and classes the server object uses, such as the EmployeeInfo
class of this example.
Use the loadjava
tool to load each of the server classes into the Oracle8i database. For the example in this section, issue the loadjava
command in the following way:
% loadjava -oracleresolver -resolve -user scott/tiger employee/Employee.class employee/EmployeeHolder.class employee/EmployeeHelper.class employee/EmployeeInfo.class employee/EmployeeInfoHolder.class employee/EmployeeInfoHelper.class employee/SQLError.class employee/SQLErrorHolder.class employee/SQLErrorHelper.class employee/_st_Employee.class employee/_EmployeeImplBase.class employeeServer/EmployeeImpl.class
You do not load any client implementation classes or any other classes not used on the server side.
It is sometimes more convenient to combine the server classes into a JAR file, and simply use that file as the argument to the loadjava
command. In this example, you could issue the command:
% jar -cf0 myJar.jar employee/Employee.class employee/EmployeeHolder.class \ employee/EmployeeHelper.class employee/EmployeeInfo.class \ employee/EmployeeInfoHolder.class employee/EmployeeInfoHelper.class \ employee/SQLError.class employee/SQLErrorHolder.class \ employee/SQLErrorHelper.class employee/_st_Employee.class \ employee/_EmployeeImplBase.class employeeServer/EmployeeImpl.class
and then give the loadjava
command as simply:
% loadjava -oracleresolver -resolve -user scott/tiger myJar.jar
The final step in preparing the application is to publish the name of the CORBA server object implementation in the Oracle8i database. See "The Name Space" for information about publishing and published objects.
For the example in this section, you can publish the server object using the publish
command as follows:
% publish -republish -user scott -password tiger -schema scott -service sess_iiop://localhost:2481:ORCL /test/myEmployee employeeServer.EmployeeImpl employee.EmployeeHelper
This command specifies the following:
publish
--run the publish command
republish
--overwrite any published object of the same name
user scott
--scott is the username for the schema doing the publishing
-password tiger
--Scott's password
-schema scott
--the name of the schema in which to resolve classes
-service sess_iiop://localhost:2481:ORCL
--establishes the service name (see also "The Service Context Class")
/test/myEmployee
--the name for the published object
employeeServer.EmployeeImpl
--the name of the class, loaded in the database, that implements the server object
employee.EmployeeHelper
--the name of the helper class
See "publish" for more information about the publish
command and its arguments.
To run this example, execute the client class using the client-side Java VM. For this example, you must set the CLASSPATH for the java
command to include:
classes.zip
)
vbjapp.jar
and vbjorb.jar
aurora_client.jar
You can locate these libraries in the lib
directory under the Oracle home location in your installation.
The following invocation of the JDK java
command runs this example. The UNIX shell variable ORACLE_HOME might be represented as %ORACLE_HOME% on Windows NT and that JDK_HOME is the installation location of the Java Development Kit (JDK), version 1.1.6:
% java -classpath .:$(ORACLE_HOME)/lib/aurora_client.jar:$(ORACLE_HOME)/jdbc/lib/classes111.zip: $(ORACLE_HOME)/sqlj/lib/translator.zip:$(ORACLE_HOME)/lib/vbjorb.jar: $(ORACLE_HOME)/lib/vbjapp.jar:$(JDK_HOME)/lib/classes.zip Client sess_iiop://localhost:2481:ORCL /test/myEmployee scott tiger
This example assumes that you invoke the client with four arguments on the command line:
From the java
command you can see why it is almost always better to use a makefile or a batch file to build CORBA applications.
One of the fundamental tasks that a CORBA programmer faces is discovering how to get a reference to a server object. The CORBA specifications permit a great deal of freedom to the implementer in this area.
As you saw in the example in the previous section, the Oracle8i solution is to publish non-transient objects in a Oracle8i database instance, using a CORBA CosNaming service. JServer provides a URL-based JNDI interface to CosNaming, to make it easy for clients written in Java to locate and activate published objects.
The name space in the database looks just like a typical file system. You can examine and manipulate objects in the publishing name space using the session shell tool. (See "sess_sh" for information about the session shell.) There is a root directory, indicated by a forward slash ('/'). The root directory is built to contain three other directories: bin
, etc
, and test
. The /test
directory is the place where most objects are published for the example programs in this guide. You can also create new directories under root to hold objects for separate projects, however, you must have access as database user SYS to create new directories under the root.
There is no effective limit to the depth that you can nest directories.
The /etc
directory contains objects the ORB uses. Do not delete objects in the /etc
directory. They are owned by SYS; therefore, to delete them, you would have to be connected in the session shell as SYS. The objects contained in /etc
are:
deployejb execute loadjava login transactionFactory
The entries in the name space are actually represented by objects that are instances of the classes oracle.aurora.AuroraServices.PublishingContext
and oracle.aurora.AuroraServices.PublishedObject
. A publishing context represents a class that can contain other objects (a directory), and the PublishedObject
class is used for the leafs of the tree, that is the object names themselves. These classes are documented in the JavaDoc on the product CD.
Published names for objects are stored in a database table. Each published object also has a set of associated permissions, maintained in a separate table in the system tablespace. Each class or resource file can have a combination (union) of the following permissions:
The holder of read permission can list the class or the attributes of the class, such as its name, its helper class, and its owner.
The holder of write permission for a context can bind new object names into a context. For an object (a leaf node of the tree), write permission allows the holder to republish the object under a different name.
You must have execute permission to resolve and activate an object represented by a context or published object name.
You can set these permissions when the objects are loaded into the database, and can use the session shell tool to view and change object permissions. See "sess_sh" for information about this tool.
Publishing means registering the object name in the database name service. The steps involved are as follows:
The JNDI lookup()
method is the normal way that a client looks up an object whose name is published in the name space. When you invoke the lookup()
method, you normally pass it a String parameter that specifies a URL containing the following:
The service name specifies a service that an IIOP presentation manages, and it represents a database instance. The format of the service URL is explained in "URL Syntax". Briefly, the service name specifies the following components:
A typical example of a service name is sess_iiop://localhost:2481:ORCL
, where sess_iiop
is the service, localhost
defaults to the host of the local database, 2481 is the default listener port for IIOP connections, and ORCL
is the SID.
For more information about the service name, see "URL Syntax".
The object name specifies the complete path name of the published object that you want to look up. For example: /test/myServer
.
See "The JNDI InitialContext Class" for further information about the lookup()
method.
A CORBA application requires that an ORB be active on both the client system and the system running the server. In looking at the examples shown so far in this chapter, it is not obvious how the ORB is activated, either on the client or the server. This section presents more information about that topic.
The client-side ORB is normally initialized as part of the processing that goes on when the client invokes the lookup()
method on the JNDI InitialContext
object that it instantiates.
If you need to get a reference to the client ORB, use the init()
method on the ORB pseudo-object to get it, as shown in this statement:
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init ();
The init()
method invoked on the client with no parameters always returns a reference to the existing client ORB.
The presentation that manages IIOP requests starts the ORB on the server when the session is created.
Objects are activated on demand. When a client looks up an object, the ORB loads the object into memory and caches it. To activate the object, the ORB looks up the class by the fully-qualified class name under which the object was published. The class name is resolved in the schema defined at publication time, rather than the caller's schema. See the description of the command-line tool "publish" for more information.
When the class is located, the ORB creates a new instance of the class, using newInstance()
. For this reason, the no-argument constructor of a persistent object class must be public. If the class implements the oracle.aurora.AuroraServices.ActivatableObject
interface (as determined by reflection), then the _initializeAuroraObject()
message is sent to the instance. (See "Using the CORBA Tie Mechanism" for an example that requires _initializeAuroraObject())
.
There is no need for the server implementation to register persistent objects with the object adapter using a boa.obj_is_ready()
call--the JServer ORB performs this automatically.
You register transient objects generated by other objects, such as persistent published objects, with the BOA using obj_is_ready()
. For an example, see the factory
demo in the examples/corba/basic/factory
directory of the product CD.
Visibroker enables you to implement interceptors. The Visibroker documentation provides details for how to create them. When you want server interceptors in Oracle8i, you must create a PUBLIC synonym for the interceptor init
class that implements ServiceInit
.
You can often simplify the implementation of a CORBA server object by using Oracle8i SQLJ to perform static SQL operations. Using SQLJ statements results in less code than the equivalent JDBC calls and makes the implementation easier to understand and debug. This section describes a version of the example first shown in "A First CORBA Application", but uses SQLJ rather than JDBC for the database access. Refer to the Oracle8i SQLJ Developer's Guide and Reference for complete information about SQLJ.
The only code that changes for this SQLJ implementation is in the EmployeeImpl.java
file, which implements the Employee
object. The SQLJ implementation, which can be called EmployeeImpl.sqlj
, is listed below. You can contrast that with the JDBC implementation of the same object in "Write the Server Object Implementation".
package employeeServer; import employee.*; import java.sql.*; public class EmployeeImpl extends _EmployeeImplBase { public EmployeeInfo getEmployee (int ID) throws SQLError { try { String name = null; double salary = 0.0; #sql { select ename, sal into :name, :salary from emp where empno = :ID }; return new EmployeeInfo (name, empno, (float)salary); } catch (SQLException e) { throw new SQLError (e.getMessage ()); } } }
The SQLJ version of this implementation is considerably shorter than the JDBC version. In general, Oracle recommends that you use SQLJ where you have static SQL commands to process, and use JDBC, or a combination of JDBC and SQLJ, in applications where dynamic SQL statements are required.
To compile the EmployeeImpl.sqlj
file, you issue the following SQLJ command:
% sqlj -J-classpath .:$(ORACLE_HOME)/lib/aurora_client.jar:$(ORACLE_HOME)/jdbc/lib/classes111.zip: $(ORACLE_HOME)/sqlj/lib/translator.zip:$(ORACLE_HOME)/lib/vbjorb.jar: $(ORACLE_HOME)/lib/vbjapp.jar:$(JDK_HOME)/lib/classes.zip -ser2class employeeServer/EmployeeImpl.sqlj
This command does the following:
.java
source to get a .class
file
ser2class
option translates SER files to .class
files
The SQLJ translation generates two additional class files:
employeeServer/EmployeeImpl_SJProfile0 employeeServer/EmployeeImpl_SJProfileKeys
which you must also load into the database when you execute the loadjava
command.
This example is available in complete form in the examples/corba/basic example directory, complete with a Makefile or Windows NT batch file so you can see how the example is compiled and loaded. See also "sqljimpl".
Oracle8i JServer updated its ORB implementation to Visibroker 3.4, which is compatible with both JDK 1.1 and Java 2.
Sun Microsystems's Java 2 contains an OMG CORBA implementation; JDK 1.1 did not contain an OMG CORBA implementation. Thus, when you imported the Inprise libraries and invoked the CORBA methods, it always invoked the Visibroker implementation. With the implementation being contained in Java 2, if you invoke the CORBA methods without any modifications--as discussed below--you will invoke the Sun Microsystems CORBA implementation, which can cause unexpected results.
The following lists the three methods for initializing the ORB on the client-side and recommendations for bypassing the Sun Microsystems CORBA implementation:
If you are using JNDI on the client to access CORBA objects that reside in the server, no code changes are necessary. However, you must regenerate your CORBA stubs and skeletons.
If your client environment uses JDK 1.1, you do not need to change your existing code. However, you must regenerate your stubs and skeletons.
If your client environment has been upgraded to Java 2, you can initialize the ORB through the oracle.aurora.jndi.orb_dep.Orb.init
method. This method guarantees that when you initialize the ORB, it will initialize only a single ORB instance. That is, if you use the Java 2 ORB interface, it returns a new ORB instance each time you invoke the init
method. Aurora's init
method initializes a singleton ORB instance. Each successive call to init
returns an object reference to the existing ORB instance.
In addition, the Aurora ORB interface manages the session-based IIOP connection.
There are several init
methods, each with a different parameter list. The following describes the syntax and parameters for each init
method.
If you execute the ORB.init
method that takes no parameters, it does the following:
public com.visigenic.vbroker.orb.ORB init();
If you execute the ORB.init
method that takes the ORB properties as the only parameter, it does the following:
public org.omg.CORBA.ORB init(Properties props);
If you execute the ORB.init
method that takes the ORB properties and ORB command-line arguments, it always creates an ORB instance and returns the reference to you.
public org.omg.CORBA.ORB init(String[] args, Properties props);
Parameter | Description |
---|---|
Properties props |
ORB system properties. |
String[] args |
Arguments that are passed to the ORB instance. |
The following example shows a client instantiating an ORB using the Aurora Orb class.
// Create the client object and publish it to the orb in the client
// Substitute Aurora's Orb.init for OMG ORB.init call
// old way: org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init ();
com.visigenic.vbroker.orb.ORB orb = oracle.aurora.jndi.orb_dep.Orb.init();
If you execute the ORB.init
method that provides the ORB properties, username, password, and role as parameters, it does the following:
You would use this method when your client chooses to not use JNDI for ORB initialization and it receives a reference to an existing object from another client. To access an active object within a session, the new client must authenticate itself to the database in one of two ways:
init
method parameters. Then, when you invoke a method on the supplied object reference, the username, password, and role are passed implicitly on the first message to authenticate the client to the database.
authenticate
method of the login object. Then, it executes any method on the object.
This method is how a second client invokes an active object in an established session.
public org.omg.CORBA.ORB init(String un, String pw, String role, boolean ssl, java.util.Properties props);
If you have implemented a pure CORBA client--that is, you do not use JNDI--you must set the following properties before the ORB initialization call. These properties direct the call to the Aurora implementation, rather than the Java 2 implementation. This ensures the behavior that you expect. The behavior expected from Visibroker is as follows:
ORB.init
more than once, JServer creates only a single ORB instance. If you do not set these properties, be aware that each invocation of ORB.init
will create a new ORB instance.
Property | Assign Value |
---|---|
org.omg.corba.ORBClass |
com.visigenic.vbroker.orb |
org.omg.corba.ORBSingletonClass |
com.visigenic.vbroker.orb |
The following example shows how to set up the OMG properties for directing the OMG CORBA init
method to the Visibroker implementation.
System.getProperties().put("org.omg.CORBA.ORBClass",
"com.visigenic.vbroker.orb.ORB"); System.getProperties().put("org.omg.CORBA.ORBSingletonClass",
"com.visigenic.vbroker.orb.ORB");
Or you can set the properties on the command line, as follows:
java -Dorg.omg.CORBA.ORBClass=com.visigenic.vbroker.orb.ORB
-Dorg.omg.CORBA.ORBSingletonClass=com.visigenic.vbroker.orb.ORB
The tools provided with Oracle8i, such as publish
, have been modified to work with either a JDK 1.1 or Java 2 environment. However, any code that has been generated or loaded with the 8.1.5 version of any tool, will not succeed. Make sure that you always use the 8.1.6 version of all tools. This rule applies to your CORBA stubs and skeletons. You must regenerate all stubs and skeletons with the 8.1.6 IDL compiler.
This section describes how a CORBA server object can call back to a client. The basic technique that is shown in this example is the following:
The IDL for this example is shown below. There are two separate IDL files: client.idl
and server.idl
:
/* client.idl */ module client { interface Client { wstring helloBack (); }; }; /* server.idl */ #include <client.idl> module server { interface Server { wstring hello (in client::Client object); }; };
Note that the server interface includes the interface defined in client.idl
.
The client code for this example must instantiate the client-side callback object and register it with the BOA so that it can be accessed by the server. The code performs the following steps to do this:
init()
method, with no parameters, on the ORB pseudo-object. This returns a reference to the existing client-side ORB.
The code to perform these steps is as follows:
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init (); org.omg.CORBA.BOA boa = orb.BOA_init (); ClientImpl client = new ClientImpl (); boa.obj_is_ready (client);
Finally, the client code calls the server object, passes it a reference to the registered client-side callback object, and prints its return value, as follows:
System.out.println (server.hello (client));
The implementation of the server-side object is very simple:
package serverServer; import server.*; import client.*; public class ServerImpl extends _ServerImplBase { public String hello (Client client) { return "I Called back and got: " + client.helloBack (); } }
The server simply returns a string that includes the string return value from the callback.
The client-side callback server is implemented like this:
package clientServer; import client.*; public class ClientImpl extends _ClientImplBase { public String helloBack () { return "Hello Client World!"; } }
The client-side object is just like any other server object. But in this callback example it is running in the client ORB, which can be running on a client system, not necessarily running inside an Oracle8i database server.
Among the CORBA examples shipped on the CD there is a very interesting variant of the callback example called printback
. This example shows how a server object can call back to a client to print strings from the server on the client's console. You can use code like this for debugging a running server object.
There is only one special consideration when you use the CORBA Tie, or delegation, mechanism rather than the inheritance mechanism for server object implementations. In the Tie case, you must implement the oracle.aurora.AuroraServices.ActivatableObject
interface. This interface has a single method: _initializeAuroraObject().
(Note that earlier releases of the Oracle8i ORB required you to implement this method for all server objects. For this release, its implementation is required only for Tie objects.)
The implementation of _initializeAuroraObject()
for a tie class is typically:
import oracle.aurora.AuroraServices.ActivatableObject; ... public org.omg.CORBA.Object _initializeAuroraObject () { return new _tie_Hello (this); ...
where _tie_<interface_name>
is the tie class generated by the IDL compiler.
You must also always include a public, parameterless constructor for the implementation object.
See the tieimpl
example in the CORBA examples set for a complete example that shows how to use the Tie mechanism. See also "tieimpl" for the code.
You can interoperate with Oracle8i from a client that uses another vendor's ORB. To do so, the vendor must provide the functionality that Oracle8i uses by being part of the database: functions such as session-based connections, extended CosNaming functions, and the login protocol. To provide this functionality, your ORB vendor must work with Oracle's Product Management to provide libraries for you.
All client-side functionality has been packaged into aurora_client.jar. This JAR file has been broken into two JAR files for interoperating with your ORB vendor:
aurora_orbindep.jar
--includes ORB independent features, such as JNDI
aurora_orbdep.jar
--includes Oracle ORB dependent functionality, such as session-based communication, the login protocol, and security context
Your ORB vendor needs to provide you the aurora_orbdep.jar
file. Thus, you include their aurora_orbdep.jar
file and the Oracle-provided aurora_orbindep.jar
file to replace aurora_client.jar
.
The aurora_orbdep.jar includes the following functionality:
Function | Description |
---|---|
login |
The login protocol performs the challenge/response protocol for authenticating the client to the database. See "IIOP Security" for more information. |
bootstrap |
The boot service obtains key services, such as CosNaming. |
extended CosNaming |
The Aurora ORB extended CosNaming to automatically instantiate an object upon first lookup. |
Session IIOP |
Session IIOP is implemented to allow one client connect to more than a single IIOP session at the same time. See "Configuring CORBA and EJB in JServer" for more information. |
Credentials |
The security context interceptor for the credential type of authentication. |
You perform the following if you choose to use the Oracle-provided ORB on your client:
aurora_client.jar
in a directory that exists in the CLASSPATH.
You perform the following if you choose to use another vendor's ORB on your client:
aurora_orbindep.jar
in a directory that exists in the CLASSPATH.
aurora_orbdep.jar
.
aurora_orbdep.jar
in a directory that exists in the CLASSPATH.
With C++ clients, the ORB vendor must provide the aurora_client.jar
file functionality in shared libraries. The vendor will make use of Oracle-provided C++ login protocol for authentication. All clients are required to authenticate themselves to the database. One of the methods for authenticating is through the login protocol.
The login protocol is an Oracle-specific design, used for logging in to a database by providing a username and password to authenticate the client. The following example shows how to write a sample C++ CORBA client to Oracle8i. This example uses the Visigenics C++ ORB for its client-side ORB.
The following C++ client uses the Visigenics C++ ORB for the client-side ORB. Your implementation can be different, depending on the type of ORB you use.
#include <Login.h> #include <oracle_orbdep.h> // set up host, port, and SID char *sid = NULL; char *host = argv[1]; int port = atol(argv[2]); if(argc == 4) sid = argv[3]; // set up username, password, and role wchar_t *username = new wchar_t[6]; username[0] = 's'; username[1] = 'c'; username[2] = 'o'; username[3] = 't'; username[4] = 't'; username[5] = '\0'; wchar_t *password = new wchar_t[6]; password[0] = 't'; password[1] = 'i'; password[2] = 'g'; password[3] = 'e'; password[4] = 'r'; password[5] = '\0'; wchar_t *role = new wchar_t[1]; role[0] = '\0'; // Get the Name service Object reference AuroraServices::PublishingContext_ptr rootCtx = NULL; // Contact Visibroker's boot service for initializing rootCtx = VisiCppBootstrap::getNameService (host, port, sid); // Get the pre-published login object reference AuroraServices::PublishedObject_ptr loginPubObj = NULL; AuroraServices::LoginServer_ptr serv = NULL; CosNaming::NameComponent *nameComponent = new CosNaming::NameComponent[2]; nameComponent[0].id = (const char *)"etc"; nameComponent[0].kind = (const char *)""; nameComponent[1].id = (const char *)"login"; nameComponent[1].kind = (const char *)""; CosNaming::Name *name1 = new CosNaming::Name(2, 2, nameComponent, 0); // Lookup this object in the Name service CORBA::Object_ptr loginCorbaObj = rootCtx->resolve (*name1); // Make sure it is a published object loginPubObj = AuroraServices::PublishedObject::_narrow (loginCorbaObj); // create and activate this object (non-standard call) loginCorbaObj = loginPubObj->activate_no_helper (); serv = AuroraServices::LoginServer::_narrow (loginCorbaObj);// Create a client login proxy object and authenticate to the DB
oracle_orbdep *_visi = new oracle_orbdep(serv);
Login login(_visi);
boolean res = login.authenticate(username, password, role);
Until Java IDEs and JVMs support remote debugging, you can adopt several techniques for debugging your CORBA client and server code.
Debug in a single address space, on a client system. Use of an IDE for client or server debugging is optional, though highly desirable.
The output of System.out.println()
in the Oracle8i ORB goes to the server trace files. The directory for trace files is a parameter specified in the INITSID.ORA file. Assuming a default install of the product into a directory symbolically named ORACLE_HOME, then the trace file would appear as
${ORACLE_HOME}/admin/<SID>/bdump/ORCL_s000x_xxx.trc
where ORCL is the SID, and x_xxx represents a process ID number. Do not delete trace files after the Oracle instance has been started, or no output is written to a trace file. If you do delete trace files, stop and then restart the server.
For debugging only, set the MTS_SERVERS parameter in your INITSID.ORA file to MTS_SERVERS = 1, and set the MTS_MAX_SERVERS to 1. Having multiple MTS servers active means that a trace file is opened for each server process, and, thus, the messages get spread out over several trace files, as objects get activated in more than one session.
You can use the technique demonstrated in the example program "printback" to redirect System.out
and System.err
println
to the client system console.
Perhaps the best way to develop and debug Java/CORBA code is to use either the second or third technique described above, then deploy into the Oracle8i ORB.
This section lists some resources that you can access to get more information about CORBA and about CORBA application development using Java.
The ORB and some of the CORBA services supplied with Oracle8i JServer are based on VisiBroker for Java code licensed from Inprise. Programming with VisiBroker, by D. Pedrick et al. (John Wiley and Sons, 1998), provides both an introduction to CORBA development from the VisiBroker point of view and an in-depth look at the VisiBroker CORBA environment.
Client/Server Programming with Java and CORBA, by R. Orfali and D. Harkey (John Wiley and Sons, 1998), covers CORBA development in Java. This book also uses the VisiBroker implementation for its examples.
You should be aware that the examples published in both of these books require some modification to run in the Oracle8i ORB. It is better to start off using the examples in the Appendices to this Guide, which are more extensive than the examples in the books cited and demonstrate all the features of Oracle8i CORBA. See also Appendix C, "Comparing the Oracle8i JServer and VisiBroker VBJ ORBs" for a discussion of the major differences between VisiBroker for Java and the Oracle8i implementation.
You can download specifications for CORBA 2.0 and for CORBA services from links available at the following web site:
http://www.omg.org/library/downinst.html
Documentation on Inprise's VisiBroker for Java product is available at:
http://www.inprise.com/techpubs/visibroker/visibroker33/