This wiki tries to explain the usage of application client container in JBoss. It consists of two parts: first an EJB is injected, the second part shows how to inject a MessageDrivenBean Queue.
Injection of EJBs
In this example, we are trying to inject an EJB into an application-client and then invoke a method on that EJB in the client. This has been tested on JBoss-5 GA. The attached example is compiled with Java5. So let's get started:
Step-1:Deploy the application
Deploy the enterprise application which contains the EJB as well as the application-client, onto the JBoss-5 GA server. Here's what the Stateless.ear contains:
META-INF/MANIFEST.MF
StatelessEJB.jar
StatelessClient.jar
a) The StatelessEJB.jar is the jar which contains the EJB. The EJB is simple and has a couple of methods:
import javax.ejb.Stateless;
@Stateless
public class GeometricModelBean
implements GeometricModelRemote, GeometricModelLocal
{
public double computeCuboidVolume(double a, double b, double c)
{
// some code here
}
public double computeCuboidSurface(double a, double b, double c)
{
// some code here
}
}
b) The StatelessClient.jar is the application client which uses the EJB (through injection). Here's the class which has the main function:
import javax.ejb.EJB;
public class GeometricModelApplicationClient
{
@EJB
public static GeometricModelRemote geometricModel;
public static void main(String[] args)
{
double dblVolume;
try
{
dblVolume = geometricModel.computeCuboidVolume(10.0D, 5.0D, 7.0D);
double dblSurface = geometricModel.computeCuboidSurface(10.0D, 5.0D, 7.0D);
System.out.println("Calculated volume: " + dblVolume + ", surface: " + dblSurface);
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
As you can see, the EJB is being injected in the client and later on used to invoke a method. The StatelessClient.jar should contain a application-client.xml and a jboss-client.xml (optional) to be identified as a application client. Here are the contents of these files:
StatelessClient.jar /META-INF/application-client.xml
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
<display-name>StatelessClient</display-name>
</application-client>
StatelessClient.jar/META-INF/jboss-client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC
"-//JBoss//DTD Application Client 5.0//EN"
"http://www.jboss.org/j2ee/dtd/jboss-client_5_0.dtd">
<jboss-client>
<jndi-name>StatelessClient</jndi-name>
</jboss-client>
And then the StatelessClient.jar/META-INF/MANIFEST.MF should point to the application client class which has the main function.
StatelessClient.jar/META-INF/MANIFEST.MF:
Manifest-Version: 1.0
Class-Path: StatelessEJB.jar
Main-Class: de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient
Note that this ear does not have an application.xml. But if required, you can have one.
When this EAR is successfully deployed on the JBoss-5 GA server, you will see the following log output on the console:
13:55:57,734 INFO [JBossASKernel] Created KernelDeployment for: StatelessClient.jar
13:55:57,734 INFO [JBossASKernel] installing bean: jboss.j2ee:ear=Stateless.ear,jar=StatelessClient.jar,name=StatelessClient,service=EJB3
13:55:57,734 INFO [JBossASKernel] with dependencies:
13:55:57,734 INFO [JBossASKernel] and demands:
13:55:57,734 INFO [JBossASKernel] and supplies:
13:55:57,734 INFO [JBossASKernel] Added bean(jboss.j2ee:ear=Stateless.ear,jar=StatelessClient.jar,name=StatelessClient,service=EJB3) to KernelDeployment of: StatelessClient.jar
13:56:00,156 INFO [JBossASKernel] Created KernelDeployment for: StatelessEJB.jar
13:56:00,156 INFO [JBossASKernel] installing bean: jboss.j2ee:ear=Stateless_BrokenAppClient2.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3
13:56:00,156 INFO [JBossASKernel] with dependencies:
13:56:00,156 INFO [JBossASKernel] and demands:
13:56:00,156 INFO [JBossASKernel] jboss.ejb:service=EJBTimerService
13:56:00,156 INFO [JBossASKernel] and supplies:
13:56:00,156 INFO [JBossASKernel] jndi:Stateless/GeometricModelBean/local-de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal
13:56:00,156 INFO [JBossASKernel] jndi:Stateless/GeometricModelBean/remote-de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote
13:56:00,156 INFO [JBossASKernel] Class:de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote
13:56:00,156 INFO [JBossASKernel] jndi:Stateless/GeometricModelBean/local
13:56:00,156 INFO [JBossASKernel] Class:de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelLocal
13:56:00,171 INFO [JBossASKernel] jndi:Stateless/GeometricModelBean/remote
13:56:00,171 INFO [JBossASKernel] Added bean(jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3) to KernelDeployment of: StatelessEJB.jar
13:56:00,437 INFO [ClientENCInjectionContainer] STARTED CLIENT ENC CONTAINER: StatelessClient
13:56:01,171 INFO [SessionSpecContainer] Starting jboss.j2ee:ear=Stateless_BrokenAppClient2.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3
13:56:01,187 INFO [EJBContainer] STARTED EJB: de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelBean ejbName: GeometricModelBean
13:56:01,203 WARN [SessionSpecContainer] Populating JBoss-specific annotation metadata manually until done by deployers: jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3
....
13:56:01,656 INFO [ServerImpl] JBoss (Microcontainer) [5.0.0.GA (build: SVNTag=JBoss_5_0_0_GA date=200809171046)] Started in 1m:8s:299ms
In your JNDIView you can now see that the application client has been bound to the JNDI:
java:comp namespace of the component jboss.j2ee:ear=Stateless.ear,jar=StatelessEJB.jar,name=GeometricModelBean,service=EJB3 :
+- EJBContext (class: javax.ejb.EJBContext)
+- TransactionSynchronizationRegistry[link -> java:TransactionSynchronizationRegistry] (class: javax.naming.LinkRef)
+- UserTransaction (class: org.jboss.ejb3.tx.UserTransactionImpl)
+- env (class: org.jnp.interfaces.NamingContext)
+- ORB[link -> java:/JBossCorbaORB] (class: javax.naming.LinkRef)
Global JNDI Namespace
+- StatelessClient (class: org.jnp.interfaces.NamingContext)
| +- UserTransaction[link -> UserTransaction] (class: javax.naming.LinkRef)
| +- metaData (class: org.jboss.metadata.client.jboss.JBossClientMetaData)
| +- env (class: org.jnp.interfaces.NamingContext)
| | +- geometricModel[link -> Stateless/GeometricModelBean/remote-de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelRemote] (class: javax.naming.LinkRef)
| +- classPathEntries (class: java.util.ArrayList)
Step-2: Use the appclient-launcher to launch the application client container.
Now that the EAR containing the bean and the application client has been deployed, the next step is to launch the application client container. Note: Just running the application client class through a java command will not get the injection working. You need the launcher to provide the application server functionalities.
From the command prompt, 'cd' to %JBOSS_HOME%\client folder. I have JBoss installed in D:\jboss-5.0.0.GA so from my command prompt, i will cd to D:\jboss-5.0.0.GA\client folder:
D:\jboss-5.0.0.GA\client>set JBOSS_HOME=d:\jboss-5.0.0.GA
D:\jboss-5.0.0.GA\client>set JAVA_HOME=c:\jdk1.6.0_10
D:\jboss-5.0.0.GA\client>set PATH=%JAVA_HOME%\bin;%PATH%
Now use the following command to launch the application client container:
D:\jboss-5.0.0.GA\client>%JAVA_HOME%\bin\java -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099 -classpath "%JBOSS_HOME%\client\jbossall-client.jar;%JBOSS_HOME%\client\jboss-metadata.jar;%JBOSS_HOME%\lib\jboss-classloader.jar;%JBOSS_HOME%\lib\jboss-classloading-spi.jar;%JBOSS_HOME%\lib\jboss-classloading-vfs.jar;%JBOSS_HOME%\lib\jboss-classloading.jar;%JBOSS_HOME%\lib\jboss-dependency.jar;%JBOSS_HOME%\lib\jboss-reflect.jar;%JBOSS_HOME%\lib\jboss-kernel.jar;%JBOSS_HOME%\lib\jboss-xml-binding.jar;%JBOSS_HOME%\lib\jboss-xml-binding.jar;%JBOSS_HOME%\lib\jboss-vfs.jar;%JBOSS_HOME%\lib\jboss-reflect.jar;%JBOSS_HOME%\common\lib\jboss-ejb3-core.jar" org.jboss.client.AppClientMain -jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient
The entire command should be in one single line. The important things to note in this command (other than the classpath stuff) is that:
a) We are passing the JNDI related properties:
-Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099
The -Djava.naming.provider.url should point to the server's Naming port.
Where is the "-Djava.naming.factory.url.pkgs" argument?
This argument is required, if the client app performs a JNDI lookup itself, and it's value has to be "org.jboss.naming.client".
But this one is not used by the Injection framework. jaikirans investigations found that the environment property Context.URL_PKG_PREFIXES does not come into picture when using injection because of the way org.jboss.ejb3.client.JNDIDependencyItem is working. It does not look into the java:comp/env namespace but just looks up and adds a dependency on StatelessClient/metaData jndiname. Later on when the actual lookup for java:comp/env namespace is done on the jndi *server*, the properties that are on the server are used.
b) We are calling the org.jboss.client.AppClientMain which is the application client container's entry point
c) We are passing -jbossclient parameter and its corresponding value de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient to org.jboss.client.AppClientMain. This is your application client main class which uses the EJB
d) We are also passing the -j2ee.clientName which is the name of your application client to org.jboss.client.AppClientMain. In this case, its StatelessClient.
e) We are passing the -launchers org.jboss.ejb3.client.ClientLauncher to org.jboss.client.AppClientMain. This one is important. The -launchers accepts a comma separate list of fully qualified implementations of org.jboss.client.AppClientLauncher interface. The org.jboss.ejb3.client.ClientLauncher is one such implementation (provided by JBoss) which is responsible for injecting EJB (and some other things) in the application client.
f) Classpath: the list of included JARs was found by "try and error" and might change with every JBoss version. The file list of this sample is for JBoss 5.0 GA.
But JBoss 6.0GA requires a different file list. To simplify this, I use wildcards to include all JAR files in "%JBOSS_HOME%\client" and "%JBOSS_HOME\lib%". A new requirement in JBoss 6 is to include also "%JBOSS_HOME%\common\lib\jboss-ejb3-vfs-spi.jar".
Here is the relevant snippet of the command:
set JBOSS_HOME=d:\jboss-6.0.0.Final
...
"%JAVA_HOME%\bin\java" ... -classpath .;"%JBOSS_HOME%\client\*;%JBOSS_HOME%\lib\*;%JBOSS_HOME%\common\lib\jboss-ejb3-vfs-spi.jar;" ...
Step-3: See the output.
When you run the command mentioned in Step-2, the application client container will invoke the main method of your application client. In our example, the main method uses the injected EJB and invokes a method on the EJB and finally prints the following output on the client side:
Calculated volume: 350.0, surface: 310.0
Note, on the server side you might see the EJB related logs (if you have any logging messages).
That's it!
P.S: The application attached here is provided by Wolfgang Knauf http://www.jboss.com/index.html?module=bb&op=viewtopic&t=143595
Why don't i see logs on the application client side?
By default, with the command used in Step-2, you will not see the log message. The log messages are really useful when debugging any issue. You have to do 2 things to see logs on the client side:
1) Add log4j.jar to the client classpath
2) Add a log4j properties or xml file to the classpath or pass -Dlog4j.configuration=[path to log4j config file] JVM option.
On my setup, i created a log4j.properties file with the following settings:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%t][%c] - <%m>%n
And then placed this file under %JBOSS_HOME%\client folder. Finally i changed the java command (in Step-2) to include the log4j.jar and also pass this log4j configuration file path as a JVM option. Here's the updated command:
D:\jboss-5.0.0.GA\client>%JAVA_HOME%\bin\java -Djava.naming.factory.initial=org.jnp.interfaces.NamingContextFactory -Djava.naming.provider.url=jnp://localhost:1099 -Dlog4j.configuration=%JBOSS_HOME%\client\log4j.properties -classpath "%JBOSS_HOME%\client\jbossall-client.jar;%JBOSS_HOME%\client\jboss-metadata.jar;%JBOSS_HOME%\client\log4j.jar;%JBOSS_HOME%\lib\jboss-classloader.jar;%JBOSS_HOME%\lib\jboss-classloading-spi.jar;%JBOSS_HOME%\lib\jboss-classloading-vfs.jar;%JBOSS_HOME%\lib\jboss-classloading.jar;%JBOSS_HOME%\lib\jboss-dependency.jar;%JBOSS_HOME%\lib\jboss-reflect.jar;%JBOSS_HOME%\lib\jboss-kernel.jar;%JBOSS_HOME%\lib\jboss-xml-binding.jar;%JBOSS_HOME%\lib\jboss-vfs.jar;%JBOSS_HOME%\lib\jboss-reflect.jar;%JBOSS_HOME%\common\lib\jboss-ejb3-core.jar" org.jboss.client.AppClientMain -jbossclient de.fhw.komponentenarchitekturen.knauf.stateless.GeometricModelApplicationClient -launchers org.jboss.ejb3.client.ClientLauncher -j2ee.clientName StatelessClient
If Log4j does not show the expected results, add "-Dlog4j.debug=true"
to the startup parameters, which will print Log4j debugging output and hopefully point you to the reason why e.g. the config file could not be found.
Injection of a Queue
(credits for this chapter go to jaikiran, I (Wolfgang) just documented his findings here, see http://www.jboss.org/index.html?module=bb&op=viewtopic&t=149330 ).
Assume a client that wants to send data to a queue (e.g. a Message Driven Bean).
You need a javax.jms.QueueConnectionFactory and a javax.jms.Queue, which will be injected by the container. As they are injected, they must be static variables in the class containing the "main" method (annotations will be added later):
private static QueueConnectionFactory queueConnectionFactory;
private static Queue queue;
This is the code to send the message (one more "Hello World" sample ;-) ):
QueueConnection queueConnection = queueConnectionFactory.createQueueConnection();
QueueSession queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
QueueSender queueSender = queueSession.createSender(queue);
//Send a text message:
TextMessage textMessage = queueSession.createTextMessage();
textMessage.setText("Hello World");
queueSender.send(textMessage);
There are two possibilities to inject the Queue and QueueConnectionFactory.
We assume that our Queue (which points to a MDB) is bound to the global JNDI name "queue/MessageBeanQueue". The QueueConnectionFactory is provided by JBoss (you can find it in the JMXConsole as ObjectName "jboss.messaging.connectionfactory").
1) From global JNDI
This one is easy: the "mappedName" attribute of the @Resource annotation points to the global JNDI names:
@Resource(mappedName="ConnectionFactory")
private static QueueConnectionFactory queueConnectionFactory;
@Resource(mappedName="queue/MessageBeanQueue")
private static Queue queue;
2) From Environment Naming Context (ENC)
The "name" attribute of the @Resource annotation points to ENC names:
@Resource(name="jms/MBConnectionFactory")
private static QueueConnectionFactory queueConnectionFactory;
@Resource(name="jms/MBQueueRef")
private static Queue queue;
Here you need two config files:
application-client.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
...
<resource-ref>
<res-ref-name>jms/MBConnectionFactory</res-ref-name>
<res-type>javax.jms.QueueConnectionFactory</res-type>
<res-auth>Container</res-auth>
</resource-ref>
<message-destination-ref>
<message-destination-ref-name>jms/MBQueueRef</message-destination-ref-name>
<message-destination-type>javax.jms.Queue</message-destination-type>
</message-destination-ref>
</application-client>
The QueueConnectionFactory as declared as a resource-ref, while the Queue is declared as a message-destination-ref !
jboss-client.xml handles the binding of the ENC entries to JBoss JNDI names:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC
"-//JBoss//DTD Application Client 5.0//EN"
"http://www.jboss.org/j2ee/dtd/jboss-client_5_0.dtd">
<jboss-client>
<jndi-name>...</jndi-name>
<resource-ref>
<res-ref-name>jms/MBConnectionFactory</res-ref-name>
<jndi-name>ConnectionFactory</jndi-name>
</resource-ref>
<message-destination-ref>
<message-destination-ref-name>jms/MBQueueRef</message-destination-ref-name>
<jndi-name>queue/MessageBeanQueue</jndi-name>
</message-destination-ref>
</jboss-client>
Same as in application-client.xml: The Queue must be declared as a message-destination-ref
A full sample can be found here: http://www.informatik.fh-wiesbaden.de/~knauf/KomponentenArchitekturen2008/mdb/MessageInjection.ear (compiled with Java 1.6, will not run with 1.5)
It contains an EJB module with a MDB, and an application client module (simple swing client with a JTextField whose content is sent to the server). The queue is declared in the module with a -service.xml file in the EJB module. Simply deploy it to the server. To run the client: extract "MessageClient.jar" from the EAR to some other directory, and start the client using the ClientLauncher (see step 2 above).
The code is commented in German, but hopefully you will understand it anyway ;-)
Comments