Binding jBPM WorkItemHandlers from a Spring Context

I’ve just spent a lump of time (too much for how simple it really is) working out how to do this with jBPM 6.3 so I’ve added this blog to help those who come after.

This has worked for me – I’m not sure that it’s the ‘right’ or most efficient way to achieve the desired result however.

I’m using Spring Boot with jBPM 6.3 (this is valid for the commercial version too – jBoss BPM Suite 6.2). I’ve successfully loaded all the appropriate jBPM services using the instructions from the jBPM 6.3 documentation¬†(though I’ve translated it to Spring Java config rather than the XML config documented), and am happily deploying and executing services.

The problem comes when trying to load WorkItemHandlers from a Spring context and I’ve fought the good fight for a week or three now on and off trying to find a good way to solve it. It’s taken time because there really isn’t a lot of help out there in Google land other than a gamut of hints indicating that many have had this issue and have solved it eventually but haven’t reflected the solution back out there.

In the end after reading quite a lot of the source code and stepping through a few times I took the suggestion from a thread in the jBoss developer forum and extended org.kie.spring.manager.SpringRuntimeManagerFactoryImpl in order to inject my own implementation of org.kie.api.runtime.manager.RegisterableItemsFactory which has a named list of Spring managed WorkItemHandlers which need to be made available to processes executed by jBPM.

My implementation of RuntimeManagerFactory factory is as follows. I’ve overridden the adjustEnvironment method to force my version of the RegisterableItemsFactory in; this method is called when setting up the runtime environment for a process execution.

package myjbpm.jbpm.common.workitemhandlers.impl;

import org.jbpm.runtime.manager.impl.SimpleRuntimeEnvironment;
import org.kie.api.runtime.manager.RegisterableItemsFactory;
import org.kie.api.runtime.manager.RuntimeEnvironment;
import org.kie.api.runtime.process.WorkItemHandler;
import org.kie.spring.manager.SpringRuntimeManagerFactoryImpl;


/**
 * This extends the jBPM {@link SpringRuntimeManagerFactoryImpl} in order to override {@link
 * SpringRuntimeManagerFactoryImpl#adjustEnvironment(RuntimeEnvironment)} method and put in our
 * own {@link RegisterableItemsFactory} implementation which has a reference to the Spring loaded {@link
 * WorkItemHandler} beans.
 */
public class CustomRegisterableItemsSpringRuntimeManagerFactoryImpl extends SpringRuntimeManagerFactoryImpl {

    private final RegisterableItemsFactory springRegisterableItemsFactory;


    public CustomRegisterableItemsSpringRuntimeManagerFactoryImpl(
            RegisterableItemsFactory springRegisterableItemsFactory) {
        this.springRegisterableItemsFactory = springRegisterableItemsFactory;
    }


    @Override
    protected void adjustEnvironment(final RuntimeEnvironment environment) {
        super.adjustEnvironment(environment);
        ((SimpleRuntimeEnvironment) environment).setRegisterableItemsFactory(springRegisterableItemsFactory);
    }
}

The matching RegisterableItemsFactory is as follows, notice that I extend jBPM’s DefaultRegisterableItemsFactory in order to maintain default functionality for other item types; these could be overridden to be retrieved from a Spring context also I expect:

package myjbpm.jbpm.common.workitemhandlers;

import org.jbpm.runtime.manager.impl.DefaultRegisterableItemsFactory;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.process.WorkItemHandler;

import java.util.Map;


public class SpringRegisterableItemsFactory extends DefaultRegisterableItemsFactory {

    private final SpringWorkItemHandlerProducer workItemHandlerProducer;


    public SpringRegisterableItemsFactory(SpringWorkItemHandlerProducer workItemHandlerProducer) {
        this.workItemHandlerProducer = workItemHandlerProducer;
    }


    @Override
    public Map<String, WorkItemHandler> getWorkItemHandlers(final RuntimeEngine runtime) {
        Map<String, WorkItemHandler> workItemHandlers = super.getWorkItemHandlers(runtime);
        workItemHandlers.putAll(workItemHandlerProducer.getWorkItemHandlers());

        return workItemHandlers;
    }
}

Where the SpringWorkItemHandlerProducer is used to translate the Spring injected java.util.List<NamedWorkItemHandler> into the java.util.Map<String, WorkItemHandler> expected by the RegisterableItemsFactory interface. It is as follows:

package myjbpm.jbpm.common.workitemhandlers.impl;

import myjbpm.jbpm.common.workitemhandlers.SpringWorkItemHandlerProducer;
import myjbpm.jbpm.domain.handler.NamedWorkItemHandler;
import org.kie.api.runtime.process.WorkItemHandler;

import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class SpringWorkItemHandlerProducerImpl implements SpringWorkItemHandlerProducer {

    private final Map&amp;amp;amp;lt;String, WorkItemHandler&amp;amp;amp;gt; workItemHandlers = new HashMap&amp;amp;amp;lt;&amp;amp;amp;gt;();


    public SpringWorkItemHandlerProducerImpl(List<NamedWorkItemHandler> workItemHandlers) {
        mapHandlers(workItemHandlers);
    }


    @Override
    public Map<String, WorkItemHandler> getWorkItemHandlers() {
        return workItemHandlers;
    }


    private void mapHandlers(final List<NamedWorkItemHandler> namedWorkItemHandlers) {
        for (NamedWorkItemHandler wih : namedWorkItemHandlers) {
            this.workItemHandlers.put(wih.getTaskName(), wih);
        }
    }
}

And NamedWorkItemHandler is:

package myjbpm.jbpm.domain.handler;

import org.kie.api.runtime.process.WorkItemHandler;


public interface NamedWorkItemHandler extends WorkItemHandler {
    String getTaskName();
}

Finally these are wired together using some simple Spring bean configuration as follows:

@Bean
public RuntimeManagerFactory runtimeManager(JtaTransactionManager transactionManager,
        UserGroupCallback userGroupCallback, RegisterableItemsFactory springRegisterableItemsFactory) {
    SpringRuntimeManagerFactoryImpl managerFactory = new CustomRegisterableItemsSpringRuntimeManagerFactoryImpl(
            springRegisterableItemsFactory);
    managerFactory.setTransactionManager(transactionManager);
    managerFactory.setUserGroupCallback(userGroupCallback);

    return managerFactory;
}


@Bean
public RegisterableItemsFactory registerableItemsFactory(
        SpringWorkItemHandlerProducer springWorkItemHandlerProducer) {
    return new SpringRegisterableItemsFactory(springWorkItemHandlerProducer);
}


@Bean
public SpringWorkItemHandlerProducer springWorkItemHandlerProducer(
        List<NamedWorkItemHandler> namedWorkItemHandlers) {
    return new SpringWorkItemHandlerProducerImpl(namedWorkItemHandlers);
}

Feedback welcome – particularly if you know a better way to do this, or can see a potential issue. I’ve only just got this working so it’s possible it’s horribly wrong once I get down the track further.