How to setup a ejb remote application using JBoss and Hibernate

Updated (04-dec-2004): Very
basic example on how you can call a bean remotely through HTTP.
Tested with JBoss 3.2.6. You can download it here
(source).
Follow this steps:
– modify build.properties with you jboss path and src/httpTest/remote/jndi.properties
with your hostname
– copy jboss-common.jar, jboss-transaction.jar, jnpserver.jar, jboss-j2ee.jar,
jboss.jar to the lib directory – run ant deploy (will deploy the ear to you jboss)
– run ant test (requires jboss running)
– inspect the code, enjoy.

Document purpose: simple walkthrough the configurations
needed to create a simple remote application using JBoss and Hibernate.

![overall.png](overall.png/image_preview) Graphic 1:Overall architecture
**I. Server part**

1. Start
with the hibernate mapping file (mapping.hdb.xml) and the hibernate.properties
for the database structure.

2. Generate
the classes (using CodeGenerator) compile them and run SchemaExport
to generate the database structure.
Note:
I am using postgresql as a database backend

3.
Download and install JBoss (using jboss 3.0.6) from jboss.org.

4. Configure
postgresql support in jboss. Copy postgresql.jar
(or what ever name the driver jar for your postgresql version might
have) to the ${jboss.home}/server/[configuration]/lib directory.
Edit (set your db config) and copy the http://www.len.ro/work/articles/jboss/ejb-remoting/postgres-service.xml
to ${jboss.home}/server/[configuration]/deploy directory.

<properties> <config-property name=”ConnectionURL” type=”java.lang.String”>jdbc:postgresql://[HOSTNAME]:5432/[DBNAME]</config-property> <config-property name=”DriverClass” type=”java.lang.String”>org.postgresql.Driver</config-property> <config-property name=”UserName” type=”java.lang.String”>[USERNAME]</config-property> <config-property name=”Password” type=”java.lang.String”>[password]</config-property> </properties>

Note:
Since I am using the “default” server [configuration]

5. Configure hibernate support in jboss. Copy the following
jars, which can be found in ${hibernate.home}/lib, to the ${jboss.home}/server/default/lib
directory: cglib.jar, commons-collections.jar,
commons-logging.jar, commons-lang.jar, jcs.jar, odmg.jar, hibernate.jar

6. This
and task can be used to create and deploy the sar.

<target name=”sar” depends=”compile”> <mkdir dir=”${build.deploy.dir}”/> <jar jarfile=”${build.deploy.dir}/hibernate.sar” index=”true”> <metainf dir=”${basedir}/conf”> <include name=”http://www.len.ro/work/articles/jboss/ejb-remoting/jboss-service.xml“/> </metainf> <fileset dir=”${basedir}” includes=”*.hbm.xml”/> <fileset dir=”${build.classes.dir}” includes=”[package for hibernate generated classes]/**” > </fileset> </jar> <copy todir=”${jboss.deploy}”> <fileset dir=”${build.deploy.dir}” includes=”*.sar”> </fileset> </copy>

7. At this point you can test the config by starting jboss.
If you see no error then everything is ok! Otherwise review the
previous steps.

8. Writing the ejb’s. I used an approach similar to the DAO
pattern
. The classes are: bussinessObject (session bean – XBean),
DAO (XDao), DataSource (this is hibernate), value object (the class
generated from the mapping xml – XData).

9. Access hibernate from the ejbs. Get the Session object:

Context ctx = new InitialContext(); SessionFactory factory = (SessionFactory) ctx.lookup(“java:/hibernate/HibernateFactory”); sess = factory.openSession();

Note:
if you use a statefull session bean then you might think to safe
the sess object. DO NOT do that since the container might want to
passivate the bean and the sess can not be serialized while it holds
an open connection.

Write a method in the session bean – XBean:

/** * * @ejb:bean name=”pilot/X” * display-name=”X Bean” * type=”Stateful” * transaction-type=”Container” * jndi-name=”pilot/X” * view-type=”remote” * * @ejb:transaction type=”Supports” * * @ejb:permission unchecked=”” * **/ public class XBean implements SessionBean{ /** * @ejb:interface-method view-type=”remote” * @ejb:permission role-name=”user” **/ public XData getX(long xId) throws RemoteException, BusinessException{ ….. fetch the sess ……… try{ X DAO dao = new XDAO(sess); return dao.getX(xId); }catch(BusinessException e){ mContext.setRollbackOnly(); throw e; }finally{ sess.close(); } ….. usual ejb code …….. }

Write the corresponding DAO method. That is done using hibernate
via the sess object.

10. As you see I use XDoclet so now you can use it to generate
everything. You can find a lot of information on this. I used the
example s from jboss (works with xdoclet 1.1.2) and the one from
middlegen (works with xdoclet 1.2.b2)

11. You should be done with the server part by now. Deploy
the ejb’s and start working on the client.
Note: you should write tests for this part. I use
junit + ant to do that.

II. Client
part

1. This
is rather easy. Obtain the session beans remotely and call methods
on them.

InitialContext lContext = new InitialContext(); lContext.getEnvironment(); System.out.println(“lContext = ” + lContext.getEnvironment()); XHome xHome = (XHome) PortableRemoteObject.narrow( lContext.lookup( “java:pilot/X” ), XHome.class ); X x=XHome.create(); x.singAndDance(); ………………………….. x.remove();

This assumes
that you have jndi.properties
file in you classpath similar to

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=[your ip address]

2. For
the GUI part I use XgMLui is a simple tool that lets developers
specify the UI (view) using an XML file, instead of hundreds or
potentially thousands of lines of Java code. The result is something
like (or the similar linux version):

III. Firewall
(here the trouble begins)

If there are
firewalls between JBoss and it’s clients we have some problems.
JBoss makes use of several ports to communicate between the client
and the server. Some of these ports are configurable static ports
but some ports are randomly assigned at runtime. The problems concentrate
on JNDI and RMI. There are several strategies to handle this kind
of problem but the easiest one is using JBoss JNDI and RMI over
HTTP options.

III.1
JNDI and RMI over HTTP

1. You
have to change the jndi.properties from:

java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=[internal machine address]

to:

java.naming.factory.initial=org.jboss.naming.HttpNamingContextFactory java.naming.provider.url=http://[external machine address]:[port]/invoker/JNDIFactory

2. Modify
the jboss subtask in ejbdoclet (to use the mergedir feature)

<jboss version=”${jboss.version}” xmlencoding=”UTF-8″ typemapping=”${type.mapping}” datasource=”${datasource.name}” destdir=”${build.dir}/META-INF” mergedir=”${src.resources.dir}” validateXml=”false” />

and copy the
jboss-container.xml file in the ${src.resources.dir}. This will
define a http rmi container:

<container-configurations> <container-configuration extends=”Standard Stateful SessionBean”> <container-name>HTTP Session</container-name> <home-invoker>jboss:service=invoker,type=http</home-invoker> <bean-invoker>jboss:service=invoker,type=http</bean-invoker> </container-configuration> </container-configurations>

Note:
XDoclet will merge file into the jboss.xml file

3. Add
a new xdoclet line to the stateful beans (this will associate these
beans with the proper container configuration:

@jboss.container-configuration
name=”HTTP Session”

4. change all naming lookups from lContext.lookup(“java:pilot/X”)
to lContext.lookup(“pilot/X”)

III.2 On
the server side

If you plan
to access you application through a firewall or to use some kind
of port forwarding then the line

java.naming.provider.url=http://[external
machine address]:[port]/invoker/JNDIFactory will not solve
all your problems. Assuming the configuration from Graphic 1. the
client will indeed try to connect to external machine:portE
and will be redirected to internal machine:portI but the
references passed to the client will be in the form internal
machine:portI/invoker/…. When the client will try to access
them an error will occur. The trick is to make Jboss believe it
is running on the external machine so it will send the proper references.
This can be achieved by editing the server/[configuration]/deploy/http-invoker.sar/META-INF/http://www.len.ro/work/articles/jboss/ejb-remoting/jboss-service.xml
to something similar to the following:

<?xml version=”1.0″ encoding=”UTF-8″?> <!DOCTYPE server> <server> <!– The HTTP invoker service configration–> <mbean code=”org.jboss.invocation.http.server.HttpInvoker” name=”jboss:service=invoker,type=http”> <attribute name=”InvokerURL”>http://[external server]:[portE]/invoker/EJBInvokerServlet</attribute> </mbean> <mbean code=”org.jboss.invocation.http.server.HttpInvokerHA” name=”jboss:service=invoker,type=httpHA”> <attribute name=”InvokerURL”>http://[external server]:[portE]/invoker/EJBInvokerHAServlet</attribute> </mbean> <!– Expose the Naming service interface via HTTP –> <mbean code=”org.jboss.invocation.http.server.HttpProxyFactory” name=”jboss:service=invoker,type=http,target=Naming”> <!– The Naming service we are proxying –> <attribute name=”InvokerName”>jboss:service=Naming</attribute> <attribute name=”InvokerURL”>http://[external server]:[portE]/invoker/JMXInvokerServlet</attribute> <attribute name=”ExportedInterface”>org.jnp.interfaces.Naming</attribute> <attribute name=”JndiName”></attribute> </mbean> <!– Expose the Naming service interface via clustered HTTP. This maps to the ReadOnlyJNDIFactory servlet URL–> <mbean code=”org.jboss.invocation.http.server.HttpProxyFactory” name=”jboss:service=invoker,type=http,target=Naming,readonly=true”> <attribute name=”InvokerName”>jboss:service=Naming</attribute> <attribute name=”InvokerURL”>http://[external server]:[portE]/invoker/JMXInvokerServlet</attribute> <attribute name=”ExportedInterface”>org.jnp.interfaces.Naming</attribute> <attribute name=”JndiName”></attribute> </mbean> </server>