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.

No comments:

Post a Comment