Joe Developer
jBPM 3.1 Domain Object auto persistence
I just went through an upgrade from jBPM 3.0 to 3.1.2. There are a number of changes, some large, some small. One of the welcome changes is more exposure of the underlying persistence mechanism - Hibernate.
jBPM allows you to store variables in a process instance - and if you write a converter to translate from a primative (or wrapper) to your type, you can easily persist user defined types in your process.
In an action handler it looks like:
executionContext.setVariable("foo", myInstanceOfFoo);
Where you've written and reistered a converter for whatever class myInstanceOfFoo is. The converter registration was SO 3.0, particularly if your type, we'll call it com.example.Foo is persisted by Hibernate. You ended writing boilerplate code.In 3.1.x the engine is smart enough to use the hibernate configuration that it's already using to see if your type is persistable by hibernate, and if so, will use its internal session factory to save and retrieve your variable when you reference it in the process.
That's neat. There's a problem though... what if your domain object implements Serializable? As it turns out, this will cause you all sorts of issues when you'd like transparent persistence if you're relying on the default configuration for variable handling.
This is due to the way jBPM looks for a way to handle persisting a variable.
jBPM first figures out how to save a variable by looking in the config file: org/jbpm/context/exe/jbpm.varmapping.xml.
As the engine is doing its magic, it is checking against each type of built in persistence strategy to figure out which one to use for your variable. When it finds one that matches, that's what it uses for saving and fetching your object. For fun, look in the DB and you'll notice there's a column in the variable_instance table that tells you what strategy it uses.
When your object is serializable, the engine by default will use the org.jbpm.context.exe.converter.SerializableToByteArrayConverter to persist it. That's not actually what I'm looking for, and I'm not sure when that WOULD be the desired behavior, but since the engine is looking at persistence strategies in order and that's found before the hibernate persisters, that's what's used...
The "desired behavior", at least in my case, was to have Hibernate persist things, and have jBPM just go to MY DB tables to rehydrate objects as needed, not pull them from some serialized blob in the DB that is frequently out of date with the domain. I want to be able to have the "variables" that jBPM is using be accurate reflections of the state of the data when the process access it.
The fix? Take the 3 hibernate based variable instance converters and move them to the top of the config file, rather than the bottom. (ok, so one of them is EJB3 based, but the same issue applies if you're in that space). jBPM will then attempt to use Hibernate persistence for your object BEFORE it attempts to serialize it.
Rebuild jBPM (or point to your modified var mapping file from a user supplied jbpm.config file), and off you go.
After that, I have to say that the variable handling is perfect - exactly what I was looking for. It is intuitive and allows a deep integration between the objects in the domain and the workflow engine.
Assuming your object is "hibernatable", say an instance of Foo, then in a handler you can "save" a foo in the process instance like so:
executionContext.setVariable("myFoo", instanceOfFoo);
Fetching the fully hydrated object that is fresh from your domain in a later node/script/handler is then as easy as:
Foo myFoo = (Foo)executionContext.getVariable("myFoo");
... some interesting stuff.
This makes it easy to use your persistent domain objects, from your tables, in a transparent manner within a process - a powerful feature.Just be warned - without a custom config OR a modification of their config, you'll have no end of wondering what's going on if you happen to have domain objects that are Serializable.
An additional note - see the first comment from Tom Baeyens (the creator of jBPM) - this issue is fixed in CVS - so if you're running head you'll not have this, and I look forward to un-doing my change upon the next release.
Posted at 11:34PM Sep 07, 2006 by saibrian in Gotcha | Comments[1]
XDoclet 1.2.3 and Listeners in TLDs
I'm a big lover of XDoclet. The 1.2.x stream has been quiet for a while, and I thought I'd read that dev on 1.x had stopped in favor of the rewrite for XDoclet 2. So the issues in 1.2.2 were just there unless you wanted to fix them. The beauty of OSS is that you can fix them - but digging into a lib like XDoclet can be a task.
Many of the issues though, are simply related to a template that needs a tweak. Those are easier to fix assuming some patience. The XDoclet templating syntax is a bit tedious, but is rather clear in its verbosity. I've tweaked a template or 2, or looked at them to understand what's available and how its working when XDoclet In Action and the docs don't address the issue.
So to the crux of the matter - I wanted to generate some mappings for Hibernate Components, and the generation facilities in 1.2.2 didn't quite do the job. My friend Google lets me know there's a new release of XDoclet, so I'll do the upgrade and all will be well - right?
The hibernate mappings for components look perfect, but I'm greeted with a nice stack trace in the browser...
A Listener is failing on startup. A listener that was recently depricated... I look in web.xml - no Listener declaration... Confusion and frustration ensue.
The problem:
My listener is showing up in the XDoclet generated TLD.
The JSP 1.2 spec allows Listener declarations in TLDs, but in XDoclet 1.2.2, they weren't generated in the TLD(s). This was "fixed" in XDoclet 1.2.3. The offending code in the XDoclet template:
<XDtClass:forAllClasses type="javax.servlet.http.HttpSessionActivationListener,javax.servlet.http.HttpSessionAttributeListener,javax.servlet.http.HttpSessionBindingListener,javax.servlet.http.HttpSessionListener,javax.servlet.ServletContextAttributeListener,javax.servlet.ServletContextListener" abstract="false"&th; <listener&th; <listener-class&th;<XDtClass:fullClassName/&th;</listener-class&th; </listener&th; </XDtClass:forAllClasses>Why is this code "offending?" Neither the TLD template nor the web.xml template make a distinction regarding listeners that should or should not be included. Actually, they don't even use the @web.listener tag now... All Listeners are blindly included.
Maybe I'm odd - but I'm all for writing some custom tags in a project and letting XDoclet nicely generate a TLD for the project's tags. In this scenario I'm not shooting for a redistributable taglib - I'm looking for a taglib for this project. If I need to use it again I'll pull it into the utility project we keep internally, but until then it belongs right where it is. As such, listeners that are used for taglib initialization are (were) marked with @web.listener, and generated into web.xml.
Now I admit, I'm lazy. The build target for the web generation is as concise and simple as possible, including **/*Listener, **/*Tag, and the other regulars. With the new template I'm forced to either develop custom tags in a different project (maybe not a bad idea), or contorting my build.xml file to generate the web.xml and TLD artifacts with very specifc filesets...
This too may be a good idea, but is another point of maint... when a new dev adds a Listener, I want them to follow the naming convention and use the appropriate @web.listener tag and trust the code gen system to do the rest of the work. Now I to document, explain, and remember that the files used for these generation tasks have to be explicitly enumerated....
Long story short - if you upgrade to XDoclet 1.2.3, watch out for this one.
Posted at 10:18PM Jul 21, 2006 by saibrian in Gotcha |