JPA 2 with Drools and jBPM

3 April 2012 1 comment

At the moment, I’m finishing off making Drools/jBPM ready to be able use JPA 2 and Hibernate 4.x. This post is in some sense a draft for the official documentation, but again, see about for why I write blog posts.

 


Using jBPM with JPA 2

 

If you’re reading this, it’ll be because you want to use Drools or jBPM with JPA 2 and maybe even with a different ORM framework. Awesome! That means you’re using jBPM and that you’re using jBPM the way you want to and are not being forced by the software to make choices you don’t want to. I heartily approve of both.

First off, if you’re using JPA 2 with drools and JBPM, you’ll need to change the all the instances of "1.0" and ..._1_0.xsd to "2.0" and ..._2_0.xsd. Don’t forget those schemaLocation values that are off beyond the right edge of your screen.

Second off, you’ll have to modify your persistence.xml and change the mapping for the ProcessInstanceInfo class. In your persistence.xml, you’ll have to remove the following line:

  <mapping-file>META-INF/ProcessInstanceInfo.hbm.xml</mapping-file>

Once that’s done, you’ll have to add a JPA2 mapping for that class to your persistence unit. If you want to, you can just change the following line in your persistence.xml:

  <mapping-file>META-INF/JBPMorm.xml</mapping-file>

to

  <mapping-file>META-INF/JBPMorm-JPA2.xml</mapping-file>

The JBPMorm-JPA2.xml file is included with the jbpm-persistence-jpa jar: if you’re curious about the entity mapping, you can find it there.

Lastly, depending on your setup, you might have to change the TransactionManagerLookup class that you’re using. This line:

<property name=”hibernate.transaction.manager_lookup_class” value=”org.hibernate.transaction.BTMTransactionManagerLookup” />

needs to be changed to:

<property name=”hibernate.transaction.jta.platform” value=”org.hibernate.service.jta.platform.internal.BitronixJtaPlatform” />

And that’s it!


Developer bits and ranting

 

The following is mostly explaining how I got to the solution that I did get to. :)

In any case, regarding enabling and testing the JPA 2 functionality in Drools/jBPM and to start with: there are XML metadata overrides. I’ve known about XML overrides for persistence annotation data for a while, but while I’ve almost used them to solve a some problems, it never really happened. Part of this has to do with the fact that the configuration for jBPM is still evolving.

In the past year, the persistence.xml in jBPM has been extracted from the jars so that users can specify their own persistence units. This  means it’s also how users will be able to specify JPA 1.0 or 2.0 in their persistence units.

Another hurdle here has been the fact that Drools/jBPM is stuck with Hibernate 3.3.x and 3.4.x jars at the moment — and those versions do not support JPA 2. This means futzing around with dependencies and maven profiles. It’s not that big of a deal but also not that elegant — but I challenge anyone who says programming that gets things done should always be elegant. It’s a goal, not a requirement.

Also, the largest Hibernate 3.3/3.4 compatibility issue is by far the @CollectionOfElements annotation: ahead of it’s time, but it’s unfortunate that Hibernate didn’t elect to stay backwards compatible by translating @CollectionOfElements to @ElementCollection underwater once Hibernate became JPA 2 compliant.

So, what I’ve done is the following:

  • I’ve created a hibernate-4 profile in the relevant projects that use persistence (drools-persistence-jpa, jbpm-persistence-jpa, and jbpm-bam, to start with).
  • This hibernate-4 profile replaces the hibernate 3.x dependencies with hibernate 4.x dependencies, and since some hibernate jars changed names between versions, it adds the correct 4.x jars to replace the 3.x jars.
    • This means that, for example, the hibernate-annotations 3.x jar will be there when it’s not needed, but that’s not that big a deal. (It looks like hibernate-annotations disappeared as soon hibernate started supporting JPA 2.0).
    • Part of what I’m doing is also making sure that no hibernate jars are actually needed in the code — we want jBPM (and Drools) to be ORM framework independent and hard-baking hibernate dependencies in won’t help that.
  • This hibernate-4 profile also uses the maven-replacer-plugin to fix all kinds of things in the persistence.xml and orm.xml files.

Lastly, before I get to the meat of this post (using JPA 2 with Drools/jBPM), I have one more fact (or rant..). Why is the order of attribute types defined in entity-mappings? WHY?!? To give you an illustration, the following mapping is incorrect:

<entity class="forest.bee.hive">
  <element-collection name="honey" target-class="double" />
  <basic name="worker" />
  <version name="queen">
  <basic name="pupa" />
</entity class>

It’s invalid because all <basic> elements must come before all <version> elements which must come before all <element-collection> elements. WTF? I once had to mediate a ridiculously overheated discussion about XSD standards — and I do realize that it’s a complicated if arcane topic — but stil, wasn’t it possible to decouple the order? Okay, maybe it was impractical in terms of the parsing modifications then necessary.

In any case, if you’re writing your own JPA 2 XML entity-mappings, stop, and do it using annotations. Otherwise, if you really must know, the order (as specified in the xsd) is:

  1. description
  2. idorembedded-id
  3. basic
  4. version
  5. many-to-one
  6. one-to-many
  7. one-to-one
  8. many-to-many
  9. element-collection
  10. embedded
  11. transient

What was also interesting were the precise semantics of that metadata-complete attribute present in the <entity> element. If you do a websearch on “metatdata-complete jpa”, you’ll quickly learn that metadata-complete=true means that the annotation information for the class is ignored in this case and that the container/persistence-context will only use the XML information.

However, I wanted to do as little work as possible and was curious as to what would happen if metadata-complete=false. And the web was silent.. until I found this little sentence on a sun blog/technical post about JEE 6:

As is the case for web fragments, you use the <metadata-complete> element in the web.xml file to instruct the web container whether to look for annotations. If you set <metadata-complete> to false or do not specify the <metadata-complete> element in your file, then during deployment, the container must scan annotations as well as web fragments to build the effective metadata for the web application. However, if you set <metadata-complete> to true, the deployment descriptors provide all the configuration information for the web application. In this case, the web container does not search for annotations and web fragments.

This quote is from Introducing the JEE 6 Platform: Part 3 and the bold lettering has been added by me to emphasize my point: if metadata-complete=false then the container will use both the annotation data and the XML data. That means that if you want to override an annotation with XML information, you must use metadata-complete=true to ensure that the container does not use the annotation metadata. This must be partially why we can get away with using the ProcessInstanceInfo.hbm.xml mapping file and leaving a @PreUpdate annotation in the file.

Lastly, I’d also like to add that I’ve had to do some minor hacking to get all of this done. The problem, in short, is the following:

  1. The main problem is that jBPM 5 contained a class that used the Hibernate 3.x exclusive @CollectionOfElements annotation.
  2. Leaving this in the code meant that we could not compile the code with Hibernate 4 (since 4 doesn’t contain that class).
  3. Which means that using a ProcessInstanceInfo.hbm.xml file — a hibernate mapping file — to configure the ProcessInstanceInfo class. Because the mapping is now in the xml, the code will compile with Hibernate 4.
  4. However, this means we have to map all fields of ProcessInstanceInfo in the ProcessInstanceInfo.hbm.xml file: Hibernate will otherwise unfortunately complain about duplicate (hibernate) mappings if we use both (JPA or Hibernate) annotations and a Hibernate mapping file.
  5. Luckily, we can leave the @PreUpdate annotation in ProcessInstanceInfo, since Hibernate 3.x doesn’t have a simple annotation that is equivalent.
  6. But Hibernate 3.3/3.4 doesn’t support byte array blob objects. This means writing a BlobUserType class that extends the Hibernate UserType in order to be able to do this: we don’t want users to have to change their schema’s. Most annoyingly (and in slight contradiction to the quote from sun above), Hibernate will complain if we use JPA (field) annotations and a hbm.xml file on/for the same class.
  7. When users use JPA 2 (possibly with Hibernate 4), they then need to make sure not to use the ProcessInstanceInfo.hbm.xml and instead to include a JPA 2 entity mapping for the ProcessInstanceInfo class — which is currently commented out in the JBPMorm.xml file.

And that’s what has happened.

 

Categories: Geen categorie

Human task and persistence: cross-cutting concerns

29 February 2012 Leave a comment

For those of you who aren’t familiar with the jBPM human task module, here’s a very brief introduction:

To start with, a “human task” describes two things:

  • In an otherwise automated business process, a “human task” is the concept that one of the steps in the process consists of and is strongly dependent on human input.
  • In a larger sense, a “human task” is a label that helps people who model business processes differentiate between system interactions and human interaction in a process.

Somewhat obviously, the “human task” aspect of BPM is problematic but important to model and formalize. Modelling human behaviour in a (computer) systems’ environment is not a simple task. There are even two specifications that describe how this should be done:

  1. To start with, we have the WS-HumanTask specification, which is the generally accepted specification for describing how a human task can be formally described.
  2. There’s also BPEL4People, which is essentially a BPEL extension for human tasks.

In any case, the jBPM human task module implements the WS-HumanTask specification. This module basically takes care of all the bookkeeping and administration specified for human tasks: who gets a task, who may get a task, who is working on a task, which tasks are closed, which content is associated with which tasks, etc.

If these were “computer tasks” instead of “human tasks”, then the BPM engine would call the computer or system responsible for the “computer task”. Then, a second or two later the computers respsonsible for the task would have completed the task and done all the bookkeeping or otherwise informed the BPM engine that an error occurred.

However, humans don’t work that fast. And they also need a human friendly method of letting the BPM engine know the status of the task. And lastly, given that computers are better at that bookkeeping thing (which is basically computation), humans also want the computers to do that. Thus, we get the WS-HumanTask specification and the jBPM implementation of that specification.


Phew, okay, now that we’ve gotten that out of the way, I can write about what I really want to.

Persistence in the jBPM human task code is a cross-cutting concern. Click on the link for wikipedia’s definition of the term, but in short, persistence has nothing to do with the human task code.

(In fact, persistence is almost always a cross-cutting concern, unless you’re working on something that only does persistence, like an ORM framework such as Hibernate.)

The easiest way to identify cross-cutting concerns is to think about how you would fix a bug in the code. For example, if you have a bug in the jbpm-human-task code because tasks aren’t being assigned to the correct users, then you look at the logic in the jbpm-human-task module dealing with task ownership. A persistence bug, however, will have nothing to do with the task ownership logic — or any of the other logic in jbpm-human-task that describes the human task bookkeeping and administration. A persistence bug will only have to do with the code in the jbpm-human-task module that well, deals with persistence. Well, at least, a persistence bug should only have to do with the persistence bug..

And that’s exactly my point: code deals with cross-cutting concerns should always be encapsulated and isolated from the rest of the code when possible. This isolation is a form of loose-coupling. Encapsulation is important so that other developers don’t mistakenly start using instances or methods that they shouldn’t — and of course, it makes finding bugs a lot easier. Loose-coupling is important because it allows developers to extend and further develop projects with relatively little problems. (Loose-coupling is one of several fundamental ideas that a developer should have hardwired into his or her brain, in fact).

Lastly, if you do not encapsulate or apply loose-coupling to your code, you are in for a world of hurt, as they say. This regardless of which cross-cutting concerns you’re actually dealing with. Lack of encapsulation or loose-coupling means project death, if left alone. I am really serious about this — and if you don’t believe me, or don’t understand how important this is, well.. then, I’m actually speechless (or I’m otherwise convinced that you haven’t studied computer science). If you then also happen to be one of my colleagues or work on my project, don’t worry, I already know who you are. <evil laugh>

Why am I writing all of this? So that I can refer to this post in the discussions that I unfortunately expect to have about this issue. And of course, as always, I’m writing this because that is what this blog is about.

Categories: Geen categorie

The unexpected benefits of TDD

24 February 2012 Leave a comment

Hi there! Do you know what TDD is? It’s Test Driven Development.

To tell the truth, no, I don’t do proper TDD: technically, you’re supposed to write the tests before you write the code, and while that’s a nice thought, not being able to write the ideas in my head down immediately gets in the way of the creative process. Tests are the equivalent of “editing”: writers of all types and kinds write first, and then edit, sometimes almost endlessly. That’s why it’s called “polished” writing.

There are a couple important benefits to TDD:  better architecture of your code and less tightly coupled code, code technical quality (less bugs, in short), and documentation through code. However, there’s one more benefit that I hadn’t run into until recently: tests help maintain backwards compatibility.

True, sometimes you will have to rewrite your tests when you modify code — but please, it should at least make you stop and think twice.

There are two things going on when you modify your tests:

  1. Every project has externally facing methods and classes (your API/SPI) and internally facing methods and classes (code used only internally by the project itself).
  2. You should be aware of what your test actually tests. In short, you should separate and write your tests according to whether they deal with externally or internally facing code.

When you modify your test, and it tests externally facing code, then that should trigger a realization about what you might be doing to users of your project.

Categories: Geen categorie

jBPM Designer presentation at FOSDEM ’12

3 February 2012 2 comments

FOSDEM is the “Free and Open Source Software Developers Meeting” and it’s happening this weekend in Brussels. It’s a fairly large conference (5000-10000 attendees) covering all sorts of free software.

And I’ve been working on a presentation for it about jBPM Designer this week. Putting a presentation together can sometimes be more work than you expect — hmm, maybe more work than I expected then, at the least.

Here, good readers, is a link to a description of the presentation. Geoffrey De Smet, another core Drools/jBPM developer, is doing the first half the presentation on Guvnor, which Designer integrates into. You can find his blog post about it here. He’s also the lead developer on the Drools Planner project which I like. “You should check it out” are the words that you just read and that you already knew were coming.

A good friend of mine who’s somewhat of an expert in learning told me that all presentations and trainings should attempt to pose one question to the viewers and answer it. In this presentation, my question is

How do we let business analysts to do their work well (so that they leave the business software developers alone)?

It’s a question which I have at times found very pertinent, even though I have worked with some very good business analysts.

Designer is a great web project that already is a polished, easily usable web application and integral part of Guvnor (from the jBPM standpoint) — and it’s getting better every day. In short, Tihomir (Surdilovic), who’s probably done most of the work on the project, has added lots of features that make it easy to develop a (BPMN 2.0) process. The Guvnor integration means that, as Tiho put it, Guvnor becomes a “one-stop shop” for developing, storing and deploying processes.

You can just open up Guvnor (in a browser), create a new process, edit it, save it, build the package and then go directly to jbpm-console and BAM!, start your process. That’s kinda nice.. !

In my presentation, I’ll go over the background of BPM, BPMN and JBPM (if you’re attending, please practice saying that 10 times quickly), some of the main features of Designer, and then give a quick (max 10 minute?) demo. We might finish a few minutes early — depending on questions.

Categories: Geen categorie

Return in finally skips the catch!

4 December 2011 Leave a comment

I was writing a JUnit Rule, when I realized that my test failures weren’t being passed through to the JUnit framework.

Here’s what my code looked like:

public Statement apply(final Statement base, FrameworkMethod method, Object target) {
  return new Statement() {
    @Override
    public void evaluate() throws Throwable {
      try {
        base.evaluate();
      }
      catch (Throwable t) {
        throw t;
      }
      finally {
        boolean something = false;
        // check something
        if( something ) {
          return;
        }
        // do what I should otherwise do
      }
    }
  };
}

Bizarrely (or not), that Throwable was not being thrown. After trying some different approaches, I realized that the return out of the finally was the cause.

Categories: Geen categorie

Software project configuration in your code repository

2 September 2011 Leave a comment

Mostly, I try to stay out of religious discussions. I’m not talking about Buddhism vs. the Mormon belief, I’m talking about discussions in which it’s been recognized that there really is no right side because the opinions of the debaters are based on belief.

For example, which is a better programming language, Perl or Haskell? Ruby or C#? I don’t care! Whatever will get the job done and what the project leader wants. I have my own personal opinion, but when it comes to work, I try to do what I get paid for. Sometimes, in the best situations, I’m being paid to make work decisions on the basis of my own personal experience and opinions — but that’s not always the case.

However, I must admit, there’s one thing that really, really irks me: project configuration files being stored in the SCM system a.k.a. your source code management system — CVS, RCS, subversion, mercurial, git: see above, I don’t care, etc.

When someone starts putting a .project file and a .classpath file and in fact, any file that begins with a “.”, then that means two things:

  • The person committing those files to the repository is basically saying: here’s how you need to set this project up (with regards to the IDE and other tools you’re using to code).
  • I either shouldn’t use the tools I usually use on this project or otherwise, I have to make allowances for the weird ways that the tools I use are going to react, because of said files.

There two things I’d like to explain and mention:

  1. Here’s what happens to me when you commit (project) configuration files to the repository:
    • I start up my IDE and look at the project. In the course of working on the project, those configuration files are going to change because I use different tools than you do!
    • When I’ve already commited my changes, and sometime later I go back to look at my project. Only, then I notice that there are things I haven’t committed — oh yeah.. my tools modified the project configuration files.
      • The thing is, unfortunately, I care too much about having my colleagues be productive and I know out of experience that committing project files is usually a religious issue. So I just work around it.
    • Otherwise, I’ve been forgetful enough that I’ve actually committed your project configuration files. And then you update your copy of the project and most likely update and recommit the project configuration files.
      • And at this point, we’re both just wasting time when we could actually be doing more productive and meaningful things. This is why I don’t do this.
      • I’ve never committed over project configuration files, but the older and less patient I get with the youth, the larger the chance is that this will happen. [Oh no!! Please don't, mriet!!]
    • This might not seem like a big thing but it is when I’m spending my whole day on a project and seeing the results of this problem every hour. It’s not the time — it’s the energy it costs me to suppress my irritation at the thoughtlessness of the developer who did it.
    • Okay, that’s a little over the top. Hopefully it was a good example of how much energy this kind of thing costs me.
  2. My work is to produce software. It’s a little more than that, but that’s what it comes down to, regardless of whether I delegate tasks to other developers or not.
    • Like any professional, I have a personal preference for the set of tools I use in my profession. I use the tools I use because they help me do my work more quickly.
      • I use a vi plugin in Eclipse and regularly get bizarre looks for this. It helps me work more quickly and that’s what it’s about!
      • When you are working with me but on something that doesn’t affect my work, then I don’t care! It’s as soon as you start to impact my work that we have a problem.
    • When you start telling me how to use my tools — I’m not talking about advice, I’m talking about telling me (see above said files) — you’re impacting how productive I am.
    • Is telling me how to use the tools I use really worth reducing my productivity? If it is, I’m curious as to why.

In short, don’t do this. You are not an island — and if you are, good luck getting interesting work. Everyone knows this, but the most important qualities of modern (software) developers is how well they can communicate and how well they can work in or lead a team It’s a big world, and it’s only getting bigger: getting along with the rest of the world only helps.

Categories: Geen categorie

jBPM 5 database testing mechanisms

22 July 2011 Leave a comment

I recently finished making a mechanism for enabling the use of different databases with a set of unit tests.

A thorough explanation of the mechanism in its entirety can be found here:
- http://community.jboss.org/wiki/JBPM5DatabaseTesting

The reason I’m also posting this here is because the mechanism that I describe can actually be built in to most maven projects that contain unit tests that use persistence.

In my case, the specifics are as follows:

  • The mechanisms are built into the jBPM framework (the code can be found here).
  • The unit tests are spefically using JTA (the Java Transaction API).
  • The Transaction layer is handled by the Bitronix transaction manager.

But none of these details mean that the same mechanism can’t be applied to any other project, including those that don’t do anything with JTA. Using JPA is to some degree intrinsic to the mechanism, although other java persistence frameworks that also use XML configuration could also be used, I think.

In short, it comes down to maven filtering + the Properties mechanism in Java + using XML configuration for JPA (persistence.xml, in this case).

Categories: CI/Buiilds, jBPM Tags:

(j)BPM in the cloud

11 July 2011 Leave a comment

Right, so I’ve been thinking about this for a little bit:

What exactly do you want from a BPM engine if it’s in the Cloud?

[Yes, here, we capitalize the word Cloud. Good, that's settled.]

Moving BPM instances

First, a minor diversion from the topic at hand:

VMware:

At one of my previous jobs/assignments/workplaces the developer machines were all virtual machines. It was fantastic for the developers (well, almost) and particularly so for the system administrators and management. Far less inventory (machines) that simply weren’t worth anything after 5 years and no more walking around doing almost physical maintenance on machines.

But the biggest advantage is that my (virtual) workstation could be moved to whichever node that the systems administrators wanted it to be moved to, and that creating a new workstation was as simple as a couple of clicks and ticks. Furthermore I could simply login whereever I wanted to — even from home via a VPN — and presto magico, have my work environment available to me exactly as I had left it!

System administrators were no longer spending as much time on figuring out where a machine was and when it could be turned off to do what — they were becoming more efficient in their job.

The Cloud

Why do you move your (j)BPM process to the cloud? Because you don’t care (and don’t want to care) about where your BPM process is running.

Sure, to be clear, you want your BPM process instance to be secure and safe and dependable: but the hardware underneath — in short, the server, anything and everything except for your process just doesn’t matter to you.

And when we’re talking about BPM processes, that means that the only thing you care about is that somewhere out there, in the wild blue yonder, your process is running.

This has some consequences.

This means that, well, at lot like my virtual workstation, my BPM process should be able to be moved, on the fly.

As a 80′s valley girl would have said it: like, the process is just stopped and then like, it’s over here, like, and then it’s running again and like, if you didn’t know, like, you wouldn’t know! Oh.. my.. god!

BPM processes in the cloud:

Huh? Exactly.

What I’m imagining is being able to move, in this case a business process and the entire related java stack just like that <snaps fingers>.

There are several things that the infrastructure needs to be able to keep consistent in this case:

  1. Encapsulated (persistence) context.
  2. Java stack.

That’s actually it, as far as I can tell. We need the data and where we were — exactly – in the process.

Persistence context:

A BPM process needs a connection to a database in most cases: usually in order to persist (save) process information at critical junctures, like the end of a process. With regards to a transportable process, that means that a process needs to be able to encapsulate and transfer it’s (not yet commited) persistence context with itself.

This doesn’t seem that hard: sure, it’s a bit of hacking but on the other hand, it’s pretty clear how we could do this.

Java Stack

This is an interesting one, because there are some interesting cases:

  • BPMN 2 step.
  • Java code that’s associated with a BPMN 2 node.

The first case is easy enough: in short, it’s a step in a BPMN 2 workflow, no associated java code, we pause the process at the end of the step, move everything and restart the process where we had paused it on the new cloud node.

The second case is more interesting. Maybe we have a long running step — a human task or some such — and we can’t wait for the step to finish. Things to think about are then the following:

  • There are already mechanisms for asynchronous execution in jBPM and other frameworks. We can probably use these to our advantage here.
  • Input from, say, a returning webservice: it might need to be “cached” and then “forwarded” to the node that jBPM is running on.
  • Otherwise, we can mark steps in the BPMN 2 process as “atomic/unmoveable” in the sense that in those steps, the process can not be transported.
  • We need to save the stack: we can already retrieve the stack using Thread.currentThread().getStackTrace(). But how can we “recreate” that stack (with all of it’s data) on the new cloud node? There will be cases where we will want to do that!
  • Related to saving the stack: what type of mechanisms or otherwise constraints do we give the process creator/user in order to ensure that the process is saveable and transportable? Do we “save points” in our XML? Do we force the user to call a “saveProcessStack” method, or otherwise build the automatic/automagic calling of that into processing methods?

Cool, more questions than answers! And lots to build on.

‘Till next time!

Categories: Cloud, jBPM Tags: , ,

Jenkins, Groovy System scripts (and Maven)

23 June 2011 1 comment

So, I spent two+ weeks doing the following:

  • Digging through the Hudson/Jenkins code
  • Learning the architecture of Hudson/Jenkins plugins
    • In particular SCM plugins, like the git plugins
  • Learning groovy
  • Writing a groovy script for use by the Jenkins groovy script plugin
    • Which retrieved variables from Hudson/Jenkins
    • And instantiated a maven build, from the script

The script/job I had to make finally works — although at the end of it all, I learned/realized that I couldn’t use the groovy system script option, but had to run the job as a normal groovy script.

Given that I had put enough time into it, I figured I’d post some of what I’ve done
in order to help others.

Jenkins Groovy build step options

Groovy build steps in Jenkins

This is a screenshot of the (Groovy) build step options in a job configuration in Jenkins (1.409.1). This functionality is added when you install the Groovy plugin for Jenkins (or Hudson).

  • The “system Groovy script” option executes a groovy script within the same JVM that Jenkins is running in.
  • The (normal) groovy script build step forks a new JVM for the script to run in.

[See the Groovy plugin link above for more info]

A system Groovy script thus has access to the heap of the Jenkins process: this allows us to access lots of information that is available within your Jenkins server, including the following:

  • Information over the current build
  • Information over other builds
  • Settings for your Jenkins server

In fact, because you not only have access to the information but can also change it (or delete information!), giving developers the option to run system Groovy scripts as part of their build is actually a little dangerous — and probably not recommended. But that’s another story.


 

The following script includes a couple of examples of how to retrieve information via the available instances and classes of the Jenkins infrastructure. It does the following:

  1. It first retrieves the ‘out’ binding.
    • I can’t find where this is documented, but when the Groovy Plugin starts it passes two things via Bindings to your script. One of these is the out variable, which you can use to print to the log saved by Jenkins. Normal println’s will NOT work here: use out.println
  2. It retrieves all build variables and other environmental variables and stores them in config
  3. It reaches into the Hudson guts and retrieves the path for all maven 3.x installations.
  4. It modifies the variables saved in configso that we can easily run maven
    • the M2_HOME has been known to cause problems for maven 3.
    • One of the parametrized variables for the build is the MAVEN_TARGET variable. This variables contains the maven targets; for example: “clean test”.
  5. In order to tell where the repo is that I want to build (in this case buildRepo, I search for the directory that this repo has been installed in.
    • This particular job has multiple git repositories, which is possible using the Multiple SCM plugin.
  6. Lastly, I run maven using a ProcessBuilder.
    • I pass all of the variables in configto the maven process.
    • I also pass through all of the output from the maven process to Jenkins.
    • Lastly, if the maven process does not end correctly, I tell Jenkins that, using build.setResult( Result.FAILURE )

 

// build and Result objects
import hudson.model.*

out.println "-" * 80

/**
 * VARIABLES
 */

def config = new HashMap()
def bindings = getBinding()
config.putAll(bindings.getVariables())

def out = config['out']

// specified variables (in Jenkins job)

def thr = Thread.currentThread()
def build = thr?.executable
def buildMap = build.getBuildVariables() 

config.putAll(buildMap)

// build/environmental variables
def envVarsMap = build.parent.builds[0].properties.get("envVars")

config.putAll(envVarsMap)

config.keySet().each { key -> 
  out.println key + ": " + config.get(key) 
}

out.println "-" * 80

// Set maven variables 
// - we can reach hudson information through static methods available to us
//   This is possible is because this is run as a sytem groovy script and has
//   access to the heap that hudson/jenkins is running on. 

def mvnBuilder = null
buildersList = hudson.tasks.Builder.all()
buildersList.each { builder -> 
  out.println "Builder: " + builder.id
  if( builder.id.equals("hudson.tasks.Maven") ) { 
    mvnBuilder = builder
  }
}

def mvn3Paths = [ : ]
mvnBuilder.installations.each { installation -> 
  out.println "Maven installation: " + installation.name
  if( installation.name.contains("maven-3") ) { 
    mvn3Paths.put( installation.name, installation.home )
  }
}

def mvn3Ver = null
def mvn3Path = null
if( mvn3Paths.size() > 0 ) { 
  mvn3Ver = mvn3Paths.keySet().sort().reverse()[0]
  mvn3Path = mvn3Paths.get(mvn3Ver)
  config
}
else { 
  out.println "UNABLE TO RETRIEVE MAVEN INSTALLATIONS FROM HUDSON!\n"
  build.setResult( Result.FAILURE )
  return -1
}

config.remove('M2_HOME')
config.put('MAVEN_HOME', mvn3Path )
config.put('M3_HOME', mvn3Path )
def mavenTarget = config['MAVEN_TARGET']

// Get the relative target dir

// closure to identify and retrieve the jbpm git repo info
def retrieveJbpmGitScm = { scm ->
  def gitScmFound = null
  if( scm.type.endsWith("GitSCM") ) { 
    scm.repositories.each { repo -> 
      repo.getURIs().each { uri -> 
        if( uri.path.endsWith("jbpm.git") ) { 
           gitScmFound = scm
        }
      }
    }
  }
  return gitScmFound
}
  
def buildScm = build.parent.scm
def jbpmGitScm = null;
if( buildScm.type.endsWith("MultiSCM") ) { 
  def scms = buildScm.configuredSCMs
  scms.each { scm ->
    if( jbpmGitScm == null ) { 
      jbpmGitScm = retrieveJbpmGitScm(scm)
    }
  }
}
else if( buildScm.type.endsWith("GitSCM") ) {  
  jbpmGitScm = retrieveJbpmGitScm(buildScm)
}

String relativeTargetDir = ""
if( jbpmGitScm != null ) { 
  relativeTargetDir = jbpmGitScm.relativeTargetDir
}
else { 
  out.println "UNABLE TO RETRIEVE JBPM GIT SCM INFORMTION FROM HUDSON!\n"
  build.setResult( Result.FAILURE )
  return 
}

/**
 * MAVEN BUILD
 */

def workspace = config['WORKSPACE']

def targets = []
(mavenTarget =~ /\w+/).each { match ->
  targets.add(match)
}
int m = 0;
String [] mvn3Cmd  = new String[2+targets.size()]
mvn3Cmd[m++] = config.get('MAVEN_HOME').toString() + File.separator + "bin" + File.separator  + "mvn" 
mvn3Cmd[m++] = "--fail-at-end"
targets.each { targetWord ->
  mvn3Cmd[m++] = targetWord
}

String processDir = workspace
if( relativeTargetDir != null && relativeTargetDir.length() > 0 ) {
  processDir += File.separator + relativeTargetDir 
}

File workspaceDir = new File(processDir)

ProcessBuilder mvnProcBuilder = new ProcessBuilder(mvn3Cmd);
mvnProcBuilder.redirectErrorStream(true);

Map env = mvnProcBuilder.environment()
env.clear()

config.keySet().each { key -> 
  try {  
    env.put(key, (String) config.get(key))
  }
  catch(ClassCastException cce) { 
    print "Unable to convert '" + key + "' to String: " 
    println cce.message
    // do nothing else..
  }
} 

mvnProcBuilder.directory(workspaceDir)

out.println "Starting maven build: " 
out.println "[cmd]: " + mvn3Cmd
out.println "[dir]: " + workspace
out.println "-" * 80

def mvnProc = mvnProcBuilder.start();

InputStream procOutput = mvnProc.getInputStream();
BufferedReader outputReader = new BufferedReader(new InputStreamReader(procOutput))

String line = null;
while((line = outputReader.readLine()) != null ) { 
  out.println( line )
}

// Just to make sure
mvnProc.waitFor()

if( mvnProc.exitValue() != 0 ) { 
  String message = "Maven build failed: ["
  mvn3Cmd.each { arg -> message += arg + " " } 
  message += "]"
  build.setResult( Result.FAILURE )
}

 

And here’s an (edited) example of the output.

Note the environmental variables that I’ve been able to retrieve as well as
some of the Jenkins information that I show.

Started by upstream project "SystemScript" build number 10

Checkout:axis-value-1 /opt/bin/jenkins-1.409.1/jobs/SystemScript/workspace/axis/axis-value-1 - hudson.remoting.LocalChannel@6e70c242
Using strategy: Default
Last Built Revision: Revision 93c2c662ccc478a5eb9e98ca36437458525d1231 (origin/5.1.x)
Checkout:scripts / /opt/bin/jenkins-1.409.1/jobs/SystemScript/workspace/axis/axis-value-1/scripts - hudson.remoting.LocalChannel@6e70c242
Fetching changes from the remote Git repository
Fetching upstream changes from git://github.com/mrietveld/myRepo.git
Commencing build of Revision 93c2c662ccc478a5eb9e98ca36437458525d1231 (origin/5.1.x)
Checking out Revision 93c2c662ccc478a5eb9e98ca36437458525d1231 (origin/5.1.x)
--------------------------------------------------------------------------------
TERM: vt100
M3_HOME: /opt/bin/apache-maven-3.0.3
JENKINS_HOME: /opt/bin/jenkins-1.409.1
JAVA_HOME: /usr/lib/jvm/jre
IMSETTINGS_MODULE: none
...
HUDSON_URL: http://127.0.0.1:8080/
...
JOB_URL: http://127.0.0.1:8080/job/SystemScript/./axis=axis-value-1/
...
BUILD_NUMBER: 10
...
EDITOR: gvim
M2_HOME: /opt/bin/apache-maven-2.2.1
PATH: /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/usr/local/libexec:/usr/ccs/bin
JENKINS_URL: http://127.0.0.1:8080/
LESS: -gS -j.25
QTLIB: /usr/lib64/qt-3.3/lib
PAGER: less
GZIP: -9v --name
KDE_IS_PRELINKED: 1
GDM_LANG: en_US.utf8
WINDOWPATH: 1
HUDSON_HOME: /opt/bin/jenkins-1.409.1
COMMON_TOOLS: /opt/bin
OSTYPE: linux
MAVEN_HOME: /opt/bin/apache-maven-2.2.1
NODE_LABELS: master
XFILESEARCHPATH: /usr/dt/app-defaults/%L/Dt
MANPATH: /usr/local/share/man:/usr/local/man:/usr/share/man:/usr/share/man/en:/usr/share/man/overrides:/usr/share/locale/man:/usr/java/jdk1.5.0_22/man:/usr/share/doc/man-pages-overrides-1.0/man
BUILD_ID: 2011-07-20_11-19-51
BUILD_TAG: jenkins-axis=axis-value-1-10
BUILD_URL: http://127.0.0.1:8080/job/SystemScript/./axis=axis-value-1/10/
axis: axis-value-1
SHELL: /bin/tcsh
WORKSPACE: /opt/bin/jenkins-1.409.1/jobs/SystemScript/workspace/axis/axis-value-1
MAVEN_TARGET: clean
ANT_HOME: /usr/share/ant
JOB_NAME: SystemScript/axis=axis-value-1
out: java.io.PrintStream@641ef158
--------------------------------------------------------------------------------
Builder: hudson.tasks.BatchFile
Builder: hudson.tasks.Shell
Builder: hudson.tasks.Ant
Builder: hudson.tasks.Maven
Builder: com.nirima.AdaptivePluginBuilder
Builder: hudson.plugins.groovy.SystemGroovy
Builder: hudson.plugins.groovy.Groovy
Maven installation: maven-2.2.1
Maven installation: maven-3.0.3
Starting maven build: 
[cmd]: [/opt/bin/apache-maven-3.0.3/bin/mvn, --fail-at-end, clean]
[dir]: /opt/bin/jenkins-1.409.1/jobs/SystemScript/workspace/axis/axis-value-1
--------------------------------------------------------------------------------
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.673s
[INFO] Finished at: Wed Jul 20 11:19:57 CEST 2011
[INFO] Final Memory: 6M/102M
[INFO] ------------------------------------------------------------------------
Finished: SUCCESS
Categories: CI/Buiilds Tags: ,
Follow

Get every new post delivered to your Inbox.