11/28/2007

Calling JBoss EJB 3 from JRuby client

In Calling EJB 3 from JRuby client, I wrote how to call EJB3 deployed to glassfish from a JRuby script. This post will show how to do it with JBoss EJB3, reusing the same EJB3 as the previous post (A simple EJB 3 on JBoss application server). Steps 1-7 (creating EJB3 classes, starting JBoss server, deploying EJB, preparing jndi.properties and log4j.properties) are exactly the same as that post. Here I will show the client script code and how to run it.

The JRuby script (testEJB.rb) is almost identical to the one for glassfish, except the default jndi name. TODO: pass it in as parameter instead of hardcoding it.
require 'java'
include_class 'javax.naming.InitialContext'

ic = InitialContext.new
foo = ic.lookup("FooBean/remote")
result = foo.echo("This is foo!")
puts "Calling the EJB 3 : #{foo} returned #{result}"
Before running the script, we need to set the environment variable CLASSPATH, which is of course different from the one for glassfish.
set CLASSPATH=%JBOSS_HOME%\lib\*;%JBOSS_HOME%\client\*;%JBOSS_HOME%\server\default\lib\*;C:\simple-ejb3\classes
jruby testEJB.rb
The above classpath value works only with JDK 6 or later. If you are stuck with JDK 5, then:
set CLASSPATH=%JBOSS_HOME%\lib\jboss-aop-jdk50.jar;
%JBOSS_HOME%\client\jbossall-client.jar;
%JBOSS_HOME%\server\default\lib\jbosssx.jar;
C:\simple-ejb3\classes
Technorati Tags: , ,

11/27/2007

A simple EJB 3 on JBoss application server

This is a simple tutorial on how to run EJB 3 app on Jboss application server. The version I'm using is JBossAS-5.0.0 beta2. The following app works with JBoss AS 5 & 6. AS 7 introduced significant changes to remote JNDI, and I will write a new post(Standalone Java Client for JBoss AS 7.1.1) to cover it.

Note that JBossAS-5.0.0 beta2 does not work with JDK 6 (see my previous post). So you will need to set environment variable JAVA_HOME to a JDK 5 location (e.g., set JAVA_HOME=C:\jdk5) for JBoss server. This also requires us to compile EJB classes with JDK 5 for the class file to be compatible with server JVM. However, it is perfectly OK to use JDK 6 for the client.

1. project directory structure:

C:\simple-ejb3> tree /A /F
+---classes
| \---foo
\---src
\---foo
Client.java
FooBean.java
FooRemote.java
2. create java src files under src\foo:
FooRemote.java:

package foo;
import javax.ejb.*;

@Remote
public interface FooRemote {
public String echo(String s);
}
FooBean.java:

package foo;
import javax.ejb.*;

@Stateless
public class FooBean implements FooRemote {
public String echo(String s) {
return s;
}
}
Client.java

package foo;
import javax.ejb.*;
import javax.naming.*;

public class Client {
public static void main(String[] args) throws Exception {
//JBoss' default remote jndi: <ejb-name>/remote
final String jndiName = "FooBean/remote";
Context ic = new InitialContext();
System.out.println("about to look up jndi name " + jndiName);
Object obj = ic.lookup(jndiName);
System.out.println("lookup returned " + obj);

FooRemote foo = (FooRemote) obj;
String s = foo.echo("Hello Foo on JBoss!");
System.out.println(foo + " echo returned " + s);
}
}
3. Compile java src. An environment variable JBOSS_HOME is set for convenience but it's not required.
C:\simple-ejb3\classes> set JBOSS_HOME=C:\jboss
C:\simple-ejb3\classes> javac -d . -classpath %JBOSS_HOME%\client\jbossall-client.jar;. ..\src\foo\*.java
4. start JBoss default config:
set JAVA_HOME=C:\jdk5
C:\simple-ejb3\classes> %JBOSS_HOME%\bin\run.bat
5. package and autodeploy ejb-jar. We can combine the 2 steps in 1 by using %JBOSS_HOME%\server\default\deploy as the destdir:
C:\simple-ejb3\classes>
jar cvf %JBOSS_HOME%\server\default\deploy\foo-ejb.jar
foo\FooBean.class foo\FooRemote.class
6. Prepare jndi.properties and log4j.properties somewhere in client classpath, e.g., simple-ejb3\classes directory:

# jndi.properties:
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099

# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=INFO, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Threshold=INFO
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=- %m%n
7. run the standalone java client. Note that I use JDK 6 on the client to use the wildcard (*) in classpath. JBoss client needs a sleuth of jars scattered in several JBoss server directories. With * in classpath I don't need to figure out which jars to include.
C:\simple-ejb3\classes> java -cp %JBOSS_HOME%\lib\*;%JBOSS_HOME%\client\*;%JBOSS_HOME%\server\default\lib\*;. foo.Client

about to look up jndi name FooBean/remote
lookup returned jboss.j2ee:jar=foo-ejb.jar,name=FooBean,service=EJB3
jboss.j2ee:jar=foo-ejb.jar,name=FooBean,service=EJB3 echo returned Hello Foo on JBoss!
If you have to run client with JDK 5, this is the detailed classpath:
%JBOSS_HOME%\lib\jboss-aop-jdk50.jar;
%JBOSS_HOME%\client\jbossall-client.jar;
%JBOSS_HOME%\server\default\lib\jbosssx.jar;.
This is the classpath necessary to run this simple client. Other more complex client may need additional jars. And who knows what will change in the next JBoss release? So it's always a good idea to use the wildcard classpath with JDK 6.

8. undeploy the ejb module:
del %JBOSS_HOME%\server\default\deploy\foo-ejb.jar
Tags: , ,

11/26/2007

JBossAS 5.0.0 beta2 cannot start with JDK 6

I wanted to run some EJB3 applications on JBoss, and so I installed the most recent version of JBoss (JBossAS-5.0.0.Beta2). When starting the server with JDK 6 (build 1.6.0_02-b06), I immediately got ClassNotFoundException.
===============================================================================

JBoss Bootstrap Environment

JBOSS_HOME: C:\tools\jboss

JAVA: C:\jdk6\bin\java

JAVA_OPTS: -Dprogram.name=run.bat -server -Xms128m -Xmx512m -XX:MaxPermSize=256m -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000

CLASSPATH: C:\jdk6\lib\tools.jar;C:\tools\jboss\bin\run.jar

===============================================================================

11:24:31,328 INFO [ServerImpl] Starting JBoss (Microcontainer)...
11:24:31,328 INFO [ServerImpl] Release ID: JBoss [Morpheus] 5.0.0.Beta2 (build: SVNTag=JBoss_5_0_0_Beta2 date=200704060017)
11:24:31,328 INFO [ServerImpl] Home Dir: C:\tools\jboss
11:24:31,328 INFO [ServerImpl] Home URL: file:/C:/tools/jboss/
11:24:31,328 INFO [ServerImpl] Library URL: file:/C:/tools/jboss/lib/
11:24:31,328 INFO [ServerImpl] Patch URL: null
11:24:31,328 INFO [ServerImpl] Server Name: default
11:24:31,328 INFO [ServerImpl] Server Home Dir: C:\tools\jboss\server\default
11:24:31,328 INFO [ServerImpl] Server Home URL: file:/C:/tools/jboss/server/default/
11:24:31,328 INFO [ServerImpl] Server Data Dir: C:\tools\jboss\server\default\data
11:24:31,328 INFO [ServerImpl] Server Temp Dir: C:\tools\jboss\server\default\tmp
11:24:31,343 INFO [ServerImpl] Server Config URL: file:/C:/tools/jboss/server/default/conf/
11:24:31,343 INFO [ServerImpl] Server Library URL: file:/C:/tools/jboss/server/default/lib/
11:24:31,343 INFO [ServerImpl] Root Deployment Filename: jboss-service.xml
11:24:31,359 INFO [ServerImpl] Starting Microcontainer, bootstrapURL=file:/C:/tools/jboss/server/default/conf/bootstrap-beans.xml
11:24:32,109 INFO [ProfileImpl] Using profile root:C:\tools\jboss\server\default
11:24:33,078 ERROR [AbstractKernelController] Error installing to Instantiated: name=DeploymentFilter state=Described
java.lang.IllegalStateException: Class not found: [Ljava.lang.String;
at org.jboss.metadata.spi.signature.Signature.stringsToClasses(Signature.java:174)
at org.jboss.metadata.spi.signature.Signature.stringsToClasses(Signature.java:125)
at org.jboss.metadata.spi.signature.Signature.getParametersTypes(Signature.java:292)
at org.jboss.metadata.plugins.loader.reflection.AnnotatedElementMetaDataLoader.getComponentMetaDataRetrieval(AnnotatedElementMetaDataLoader.java:138)
at org.jboss.metadata.plugins.context.AbstractMetaDataContext.getComponentMetaDataRetrieval(AbstractMetaDataContext.java:280)
at org.jboss.metadata.spi.retrieval.MetaDataRetrievalToMetaDataBridge.getComponentMetaData(MetaDataRetrievalToMetaDataBridge.java:159)
at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.methodHasAnnotations(AOPConstructorJoinpoint.java:202)
at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.hasMethodMetaData(AOPConstructorJoinpoint.java:172)
at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.hasInstanceOrJoinpointMetaData(AOPConstructorJoinpoint.java:152)
at org.jboss.aop.microcontainer.integration.AOPConstructorJoinpoint.dispatch(AOPConstructorJoinpoint.java:99)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.dispatchJoinPoint(KernelControllerContextAction.java:103)
at org.jboss.kernel.plugins.dependency.InstantiateAction.installActionInternal(InstantiateAction.java:52)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.installAction(KernelControllerContextAction.java:197)
at org.jboss.kernel.plugins.dependency.KernelControllerContextAction.install(KernelControllerContextAction.java:136)
at org.jboss.dependency.plugins.AbstractControllerContextActions.install(AbstractControllerContextActions.java:51)
at org.jboss.dependency.plugins.AbstractControllerContext.install(AbstractControllerContext.java:233)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:724)
at org.jboss.dependency.plugins.AbstractController.incrementState(AbstractController.java:445)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:555)
at org.jboss.dependency.plugins.AbstractController.resolveContexts(AbstractController.java:489)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:289)
at org.jboss.dependency.plugins.AbstractController.install(AbstractController.java:192)
at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBean(AbstractKernelDeployer.java:302)
at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deployBeans(AbstractKernelDeployer.java:272)
at org.jboss.kernel.plugins.deployment.AbstractKernelDeployer.deploy(AbstractKernelDeployer.java:119)
at org.jboss.kernel.plugins.deployment.BasicKernelDeployer.deploy(BasicKernelDeployer.java:64)
at org.jboss.kernel.plugins.deployment.xml.BasicXMLDeployer.deploy(BasicXMLDeployer.java:76)
at org.jboss.kernel.plugins.deployment.xml.BasicXMLDeployer.deploy(BasicXMLDeployer.java:146)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.deploy(ProfileServiceBootstrap.java:295)
at org.jboss.system.server.profileservice.ProfileServiceBootstrap.bootstrap(ProfileServiceBootstrap.java:222)
at org.jboss.kernel.plugins.bootstrap.AbstractBootstrap.run(AbstractBootstrap.java:89)
at org.jboss.system.server.profileservice.ServerImpl.doStart(ServerImpl.java:403)
at org.jboss.system.server.profileservice.ServerImpl.start(ServerImpl.java:342)
at org.jboss.Main.boot(Main.java:210)
at org.jboss.Main$1.run(Main.java:522)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ClassNotFoundException: [Ljava.lang.String;
A search showed this JBoss bug JBAS-4491. As mentioned by one user in the report, one workaround is to use JDK 5. I don't like keeping multiple versions of JDK in my machine, and I have been running my other java applications with JDK 6 for some time. I will wait for JBossAS-5.0.0.Beta3.

Update on 10/24/2008 Friday: with more recent releases, e.g., jboss-5.0.0.CR2 & jboss-5.0.0.CR1, JBoss AS runs fine on JDK 6.

Technorati Tags:

11/22/2007

Calling EJB 3 from JRuby client

Calling EJB 3 from JRuby client is very similar to calling EJB 3 from a java standalone client, with fewer lines of code. In this post, I had a sample showing how to call EJB 3 from a standalone java client. The following JRuby test client uses the same business interface and bean class.
require 'java'
include_class 'javax.naming.InitialContext'

ic = InitialContext.new
foo = ic.lookup("foo.FooRemote")
result = foo.echo("This is foo!")
puts "Calling the EJB 3 : #{foo} returned #{result}"
This is the exact command line how to call an EJB deployed on Glassfish v2:
set CLASSPATH=%GLASSFISH_HOME%\lib\javaee.jar;%GLASSFISH_HOME%\lib\appserv-rt.jar;C:\simple-ejb3\dist\foo-client-view.jar
jruby testEJB.rb
Calling the EJB 3 : foo._FooRemote_Wrapper@e9738564 returned This is foo!
Noteworthy points:

1. My JRuby is ruby 1.8.5 (2007-11-01 rev 4810) [x86-jruby1.0.2]. It only supports setting classpath in environment variable, though I prefer to set classpath via -cp or -classpath.

2. The classpath is different for other application servers. At a minimum, you need to include the JavaEE API jar, typically named javaee.jar, and jars for application server naming service and other resources. To invoke more complex EJBs even in glassfish, you may need to include more glassfish jars.

3. No need to use a Properties to instantiate the initialContext. It saves us a couple of lines of code in both JRuby and Java clients.

Technorati Tags: , ,

11/16/2007

When to forget about constructors in JavaEE

Sometimes I see people do some sort of validation inside the constructors of a JavaEE component class, like EJB or servlet class. They do this mostly for testing or debugging purpose, but always surprised to see certain fields have not been injected. For example, the following validation in the constructor will shows the SessionContext is null!
@Stateless(mappedName="FooBean")
public class FooBean implements FooRemote {
@Resource
private SessionContext sctx;

//DO NOT DO THIS
public FooBean() {
System.out.println("Inside constructor, Injected SessionContext: "
+ sctx);
if(sctx == null) {
throw new IllegalStateException
("SessionContext not injected! Something is wrong!!");
}
}
//business methods...
}
This is an application bug and nothing is wrong with the application server. The right way to validate injection fields is the @PostConstruct method. In the @PostConstruct method, all injections are guaranteed to have completed.
@Stateless(mappedName="FooBean")
public class FooBean implements FooRemote {
@Resource
private SessionContext sctx;

@PostConstruct
private void validate() {
System.out.println("Inside @PostConstruct, Injected SessionContext: "
+ sctx);
}
//business methods...
}
Technorati Tags: , ,

11/14/2007

Shortcut key changes in NetBeans 6.0

There are a lot of good things to say about NetBeans 6.0. But if you are upgrading from earlier versions, be aware of these changes in keyboard shortcuts.

TasksNetBeans 5.xNetBeans 6.0
Expand code template on ...SpacTab
Format codeCtrl-Shift-F Alt-Shift-F
Fix importsAlt-Shift-FAlt-Shift-I
Go to typeAlt-Shift-OCtrl-O
Toggle java commentsCtrl-Shift-T Alt-Shift-W in beta 2,
Ctrl-Slash in RC 1
Toggle bookmarkCtrl-F2Supposed to be Ctrl-Shift-M,
but not working.
No such shortcut in the menu, either
Go to next bookmarkF2Ctrl-Shift-Period
Go to previous bookmarkShift-F2Ctrl-Shift-Comma


These are what I have noticed so far. Now I will have to change my keyboard shortcut habits. It is inconvenient, to say the least. Some of these changes will only cause confusion. Why would one think Tab, rather than the old Space, should be pressed for code completion? I have been using Space for code completion in all IDE, even including VIM. The only place I use Tab for completion is in tcsh or bash. Maybe this idea was borrowed from tcsh or bash? Thankfully, they didn't choose Esc.

Another source of confusion, Ctrl-Shift-F is pretty much the standard shortcut for code formatting. It works this way in NetBeans releases prior to 6.0, and Eclipse as well. Now NetBeans 6.0 slated Ctrl-Shift-F for a new feature called Find in Projects. To format code in NetBeans 6.0? one need to press Alt-Shift-F, which in NetBeans 5.x stands for Fix Imports.

Technorati Tags: ,

11/13/2007

Glassfish asadmin command auto suggestions

In previous post, I mentioned Glassfish asadmin command auto suggestions. When you enter an invalid command, glassfish can suggest how to fix it. It has been very handy for me, given that most asadmin commands are quite verbose with multiple words concatenated with "-." More examples of using asadmin auto-correction:

Say I want to work with glassfish cluster but don't know what commands are available:
C:\glassfish\bin> asadmin cluster
Closest matching command(s):
configure-ha-cluster
create-cluster
delete-cluster
list-clusters
remove-ha-cluster
start-cluster
stop-cluster
CLI001 Invalid Command, cluster.
To do something with glassfish server jvm options:
C:\glassfish\bin> asadmin jvm
Closest matching command(s):
create-jvm-options
delete-jvm-options
generate-jvm-report
CLI001 Invalid Command, jvm.
For database operations:
C:\glassfish\bin>asadmin database
Closest matching command(s):
start-database
stop-database
CLI001 Invalid Command, database.
How to manage jdbc-related tasks:
C:\glassfish\bin>asadmin jdbc
Closest matching command(s):
create-jdbc-connection-pool
create-jdbc-resource
delete-jdbc-connection-pool
delete-jdbc-resource
list-jdbc-connection-pools
list-jdbc-resources
CLI001 Invalid Command, jdbc.
How to do jms stuff:
C:\glassfish\bin>asadmin jms
Closest matching command(s):
create-jms-host
create-jms-resource
create-jmsdest
delete-jms-host
delete-jms-resource
delete-jmsdest
flush-jmsdest
jms-ping
list-jms-hosts
list-jms-resources
list-jmsdest
CLI001 Invalid Command, jms.
Anything to do with glassfish connection-pool or thread-pool:
C:\glassfish\bin>asadmin pool
Closest matching command(s):
create-connector-connection-pool
create-jdbc-connection-pool
create-threadpool
delete-connector-connection-pool
delete-jdbc-connection-pool
delete-threadpool
list-connector-connection-pools
list-jdbc-connection-pools
list-threadpools
ping-connection-pool
CLI001 Invalid Command, pool.
Technorati Tags: ,

11/12/2007

List jndi entries to debug JNDI lookup problems

What will you do if you have a problem with JNDI lookup? Maybe something you think should be there but a JNDI lookup throws NamingNotFoundException instead? I've seen people add the following debug code:
InitialContext ic = new InitialContext();
System.out.println(ic.list("java:comp/env"));
There is a better way of doing that, without adding these debugging statements. In GlassFish, you can use the command "asadmin list-jndi-entries":
C:\>%GLASSFISH_HOME%\bin\asadmin list-jndi-entries
Jndi Entries for server within root context:

foo.FooRemote: javax.naming.Referencecustom:
com.sun.enterprise.naming.TransientContextfoo.FooRemote__3_x_Internal_RemoteBusinessHome__: javax.naming.Reference
jdo: com.sun.enterprise.naming.TransientContext
mail: com.sun.enterprise.naming.TransientContext
foo.FooRemote#foo.FooRemote: javax.naming.Reference
jdbc: com.sun.enterprise.naming.TransientContext
UserTransaction: com.sun.enterprise.distributedtx.UserTransactionImpl
ejb: com.sun.enterprise.naming.TransientContext

Command list-jndi-entries executed successfully.
C:\>
list-jndi-entries is not recursive and does not list the content of any subcontext. To drill down to a subcontext, specify a --context param:
C:\>%GLASSFISH_HOME%\bin\asadmin list-jndi-entries --context ejbmgmt:
com.sun.enterprise.naming.impl.TransientContext
Command list-jndi-entries executed successfully.

C:\>%GLASSFISH_HOME%\bin\asadmin list-jndi-entries --context ejb/mgmt
MEJB: org.glassfish.kernel.javaee.MEJBNamingObjectProxy

Command list-jndi-entries executed successfully.
What if you don't remember the exact command and subcommand? GlassFish can help you find what you mean, as long as you give it a hint. There is never a need to remember these asadmin subcommands in glassfish. For example, this is how I ran the above command:
C:\>%GLASSFISH_HOME%\bin\asadmin jndi
Closest matching command(s): create-jndi-resource
delete-jndi-resource
list-jndi-entries
list-jndi-resources
CLI001 Invalid Command, jndi.

C:\>%GLASSFISH_HOME%\bin\asadmin list-jndi-entries

11/02/2007

How to get container-managed EntityManager in servlets

In this previous post, I wrote about the danger of using field/setter injection to get EntityManager in servlets A follow-up post compares container-managed EntityManager vs application-managed EntityManager.

What if I need a container-managed EntityManager in my servlet class? There are 2 ways to do that:

1. type-level injection + JNDI lookup


@PersistenceContext(unitName="my-pu", name="persistence/em")
public class FooServlet extends HttpServlet {
@Resource private UserTransaction ut;

protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
EntityManager em = null;
try {
Context ic = new InitialContext();
em = (EntityManager) ic.lookup
("java:comp/env/persistence/em");
} catch (NamingException e) {
throw new ServletException(e);
}
//call EntityManager methods inside transaction
try {
ut.begin();
Employee employee = new Employee(...);
em.persist(employee);
ut.commit();
} catch (Exception e) {
throw new ServletException(e);
}
}
}

2. web.xml + JNDI lookup

public class FooServlet extends HttpServlet {
@Resource private UserTransaction ut;
//the rest is the same as listed in bullet 1

The persistence-context-ref is declared in web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.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/web-app_2_5.xsd">
<servlet>
<servlet-name>FooServlet</servlet-name>
<servlet-class>com.foo.abc.FooServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FooServlet</servlet-name>
<url-pattern>/foo</url-pattern>
</servlet-mapping>
<persistence-context-ref>
<persistence-context-ref-name>persistence/em</persistence-context-ref-name>
<persistence-unit-name>my-pu</persistence-unit-name>
</persistence-context-ref>
</web-app>