Thursday, July 1, 2010

Migrating to JBoss 5.1 and JBoss 6 from JBoss 4.2.X

Introduction


Today's topic is that of migrating from JBoss 4.2.X to JBoss 5.1 and ultimately JBoss 6.

Why is it even a problem you might ask? Well JBoss 4 was built for the JEE4 standard. JBoss 4.2 added support for EJB 3.0 however - this does *not* make it a full JEE5 application server. JBoss 5.1 on the other hand IS a JEE5 compliant server. The end result is that AS5.1 does a few things a little differently to follow specifications, and it are those little differences that can cause you the most grief.

Meanwhile, AS6 has also hit the streets and I have had plenty of time to experiment with it. The latter part of this article will deal with JBoss 6 specific migration steps. I wouldn't try to make the migration in one go; first get your application to work on JBoss 5.1 and then move on to JBoss 6. Thats why I do the article in that order as well.


Package changes

Applies to: JBoss 5.1 and JBoss 6

When you use JBoss, you might be inclined to use certain JBoss specific features. One such features is the @Service annotation, which turns an EJB into a singleton instance. Another is the @LocalBinding annotation (or its brother @RemoteBinding), through which you can override the JNDI name of EJBs. Then there is the @Management annotation, to expose callable methods through the JBoss JMX-manager. Finally there is the @Depends annotation, with which you can make sure that JBoss deploys resources in a correct order.

These annotations are in JBoss specific packages, and they changed from JBoss 4.2 to JBoss 5.1. This also means you'll need to update your maven dependencies, if you use maven. The old and new situation are like follows.

JBoss 4.2.X:
<dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-annotations-ejb3</artifactId>
        <version>4.2.2.GA</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-jmx</artifactId>
        <version>4.2.2.GA</version>
        <scope>provided</scope>
      </dependency>
      <dependency>
        <groupId>jboss</groupId>
        <artifactId>jboss-ejb3</artifactId>
        <version>4.2.2.GA</version>
        <scope>provided</scope>
      </dependency>

JBoss 5.1 / 6:
<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 added the exclusions to the JBoss 5.1 maven dependency to be sure you get no pollution of your classpath.


Consequently, you need to change some packages on your code for the annotations to still work under JBoss 5.1!

Jboss 4.2.X:
import org.jboss.annotation.ejb.Service;
import org.jboss.annotation.ejb.Management;
import org.jboss.annotation.ejb.LocalBinding;

JBoss 5.1/6:
import org.jboss.ejb3.annotation.Management;
import org.jboss.ejb3.annotation.Service;
import org.jboss.ejb3.annotation.LocalBinding;

If you do not remember to change the packages then your code most likely will still compile just fine, but the JBoss 4.2.X annotations simply won't do anything!

If you only use the @LocalBinding or @RemoteBinding annotations and you need a cross-platform alternative (well, at least across JBoss platforms) that works all the way up to and including JBoss 6, then don't use the annotations. In stead, override the JNDI names using a META-INF/jboss.xml file, like this:

<?xml version='1.0' encoding='UTF-8' ?>
<jboss>
  <enterprise-beans>
    <session>
      <ejb-name>MyEjbBean</ejb-name>
      <jndi-name>MyEjbBean/remote</jndi-name>
      <local-jndi-name>MyEjbBean/local</local-jndi-name>
    </session>
  </enterprise-beans>
</jboss>

The name has to match the name of your EJB; if you do not specify a name it will be the name of the EJB bean class. With the jndi-name and local-jndi-name you specify under which JNDI name the remote and local interfaces of your EJB should be available. If your EJB doesn't have one of the interfaces then don't specify the matching XML element either.

Injecting 'external' EJBs

Applies to: JBoss 5.1 and JBoss 6
JBoss 4.2.3 is quite forgiving when it comes to performing EJB injections using the @EJB annotation. If classpath isolation is turned off (which it is by default), you can even inject EJBs that are not part of your EAR and can even be packaged inside another EAR.

No so any more under JBoss 5.1 or JBoss 6; the injection will fail. JBoss 5.1 will even give a NullPointerException upon deployment (which is a bug fixed in JBoss 6). Fact of the matter is that it is against the EJB specifications to be able to do so; EJBs should only be visible within their own protected little environment (the EJB module or EAR they are part of). If you want to communicate with the EJB from outside that protected environment, you give the EJB a remote interface and you do a JNDI lookup.

JMS changes

Applies to: JBoss 5.1 and JBoss 6
With each release of JBoss you get a new JMS "engine" - JBoss 5.1 has a different one from JBoss 4.2.X and JBoss 6 has yet another one (HornetQ). Its a fact of life, we have to deal with it.

The biggest change from JBoss 4.2.X to JBoss 5.1 for me has been that AS5.1 no longer auto-deploys queues! I have searched high and low, I did not find a way to make the JMS engine of AS5.1 do it. To illustrate, I could have this piece of code to run under JBoss 4.2.X:

@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
    @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/TEST_QUEUE") })

And if "queue/TEST_QUEUE" did not exist yet, JBoss would create it for me upon server startup. Not anymore. Even if you did deploy your queues manually under JBoss 4.2.X, there is still a change that you need to make. This is how you would deploy a queue in JBoss 4.X (in any XXXX-destinations-service.xml file of your choice):

<mbean code="org.jboss.mq.server.jmx.Queue"
  name="jboss.mq.destination:service=Queue,name=TEST_QUEUE">
    <depends optional-attribute-name="DestinationManager">jboss.mq:service=DestinationManager</depends>
  </mbean>


This is how you deploy that same queue in JBoss 5.1:

<mbean code="org.jboss.jms.server.destination.QueueService"
    name="jboss.messaging.destination:service=Queue,name=TEST_QUEUE"
    xmbean-dd="xmdesc/Queue-xmbean.xml">
    <depends optional-attribute-name="ServerPeer">
      jboss.messaging:service=ServerPeer
    </depends>
    <depends>jboss.messaging:service=PostOffice</depends>
  </mbean>


And here is how you would do it in JBoss 6. Define a file anywhere in your deploy directory named APPNAME-hornetq-jms.xml, where APPNAME is something that would link the file to your application. Give it the following content:

<configuration xmlns="urn:hornetq"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">

<queue name="TEST_QUEUE">
      
</queue>
</configuration>

Notice the fact that you have to append /queue/ to the queue path yourself.

That's all really; its only slightly annoying, not hard. For JBoss 6, also check out the HornetQ specific section further down this article to read about more issues you may run into.


Facelets

Applies to: JBoss 5.1

In order to use facelets as the view manager in JBoss 5.1, I highly recommend to use a specifically patched version of it, in stead of 1.1.14. This at least will guarantee that you won't run into issues, especially when using JBoss Seam and Richfaces.

<dependency>
        <groupId>com.sun.facelets</groupId>
        <artifactId>jsf-facelets</artifactId>
        <version>1.1.15.B1</version>
      </dependency>


JBoss Seam

Applies to: JBoss 5.1
To work with JBoss Seam, I suggest reading my Seam setup guide. It contains specific JBoss 5.1 patch steps to help you prevent/overcome problems.

Seam setup guide

Under JBoss 6 it is recommended to use the built in CDI functionality; if you need more than that check out Seam 3.

JBoss JBPM

Applies to: JBoss 5.1 and JBoss 6
You have two basic options for using JBPM:

  • Use JBPM 4.3, which deploys without problems in JBoss 5.1, but has a limited and buggy toolset
  • Follow my JBPM3 guide for patching the JBPM 3.2.9 manager to work on JBoss 5.1
  • Additionally, follow my JBPM3 guide for further patching the JBPM 3.2.9 manager to work on JBoss 6 also.


Beware of the main-class manifest element

Applies to: JBoss 5.1 and JBoss 6
JBoss 5.1 is a lot more picky about what you put in the manifest of your ear and jar library files. If a jar has a main-class element and this class just happens to not exist, deployment of the ear that the library is part of will completely fail!, while JBoss 4 would simply ignore it. Be warned!


ejb-jar.xml

Applies to: JBoss 5.1 and JBoss 6
Sometimes you'll want to add an ejb-jar.xml file to your ejb modules, for example to install an interceptor. Beware though, because as of JBoss 5.1 you have to use the proper JEE5 specified XML layout. In JBoss 4 your ejb-jar.xml might have simply looked like this:

<?xml version="1.0"?>
<ejb-jar>
   <assembly-descriptor>
      <interceptor-binding>
         <ejb-name>*</ejb-name>
         <interceptor-class>your.interceptor.class</interceptor-class>
      </interceptor-binding>
   </assembly-descriptor>
</ejb-jar>

That doesn't fly anymore since JBoss 5.1. In stead you have to use this:


<ejb-jar version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
  <interceptors>
    <interceptor>
      <interceptor-class>your.interceptor.class</interceptor-class>
    </interceptor>
  </interceptors>

  <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>your.interceptor.class</interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>


Deployment ordering

Applies to: JBoss 5.1
In JBoss 4.2.X you could specifically configure deployment ordering. In JBossAS 5.1 this is no longer possible by default. But since AS5.1 is a highly modular and flexible system, you can get it back.

Open up the file JBOSS_HOME\server\instance\conf\bootstrap\deployers.xml and add the following content, modified to your needs:

<bean name="topContextComparator">
<constructor factoryClass="org.jboss.system.deployers.LegacyDeploymentContextComparator" factoryMethod="getInstance"/>
  <property name="suffixOrder" class="java.util.Map">  
    <map keyClass="java.lang.String" valueClass="java.lang.Integer">  
    <entry>  
      <key>myapp2.ear</key>  
      <value>100</value>  
    </entry>  
    <entry>  
      <key>myapp3.ear</key>  
      <value>200</value>  
    </entry>  
    <entry>  
      <key>myapp1.ear</key>  
      <value>300</value>  
    </entry>  
    </map>  
  </property>  
</bean>  

This will deploy the applications in the order myapp2.ear, myapp3.ear and finally myapp1.ear. Note that these are suffixes, so you can also use partial filenames or even only extensions as the key. For example, to force EJB jars to deploy before EAR files, you could do this:

<entry>  
      <key>.jar</key>  
      <value>100</value>  
    </entry>  
    <entry>  
      <key>.ear</key>  
      <value>200</value>  
    </entry>  

When using this function, be careful. You may break deployment by screwing up the deployment ordering in stead of fixing it.

Remote to local interface translation

Applies to: JBoss 5.1 and JBoss 6
This is more behavior I have seen while porting JBPM 3 to Jboss 5.1. It appears that under JBoss 4.2.X, when you do a JNDI lookup of a remote interface in the local server, JBoss automatically transforms that lookup into the local interface in stead. JBoss 5.1 however gives you exactly what you wish for and you'll get the remote interface, even if it is a local bean. If you are trying to port over an application that mixes up local and remote interfaces, you'll have to clean this up first.

You can recognize when you have this problem when you get a ClassCastException error that looks like '$ProxyXXX cannot be cast to local interface class'.


Jboss 6 HornetQ user authentication

Applies to: JBoss 6

JBoss 6 comes with HornetQ on board. This is an incredibly powerful JMS engine. However when porting over an application that is expecting JbossMQ or Jboss Messaging to be available, simply changing the queue configuration file might not be enough.

Case in point: deploy the patched JBPM manager to Jboss 6 and you may run into messages during deployment stating that user 'null' cannot be authenticated. This is because the JBPM manager tries to connect using no credentials at all which would have to result in a default user being used, which we don't have. I have seen many a forum post in this very problem, all without stating what exactly you have to do to really fix this.

The quick work around I have found so far is to disable JMS user authentication entirely, which you can do by modifying the deploy/hornetq/hornetq-configuration.xml file and adding the following line:

<security-enabled>false</security-enabled>

That should stop the authentication failures. You can ask yourself if authentication is really required in your environment. If you do you'll have to dig deep into the documentation. When I figure out how to properly configure a default user under JBoss 6 I will add that here. But as said, not much luck so far :/

Jboss 6 forcing JSF 1.2

Applies to: JBoss 6

You may want to run older JSF 1.2 based applications on JBoss 6. If the application does not provide a JSF instance itself (which is not uncommon when it used to be deployed to JBoss 4.2.3 or JBoss 5.1), JBoss 6 will initialize JSF 2.0.3 by default. It might just be however that the application is not compatible with it, in which case you will want to force JBoss to initialize JSF 1.2 for it.

Luckily you can easily do this and you have two ways to do so.

- deploy JSF 1.2 in the WAR; JBoss will then use that version
- add the following to the web.xml of the WAR:

<context-param>
  <param-name>org.jboss.jbossfaces.JSF_CONFIG_NAME</param-name>
  <param-value>Mojarra-1.2</param-value>
</context-param>

Since this is a JBoss 6 specific context parameter, it will have zero influence when deploying your application on JBoss 5.1 or JBoss 4.2.3 even; in other words you can safely add this to all your existing web applications in preparation for a future upgrade.

Jboss 6 upgrading JSF

Applies to: JBoss 6
Upgrading JSF on JBoss couldn't be simpler. I do not recommend overwriting the already existing JSF 2.0.3 that is in there; you will want the factory defaults to be available for testing purposes. What you can do is add your own JSF configuration to JBoss.

First of all, download the latest version of Mojarra 2.0 here.. Now, go to your JBoss server directory and navigate to the deployers/jsf.deployer sub directory.

Here you will find all the available JSF implementations. Make a copy of the Mojarra-2.0 directory, naming it Mojarra-2.1.1 (or whatever version you have downloaded).

In this new directory, replace the jsf-impl.jar and jsf-api.jar files with the ones you downloaded.

Now to make the new JSF version available, alter the META-INF/jsf-integration-deployer-jboss-beans.xml file, adding the following to the JsfConfigurations:

<entry>
  <key>Mojarra-2.1.1</key>
  <value>${jboss.server.home.url}deployers/jsf.deployer/Mojarra-2.1.1</value>
</entry>

Make sure the path matches whatever directory you created.

Finally to make a web application use your new version in stead of the standard 2.0.3 version, add the following to the web.xml of your war:

<context-param>
    <param-name>org.jboss.jbossfaces.JSF_CONFIG_NAME</param-name>
    <param-value>Mojarra-2.1.1</param-value>
  </context-param>

Where the param-value matches the key of the entry you added earlier. Thats it! Now if you run into some sort of conflict and you want to try out if it is an issue with the version of Mojarra you have, you can always comment out the context-param in your web.xml and try with Mojarra 2.0.3!

Jboss 6, Oracle and XA datasources

Applies to: Jboss 6
When you create an Oracle (10?) XA datasource under JBoss 6, you may get these errors continuously after server startup:

ARJUNA-16027 Local XARecoveryModule.xaRecovery got XA exception XAException.XAER_RMERR: javax.transaction.xa.XAException
at oracle.jdbc.xa.OracleXAResource.recover(OracleXAResource.java:638) [:11.2.0.1.0]
etc.

Notice how an exception is thrown by the JDBC driver, the recover() method specifically (apparently this is never called by the JBoss 5.1 transaction manager?). As it turns out, this has to do with user rights. To fix it you need to give the user used in the XA datasource the following rights:

grant select on pending_trans$ to USER;
grant select on dba_2pc_pending to USER;
grant select on dba_pending_transactions to USER;
grant execute on dbms_system to USER;

What I'm not 100% clear on yet is if this is Oracle 10 specific or not; it may be that this problem does not exist when using Oracle 11. I need to do some tests to verify that.

In any case when you do run into this issue, ask your friendly neighborhood DBA to pass out the above user rights to get rid of the issue.


This is not the end

Even though I sign off this blog article and I make it available to the public, this article is by no means finished. If and when I do unravel more migration headaches I will be sure to add them promptly.