Monday, November 22, 2010

JBoss 5.1 and 6.x fun facts

This document will be a collection of howto's, facts and snippets regarding the JBoss 5.1 (and higher) application server. Perhaps there is one in here that you didn't know yet? At the very least it is very copy/pastable.

This is one of those articles that is not finished right from the start. I keep adding to this in my own time. For example I'm slowly adding in JBoss 6.x stuff. As it is this article will be mostly oriented on JBoss 5.1, but with additions on how JBoss 6 may differ.


Maven dependencies to compile and unit test EJB code

When Mavenizing your project, you may run into the problem of needing libraries to be able to compile and possibly unit test all the EJB code. There are a few possibilities. Note that you need specific maven repositories setup, not only Maven central. For more information on these Maven repositories, check my Maven article.

1)Add JBoss to your maven artifacts. This will add MANY transitive dependencies to the classpath, most of which you don't need. But it is certainly the least work to setup. All JBoss instances are in the JBoss maven repository.

<dependency>
    <groupid>org.jboss.jbossas</groupid>
    <artifactid>jboss-as-client</artifactid>
    <version>5.1.0.GA</version>
</dependency>

(you can also use version 6.0.0.Final and version 6.1.0.Final).

2) Use the javaee API. This is a jar provided by Oracle which allows you to compile JavaEE code. However: they are only stub classes so you cannot use it in for example unit tests, you'll only get exceptions. These dependencies are available through the java.net Maven repository.

JEE5 API:
<dependency>
    <groupid>javaee</groupid>
    <artifactid>javaee-api</artifactid>
    <version>5</version>
</dependency>

JEE6 API:
<dependency>
    <groupid>javax</groupid>
    <artifactid>javaee-api</artifactid>
    <version>6</version>
</dependency>

3) use very specific APIs. All the required components are actually available as separate dependencies, you just need to know which ones. For JEE5 applications, use the following set from the java.net Maven repository:

<dependency>
 <groupId>javax.jms</groupId>
 <artifactId>jms</artifactId>
 <version>1.1</version>
 <scope>provided</scope>
</dependency>
<dependency>
 <groupId>javax.ejb</groupId>
 <artifactId>ejb-api</artifactId>
 <version>3.0</version>
 <scope>provided</scope>
</dependency>
<dependency>
 <groupId>javax.persistence</groupId>
 <artifactId>persistence-api</artifactId>
 <version>1.0</version>
 <scope>provided</scope>
</dependency>
<dependency>
 <groupId>javax.transaction</groupId>
 <artifactId>jta</artifactId>
 <version>1.1</version>
 <scope>provided</scope>
</dependency>
<dependency>
 <groupId>javax.mail</groupId>
 <artifactId>mail</artifactId>
 <version>1.4.4</version>
 <scope>provided</scope>
</dependency>
<dependency>
 <groupId>javax.servlet</groupId>
 <artifactId>servlet-api</artifactId>
 <version>2.4</version>
 <scope>provided</scope>
</dependency>
<dependency>
  <groupId>javax.annotation</groupId>
  <artifactId>jsr250-api</artifactId>
  <version>1.0</version>
  <scope>provided</scope>
</dependency>

The jsr250-api dependency is not strictly required as Java6 provides those packages out of the box, but I like to keep my poms as "java 5 friendly" as possible.

This provides the EJB 3.0 API. What if you want to compile for JEE6 / EJB 3.1? You're out of luck, Oracle does not provide an EJB 3.1 API that I know of. Any documentation you'll find on the internet in relation to Maven will point to the JEE6 API, but as you know that one cannot work in unit tests.

Our friends at JBoss come to the rescue however. If you want to compile for JEE6, use the following dependency from the JBoss Maven repository in place of the EJB 3.0 dependency:

<dependency>
  <groupId>org.jboss.ejb3</groupId>
  <artifactId>jboss-ejb3-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
  <exclusions>
    <exclusion>
     <groupId>jboss.jbossws</groupId>
     <artifactId>jboss-jaxrpc</artifactId>
    </exclusion>
    <exclusion>
     <groupId>org.jboss.javaee</groupId>
     <artifactId>jboss-transaction-api</artifactId>
    </exclusion>
  </exclusions>
</dependency>

Again, I provide some exclusions to keep your classpath as clean as possible.


Which one of these three methods you should use is really up to your needs. If you only need to compile and you do not unit test (that is going to bite you some day), by all means use method 2) and only include the JEE API. If you are using a unit testing framework such as Arquillian which runs unit tests inside the container, you have no choice but to use method 1). If you are like me and you unit test your EJB code as if they are plain old Java classes, method 3) is really the most efficient way to go.


Exploded deployment

I don't want to repeat what I documented before. If you want to learn about exploded deployments, I suggest you check out my hot deployment article.


Subdirectories

JBoss allows you to deploy your files inside a subdirectory in the deploy directory. The server scans fully recursive. You could create the next Facebook application called Acebook, and deploy files like this:

deploy\acebook\acebook.ear
deploy\acebook\acebook-ds.xml
deploy\acebook\acebook-scheduler-service.xml
etc.

You see where it benefits: you can keep all application related files close together. This also makes it easier to create an installer for your application should you need one.


Service MBeans

Did you know that you can add services to a JBoss instance? Even roll your own?

JBoss defines a naming convention for this - any file in your deploy directory (or a subdirectory of the deploy directory) that is called XXX-service.xml, JBoss will treat like a file defining additional mbean services. Replace XXX with any file that you want.

The content of such a file must be like this:

<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="CLASSNAME" name="SERVICEDECLARATION">
...
</mbean>

<mbean code="CLASSNAME" name="SERVICEDECLARATION">
...
</mbean>
</server>

You can define any type of mbean in there and any number that you like. Although the deploy directory already contains a mail-service.xml and a scheduler-service.xml, you are not obliged to declare mail and scheduler services in those files; you can put them all in your own acebook-service.xml file if you want.

I suggest you snoop around your deploy directory and inspect any file that ends with -service.xml.

The property service

An often overlooked little gem, the property service can be a useful little thing. Its function is quite simple: to define system properties. Being able to define those in a service helps to work around the fact that properties are usually very much bound to an environment.


An example:

<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.varia.property.SystemPropertiesService" name="jboss:type=Service,name=SystemProperties">
  <attribute name="Properties">
    acebook.backup.dir=/usr/gimby/acebook/backups
    acebook.testmode=true
    acebook.email.from=gimby@somewhere.cm
  </attribute>
</mbean>
</server>

These properties are now available through the System.getProperty() call.

Note:
A standard JBoss 5.1 installation will already have a file properties-service.xml that defines the properties service mbean; you should add all your properties to the existing one. Adding another one will create a deployment error, as MBeans must have unique names.


The scheduler service

JBoss has a built in scheduler that can be of great use to you. It may not be as powerful as for example Quartz, it gets the job done.

The things you can schedule are MBeans, otherwise known as service beans. To be able to create MBeans, you need a JBoss specific library; the maven details are:

<dependency>  
  <groupId>org.jboss.ejb3</groupId>  
  <artifactId>jboss-ejb3-ext-api</artifactId>  
  <version>1.1.1</version>  
  <scope>provided</scope>  
  <exclusions>  
    <exclusion>  
       <groupId>org.jboss.javaee</groupId>  
       <artifactId>jboss-ejb-api</artifactId>  
    </exclusion>  
    <exclusion>  
      <groupId>org.jboss.metadata</groupId>  
      <artifactId>jboss-metadata</artifactId>  
    </exclusion>  
  </exclusions>  
</dependency>  

I add the exclusions to not pollute the classpath; you don't need those transient dependencies (and THEIR transient dependencies, etc).

Now on to the code. Lets create a useful service that prints hello ever 5 seconds. Here's the code:

@Local
public interface HelloWorld{
  public void sayHello();
}

@Service(objectName = "myhello:service=HelloWorld", name = "HelloWorld")
@Management(HelloWorld.class)
public class HelloWorldBean implements HelloWorld {
  
  public void sayHello(){
    System.out.println("Hello!");
  }
}

Note that the management interface is mandatory.

Now to schedule this MBean, we need to add a service in an XXX-service.xml. For example, you could use the existing scheduler-service.xml file.

<mbean code="org.jboss.varia.scheduler.Scheduler"
          name=":service=HelloWorldScheduler">
      <attribute name="StartAtStartup">true</attribute>
      <attribute name="SchedulableMBean">myhello:service=HelloWorld</attribute>
      <attribute name="SchedulableMBeanMethod">sayHello</attribute>
      <attribute name="InitialStartDate">NOW</attribute>  
      <attribute name="SchedulePeriod">5000</attribute>
      <attribute name="InitialRepetitions">-1</attribute>
      <attribute name="FixedRate">true</attribute>
      <depends>myhello:service=HelloWorld</depends>
   </mbean>

Startup JBoss with this class deployed in it (for example in an EJB module) and if everything is correct, you should see "hello!" appearing in your log every 5 seconds. Not very useful I know, but I'm sure you get the idea :)

Note:
By adding the management interface, you can now also invoke the sayHello() method through the jmx-console! Go to http://localhost:8080/jmx-console and see for yourself.

And also note that through the jmx-console, you can stop and (re)start scheduled instances of Mbeans.

A word of caution: the @Service and @Management annotations are JBoss specific and thus not portable... not even across versions of JBoss unfortunately. I deal with that myself by keeping MBeans away from my actual application, often deploying them as a separate EJB jar.

Classpath isolation


Why oh why is this turned off by default? (well actually: since JBoss 6 it is turned on by default).

Classpath isolation. Turn it on because really, it saves so many headaches. What is it? Basically it is to create a private little island within JBoss for each of your EAR packaged applications. They each have their own classloader and thus their own private set of classes; it doesn't share anything from the outside world, it doesn't share anything with the outside world (not even local interfaces!)

So what does it help? Library clashes. Each EAR can have its own private version of a library (Jboss Seam 2 being the prime example; without classpath isolation you can run into many conflicts with it); one application can be running on Hibernate 3.2.6 while another is running on Hibernate 3.5 - it doesn't matter.

Now don't get me wrong, to be able to use classpath isolation you need a clean classpath. This can be especially troublesome when you are using Maven and you don't regulate with an iron fist which libraries go into your application. You can get into trouble when you have JBoss libraries in your application (those provided by the JBoss instance) - you will probably get into the conflict of the same class being loaded from two different classloaders - to Java it are then two different classes and objects created from them are not assignable to each other.

So the resolution:

- turn on classpath isolation
- clean out redundant libraries from your classpath
- live long and prosper

That leaves the question HOW to turn it on. Quite simple; in the deployers directory of your instance, locate the file ear-deployer-jboss-beans.xml. In this file, probably all the way at the bottom of it, locate this piece of xml:

<bean name="EARClassLoaderDeployer" class="org.jboss.deployment.EarClassLoaderDeployer">
      <property name="isolated">false</property>
   </bean>

Simply change the false to true and you're done.


EJB Transaction timeout

EJB methods tend to have a running transaction. This transaction has a timeout value, which is by default set to 5 minutes in JBoss 5.1 (and JBoss 6).

So what if you invoke some code that may take longer than 2 minutes to run? You have a few basic options.

- run the code in an EJB method with no transaction at all (NOT_SUPPORTED)
- use an EJB timer (to be discussed later)
- use the EJB 3.1 feature to create an asynchronous EJB call
- increase the EJB timeout

The latter is the easiest, but of course not entirely safe. Say you increase the transaction timeout to 10 minutes, whats to stop the code from taking 30 minutes to complete? But in those situations where you know how much time you need, it is valid to increase the timeout.

To do that, you need to open the file deploy/transaction-jboss-beans.xml of your instance, and locate the section marked:

<property name="transactionTimeout">300</property>

This is the transaction timeout. It is in seconds, so 300 seconds = 5 minutes. Adjust according to your needs.


them pesky log entries

One thing I wish the JBoss devs would be a little more strict in, is the produced logging. Take a vanilla JBoss 5.1 installation and it will definitely give you scary looking trace logs that seem to indicate something is wrong.

In fact, these logs are not for your eyes; they are there as a sort of reminders for the JBoss devs. But unfortunately you still see them. I got annoyed by it, so I tweaked the conf/jboss-log4j.xml file until I saw only logs that I wanted to see.

To get the same clean logging environment as I have, I suggest that you add the following configuration options:


  <category name="com.arjuna">
     <priority value="WARN"/>
  </category>

   <category name="org.jboss.cache.interceptors.CacheLoaderInterceptor">
    <priority value="ERROR"/>
   </category>   

   
   <category name="org.hibernate">
      <priority value="ERROR"/>
   </category>

  
  <category name="org.jboss.ejb3">
      <priority value="ERROR" />
  </category>

  
  <category name="org.jboss.resource.deployers.RARDeployment">
      <priority value="ERROR" />
  </category>


The file should already have entries for Facelets, Seam and Ajax4jsf.

I'll give a short overview:

com.arjuna
this is the transaction manager used. Most of what it logs is basically useless, but if you are having problems that you might suspect are transaction related, comment out this category.

org.hibernate (all entries)
Hibernate likes to give trace messages that are basically of the type "todo, you are using deprecated stuff" - unfortunately it is not your problem, but that of the Hibernate devs.

org.jboss.ejb3.interceptors
The same story as Hibernate - the EJB module likes to give regular trace deprecated notices that are really not interesting to you and me.


That's it for basic JBoss 5.1 - luckily the 5.1 configuration already contains most of the entries I had to add myself to JBoss 4.2.X.


EJB 3.0 - asynchronous EJB invocations

Note that this is not JBoss specific, but it is a nice fun fact anyway. The EJB 3.1 spec (part of JEE6) adds support for asynchronous EJB invocation. But what if we are not using JEE6 yet?

Slightly less documented, since EJB 2.x there is built in timer support. JBoss can even make these timers persistent (in the TIMER table that JBoss will create in the database pointed to by DefaultDS).

Timers can be used to, after a specific interval, invoke a certain EJB method. In other words, this is an asynchronous EJB invocation. We can abuse this fact to easily do a long-running task, for example the processing of a file, in the background while our application goes its merry own way. When the EJB method finishes you can implement some kind of notification if you want, that is up to you.

On the code level, you create an EJB and into it you inject a TimerService.

@Resource
  private TimerService timer;


Now we need to define an EJB call that is going to be invoked asynchronously.

@Timeout
  @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  public void myAsynchronousTask(Timer timer) {
     ...
  }

The method has to be of return type void and accept a Timer as a parameter; the name is up to you. The @Timeout annotation defines which method will be invoked by the timer. I annotate this method as not having a transaction; this is up to you, but realize that if it is a long running task, you may get a transaction timeout. In general I'd expect these kind of tasks to not need an active transaction (but it may invoke other EJB calls that do run in their own transaction).

All we need now is a way to actually schedule the task.

@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  public void invokeTask(MyObject myObject) {
    timer.createTimer(0L, myObject);
  }

createTimer() schedules the task, in this case with a delay of 0 milliseconds, meaning the asynchronous call will happen instantly. The second parameter is a generic object you can pass along to the invoked EJB call; you can fetch it back like this:

@Timeout
  @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
  public void myAsynchronousTask(Timer timer) {
     
     MyObject myObject = (MyObject) timer.getInfo();
     ...
  }

Now all you have to do is call invokeTask() - your code will continue as if nothing has happened, but in the background the server will be busy running your long-running myAsynchronousTask() call. Neat, isn't it!?


A word of caution: persistent timers will go through a datasource. If the datasource you use for JBoss and for your application are different, make sure to use an XA datasource for your application because you are effectively creating a distributed transaction. The JBoss datasource can stay a local-tx-datasource.

Defining a context for a web module

There are multiple ways to couple a context name to a web module.

Option 1: define in the application.xml
If you deploy your application as an EAR you don't have to think long about it. Just declare the web root in your application.xml file, like this:

<module>
  <web>
    <web-uri>your-web-module.war</web-uri>
    <context-root>/appname</context-root>
  </web>
</module>

When the JBoss instance is running on the localhost, the application will be available under http://localhost:8080/appname.

Option 2: jboss-web.xml
If you deploy only a war, then you can add a file META-INF/jboss-web.xml, with the following basic configuration:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC    
"-//JBoss//DTD Web Application 5.0//EN"    
"http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd";>
<jboss-web>
    <context-root>appname</context-root>
</jboss-web>

There is even an option 3: defining a context.xml file in your war. I would stick with one of the two defined options though. The JBoss documentation also advises you to use jboss-web.xml over context.xml. The context.xml really only exists for compatibility with Apache Tomcat (which the web components of JBoss are built on).

Option 4? Don't define any context anywhere. In that case the context will be equal to the name of your war; so if your war is named myapp.war, the context will be /myapp.


The JNDI view

You know about the nifty jmx-console built into JBoss right? It provides many functions that are useful for maintenance and monitoring. One of the nice features it provides is the jndiView. Invoking the list method on this MBean will give you a nice overview of the entire JNDI tree as it exists at that point in JBoss; this can help you diagnose deployment problems as it not only tells you which resources (such as EJBs) are deployed or aren't deployed, but it also tells you under which name they can be looked up.


Defining your own JNDI name for an EJB

You know, or should know, that by default under JBoss an EJB will have a JNDI name with the following pattern:

EARNAME/BeanName/INTERFACE

Where EARNAME is the name of the ear you deployed, BeanName is the actual name of the EJB bean class and INTERFACE is either local or remote. When you deploy an EJB jar without an EAR, the pattern is BeanName/INTERFACE.

Using a JBoss specific annotation you can define at the code level what the JNDI lookup name for a specific EJB is going to be, overriding the name that JBoss assigns to it. The annotation in question is @LocalBinding; to use it in your code you need to have the jboss-ejb3-ext-api dependency on your classpath, as is described in the scheduler paragraph.

You use this annotation like this:

@Stateless
@LocalBinding(jndiBinding = "MyEjb/local")
public class MyEjbBean implements MyEjbLocal

Of course there is also a @RemoteBinding annotation. Try it out; if you check the jndiView function of the jmx-console, you'll notice that the EJB (or the local interface of that EJB to be more exact) is now deployed under the name you specified here!

Note that you can provide any name you want (although there may be a restriction on the characters you can use); you could even use a name as simple as this:

@Stateless
@LocalBinding(jndiBinding = "MyEjb")
public class MyEjbBean implements MyEjbLocal

Just beware that simplifying the name too much will invite name clashes.

Watch it!
These are JBoss specific annotations; using them makes it so your code is not portable among different application servers. In fact, it is not even portable between JBoss versions. A more portable way (across JBoss versions anyway) is to configure your EJBs in a META-INF/jboss.xml file in your EJB:

<jboss>
  <enterprise-beans>
    <session>
      <ejb-name>MyEjbBean</ejb-name>
      <local-jndi-name>MyEjb</local-jndi-name>
    </session>
  </enterprise-beans>
</jboss>

local-jndi-name is the name for the local binding. If you want to override the remote binding also, use jndi-name.

Looking up a remote interface from a client

Seriously, you want to do a remote EJB lookup? I have one piece of advice for you: turn the EJB into a jax-ws webservice. Not only does it make your EJB far more portable, but you also don't need a whole host of JBoss dependencies on your client's classpath to make it work; a basic Java 6 runtime is enough. I even wrote an article to show how it is done.

If you must persist, here's how you do it.

STEP 1: give the EJB of choice a remote interface (duh)
STEP 2: in the client, define the correct JNDI lookup properties

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
env.put(Context.URL_PKG_PREFIXES, "org.jboss.naming:org.jnp.interfaces");
env.put(Context.PROVIDER_URL, "jnp://localhost:1099");

InitialContext context = new InitialContext(env);

Of course, substitute localhost for the correct host name when you are not connecting to a server running on the localhost.

Hardcoding connection properties is hardly a good idea; you can also create a file jndi.properties and put it on the root of the classpath of your client. Give it the following content:

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


And then change your code to simply this:

InitialContext context = new InitialContext();


STEP 3: lookup the bean of your choice.

MyBeanRemote ejb = (MyBeanRemote) context.lookup("MyEar/MyBean/remote");

Trying this code will net you ClassNotFound errors. To be able to make it work you need the JBoss client libraries on your classpath, which you can find in the JBOSS_HOME/client directory of your server installation. You can start with adding jbossall-client.jar to your client's classpath; you may need to add a few more additional transient dependencies such as commons-logging. Only one way to find out; keep trying and adding missing dependencies until you stop getting errors :/


This is the official way it should work when running JBoss 5.1. For completeness, if you want to do the same for a JBoss 4.2.X server, you'd need to change the code to the following:

InitialContext context = new InitialContext();
Object key = context.lookup("MyEar/MyBean/remote");
MyBeanRemote ejb = (MyBeanRemote)  PortableRemoteObject.narrow(key, MyBeanRemote.class);

The old 'EJB 2.X' way of doing it, even if you are looking up an EJB 3.0 bean. This may seem weird, but you have to remember that JBoss 4.2.X is basically a J2EE 1.4 server with EJB 3.0 support patched into it.

Changing port bindings

By default, JBoss listens on port 8080 (HTTP) and 8443 (HTTPS). If you want to put JBoss in a production environment, you will want to change this to the default port 80 to not have to add the port to the web url.

To do this, we need to modify the conf/bindingservice.beans/META-INF/bindings-jboss-beans.xml file.

To change the web port, search for the following: jboss.web:service=WebServer. Here, change the default port 8080 to 80, or whatever port you want. Search for the same search string again; this time you'll get the HTTPS binding; change the 8443 port to 443 or whatever port you want.

Note that it is not recommended to change the default port when you are deploying to this server from Eclipse.


Reaching a JBoss instance through a network

Note: this works on any JBoss up to and including JBoss 6.

Need to give a quick demo in some office and you don't have time to setup a pc there? No problem: you can simply run your own development JBoss by making it listen on your local (internal) IP address.

To do this, go to the bin directory of your JBoss server and execute the following command:

run -c INSTANCENAME -b IP.ADDRESS.GOES.HERE

Where INSTANCENAME is the name of the JBoss instance you want to start (default for example) and of course input your computer's internal IP address after the -b switch.

Try it out by going to any other computer in the same network and trying to reach a webapp in your instance in any browser by using your computer's IP address as the hostname.


Performance tuning:disable JSP checks

In this day and age, are you still using JSPs? I hope not, there are far better alternatives out there like Velocity, Wicket, Play framework or Facelets.

So one thing we can safely do is turn of periodic checks that are done to JSPs, such as checking for changes. This can improve runtime performance slightly.

Open the file deployers/jboss-web.deployer/web.xml. In this file, search the declaration of the jsp servlet. Here you will most likely have to add the following two parameters:

<init-param>
  <param-name>reloading</param-name>
  <param-value>false</param-value>
</init-param>

<init-param>
  <param-name>checkInterval</param-name>
  <param-value>200000</param-value>
</init-param>

<init-param>
  <param-name>development</param-name>
  <param-value>false</param-value>
</init-param>

This will not completely disable JSP modification checks (for some reason a value of 0 for checkInterval was not accepted by my server instance, while the Tomcat documentation seems to suggest this will turn the feature off), but it will at least make them very infrequent.


Disable AJP

By default JBoss enables AJP. If you want to use the HTTP connector directly, you are best of disabling AJP altogether. Simply do this by opening deploy/jbossweb.sar/server.xml and commenting out the ajp connector you find there.

Note: be wary when you do this; turning off AJP might decrease performance in certain cases. If you have no reason to turn it off, then don't.


Performance tuning by removing unneeded services

(This applies to JBoss 5.1 only).

JBoss 5.1, although you don't see it directly, is quite modular. It has a service stack that are tiny mini applications within JBoss that can be included and excluded by choice.

By default, JBoss takes some time to start up. On a fast machine with a fast harddrive it can still take up a good 25 seconds before you're up and running. By removing a few of the services you aren't using you might be able to shave a few seconds off. When you start to remove services, be sure that you do not do this in the default server instance; you should keep that one unmodified just in case you need to restore some mistakes you made.

I could sum it up myself, but it is easier to just look at this forum post that sums it up very well:

http://community.jboss.org/wiki/jboss5xtuningslimming

I wouldn't recommend blindly stripping everything mentioned in this article! Please apply some careful consideration to it ;) Removing all unnecessary services and preventing excessive logging in my instance, I managed to shave 3 seconds off the startup time. Is it really worth it? You be the judge. The article does mention a way to reduce memory usage though, which is worth checking out.

JBPM 3.2.9 on JBoss 5.1

introduction


Yet another article about JBPM on JBoss 5.1. The last time around I based the article on JBPM 3.2.6. Unfortunately I discovered after writing that article that this version still contains some blocking issues.

The latest and most stable installment in the JBPM 3 branch is version 3.2.9; unfortunately installing it is not the exact same process as older versions. I wanted to preserve the knowledge in the old article, hence I created this new one.

When installing JBPM 3, I highly recommend you to go for JBPM 3.2.9.

You may want to read through the old article (and its comments) before continuing with this one; it contains some additional information I am too lazy to include in this article :)

Note:
Even though I target JBoss 5.1, you can use the exact same steps to get JBPM to run on JBoss 4.2.3. The only difference is in the choices you make yourself during the installer phase (choose JBoss 4.2.3 in stead of JBoss 5.1 as the target server).


Downloading and installing

For some reason or another, JBPM 3.2.9 has no official package on the JBoss website. Most likely this is a support issue; Redhat still makes money on licensed support for JBoss products. Anyway, it isn't all that hard, it just requires a few steps to get things running.

First of all, you need to download the distribution. Since there is no official download package, we'll have to pull it from the Maven repository. You can find the distribution package here:

https://repository.jboss.org/nexus/content/groups/public/org/jbpm/jbpm3/jbpm-distribution/3.2.9/.

Download the file 'jbpm-distribution-3.2.9-installer.jar', which is an executable jar that will run the installer.

The installer will ask you three basic questions;

- where do you want to install the JBPM runtime; this can be any directory you want. The runtime contains all the libraries, documentation, configuration and sources.

- which components do you want to install (leave at default; only install JBPM runtime, not Eclipse or JBossAS).

- In which JBoss server and instance do you want to install the JBPM manager.

a difference in previous releases of JBPM is that the JBPM 3.2.9 installer also knows about JBoss 5.1 and Oracle, which is a very nice thing. When you choose JBoss 5.1 it will generate for example the correct JMS queue configuration file. When you choose Oracle it will generate a datasource and Hibernate cfg settings.

The configuration of the JBoss instance is in two parts; first you choose the version and the instance name ('default' for example), second you choose where the server is installed. Point to the root directory of your JBoss installation.

To verify the installation, you should end up with two things:

- the jbpm runtime directory
- in the JBoss instance you chose, there should now be a subdirectory 'jbpm' in the instance's deploy directory.


patching hibernate.cfg.xml

Unfortunately the generated hibernate.cfg.xml contains a flaw, lets fix that right away. Open it up (find it in the jbpm/jbpm-service.sar/hibernate.cfg.xml) and make the following change.

Change this:

java:comp/env/jdbc/JbpmDataSource

To this:

java:JbpmDS


Even more unfortunately is that even with this change in place, JBPM won't work at this point. Lets dive a little deeper.

Patching the enterprise.jar


Even with all the improvements in place, the code still doesn't work out of the box on JBoss 5.1 (or even JBoss 4.2.x). There are some JNDI lookups done in the enterprise.jar module that simply won't fly. So we have no choice but to patch the code ourselves.

The source itself can be found in the JBPM runtime installation directory (src/jbpm-enterprise-sources.jar). Create a project in your favorite IDE so you can build this code as a jar; you'll need the jbpm-jpdl 3.2.9 dependency at least (its in the lib directory of your JBPM runtime). Since the enterprise.jar builds against JEE technologies such as JMS, you'll also need to add the J2EE 1.5 dependency to your project. Most IDEs allow you to create a JEE based project that will do this automatically for you. Or you can do like me and use Maven.

Anyway, once you get the code to compile, you'll need to make the following changes to it. I'll list the line of code to change, and what to change it to.


ejb.impl.CommandListenerBean

From:

jmsConnectionFactory = (ConnectionFactory) jndiContext.lookup("java:comp/env/jms/JbpmConnectionFactory");

To:

jmsConnectionFactory = (ConnectionFactory) jndiContext.lookup("java:JmsXA");

From:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) jndiContext.lookup("java:comp/env/ejb/LocalCommandServiceBean");

To:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) jndiContext.lookup("java:ejb/CommandServiceBean");

From:

deadLetterQueue = (Destination) jndiContext.lookup("java:comp/env/jms/DeadLetterQueue");

To:

deadLetterQueue = (Destination) jndiContext.lookup("queue/DQL");


ejb.impl.TimerEntityBean

From:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) JndiUtil.lookup("java:comp/env/ejb/LocalCommandServiceBean", LocalCommandServiceHome.class);


To:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) JndiUtil.lookup("java:ejb/CommandServiceBean", LocalCommandServiceHome.class);

msg.jms.JmsMessageServiceFactory

From:

String connectionFactoryJndiName = "java:comp/env/jms/JbpmConnectionFactory";

To:

String connectionFactoryJndiName = "java:JmsXA";

From:

String destinationJndiName = "java:comp/env/jms/JobQueue";

To:

String destinationJndiName = "queue/JbpmJobQueue";

scheduler.ejbtimer.EjbSchedulerServiceFactory

From:

String timerServiceHomeJndiName = "java:comp/env/ejb/LocalTimerServiceBean";

To:

String timerServiceHomeJndiName = "java:jbpm/LocalTimerServiceBean";


scheduler.ejbtimer.TimerServiceBean

From:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) JndiUtil.lookup("java:comp/env/ejb/LocalCommandServiceBean", LocalCommandServiceHome.class);

To:

LocalCommandServiceHome commandServiceHome = (LocalCommandServiceHome) JndiUtil.lookup("java:ejb/CommandServiceBean", LocalCommandServiceHome.class);

scheduler.ejbtimer.EntitySchedulerServiceFactory

From:

String timerEntityHomeJndiName = "java:comp/env/ejb/TimerEntityBean";

To:

String timerEntityHomeJndiName = "java:jbpm/TimerEntityBean";

As you can see, these are only changes to JNDI lookups. I discovered the correct names by checking the jboss.xml file and the jndiView through the jmx-console application.


Build the jbpm-enterprise.jar file (if you get a different filename, rename it). Remove the exploded enterprise.jar directory from the jbpm directory that was added to your JBoss instance, and replace it with the jar you just built.


Setting up the JBPM manager to work with Oracle


Note:
If you use a different DBMS the steps will be exactly the same, only you'll need to use/modify differently named files.


Of course, install an oracle driver into the instance's lib directory. The installer has already generated a datasource file, but you'll still need to modify it to point to the schema of your choice. You'll find the file in the jbpm subdirectory of the JBoss instance you configured during the installation process, under the name jbpm-oracle-ds.xml.

A note about the datasource:
The datasource defines an MBean jboss.jca:service=OracleXAExceptionFormatter; when you use Oracle you may already have this MBean declared in your own datasource file. If you do, remove it from the JBPM generated datasource, or you'll get a duplicate MBean error during startup.


To be able to work with JBPM, you need to create a set of tables in the oracle schema you configure. Luckily the runtime installation comes with the script you need; simply invoke the file RUNTIME_INSTALL_DIR/database/jbpm.jpdl.oracle.sql.

Finally to be able to work with the manager we need some users and roles. You can execute the following statements to get a few defaults:

INSERT INTO JBPM_ID_GROUP VALUES(1,'G','user','security-role',NULL);
INSERT INTO JBPM_ID_GROUP VALUES(2,'G','manager','security-role',NULL);
INSERT INTO JBPM_ID_GROUP VALUES(3,'G','admin','security-role',NULL);

INSERT INTO JBPM_ID_USER VALUES(1,'U','manager','','manager');
INSERT INTO JBPM_ID_USER VALUES(2,'U','user','','user');
INSERT INTO JBPM_ID_USER VALUES(3,'U','shipper','','shipper');
INSERT INTO JBPM_ID_USER VALUES(4,'U','admin','','admin'); 

INSERT INTO JBPM_ID_MEMBERSHIP VALUES(1,'M',NULL,NULL,1,1);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(2,'M',NULL,NULL,1,2);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(3,'M',NULL,NULL,1,3);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(4,'M',NULL,NULL,2,1);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(5,'M',NULL,NULL,3,1);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(6,'M',NULL,NULL,4,1);
INSERT INTO JBPM_ID_MEMBERSHIP VALUES(7,'M',NULL,NULL,4,3);


At this point you should be able to succesfully deploy the JBPM manager without any errors, open up the jbpm-console webapplication and login as manager/manager!


Deploying from Eclipse


Now that JBossAS is all setup, we need to do some work in Eclipse to be able to add a JBPM process to a project. Note that I am assuming you are using Eclipse 3.5.2 or higher. Also you need the JEE version of Eclipse.

The thing we need is our good friend, JBoss Tools. This wonderful set of Eclipse plugins can give you great joy, if you take the time to learn how to work with it. Jboss Tools contains far more tools beyond JBPM; in this document I'll limit it to the JBPM tools.

In order to install JBoss tools you just need to point the Eclipse software installer to the download site. Find the correct download site for your version of Eclipse on this page:

http://www.jboss.org/tools/download

Note that at the time of writing, there was no stable release yet for Eclipse Helios, so if that is still true when you read this, simply use the latest development milestone. I can assure you that the JBPM 3 part of the plugin is stable.

This will present you with quite an impressive list of available plugins; I suggest you install at least these ones:

  • JBossAS tools
  • JPBM3 tools SDK
  • Jboss BPEL editor SDK

This will give you tools for deploying to a JBoss server and the JBPM 3 visual process editor.

After installation (and restarting Eclipse) we still have one setup step to go. We should configure our JBPM runtime. Click:

window -> preferences -> JBoss JBPM -> runtime locations

Click Add and add the runtime by selecting the directory where you installed it. Also give it a descriptive name. After adding it make it the default runtime by checking the checkbox in front of the name and click the apply button.

To be sure, select jBPM 3 -> server deployment. Make sure that the following information is filled in here;

server name = localhost
server port = 8080
server deployer = /gpd-deployer/upload

Check use credentials, and for the username/password input manager/manager.


That's it for the configuration part. But we still need to install one last piece of software.

Installing the GPD-deployer

Starting from JBPM 3.2.8, the deployer servlet is no longer part of the jbpm-manager itself. This is done for security reasons; you don't want this servlet in a production environment (in stead you deploy processes through the jbpm-console web application).

If we want to deploy processes from Eclipse, we need to install the gpd-deployer web application.

Download it here: http://repository.jboss.org/nexus/content/groups/public-jboss/org/jbpm/jbpm3/gpd-deployer/3.2.9/.

Get the file gpd-deployer-3.2.9.war and put this war in the jbpm subdirectory of your JBoss instance. Start the server and verify that everything still deploys fine.


You can now try it out if it all works; in any project click:

new -> other -> JBoss jBPM -> jBPM 3 Process Definition

Give your process definition a name and if all is correct, you'll be presented with the JBPM visual process editor! When deploying your created process, be sure to check the deployment configuration again (you can use the test connection button to verify everything is setup correctly).


JBPM and classpath isolation

Sometimes you have to work in an environment where classpath isolation is turned off. You'll find that when you have a JSF based application running next to the jbpm-manager in such situations, you get a strange 'jbpm configuration is null' error when you try to see a page. Opening the jbpm-console application will magically fix that issue.

If you are in this situation you have one of two options.

- turn on classpath isolation (really, you should do that!)
- patch your web module so that it can play nice with the jbpm-manager application.

To do this you need to add two dependencies to your war module:

- jsf-console-jbpm4jsf (maven groupId = org.jbpm.jbpm3, artifactId = jsf-console-jbpm4jsf, version = version 3.2.9)
- JBoss gravel (maven groupId = org.jboss.gravel, artifactId = gravel, version = 1.0.0.GA)

Then to magically fix the problem, add a file 'jbpm4jsf-config.xml' to the WEB-INF of your war, with the following default content:

<?xml version="1.0" encoding="UTF-8"?>
<jbpm4jsf-config xmlns="http://jbpm.org/jbpm4jsf/config">

    
    <authentication-service>
        <use-jsf-actor>true</use-jsf-actor>
    </authentication-service>

    <!-- This entry facilitates the usage of process image URLs -->
    <process-file-matcher>
        <pattern>^/app/pi/(\d+).jpg$</pattern>
        <file-name>processimage.jpg</file-name>
        <content-type>image/jpg</content-type>
    </process-file-matcher>

</jbpm4jsf-config>

Now boot JbossAS and open a webpage of your application: problem solved.


In closing

At some point in time I'll write a beginners guide to JBPM development. But perhaps it will be on JBPM 5 in stead, which is looking quite cool indeed. I hope this article will give you some benefit; I already know I'm not the only one out there that has to use this old technology!

Thursday, November 4, 2010

Hot-deploying from Eclipse

Article note: This article is in dire need of example screenshots. I'll add them as soon as I've made them.


Today we have fancy IDEs like Eclipse and Netbeans that can do many mundane tasks for us. One such task is building and packaging a JEE application (although I still prefer to do that through Maven).

The one thing where any IDE fails, in my honest opinion, is development deployment of a JEE application. Netbeans gets far when deploying a simple jsp/servlet based web application, but when taking the next step and trying to deploy a full JEE application, things just don't work like I want them to. Either certain resources are not deployed, they are deployed in packaged form, they contain the wrong names or they end up in a place I don't want them to be.

Now you can try to get specialized plugins like Eclipse's JBossTools to do the deployment for you, or you do it my way - which is to take full control over application deployment. What I will describe to you now is a way to setup your development environment, using Eclipse, to hot-deploy files to the server as you modify them (automatically of course), making it quite easy to debug and fix your web applications without endless restart cycles.

What is hot-deployment?
Simply to "swap in" classes and web resources in a running server. Most modern servers can instantly swap in servlets, EJBs, MDBs and even configuration resources such as JSF configuration files and datasources without having to restart either the server or the web application (you can force a webapp to reload by touching the web.xml file; JBoss 5.1 even does it when you modify other resource files, such as the application.xml or a JSF configuration file).

The only rule is that there must not be a change in method signature, such as a change in parameters or added/removed fields or methods. When this happens the server needs a reboot unfortunately to load in the changes.

Also, it is not possible (yet) to "swap in" resources that are discovered during server startup, such as entities. You also won't see changes to static members until you reboot (a good reason to avoid statics when you can IMO).


The project setup

Since I'm a big fan of Maven, lets take the average Maven project as an example. When we create a Maven project with an EJB, WAR and EAR module, we'll end up with the following set of directories after a clean install:

Eclipse resources:
example/exampe-ejb/src/main/java
example/example-ejb/src/main/resources
example/example-ejb/target/classes
example/example-web/src/main/java
example/example-web/src/main/webapp
example/example-web/src/main/resources
example/example-web/target/classes

Maven specific resources:
example/example-ear/target/example/lib
example/example-ear/target/example/META-INF
example/example-web/target/example-web/WEB-INF/lib

Of course most of these names are dependent on settings in the pom files. Note that to let Maven create consistent directory names, you should give all your modules finalName entries to make sure no version numbers end up in the filenames (as described in my maven article).

The last three entries are specific to Maven and are quite important to us - Maven is responsible for not only collecting all the dependencies of the EAR and the WAR module, but also for generating the application.xml file. We'll need these resources if we want to succesfully hot-deploy the application. All the other resources come from the Eclipse project and are those that are actively developed.

Don't use Maven?
Well you still need the libraries and application.xml file. One option open to you is to simply collect these resources yourself - put them in a directory 'hot-deploy' and in this directory create subdirectories for the EAR and the WAR, in which you put the libraries and application.xml file. This is no problem as these resources should be mostly static.


Eclipse project setup

Note that on the Eclipse side, you don't need more than a mavenized Java project; no need for fancy JEE, EJB or whatever projects. Maven will do all our stuff, Eclipse shouldn't. (but you may want to install the JBossTools plugin set and add JSF capabilities to your project).

There is one requirement: build automatically must be activated for this to work!


Enter the Eclipse filesync plugin

The one plugin that makes the whole setup possible is the excellent filesync plugin for Eclipse, which you can download here:

filesync plugin

Or simply use the Eclipse Marketplace, where you can also find it.

This plugin allows you to manually define which parts of your project you want to sync (and which you specifically do not) and where to put them. Not only that, but synced files will be monitored, and when you modify them in the source location, they are automatically synced to the destination location (again: if build automatically is turned on so the .class file is automatically recreated).

Configure the hot-deploy settings

Lets create a basic configuration. Lets say we have a JBoss 5.1 server with an instance 'myapp' where we want to hot-deploy our phantom mavenized JEE application. Open up the project properties; now that the filesync plugin is installed, there should be a menu item file synchronization present. Select it.


The first thing you will want to do in the resulting window, is add all the directories of your project that you want to sync to the server. This will be the following in our maven project:

WhatWhy
example-ejb/target/classesEJB classes and resources (Eclipse)
example-web/target/classesWAR classes and resources (Eclipse)
example-web/src/main/webappWAR web resources (Eclipse)
example-ear/target/example/libEAR library dependences (Maven)
example-ear/target/example/META-INFgenerated application.xml (Maven)
example-web/target/example-war/libWAR libraries (Maven)

These folders contain all the resources we need to construct our full EAR application. Note that things that are dynamic are taken from the directories where Eclipse does its thing; this makes it so the hot-deployment will work on save. If you would take the Maven generated classes the hot-deploy would only work after you do a mvn clean install and refresh the project view.

Now before we continue, there are two more configurations to be done. First of all, check the checkboxes allow different target folders and use current date for destination files.

Next, see the input field default target directory. This is where we want to put our files; we make this point to our deployed EAR directory.


EAR... directory?

Yes, not an EAR file, but an EAR directory. JBoss, like many other JEE environments, supports what can be called exploded deployment. This means that you don't package up your application in an EJB jar, a web WAR and all that inside an application EAR; in stead, you create a folder structure that pretends to be these files.

Thus, go to your JBoss instance's deploy directory and create there the following directory structure:

deploy\example.ear
deploy\example.ear\META-INF
deploy\example.ear\example-ejb.jar
deploy\example.ear\example-web.war
deploy\example.ear\example-web.war\WEB-INF
deploy\example.ear\example-web.war\WEB-INF\classes

Making this directory structure by hand makes it easy for us to configure the next steps of filesync as it will allow the plugin to open up the existing directory by default in stead of starting at the root.

Now, make the default target directory point to the myapp.ear directory you just created. You are now ready for the final steps.

Configure destinations

With our list of source directories input into filesync and the allow different target directories checkbox checked, you will now see an option target folder under each source item. Here we can now configure where the content of the source items should be hot-deployed to. The following table illustrates the mapping.

FromTo
example-ejb/target/classesexample.ear/example-ejb.jar
example-web/target/classesexample.ear/example-war.war/WEB-INF/classes
example-web/src/main/webappexample.ear/example-war.war
example-ear/target/example/libexample.ear/lib
example-ear/target/example/META-INFexample.ear/META-INF
example-web/target/example-web/libexample.ear/example-web.war/WEB-INF/lib

Testing it out

With the configuration part done, save settings and close the project properties. Now, right-click on the project node in the project explorer and you should see an item force file synchronization. This does exactly what it describes; it will synchronize all modified files from the source locations to the destination locations.

Tip: with the server running, the force file synchronization will most certainly cause the application to be redeployed.

Now open up your deploy directory and see where the files ended up in the server; if everything is correct you should now have a flat directory structure matching exactly how the EAR file would be composed. If not, make modifications to the filesync settings until you get the correct setup.

The final step is simple: start JBoss and check the startup log for errors.

Oh and by the way: filesync is totally capable of recreating the directories. You can safely remove the hot-deployed application from the deploy directory, force file sync and have the complete application recreated fresh. This is sometimes necessary when certain items have been removed from the source; filesync does not automatically remove them in the destination environment. I tend to make it a habit to remove and completely redeploy an application whenever I make big changes.

Testing it out part 2: hot-deploy a modified file


You have a hot-deployed application now? Good. Now lets try out the real benefit of this plugin. Run the server and open up your web application, for example a specific page. Now modify the content of this page in Eclipse and save.

Reload the page in the browser - if everything is correct, you should now see your modified page. And all you had to do was save the file.

Try it out with EJBs and servlets if you want, you'll see the changes made to them (if they don't break the rules explained earlier) are automatically available in your running application.


Troubleshooting


Problem: large files (such as libraries) end up as 0 bytes in size after hot-deployment
Solution: increase the permgen space for Eclipse to 256mb. You can do this in the eclipse.ini file (XXMaxPermSize). If that doesn't help, you probably removed a file and forgot to refresh the project view.

Problem: 'module does not exist' error during startup
Solution: check the application.xml file that maven generated; the name of the EJB and WAR modules must be the same as the directories you created in the JBoss deploy directory.

Problem: library, class or resource is not hot-deployed while it exists
Solution: filesync only sees what Eclipse sees; did you refresh your project view?

Problem: files in the resources directories aren't synced
Solution: for some reason, sometimes Eclipse wants to exclude all files in the resources directories by default. Check this in the build path of your project settings.

Problem: filesync throws an error when I select its menu item in the project properties
Solution: this is an Eclipse thing; sometimes the project facets are screwed up. First try restarting Eclipse. If that doesn't work, open the .project file of your project, you should see something along the lines of:

<buildSpec>
  <buildCommand>
   <name>org.eclipse.jdt.core.javabuilder</name>
   <arguments>
   </arguments>
  </buildCommand>
  <buildCommand>
   <name>org.maven.ide.eclipse.maven2Builder</name>
   <arguments>
   </arguments>
  </buildCommand>
  <buildCommand>
   <name>de.loskutov.FileSync.FSBuilder</name>
   <arguments>
   </arguments>
  </buildCommand>
 </buildSpec>
 <natures>
  <nature>org.maven.ide.eclipse.maven2Nature</nature>
  <nature>org.eclipse.jdt.core.javanature</nature>
 </natures>

If it all looks fine, you can try removing/renaming the .settings/de.loskutov.FileSync.prefs file in your workspace. But this will destroy all settings you did for filesync.


Problem: JBoss doesn't see the changes I made
Solution: I don't really have a solution, but I'm pretty sure there are ways to specifically configure JBoss to not scan for resource changes. That will of course defeat the purpose of hot-deployment. A JBoss instance you download directly from the JBoss website should work flawlessly. If not: well it works on my machine!

Monday, November 1, 2010

the ease of jax-ws 2.x

Webservices. I've had a long time hate-hate relationship with them. SOAP for example is a good idea, but it just isn't a standard. Try and make a Java client talk with a .NET service - it ain't pretty.

Still I have come to respect a little fellar called jax-ws, which makes it so incredibly easy to open up a class to the brave new world, with only a few annotations (and a server like JBossAS). This here article will describe what you need to do, from the server setup to the maven poms to the actual code.


Patching JBoss for compatible webservices

JBoss comes with its own webservices stack that out of the box just doesn't really cut it in my opinion, although it depends on the version of JBoss of course. But there is one thing that saves it all; the Metro webservices stack. Installing JBossWS Metro basically makes your JBoss be able to provide webservices compatible with most of anything. Note that this will have little to no effect on connecting to webservices; jax-ws does all the work there.

Which version of the metro patch you need, depends on which version of JBossAS you use. I especially recommend installing it on JBoss 4.2.X variants.

JBoss 4.2.3 - metro patch 3.1.1GA.
Jboss 5.1 - metro patch 3.2.0GA.

If you are working with JBossAS 6, you may want to experiment with the stack that comes pre-installed. It may just work for you. In this article I will assume JBoss 5.1.

The metro webservices stack can be installed using Apache ANT. Unzip to a temp folder and rename the file ant.properties.example to ant.properties. Now inside this file, alter the propery jboss510.home to your JBoss 5.1 root directory. Afterwards, run:

ant deploy-jboss510

And voila, the metro patch has been installed!


Jax-ws Maven setup

The server now prepared, we'll want to create a project to do two things:

1) create a webservice
2) create a client to talk to this webservice

So we'll create a project built out of two modules, which I will simply call *server* and *client*.


The parent pom

If you don't mind, I am going to cut these poms a little short. For examples of more complete poms, see my article on the Maven2 pom templates.

We define two modules:

<modules>
    <module>server</module>
    <module>client</module>
  </modules>

Our dependency management for this project is very basic.

<dependencyManagement>
     <dependencies>
       <dependency>
         <groupId>wstestapp</groupId>
         <artifactId>wstestapp-client</artifactId>
         <version>1.0.0-SNAPSHOT</version>
         <type>ejb</type>
      </dependency>
       <dependency>
         <groupId>wstestapp</groupId>
         <artifactId>wstestapp-server</artifactId>
         <version>1.0.0-SNAPSHOT</version>
         <type>ejb</type>
       </dependency>
       <dependency>
         <groupId>javax.javaee</groupId>
         <artifactId>javaee-api</artifactId>
         <version>5</version>
         <scope>provided</scope>
       </dependency>
     </dependencies>
   </dependencyManagement>

We'll add one more later on, but for now this is enough. I added the jee dependency to be able to compile EJBs; if you use JBossAS6 then modify the dependency accordingly (you need javax:javaee-api:6.0). Make sure that the JEE dependency is provided of course. You need the java.net Maven repository for these dependencies to be found.

Notice that there is absolutely no dependency for jax-ws in there; that is because jax-ws is part of Java6, so we already have everything we need to compile jax-ws and JAXB code.

That being said, we need Java6 to compile this code. Configure as follows:

<plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <inherited>true</inherited>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
          <optimize>true</optimize>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>

Again, for a more complete example of plugin management in Maven, see my article on it.

The client and server pom


Again these are really basic and I'm going to show only the interesting part, which are the dependencies. The rest you can figure out for yourself from my Maven article. In both modules, define the following dependency:

<dependencies>
    <dependency>
      <groupId>javax.javaee</groupId>
      <artifactId>javaee-api</artifactId>
    </dependency>
  </dependencies>

Again, we'll add a couple more later. But for now this is all you need.


On disc, the folder structure of your project should be something like this:

projectdir/wstestapp/pom.xml
projectdir/wstestapp/client/pom.xml
projectdir/wstestapp/client/src/main/java
projectdir/wstestapp/client/src/main/resources
projectdir/wstestapp/client/target/classes
projectdir/wstestapp/server/pom.xml
projectdir/wstestapp/server/src/main/java
projectdir/wstestapp/server/src/main/resources

Standard Maven project setup really. Note that I explicitely listed the client target/classes directory; you'll see why later on when we generate the client stub classes.


The webservice EJB


The power of JAX-WS webservices lies in the fact that with only a few simple annotations, you can transform an EJB into a webservice. Lets see that right now. We define a simple hello world EJB:

local interface
@Local
public interface WebServiceTestLocal {

  public String helloWorld();
  public String helloUser(String name);
}

EJB
@Stateless
public class WebServiceTest implements WebServiceTestLocal {
  
  public String helloWorld(){
    return "Hello world!";
  }
  
  public String helloUser(String name){
    return "Hello " + name + "!";
  }
}

This EJB defines two methods: a simpel hello world one and a method that will echo back a name we send to it.

Now let me write the exact same EJB again, but this time turned into a webservice.

local interface
@Local
@WebService()
@SOAPBinding(style = javax.jws.soap.SOAPBinding.Style.DOCUMENT)
public interface WebServiceTestLocal {

  public String helloWorld();
  public String helloUser(String name);
}

EJB
@Stateless
@WebService(endpointInterface = "wstest.WebServiceTestLocal",serviceName="WebServiceTest")
public class WebServiceTest implements WebServiceTestLocal {
  
  public String helloWorld(){
    return "Hello world!";
  }
  
  public String helloUser(@WebParam String name){
    return "Hello " + name + "!";
  }
}

That is it! You have now succesfully created a SOAP webservice out of your EJB! Deploy this as an EJB on JBoss 4.2.3 or 5.1 and when you boot the instance you will notice in the logging that it secretly deployed the webservice as a war (even though it is an EJB), making it available through the web. If everything is correct, you'll get something close to the following logging in your server (Note: JBoss 5.1 logging will be added later):

JBoss 4.2.3 log output
JmxKernelAbstraction] installing MBean: jboss.j2ee:jar=wstest.jar,name=WebServiceTest,service=EJB3 with dependencies:
[EJBContainer] STARTED EJB: wstest.WebServiceTest ejbName: WebServiceTest
EJB3Deployer] Deployed: file:/serverpath/server/wstest/deploy/wstest.jar/
[DefaultEndpointRegistry] register: jboss.ws:context=wstest,endpoint=WebServiceTest
[TomcatDeployer] deploy, ctxPath=/wstest, warUrl=.../tmp/deploy/wstest.jar968294628122087434.war/
[WSDLFilePublisher] WSDL published to: file:/serverpath/server/wstest/data/wsdl/wstest.jar/WebServiceTest811903617484336490.wsdl

Open a browser and navigate to http://localhost:8080/wstest/WebServiceTest?wsdl to see the WSDL that we are going to use to create a client for this baby.

Note on the url:
The url is determined by the name of the jar; in this case the name is 'wstest.jar', so the webservice context is 'wstest'. The serviceName declared in the annotations is the actual name of the webservice that you invoke.

Note on deployment
Don't know what to do at this point? Perhaps my article on hot deployment can help you!

Running the webservice without JBoss


Jax-ws provides us a little tool to make the webservice available without even running Jboss. What you can do is add the following main() method to your webservice EJB:

public static void main(String[] args) {

    Endpoint.publish(
       "http://localhost:8080/wstest/WebServiceTest",
       new WebServiceTest());
  }


Now you can run your EJB as a standalone java application. You know when it works when nothing happens - no output but also no return to the prompt. This means the webservice is listening, and you can connect to it from a client.


Generating the client code


Before we can create a client, we first need to generate some stub code that will allow us to talk to the webservice's exposed methods. This can be very easily done with a tool called wsgen; you'll find that this tool is part of your Java6 JDK. For ease of use, add the bin directory of your JDK to the system PATH variable. Also, make sure that your JBoss server is still running.

Open up a command prompt. I'll be assuming you use an IDE like Eclipse and that you have created a maven module 'client' in it. Navigate to the root directory of your client module. If the directory 'client/target/classes' does not exist yet, create it now.

Now, invoke this command:

wsimport -keep -s src\main\java -d target\classes -p wstest.client http://localhost:8080/wstest/WebServiceTest?wsdl

This will generate not only the class files for the client stub, but also puts the source files in your client project (Maven specific paths - adjust to your own project environment if you don't use Maven). Here is a breakdown of the arguments:

ArgumentDescription
-keepThis will trigger the sourcefiles to be generated
-sThis is the location where to put the source classes
-dThis is the location where to put the generated class files
-pThis is the package of the generated stub classes

And finally the url of the WSDL is provided; this is the reason why your server should still be running. Note that the generation of the class files is quite redundant as your IDE will now create them for you from the source files. But at least now you have seen how to do it.

If your generated code does not compile...
You may be using a Mavenized Eclipse project and the javaee 5 API. At this point you might get compile errors on the generated stub classes. This problem lies in the ordering of the build path and is easily fixed.

Open up the project properties in Eclipse, choose build path and select the order and export tab. Here, make sure that your java runtime is above the maven dependencies in the list; this ensures that the jax-ws of your Java6 runtime is used and not the one that is part of the JEE5 API, which is unfortunately outdated.

We now have the tools we need to invoke the webservice from a client!


Command line client

First lets make a little command line client to test it out. This is the code:

public class HelloWorldTest {
  
  static WebServiceTest service = new WebServiceTest();

  
  public static void main(String[] args){
    
    WebServiceTestLocal port = service.getWebServiceTestPort();
    String test = port.helloWorld();
    System.out.println("WebServiceTest said: " + test);
    
    test = port.helloUser("gimby");
    System.out.println("WebServiceTest said: " + test);
  }
}

TheWebServiceTest and WebServiceTestLocal classes are from the generated stub classes; to make this work, simply make sure the classes are on the classpath when you compile/run this (most likely by adding this test class to the same module). Run the client with either JBoss or the commandline service running and you should get the response back!

the EJB client

Now for the real deal; getting it to work in an EJB environment. It is quite easy actually.

Local interface
@Local
public interface HelloWorldService {

  public String helloWorld();
  public String helloUser(String username);
}

Bean
@Stateless
public class HelloWorldServiceBean implements HelloWorldService {

  @WebServiceRef
  static WebServiceTest service = null;
  
  
  public String helloWorld(){
    WebServiceTestLocal port = service.getWebServiceTestPort();
    return port.helloWorld();
  }
  
  public String helloUser(String username){
    
    WebServiceTestLocal port = service.getWebServiceTestPort();
    return port.helloUser(username);
  }
}


That's it! The @WebServiceRef annotation will make JBoss inject an instance of the webservice for us (of course, make sure the stub classes are in the classpath of your client. In fact, just make sure the classes are IN your client, like I suggested during the wsimport step).


Of course, we cannot just invoke an EJB, we need some kind of interface to it.

Creating a simple test JMX interface

JBoss allows us to setup a neat little JMX-interface. Before we can do that, we need to add some dependencies to our project. This is different for JBoss 4.2.X and Jboss 5.1, I'll list them both:

Jboss 4.2.3
In the parent pom, add these dependencies to the dependency management:

<dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-annotations-ejb3</artifactId>
        <version>4.2.3.GA</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-jmx</artifactId>
        <version>4.2.3.GA</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-ejb3</artifactId>
        <version>4.2.3.GA</version>
        <scope>provided</scope>
      </dependency>

And in your client pom, add these dependencies as well:

<dependency>
      <groupId>jboss</groupId>
      <artifactId>jboss-annotations-ejb3</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jboss</groupId>
      <artifactId>jboss-ejb3</artifactId>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>jboss</groupId>
      <artifactId>jboss-jmx</artifactId>
      <scope>provided</scope>
    </dependency>


JBoss 5.1
We need only one dependency here. I'll shorten this to the parent pom, you should know what to do in your client pom:

<dependency>
        <groupId>org.jboss.ejb3</groupId>
        <artifactId>jboss-ejb3-ext-api</artifactId>
        <version>1.1.1</version>
        <scope>provided</scope>
        <exclusions>
          <exclusion>
            <groupId>org.jboss.javaee</groupId>
            <artifactId>jboss-ejb-api</artifactId>
          </exclusion>
          <exclusion>
            <groupId>org.jboss.metadata</groupId>
            <artifactId>jboss-metadata</artifactId>
          </exclusion>
        </exclusions>
      </dependency>

The exclusions prevent classpath polution - otherwise you get a big chunk of JBoss in your dependencies AND local maven repository that you really do not need.


Now that we have the needed dependencies, writing the code is simple:

JMX local interface
@Local
public interface JmxHelloWorld {
  
  public String helloWorld();
  public String helloUser(String username);
}

JMX bean
@Service(objectName = "wstest:service=JmxHelloWorld", name = "JmxHelloWorld")
@Management(JmxHelloWorld.class)
public class JmxHelloWorldBean implements JmxHelloWorld {
  
  @EJB
  private HelloWorldService service;
  
  
  public String helloWorld(){
    return service.helloWorld();
  }
  
  public String helloUser(String username){
    
    return service.helloUser(username);
  }
}

So basically the JMX bean becomes a wrapper around our EJB. However, when you deploy this in JBoss, you can then go to the jmx-console. In the list you should find an entry called wstest, with the single JmxHelloWorld entry. Click on it to expose the two EJB methods; invoking them should net you the response of the webservice!


A note on deployment ordering

When you deploy both the webservice and the client EJB module to the same JBoss instance, you might run into deployment ordering problems. What should happen is that the webservice should deploy before the client EJB, as the client EJB depends on it.

You can enforce this in JBoss by deploying the client jar to a subdirectory deploy.last, such that the jar will be:

JBOSS_HOME/server/INSTANCE/deploy/deploy.last/client.jar

Whatever is in the deploy.last directory will be deployed after everything else.

I only add this section just in case you run into problems, Jboss should be able to work out the dependency injection ordering itself. If you need finer grained control, my JBoss 5.1 migration guide contains a section on enforcing specific deployment ordering.

Going deeper: complex types

Until now we've seen the typical "hello world!" type of example that doesn't really cut it if you really want to use this technology. Of course we want to be throwing around our own objects. This is surprisingly easy using Jax-ws. All you do is create your POJO classes and you add a few JAXB annotations here and there.

Lets see an example where a person can be registered through our webservice.

public RegisterResult register(@WebParam Person person){
  
  RegisterResult result = new RegisterResult();
  try{
    // register person here and get the generated ID
    result.setResult(Boolean.TRUE);
    result.setID(the_generated_id);

  } catch(Throwable t){
    // registration failed
    result.setResult(Boolean.FALSE);
    result.setMessage("Registration resulted in an error.");
    return result;
  }

  return result;
}

The Person class is a simple POJO with basic properties. Name, address, etc. As long as you use basic types, you don't have to do anything special. However: use object types, not primitive types. And provide a no-arg constructor. These objects will be marshalled/unmarhalled by the JAXB API, so if you want to know more about what it can do I suggest you read up on it.


public class Person {
  private String name;
  private String address;
  private String zipcode;
  private String city;
  private Boolean bald;

  public Person(){
  }

  ...
}

The interesting class is the RegisterResult. This object gives some feedback to the callee.

@XmlRootElement
public class RegisterResult {
  private Boolean result;
  private String message;
  private String ID;

  ...
}

This object allows for some basic communication back to the client. Note the XmlRootElement annotation; this is a JAXB annotation that marks this object as being something that JAXB can marshall/unmarshall. Usually you don't even need this annotation, but it is good practice to put it there anyway. When you want to return a List of objects for example, all objects in the list need to be annotated with @XmlRootElement. Plus the annotation adds documentation to your code stating it will be used by webservice calls.

The ID can be used by the client to later do more operations on the registered person. For example, the client may want to retrieve the information back.

public FetchResult getPerson(@WebParam String ID){
  // fetch person based on the ID here.
  Person person = findPersonByID(ID);

  FetchResult result = new FetchResult();
  result.setPerson(person);
  result.setResult(Boolean.TRUE);

  return result;
}

You see I like returning result objects. Your mileage may vary. But lets see this FetchResult object.

@XmlRootElement
public class FetchResult {

  private Boolean result;
  private Person person;
  
  public FetchResult(){
  }

  public Boolean getResult() {
    return result;
  }

  public void setResult(Boolean result) {
    this.result = result;
  }

  @XmlElement(name="person")
  public Person getPerson() {
    return person;
  }

  public void setPerson(Person person) {
    this.person = person;
  }
}

FetchResult is a little more interesting because it contains a nested complex type object; our Person. Using the @XmlElement annotation we can add our Person object as a complex type into the SOAP response. It will end up in a person subelement.

Conclusion

In this article I gave you a few basic tools when working with jax-ws webservices;

- how to apply the metro patch to JBossAS for a more compatible webservice stack
- how to turn an EJB into a webservice
- how to run the webservice from the commandline
- how to run the webservice in JBoss itself
- how to generate client stub classes from the webservice
- how to create a commandline test client
- how to create an EJB consumer client
- how to create a JBoss JMX interface to test the EJB consumer client

Hopefully powered with this knowledge, webservices from this day forward will no longer be a chore.

Monday, September 13, 2010

Richfaces - the modal panel

Richfaces is a very cool framework that "just works". However, to get it to "just work" requires some experience and lots of Google searches - unless you fork over cash for a good book of course. Even then, there is always that one step to far kind of deal where you want to take the technology into overdrive and the documentation doesn't go beyond the basics.

The coolest feature of Richfaces is of course its built in Ajax support (which has partly made its way into JSF 2.0). Part of that Ajax toolbox is the modal panel - a popup div you can drag and optionally resize. The modal panel is excellent for showing details and performing inserts/updates on records. It is a beast to get to grips with though. I've been through the pain, I hope this blog post can help some people to do it in less time.

Even though I am a user of JBoss Seam, I'll keep the code in this article pure JSF (and Richfaces of course). Also, this is not a JSF or Richfaces beginners tutorial. I expect you to know how JSF works and how to setup Richfaces. If you do not, the reference guide explains it quite well.

Finally, I assume you will use Facelets as the view technology, and I also assume you know how Facelets templating works.

A technical warning

Richfaces uses JQuery for some of its components - the modal dialog is one of them. If you include JQuery in the page yourself, you will most likely break the richfaces components. Don't say I didn't warn you!

The basics of a modal panel

Panels are excellent to turn into reusable components. That's why the best strategy to take is to turn them into include files.

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
  xmlns:ui="http://java.sun.com/jsf/facelets"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:f="http://java.sun.com/jsf/core"
  xmlns:rich="http://richfaces.org/rich"
  xmlns:a4j="http://richfaces.org/a4j">

<rich:modalPanel id="edituser" width="420" height="420" resizeable="false">
  <f:facet name="header">Add user</f:facet>
  <a4j:outputPanel layout="block" id="edituserouter">
    <h:form id="edituserform">    

    <table>
      <tr>
        <td>Name:</td>
        <td>
          <h:inputText value="#{editUserForm.name}" size="20"/>
        </td>
      </tr>
      <tr>
        <td>Username:</td>
        <td>
          <h:inputText value="#{editUserForm.username}" size="20"/>
        </td>
      </tr>
      <tr>
        <td>Password:</td>
        <td>
          <h:inputText value="#{editUserForm.password}" size="20"/>
        </td>
      </tr>
   </table>

    <div class="panelbuttons">

      <h:commandLink action="#{editUserForm.save}"
                          value="Add user" />
      <a4j:commandLink actionListener="#{editUserForm.cancel}"
                          value="Close"
                          oncomplete="#{rich:component('edituser')}.hide(); return false"/>
    </div>
    </h:form>
  </a4j:outputPanel>
</rich:modalPanel>
</ui:composition>

There; slap this in its own xhtml file and you can include it wherever you need the add user dialog. Like this:

<ui:include src="panels/edituser.xhtml"/>

(I tend to put my include files with panels in a panels subdirectory, adjust to your own environment).

This first example is going to be about a dialog to add a user - later on I'll expand it so the exact same dialog can be used to both add and edit a user.

Lets take a moment and examine the above basic skeleton, shall we?

The basic elements of a modal dialog


A modal dialog, at least mine, has three parts:

  • A title bar
  • A body with a form
  • A button bar with action/close links


The title part is easy; Richfaces manages it for us. The only thing we need to do is give the thing a header facet and you're done. There are more options to explore here, but I like to keep it simple. The width and height attributes do exactly that: give the dialog a specific width and height. Finally the resizeable attribute prevents the user from being able to resize the dialog - by default this is enabled.

The edituserouter panel is actually paramount to the entire setup - this wraps the body of the dialog. This allows us to not only target it in a4j rerender actions (rerendering the entire dialog does not work like you would want it), but it also allows you to attach an overflow:auto CSS attribute to it to make the body of the dialog scrollable should you need it!

For the buttons I use commandLinks. The 'add user' link calls an action method while the cancel link invokes an action listener. The cancel link right now does its magic through an ajax call - the only thing it has to do is hide the dialog, but the action listener does an additional thing, which we'll see in a moment.

Notice the following piece of code:

#{rich:component('edituser')}.hide();

That will result in a piece of javascript that references a javascript object that backs the modal dialog, allowing us to hide it. Incidentally, in the page you want to actually show the dialog you can and must do the following:

<a4j:commandButton value="Add user"
                      actionListener="#{myForm.addUser}"
                      oncomplete="#{rich:component('edituser')}.show()"
                      reRender="edituserouter"/>

(of course you can also use an a4j:commandLink) This piece of logic performs three tasks:

  • Calls an action listener through an Ajax invocation so we can prepare the content of the dialog
  • Makes the dialog visible
  • Rerenders the body part of the dialog to react to our preparations on the server side. This is going to be especially important when we add edit capabilities



The backing beans


Alright, we want to create a dialog to add a user. You can make any kind of form that you want, I'm keeping it simple: our user is going to have a name, a username and a password. We need a backing bean for that; we are going to store this in a session scoped bean of my own design. Like this (note, don't forget to configure in the faces-config.xml):

public class EditUserForm {

  private String name;
  private String username;
  private String password;


  public void reset(){

    name = null;
    username = null;
    password = null;
  }


  public String save(){

    if(name.trim().length() == 0){
      addErrorMessage("Please input a name.");
      return null;
    } else if(username.trim().length() == 0){
      addErrorMessage("Please input a username.");
      return null;
    } else if(password.trim().length() == 0){
      addErrorMessage("Please input a password.");
      return null;
    }

    // data is valid, call DAO method to insert user here
    // userDao.insertUser(name, username, password);    

    // reset form to not let stale data linger
    reset();

    // reload the page that opened the dialog by navigating to its identifier
    // note that returning null will cause the page to not reload at all
    return "insert_identifier_here";
  }


  public void cancel(ActionEvent event){
   reset();
  }
  
  private void addErrorMessage(String msg) {
    FacesContext.getCurrentInstance().addMessage(null,
      new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, null));
  }

  // getters and setters here
}


Again some key pieces here:

  • The reset() method I add to all my session scoped beans that temporarily hold data I need, like in a dialog. This allows me to start with a clean slate whenever I desire.
  • I already added some validations to the save action method - I don't believe in JSF validators. For one they get in the way of Ajax events. No, I like to do the validations where the data is actually used - in the action method itself.

    The example doesn't have the capacity yet to show the validation errors - we'll get to that in a moment.
  • The navigation rule you return determines what happens after the save action is performed - if you return null, the dialog will close but the main page won't reload. If you need the inserted (or updated) data to actually show in your main page, you can return the navigation identifier of the page in stead; this will cause the page to reload.

We now have all the tools to make the dialog do its magic! The only thing we're missing is the action handler we want to call when we open the dialog in our main page; its very simple for now:

public class MyForm {
  private EditUserForm editUserForm;

  public void addUser(ActionEvent event){
    editUserForm.reset();
  }
  
  // note: use JSF dependency injection
  public EditUserForm getEditUserForm() {
    return editUserForm;
  }

  public void setEditUserForm(EditUserForm euf) {
   this.editUserForm = euf;
  }
}

For our add functionality, its no more than this - we want to be sure we don't have old data, so we reset() our EditUserForm session scoped bean.


Ajax and validation errors

When the user inputs bogus data into the form (or no data at all), we will want to inform said user of this WITHOUT closing the dialog or causing any kind of page reload. This is a bit tricky, but it can be done.

First of all, we print messages in the dialog:

<h:form id="edituserform">

<h:messages layout="table" errorClass="errorMessage"/>
...
</h:form>

In order to know if there are validation errors, we should make the backing bean be able to tell us:

public boolean isHasErrors() {
    return FacesContext.getCurrentInstance().getMessages().hasNext();
  }
  // empty setter so JSF doesn't complain about it missing on a postback
  public void setHasErrors(boolean f){
  }


Now we can add that as an element of the form...

<h:form id="edituserform">

<h:inputHidden id="hasErrors" value="#{editUserForm.hasErrors}" />
<h:messages layout="table" errorClass="errorMessage"/>

...
</h:form>

And now we can transform our add user link into the following:

<a4j:commandLink action="#{editUserForm.save}" 
                   value="Add user"
                   oncomplete="checkErrors(); return false;"
                   reRender="edituserouter"/>

Basically this says 'if there are no errors after the action method has run, you can close the dialog'. The rerender will make it so any error messages are actually displayed inside the dialog. If there are none it will rerender a dialog that isn't even visible - oh well.

We are missing the checkErrors() javascript function:

<script type="text/javascript">
  //<![CDATA[
    function checkErrors(){
      if (document.getElementById('edituserform:hasErrors').value=='false') {
         #{rich:component('edituser')}.hide(); 
      }
    }
  //]]>
  </script>

Nothing too surprising there. You could inline this piece of code into the oncomplete attribute if you want. Note that for the getElementById call to work, both the form and the hidden input elements need to have manually set IDs so you can be sure what ID JSF will generate for the input element. You could choose to add the prependId="false" attribute to the form so JSF doesn't add form IDs to its elements (might be Mojarra specific).

Now you have all the tools at your disposal to create a modal dialog in which you can add a new user, perform validations on the inputted values and have full control over when the dialog is shown and hidden. Lets take it one little step further and add edit capabilities as well.


Creating an add/edit dialog

The trick is to make the code flexible enough that you can go either way - the magic is in two parts:

  • OPTIONALLY you will want to prefil data into the form, from an existing object
  • Come the time to save the data, you will want to either insert or update it

Anything else should be the same, except for some labels in the dialog perhaps (its a bit confusing to have an 'add user' link when you are actually modifying an existing one). That is basic JSF stuff, I'm sure you can figure out how to get that done.

The way I make editing work is by adding a setExistingXXX() method to the session scoped bean. Lets say we have a User entity, we can use that:

private User existing = null;
  ...

  public void setExistingUser(User user){
    existing = user;
    name = user.getName();
    username = user.getUsername();
    password = user.getPassword();
  }

  public User getExistingUser(){
    return existing;
  }

  public void reset(){
    existing = null;
    ...
  }

Okay, now we can prefill the form with information from an existing user. Now we need to be able to actually get that object into the session scoped bean. There are multiple ways to rome, I like to stick to the basics: pass an ID value as a request parameter.

In a user listing page, we might have something like this:

<h:dataTable value="#{myForm.users}" var="item">
<h:column>
  <f:facet name="header">
    <h:outputText value="name"/>
  </f:facet>
  <h:outputText value="#{item.name}"/>
</h:column>
</h:dataTable>

A bit basic, but this datatable will show the names of all users in our system. I'll leave the implementation of the getUsers() method of the MyForm backing bean up to your imagination.

Now we want an edit link per user. We add another column:

<h:column>
<a4j:commandLink oncomplete="#{rich:component('edituser')}.show()"
                 reRender="edituserouter"
                 actionListener="#{myForm.editUser}"
                 value="edit">

  <f:param value="#{item.id}" name="userId" />
</a4j:commandLink>
</h:column>

So we create a commandLink that invokes the actionListener editUser and passes the ID of the user along as a request parameter in the Ajax request. Upon completing the dialog is made visible and the body panel is rerendered to show the data we have set in the session bean.

Lets see that editUser backing bean method:

public void editUser(ActionEvent event){
    editUserForm.reset();
    
    Long toEdit = Long.parseLong(getRequestParameter("userId"));

    // implement your own DAO logic here to fetch the user
    User user = userDao.getUser(toEdit);
    editUserForm.setExistingUser(user);
  }

  private String getRequestParameter(String name) {
    return FacesContext.getCurrentInstance().getExternalContext()
                       .getRequestParameterMap().get(name);
  }

There, that allows us to fetch the ID we passed to the link, fetch the user using your own DAO logic and then pass it along to the EditUserForm session scoped bean. And we're set, the form is now filled with the user's data!

Now we just need to save it back. I use a very simple trick to know if I want to insert or update an entity:

public String save(){
    // validations here

    if(existing != null){
      // update existing entity
    } else {
      // insert new entity here
    }

    // problem here: see next paragraph
    return "insert_identifier_here";
  }

Thats it. No that isn't it, you still need to DAO methods to actually perform the insert/update; you should be able to deal with that yourself though. But now you have effectively made it so you can use the exact same dialog and the exact same backing form to do either an insert or an update!

Using the same dialog in multiple views

There is one problem with the reusable dialog: it always returns the same view identifier upon saving. If you want to use the popup in multiple views, you want it to return to the view it was opened in.

For now, I see only one solution: set the view identifier when you initialize the dialog.

public void editUser(ActionEvent event){
    editUserForm.reset();
    
    // setViewIdentifier() is a new method
    editUserForm.setViewIdentifier("edituserlist");

    ...
}

public String save(){

    ...

    return getViewIdentifier();
  }

This way you can set a different view identifier in each page you use the dialog in and have it redirect to the correct page when you save.

It doesn't work!


Here are some things to check:

  • Did you include the dialog into the page?
  • Are you including the JQuery library in the page? (don't)
  • Use firefox and check the error console to see if there are javascript errors. Usually there is a typo somewhere.
  • Is javascript even enabled in the browser?
  • Check the server logs to see if there aren't any hidden error logs in there - ajax invoked method calls tend to not cause the error to display in the browser.
  • Are you sure that the EditUserForm bean is session scoped?

If this doesn't answer your question... well good luck figuring it out then!

Conclusion

There you go. What I have presented in this article is everything I personally desire from a modal dialog. I hope with the tools and knowledge I have presented here, you can add cool popup dialogs to your webapp as well. Happy coding!

A last note: this code is taken from one of my existing projects and thus should work, but of course I had to cut it down and massage it a little to make it easier to read and understand; a typo might have slipped in here and there. Sorry if it happened!