Skip to content


Drools configuration with Spring (Part 1)

Drools is a business rule management system (BRMS). It based on rules engine, using an implementation of the Rete algorithm.
Drools supports the JSR-94 standard for its business rule engine and enterprise framework for the construction, maintenance, and enforcement of business policies in an organization, application, or service.
More about Drools is here.

A business process or workflow describes the order in which a series of steps need to be executed, using a flow chart. This makes it much easier to describe a complex composition of various tasks. Processes are especially useful in describing state-based, long-running processes. Drools Flow allows end users to specify, execute and monitor (a part of) their business logic using these processes. The Drools Flow process framework is easily embeddable into any Java application (as a simple Java component) or can run standalone in a server environment.

This article is about practical implementation of rule flow with Spring. In my case  flow include validation step at beginning that validates objects, then executes several tasks. Execution of task depends on rules define in flow descriptor. Flow itself defined in flow file (.rf).

Simple flow diagram:

Before flow could execute we need to perform certain action as create session, load flow and validation (.drl) files, setup globals and work items for flow.  To simplify controls and manage this action I create integration classes.

1. Process Descriptor

package com.company.workflow.integration;

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

import org.drools.builder.ResourceType;
import org.springframework.core.io.Resource;

public class ProcessDescriptor
{
	private final Map	        resourceMap;
	private Map<String, Object>	globals		= new HashMap<String, Object>();
	private Map<String, Object>	workItems	= new HashMap<String, Object>();
	private Boolean			validation	= true;

	public ProcessDescriptor(final Map<Resource, ResourceType> resourceMap)
	{
		this.resourceMap = resourceMap;
	}

	public Map <Resource, ResourceType> getResourceMap()
	{
		return resourceMap;
	}

	public Map<String, Object> getGlobals()
	{
		return globals;
	}

	public void setGlobals(final Map<String, Object> globals)
	{
		this.globals = globals;
	}

	public Boolean getValidation()
	{
		return validation;
	}

	public void setValidation(final Boolean validation)
	{
		this.validation = validation;
	}

	public Map<String, Object> getWorkItems()
	{
		return workItems;
	}

	public void setWorkItems(final Map<String, Object> workItems)
	{
		this.workItems = workItems;
	}
}

2. KnowledgeSessionLookupImpl.java

package com.company.workflow.integration;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseConfiguration;
import org.drools.KnowledgeBaseFactory;
import org.drools.builder.KnowledgeBuilder;
import org.drools.builder.KnowledgeBuilderFactory;
import org.drools.builder.ResourceType;
import org.drools.io.ResourceFactory;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.process.WorkItemHandler;
import org.drools.runtime.process.WorkItemManager;
import org.springframework.core.io.Resource;

import com.company.workflow.services.ReportFactory;
import com.company.workflow.services.ValidationReport;
import com.company.workflow.services.impl.DefaultReportFactory;
import com.company.workflow.workingitems.BaseWorkItemHandler;

public class KnowledgeSessionLookupImpl implements KnowledgeSessionLookup
{
	private KnowledgeBase					knowledgeBase;
	private Map<String, ProcessDescriptor>	processConfig	= new HashMap<String, ProcessDescriptor>();
	private Map<String, WorkItemHandler>	systemHandlers	= new HashMap<String, WorkItemHandler>();

	public KnowledgeSessionLookupImpl(final Map<String, ProcessDescriptor> config)
	{
		processConfig = config;
	}

	public void init()
	{
	}

	public StatefulKnowledgeSession loadSession(final int sessionId)
	{
		final StatefulKnowledgeSession session = knowledgeBase.newStatefulKnowledgeSession();
		return session;
	}

	public StatefulKnowledgeSession newSession(final String processName) throws IOException
	{
		final KnowledgeBuilder builder = KnowledgeBuilderFactory.newKnowledgeBuilder();
		final ProcessDescriptor proc = processConfig.get(processName);
		if (proc == null)
		{
			throw new RuntimeException("No process descriptor found for [" + processName + "] process");
		}
		if ((proc.getResourceMap() == null) || proc.getResourceMap().isEmpty())
		{
			throw new RuntimeException("Empty list of flow and rules for [" + processName + "] process");
		}
		for (final Entry<Resource, ResourceType> entry : proc.getResourceMap().entrySet())
		{
			builder.add(ResourceFactory.newInputStreamResource(entry.getKey().getInputStream()), entry.getValue());
		}

		if (builder.hasErrors())
		{
			throw new RuntimeException(builder.getErrors().toString());
		}
		final KnowledgeBaseConfiguration configuration = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();

		knowledgeBase = KnowledgeBaseFactory.newKnowledgeBase(configuration);
		knowledgeBase.addKnowledgePackages(builder.getKnowledgePackages());

		final StatefulKnowledgeSession session = knowledgeBase.newStatefulKnowledgeSession();

		for (final Entry<String, Object> entry : proc.getGlobals().entrySet())
		{
			session.setGlobal(entry.getKey(), entry.getValue());
		}
		if (proc.getValidation())
		{
			final ReportFactory reportFactory = new DefaultReportFactory();
			session.setGlobal("reportFactory", reportFactory);
			final ValidationReport report = reportFactory.createValidationReport();
			session.setGlobal("validationReport", report);
		}

		final WorkItemManager manager = session.getWorkItemManager();
		for (final Entry<String, WorkItemHandler> entry : systemHandlers.entrySet())
		{
			manager.registerWorkItemHandler(entry.getKey(), entry.getValue());
		}

		for (final Entry<String, Object> entry : proc.getWorkItems().entrySet())
		{
			final WorkItemHandler wi = (WorkItemHandler) entry.getValue();
			manager.registerWorkItemHandler(entry.getKey(), wi);
		}

		return session;
	}

	public void setKnowledgeBase(final KnowledgeBase knowledgeBase)
	{
		this.knowledgeBase = knowledgeBase;
	}

	public KnowledgeBase getKnowledgeBase()
	{
		return knowledgeBase;
	}

	public void setSystemHandlers(final Map<String, WorkItemHandler> systemHandlers)
	{
		this.systemHandlers = systemHandlers;
	}

}

3. ReportFactory .java

public interface ReportFactory {
	  ValidationReport createValidationReport();

	  Message createMessage(Message.Type type, String messageKey,
	      Object... context);
}

4. Message .java

public interface Message {
	public enum Type {
		ERROR, WARNING
	}
	Type getType();
	String getMessageKey();
	List<Object> getContextOrdered();
}

5. ValidationReport .java

public interface ValidationReport {

	Set<Message> getMessages();
	Set<Message> getMessagesByType(Message.Type type);
	boolean contains(String messageKey);
	boolean addMessage(Message message);
	boolean hasErrors();
	boolean hasWarrnings();
}

6. ErrorMessage.java

public class ErrorMessage implements Message
{
	private String			messageKey	= null;
	private List<Object>	context		= null;

	public ErrorMessage()
	{

	}

	public ErrorMessage(final String key, final List<Object> objs)
	{
		messageKey = key;
		context = objs;
	}

	public List<Object> getContextOrdered()
	{
		return context;
	}

	public String getMessageKey()
	{
		return messageKey;
	}

	public Type getType()
	{
		return Message.Type.ERROR;
	}

	@Override
	public String toString()
	{
		return messageKey;
	}
}

7. WarrningMessage .java

public class WarrningMessage implements Message {
	  private String messageKey = null;
	  private List<Object> context = null;

	public WarrningMessage(String key, List<Object> objs){
		messageKey=key;
		context = objs;
	}
	public List<Object> getContextOrdered() {
		return context;
	}

	public String getMessageKey() {
		return messageKey;
	}

	public Type getType() {
		return Message.Type.WARNING;
	}
}

8. DefaultReportFactory.java

public class DefaultReportFactory implements ReportFactory {

	public Message createMessage(Type type, String messageKey,
			Object... context) {
		if( Type.ERROR.equals(type))
			return new ErrorMessage( messageKey, Arrays.asList(context));
		if( Type.WARNING.equals(type))
			return new WarrningMessage( messageKey, Arrays.asList(context));

		throw new IllegalArgumentException(
	          "Wrong Message Type");
	}

	public ValidationReport createValidationReport() {
		return new ValidationReport();
	}
}

9.WorkItem

Each work item handler should implement interface org.drools.runtime.process.WorkItemHandler.
SampleWorkItemHandler .java

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

import org.drools.runtime.process.WorkItem;
import org.drools.runtime.process.WorkItemHandler;
import org.drools.runtime.process.WorkItemManager;

public abstract class SampleWorkItemHandler implements WorkItemHandler
{

	@Override
	public void abortWorkItem(final WorkItem workItem, final WorkItemManager manager)
	{
		// Actions on abort
	}

	@Override
	public void executeWorkItem(final WorkItem workItem, final WorkItemManager manager)
	{
		// Method implementation
		//...
		//If need to update object in flow
		protected Map<String, Object>		results	= new HashMap<String, Object>();
		manager.completeWorkItem(workItem.getId(), results);
		//..
		// if not
		//manager.completeWorkItem(workItem.getId(), null);
	}
}

10. Deployment Context.xml

	<bean name="workItemBusinessOperation_1"
		class="com.company.workflow.workingitems.SampleWorkItemHandler">
		<property name="property_wi_1" ref="property_wi_1" />
	</bean>
	<bean name="workItemBusinessOperation_2"
		class="com.company.workflow.workingitems.OtherWorkItemHandler">
		<property name="property_wi_2" ref="property_wi_2" />
	</bean>
	...
	<bean name="workflowProcess1" class="com.company.workflow.integration.ProcessDescriptor"
		scope="prototype">
		<constructor-arg>
			<map>
				<entry key="classpath:rules/validate1.drl" value="DRL" />
				<entry key="classpath:flow/processFlow1.rf" value="DRF" />
			</map>
		</constructor-arg>
		<property name="workItems">
			<map>
				<entry key="businessOperation_1">
					<ref bean="workItemBusinessOperation_1" />
				</entry>
				<entry key="businessOperation_2">
					<ref bean="workItemBusinessOperation_2" />
				</entry>
				...
			</map>
		</property>
	</bean>

	<bean id="knowledgeSessionLookup" init-method="init"
		class="com.company.workflow.integration.KnowledgeSessionLookupImpl">
		<constructor-arg>
			<map>
				<entry key="workflowProcess1">
					<ref bean="workflowProcess1" />
				</entry>
				...
			</map>
		</constructor-arg>
		<property name="systemHandlers">
			<map>
				<entry key="NotificationWorkItemHandler">
					<ref bean="NotificationWorkItemHandler"/>
				</entry>
			</map>
		</property>
	</bean>

	<bean name="workflowProcessFactory" class="com.company.workflow.integration.WorkflowProcessFactory">
		<constructor-arg ref="knowledgeSessionLookup" />
	</bean>

—————————————–
See nex post about WorkflowProcessFactory and AOP way to integrate drools into application.

Posted in development.

Tagged with .


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

You must be logged in to post a comment.