Our next task is to build a small EJB "hello world" example running in a J2EE Application Server, accessed by a .NET client written in C# using J-Integra® Espresso. Even though an EJB example should not be much more complicated than the "standard" Java RMI example, there is added complexity because of the fact that the concrete EJB deployment process is dependent on the AppServer's vendor. Therefore, this part of the example is not really vendor independent. We will concentrate here on the Sun ONE Application Server delivered together with the J2EE SDK 1.4. Refer to your AppServer's vendor documentation for adaptation to other J2EE platforms.
To create an EJB for deployment within an arbitrary J2EE/EJB server, we have to create a couple of Java files, namely the Home Interface, the Remote Interface, the Bean Class, and an appropriate XML Deployment Descriptor.
All the Java classes and interfaces have to be derived from the appropriate standard parents, located within the package javax.ejb. Additionally, we have to throw not only the standard java.rmi.RemoteException, but also some other EJB specific exceptions. Beside this, the Java code pretty much resembles our Java RMI example.
We start with the Home Interface, located in our "Hello" package (Hello\Greetings_Home.java):
package Hello; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface Greetings_Home extends EJBHome { public Greetings_Remote create() throws RemoteException, CreateException; } |
For our example we implement a simple stateless Session Bean. Accordingly, our Home Interface has to define a create() method.
The Remote Interface looks pretty much the same as in the Java RMI example except for the different parent class (Hello\Greetings_Remote.java):
package Hello; import javax.ejb.EJBObject; import java.rmi.RemoteException; public interface Greetings_Remote extends EJBObject { public String hello ( String name) throws java.rmi.RemoteException; } |
Finally, we need an appropriate implementation for our interfaces. Even though our Home Interface publishes only a simple create() method, we have to provide the full spectrum of a Session Bean's lifecycle management methods due to the bean contract specification. So we provide some almost empty implementations, just writing a short trace message to System.out. Note that there is no need for these lifecycle methods to declare throwing any specific exceptions since these methods are only delegates that are used locally by code generation during deployment. For the same reason, the bean implementation does not syntactically implement its Remote Interface.
Below is the bean implementation (Hello\Greetings_Bean.java) :
package Hello; import java.rmi.RemoteException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import javax.ejb.CreateException; public class Greetings_Bean implements SessionBean { public Greetings_Bean () { super (); } // Session Bean contract methods private SessionContext m_ctx = null; public void setSessionContext(SessionContext ctx) {m_ctx = ctx;} public void ejbCreate() throws RemoteException, CreateException { System.out.println("ejbCreate() on " + this); } public void ejbRemove() { System.out.println("ejbRemove() on " + this); } public void ejbActivate() { System.out.println("ejbActivate() on " + this); } public void ejbPassivate() { System.out.println("ejbPassivate() on " + this); } // Remote Interface implementation public String hello(String name) throws java.rmi.RemoteException { return "Greeting from J2EE Server to " + name; }; } |
The last file we need for our EJB is an appropriate Deployment Descriptor describing the bean structure, transactional behavior (here: bean managed), and its unique EJB name. The optional display name is only for use in management tools (ejb-jar.xml):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'> <ejb-jar> <enterprise-beans> <session> <display-name>Hello</display-name> <ejb-name>Hello</ejb-name> <home>Hello.Greetings_Home</home> <remote>Hello.Greetings_Remote</remote> <ejb-class>Hello.Greetings_Bean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Bean</transaction-type> </session> </enterprise-beans> </ejb-jar> |
The procedure discussed so far is common to all AppServers. Now we have to take care of the vendor specific deployment procedure (e.g. providing the actual name for the naming service). In case of the Sun ONE AppServer, this is done by a second deployment descriptor (sun-ejb-jar.xml):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Sun ONE Application Server 8.0 EJB 2.1//EN" "http://www.sun.com/software/sunone/appserver/dtds/sun-ejb-jar_2_1-0.dtd"> <sun-ejb-jar> <enterprise-beans> <unique-id>1</unique-id> <ejb> <ejb-name>Hello</ejb-name> <jndi-name>ejb/Hello</jndi-name> </ejb> </enterprise-beans> </sun-ejb-jar> |
Finally, we are ready to compile and deploy our bean. Sun prefers the Ant tool for this task, which is very popular within the Java community. It is a sophisticated tool using XML files as its input. Here is an example (supposing the default path for the Sun ONE AppServer was accepted during installation). It is documented here for the mere sake of completeness, so we will not comment on it any further (build.xml):
<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE project [ <!ENTITY include SYSTEM "c:/Sun/AppServer/samples/common.xml"> ]> <project name="serialization" default="core" basedir="."> <property name="appname" value="Hello"/> <property name="sample.home" value="c:/Sun/AppServer/samples/"/> <property name="app.pkg" value="Hello"/> <property name="jar.pkg" value="Hello"/> <property name="javadoc.pkgnames" value="Hello.*" /> <property name="jarDD" value="ejb-jar.xml,sun-ejb-jar.xml"/> &include; <target name="clean" depends="clean_common"/> <target name="init" depends="init_common"> <condition property="deploy.file" value="${assemble.ear}/${ear}"> <available file="${assemble.ear}/${ear}"/> </condition> <property name="deploy.file" value="../${ear}"/> <condition property="verify.file" value="${assemble.ear}/${ear}"> <available file="${assemble.ear}/${ear}"/> </condition> <property name="verify.file" value="../${ear}"/> </target> <target name="copy_ear" depends="init"> <delete file="../${ear}"/> <copy file="${assemble.ear}/${ear}" todir=".." /> </target> <target name="compile" depends="compile_common" /> <target name="javadocs" depends="javadocs_common" /> <target name="deploy" depends="init, deploy_common"/> <target name="undeploy" depends="undeploy_common"/> <target name="verify" depends="init, verify_common"/> <target name="jar" depends="init,create_ejbjar_common"/> <target name="ear" depends="init,jar,create_ear_common"/> <target name="core" depends="compile,jar,ear" /> <target name="all" depends="core,javadocs,verify,deploy"/> </project> |
The make process may be started by calling Sun's "asant" on the project root (where the build.xml is located).
As before, we move to CORBA and generate the IDL code from the Java JAR file using the J-Integra® Espresso IDL generator:
> mkdir ..\IDL > set classpath=%classpath%;C:\Sun\AppServer\lib\j2ee.jar > IcsJava2Idl -cp %Classpath%;.\ejb\assemble\jar\helloEjb.jar -v -p -d ..\IDL Hello.Greetings_Home |
For the .NET client, we have a project root of its own. Assuming it's a directory "DotNetClient" located parallel to the Java project root, we may now call the J-Integra® Espresso IDL compiler:
> mkdir ..\IDL > IcsIdl2Cs -d ..\DotNetClient\Generated ..\IDL\Hello.idl |
For the .NET client we again create a console application adding the following files and references:
The code is very similar to the client in the Java RMI server example with the only difference being that we have to initialize the ORB with the address for the AppServer's Naming Service (assuming that a local installation of the Sun ONE AppServer with its CORBA Naming Service listening on port 3700 is used), and retrieve the server object's Home Interface reference to create a new Session Bean instance to call.
The .NET clients main (main.cs) looks as follows:
using System; using Ics.J2EE; using Ics.Runtime; namespace DotNetClient { class HelloClient { private Ics.CORBA.ORB orb; static void Main(string[] args) { HelloClient oHelloClient = new HelloClient(); oHelloClient.run( args); Console.WriteLine( "bye"); } private void run(string[] args) { Hello.Greetings_Remote oGreetings; J2eeQuickConfig qc = new J2eeQuickConfig(ApplicationServer.SUN_JDK_RMI_IIOP); // For Sun application Server qc.SetNamingServicePort(3700); // Set the host //qc.SetApplicationServerHost("some_machine"); // Default Java version 1.4.2. Options are JDK_131, JDK_141, JDK_142 and JDK_150 qc.SetTargetJavaVersion(JavaVersion.JDK_150); // Turn on log with name Log.txt and without appending exist log file // qc.SetApplicationLog(OrbErrorLevel.ERRORS, "Log.txt", false); J2eeEasyContext.Init(qc); Hello.Greetings_Home oGreetingsHome = (Hello.Greetings_Home) J2eeEasyContext.Lookup("ejb/Hello", "Hello.Greetings_Home"); oGreetings = oGreetingsHome.create(); string strRet = oGreetings.hello("Ics Client"); System.Console.WriteLine("{0}", strRet); System.Console.WriteLine("Press Enter to exit."); System.Console.ReadLine(); J2eeEasyContext.ShutdownRuntime(); } } } |
Start the Sun ONE AppServer and deploy your bean by copying Hello.ear into the AppServer's autodeploy directory (from the Java project root):
> asadmin start-domain domain1 > copy ..\assemble\ear\Hello.ear c:\Sun\AppServer\domains\domain1\autodeploy |
Finally, start the .NET client.