Monday, August 16, 2010

Custom Hibernate configuration for the JBPM console

That Hibernate configuration exception still bothered me, so I looked into it as well.

It basically says that the JBPM console can find the application's Hibernate configuration file but fails to create the Hibernate Configuration object out of the file. The reason: the application is using Hibernate Annotations with corresponding entries in the config file, but the JBPM console tries to read the file as a plain Hibernate configuration (with xml mapping files). No surprise this fails.

I looked at the application code to see how the configuration file is read and how the Configuration object is used. The application has some utility class to load the configuration as Hibernate Annotation configuration. Each time after the application asks JBPM for a new JbpmContext it does jbpmContext.setSessionFactory(Util.getHibernateSessionFactory()). The JBPM console uses the Configuration the same way (jbpmContext.setSessionFactory), it just loads it differently.

No, I do not want to change the code of the JBPM console. I need to find a different way to make sure the JBPM console can find the correct SessionFactory object. No, actually I do not care about the console: it just passes the SessionFactory to JBPM. I need to make sure JBPM sees the correct SessionFactory object. What choices do I have? Option 1: register the Hibernate SessionFactory in JNDI, built-in Hibernate functionality. Option 2: there is a possibility to replace the way the JBPM configuration file is loaded so it is possible to create a custom JbpmContext, or, in my case, just a standard JbpmContext, but with the correct Hibernate SessionFactory.

Some experiments proved that it is not that easy. Registering a SessionFactory in JNDI does not work out of the box because both OpenEJB and OC4J treat the JNDI name as a local name, and the SessionFactory registered in one place is not visible in the console. And the reading of the JBPM configuration is "all or nothing" solution: there is just no way to get the created JbpmContext right after it is created without duplicating a lot of the existing JBPM code.

I think I would eventually make either of the solution work but I got distracted. Not for the first time a much better solution just came to me while I was busy with some other things.

JbpmContext itself does not do anything with SessionFactory: the only thing JbpmContext.setSessionFactory() does is passing the SessionFactory down to PersistencyService. And PersistencyService is configured in jbpm.cfg.xml (org.jbpm.persistence.db.DbPersistenceServiceFactory in our case). The solution: create the subclass of DbPersistenceServiceFactory that does just one thing: passes the application's SessionFactory to the superclass, the rest is up to the superclass. With the subclass registered in jbpm.cfg.xml the console does not give the Hibernate configuration errors any more; instead it nicely displays our processes.

The solution also makes all those jbpmContext.setSessionFactory(Util.getHibernateSessionFactory()) in the application code redundant, so I deleted them: less code less potential problems.

Friday, August 13, 2010

Deploying JBPM Console in OC4J

I actually enjoy that kind of work: usually it is short enough for me not to get bored, and I get a chance to try different things and do something I have not yet done. Sure I have to deal quite often with some outdated or obscure technology, but I do not mind. So I took up another challenge related to the same technologies: OC4J, JBPM and (this time) JSF: integrating JBPM JSF console into the application and deploying all that as a single EAR.

The team has managed to get the JBPM console WAR up and running in OC4J 10.1.3 as a standalone application but they wanted to integrate the JBPM console jar (and not the WAR) in their application. They were planning to have a somewhat different web interface than the JBPM console WAR provided and to reuse the functionality of classes in the jar. But they failed citing all kinds of incompatibilities between OC4J, JBMP and JSF. That sounded strange because they managed to overcome the only "incompatibility" I was aware of: the JBPM JSF console uses JSF 1.2 RI, and OC4J 10.1.3 does not support JSF 1.2, only JSF 1.1. (This is not actually an incompatibility from my point of view: the real incompatibility is between the required JSP version for JSF 1.2 and the version provided by the OC4J. But the JBPM console uses facelets and not JSPs.)

First I repeated the exercise of deploying JBPM console WAR in OC4J: I needed a reference point to say, OK, this works. The WAR file could not be deployed as is (it relies on some jars that are not in its WEB-INF/lib), so I had to add those jar. I also had to change some configuration files: hibernate to point to the project's database, and web.xml to get rid of all security configuration (security was not crucial at that moment, and it can be configured correctly later). In addition I had to add couple of project jars (EJBs) to WEB-INF/lib: the existing processes refer to some project classes, and the JBPM console gave some ClassNotFoundExceptions. Within 30 minutes I got the console deployed and running.

Next step: adding JBPM console jar to the project's pom.xml files. This turns to be interesting, mostly because of what the team had achieved thus far. For example, one of the WARs had more than 90 jars in WEB-INF/lib, some of them even twice, with different versions. And ant.jar? Or junit.jar? Who are those people? Er..., I mean, why do you need them at runtime? And myfaces 1.1? No wonder they could not get a JSF 1.2 stuff working. Time to do some cleanup... Why oh why some projects just can do it right? Why do I need all that apache jackrabbit stuff if JBPM has (but does not use) couple of classes that depend on it? Or just because JBPM has couple of ant tasks I need ant and all its dependencies in my WEB-INF/lib?

Right, 23 jars left, time to add JBPM console jar; copy all those facelets pages from the original JBPM console WAR and change web.xml. Package, deploy, ... wow, no errors? Now, where did I see that before? The application itself works and does exactly what it did before adding the JBPM console, but each page of the console I tried shows a nice JSF exception saying more or less "JSF is not initialized, can't find JSF factories".

OK, the JBPM console WAR deployed as a separate application works. The same code, just packaged differently, does not. Find 5 differences? Well, there are more, but two important are: WAR vs. EAR deployment, and "required jars in WEB-INF/lib of the WAR" vs. "skinny WARs in EAR".

Being lazy I did not want to make an EAR out of WAR. I also knew that OC4J makes an EAR out of WAR during the deployment. So I took that EAR and deployed it separately, and it worked. The first difference is ruled out.

On to the "skinny" WAR. All the EJB jars and web WARs that ended up in EAR have a lot of common dependencies, so it is only logical to have all of those dependencies in EAR/lib. It is also more "spec compliant". But maven makes creation of such an EAR hmmm ... interesting. I just hate to think what amount of work I would need to do if I needed some dependencies in EAR/lib and some in WEB-INF/lib and most importantly what amount of work would it be to maintain this structure. Thanks, maven.

Back to the JSF problem: I manually added all the jars from EAR/lib into the WAR with the JBPM console under WEB-INF/lib and then repackaged the EAR. Deploy, no errors, page access ... wow, I can see it. The page displays some error message about Hibernate configuration, but at least the menu and some other elements of the console are also displayed. OK, so the "skinny" WAR is the problem. But why?

I should say debugging the problem was quite an interesting process. Staring with just being able to start an OC4J server with the application under debugger from Eclipse WST on to all that package, undeploy, redeploy, deploy the JBPM console WAR, deploy the application EAR, scratch the head, "this just can't be", and so on. The end result is:

1. FacesServlet uses some factory implementations and complains if they are not found. The factory discovery process looks at several places and in case of JSF RI it finds only those factories that were already registered before the lookup.

2. The JSF implementation (JSF RI) registers the factories at startup with the help of some javax.servlet.ServletContextListener. This listener is invoked if JSF RI jars are placed under WEB-INF/lib and not invoked in case of "skinny" WAR.

3. The listener is not configured in any web.xml, and that was the reason for "this just can't be": the listener is invoked but it is not registered!

4. After some search I have found the listener in JSF RI.jar/META-INF/jsf_core.tld. OK, so it is also possible to add listeners to TLD descriptors (as of JSP 1.2)?! I did not know that. And it looks like OC4J looks for TLD only in jars from WEB-INF/lib, and not in classpath. And as usual with specifications the JSP 1.2 spec is not really clear here:

Tag libraries that have been delivered in the standard JAR format can be dropped directly into WEB-INF/lib.

J2EE 1.4 tutorial says the following:

Tag files can be packaged in the /META-INF/tags/ directory in a JAR file installed in the /WEB-INF/lib/ directory of the web application.

A lot of "cans" and no "must"/"must not" or "should"/"should not".

5. Anyway, the application must be deployed under OC4J and the rest is just theorization. The solution I chose is simple: all the listeners mentioned in JSF RI.jar/META-INF/jsf_core.tld are added to web.xml. Copy-paste, package ("skinny" WAR!), deploy, browser, open the page, and bingo! The menu and the frame of the console are there. And that other error message from Hibernate as well.

The solution has some pros and cons, as usual.
1. Pro: it works.
2. Pro: OC4J does not read unnecessary JSF JSP TLD files: the application does not use JSF JSP.
3. Pro: OC4J stopped complaining about an unsupported JSP TLD version.
4. Con: this is something that should be remembered and documented. If for any reason (migration to a different server, changes to packaging, etc.) the same javax.servlet.ServletContextListener is found registered twice the application startup would fail.

So that is it: I am done with the boring stuff and all the exiting things are left as exercise to the team.