package org.drools.compiler;
/*
* Copyright 2005 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.drools.definition.process.Connection;
import org.drools.definition.process.Node;
import org.drools.definition.process.NodeContainer;
import org.drools.definition.process.Process;
import org.drools.definition.process.WorkflowProcess;
import org.drools.lang.descr.ActionDescr;
import org.drools.lang.descr.ProcessDescr;
import org.drools.process.builder.ProcessNodeBuilder;
import org.drools.process.builder.ProcessNodeBuilderRegistry;
import org.drools.process.core.Context;
import org.drools.process.core.ContextContainer;
import org.drools.process.core.context.exception.ActionExceptionHandler;
import org.drools.process.core.context.exception.ExceptionHandler;
import org.drools.process.core.context.exception.ExceptionScope;
import org.drools.process.core.validation.ProcessValidationError;
import org.drools.process.core.validation.ProcessValidator;
import org.drools.rule.builder.ProcessBuildContext;
import org.drools.ruleflow.core.RuleFlowProcess;
import org.drools.ruleflow.core.validation.RuleFlowProcessValidator;
import org.drools.workflow.core.Constraint;
import org.drools.workflow.core.impl.DroolsConsequenceAction;
import org.drools.workflow.core.impl.WorkflowProcessImpl;
import org.drools.workflow.core.node.ConstraintTrigger;
import org.drools.workflow.core.node.MilestoneNode;
import org.drools.workflow.core.node.Split;
import org.drools.workflow.core.node.StartNode;
import org.drools.workflow.core.node.Trigger;
import org.drools.xml.XmlProcessReader;
/**
* A ProcessBuilder can be used to build processes based on XML files
* containing a process definition.
*
* @author <a href="mailto:kris_verlaenen@hotmail.com">Kris Verlaenen</a>
*/
public class ProcessBuilder {
private PackageBuilder packageBuilder;
private final List<DroolsError> errors = new ArrayList<DroolsError>();
private Map<String, ProcessValidator> processValidators = new HashMap<String, ProcessValidator>();
private static final String XSL_FROM_4_TO_5 = "/org/drools/xml/processes/RuleFlowFrom4To5.xsl";
private static final String PROCESS_ELEMENT_WITH_NAMESPACE = "<process xmlns=\"http://drools.org/drools-4.0/process\"\n" + " xmlns:xs=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ " xs:schemaLocation=\"http://drools.org/drools-4.0/process drools-processes-4.0.xsd\"\n";
public ProcessBuilder(PackageBuilder packageBuilder) {
this.packageBuilder = packageBuilder;
this.processValidators.put( RuleFlowProcess.RULEFLOW_TYPE,
RuleFlowProcessValidator.getInstance() );
}
public List<DroolsError> getErrors() {
return errors;
}
public void buildProcess(final Process process, String url) {
((org.drools.process.core.Process) process).setURL( url );
boolean hasErrors = false;
ProcessValidator validator = processValidators.get(((Process)process).getType());
if (validator == null) {
System.out.println("Could not find validator for process " + ((Process)process).getType() + ".");
System.out.println("Continuing without validation of the process " + process.getName() + "[" + process.getId() + "]");
} else {
ProcessValidationError[] errors = validator.validateProcess( (WorkflowProcess) process );
if ( errors.length != 0 ) {
hasErrors = true;
for ( int i = 0; i < errors.length; i++ ) {
this.errors.add( new ParserError( errors[i].toString(),
-1,
-1 ) );
}
}
}
if ( !hasErrors ) {
// generate and add rule for process
String rules = generateRules( process );
try {
packageBuilder.addPackageFromDrl( new StringReader( rules ) );
} catch ( IOException e ) {
// should never occur
e.printStackTrace( System.err );
} catch ( DroolsParserException e ) {
// should never occur
e.printStackTrace( System.err );
}
if (packageBuilder.getPackage() != null) {
ProcessDescr processDescr = new ProcessDescr();
processDescr.setName(process.getPackageName());
processDescr.setUrl( url );
PackageRegistry pkgRegistry = this.packageBuilder.getPackageRegistry( this.packageBuilder.getPackage().getName() );
DialectCompiletimeRegistry dialectRegistry = pkgRegistry.getDialectCompiletimeRegistry();
Dialect dialect = dialectRegistry.getDialect( "java" );
dialect.init(processDescr);
ProcessBuildContext buildContext = new ProcessBuildContext(
this.packageBuilder,
this.packageBuilder.getPackage(),
process,
processDescr,
dialectRegistry,
dialect);
buildContexts( ( ContextContainer ) process, buildContext );
if (process instanceof WorkflowProcess) {
buildNodes( (WorkflowProcess) process, buildContext );
}
this.packageBuilder.getPackage().addProcess( process );
pkgRegistry.compileAll();
pkgRegistry.getDialectRuntimeRegistry().onBeforeExecute();
}
}
}
public void buildContexts(ContextContainer contextContainer, ProcessBuildContext buildContext) {
List<Context> exceptionScopes = contextContainer.getContexts(ExceptionScope.EXCEPTION_SCOPE);
if (exceptionScopes != null) {
for (Context context: exceptionScopes) {
ExceptionScope exceptionScope = (ExceptionScope) context;
for (ExceptionHandler exceptionHandler: exceptionScope.getExceptionHandlers().values()) {
if (exceptionHandler instanceof ActionExceptionHandler) {
DroolsConsequenceAction action = (DroolsConsequenceAction)
((ActionExceptionHandler) exceptionHandler).getAction();
ActionDescr actionDescr = new ActionDescr();
actionDescr.setText( action.getConsequence() );
Dialect dialect = buildContext.getDialectRegistry().getDialect( action.getDialect() );
dialect.getActionBuilder().build( buildContext, action, actionDescr );
}
}
}
}
}
@SuppressWarnings("unchecked")
public void buildNodes(WorkflowProcess process, ProcessBuildContext context) {
ProcessNodeBuilderRegistry nodeBuilderRegistry = packageBuilder.getPackageBuilderConfiguration().getProcessNodeBuilderRegistry();
processNodes(process.getNodes(), process, context.getProcessDescr(), context, nodeBuilderRegistry);
if ( !context.getErrors().isEmpty() ) {
this.errors.addAll( context.getErrors() );
}
context.getDialectRegistry().addProcess( context );
}
private void processNodes(
Node[] nodes, Process process, ProcessDescr processDescr,
ProcessBuildContext context, ProcessNodeBuilderRegistry nodeBuilderRegistry) {
for ( Node node : nodes ) {
ProcessNodeBuilder builder = nodeBuilderRegistry.getNodeBuilder( node );
if ( builder != null ) {
// only build if there is a registered builder for this node type
builder.build( process,
processDescr,
context,
node );
}
if ( node instanceof NodeContainer ) {
processNodes( ((NodeContainer) node).getNodes(),
process,
processDescr,
context,
nodeBuilderRegistry );
}
if ( node instanceof ContextContainer ) {
buildContexts( (ContextContainer) node,
context );
}
}
}
public void addProcessFromFile(final Reader reader, final String url) throws Exception {
PackageBuilderConfiguration configuration = packageBuilder.getPackageBuilderConfiguration();
XmlProcessReader xmlReader = new XmlProcessReader( configuration.getSemanticModules() );
final ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
final ClassLoader newLoader = this.getClass().getClassLoader();
try {
Thread.currentThread().setContextClassLoader( newLoader );
String portRuleFlow = System.getProperty( "drools.ruleflow.port", "false" );
Reader portedReader = null;
if ( portRuleFlow.equalsIgnoreCase( "true" ) ) {
portedReader = portToCurrentVersion( reader );
} else {
portedReader = reader;
}
Process process = xmlReader.read(portedReader);
buildProcess( process, url );
} finally {
Thread.currentThread().setContextClassLoader( oldLoader );
}
reader.close();
}
private Reader portToCurrentVersion(final Reader reader) throws Exception {
String xml = convertReaderToString( reader );
if ( xml.indexOf( "<org.drools.ruleflow.core.impl.RuleFlowProcessImpl " ) >= 0 ) {
// Not a current version convert it.
String version5XML = XSLTransformation.transform( XSL_FROM_4_TO_5,
xml,
null );
// Add the namespace attribute to the process element as it is not added by the XSL transformation.
version5XML = version5XML.replaceAll( "<process ",
PROCESS_ELEMENT_WITH_NAMESPACE );
return new StringReader( version5XML );
}
return reader;
}
private String convertReaderToString(Reader reader) throws IOException {
final StringBuffer text = new StringBuffer();
final char[] buf = new char[1024];
int len = 0;
while ( (len = reader.read( buf )) >= 0 ) {
text.append( buf,
0,
len );
}
return text.toString();
}
private String generateRules(final Process process) {
StringBuilder builder = new StringBuilder();
if ( process instanceof WorkflowProcessImpl ) {
WorkflowProcessImpl ruleFlow = (WorkflowProcessImpl) process;
builder.append( "package " + ruleFlow.getPackageName() + "\n" );
List<String> imports = ruleFlow.getImports();
if ( imports != null ) {
for ( String importString: imports ) {
builder.append( "import " + importString + ";\n" );
}
}
List<String> functionImports = ruleFlow.getFunctionImports();
if ( functionImports != null ) {
for ( String importString: functionImports ) {
builder.append( "import function " + importString + ";\n" );
}
}
Map<String, String> globals = ruleFlow.getGlobals();
if ( globals != null ) {
for ( Map.Entry<String, String> entry: globals.entrySet()) {
builder.append( "global " + entry.getValue() + " " + entry.getKey() + ";\n" );
}
}
Node[] nodes = ruleFlow.getNodes();
for ( int i = 0; i < nodes.length; i++ ) {
if ( nodes[i] instanceof Split ) {
Split split = (Split) nodes[i];
if ( split.getType() == Split.TYPE_XOR || split.getType() == Split.TYPE_OR ) {
for ( Connection connection : split.getDefaultOutgoingConnections() ) {
Constraint constraint = split.getConstraint( connection );
if ( "rule".equals( constraint.getType() ) ) {
builder.append( createSplitRule( process,
connection,
split.getConstraint( connection ).getConstraint() ) );
}
}
}
} else if ( nodes[i] instanceof MilestoneNode ) {
MilestoneNode milestone = (MilestoneNode) nodes[i];
builder.append( createMilestoneRule( process,
milestone ) );
} else if ( nodes[i] instanceof StartNode ) {
StartNode startNode = (StartNode) nodes[i];
List<Trigger> triggers = startNode.getTriggers();
if ( triggers != null ) {
for ( Trigger trigger : triggers ) {
if ( trigger instanceof ConstraintTrigger ) {
builder.append( createStartConstraintRule( process,
(ConstraintTrigger) trigger ) );
}
}
}
}
}
}
return builder.toString();
}
private String createSplitRule(Process process,
Connection connection,
String constraint) {
return "rule \"RuleFlow-Split-" + process.getId() + "-" + connection.getFrom().getId() + "-" + connection.getTo().getId() + "\" \n" + " ruleflow-group \"DROOLS_SYSTEM\" \n" + " when \n" + " " + constraint + "\n" + " then \n"
+ "end \n\n";
}
private String createMilestoneRule(Process process,
MilestoneNode milestone) {
return "rule \"RuleFlow-Milestone-" + process.getId() + "-" + milestone.getId() + "\" \n" + " ruleflow-group \"DROOLS_SYSTEM\" \n" + " when \n" + " " + milestone.getConstraint() + "\n" + " then \n" + "end \n\n";
}
private String createStartConstraintRule(Process process,
ConstraintTrigger trigger) {
String result = "rule \"RuleFlow-Start-" + process.getId() + "\" \n" + " when\n" + " " + trigger.getConstraint() + "\n" + " then\n";
Map<String, String> inMappings = trigger.getInMappings();
if ( inMappings != null && !inMappings.isEmpty() ) {
result += " java.util.Map params = new java.util.HashMap();\n";
for ( Map.Entry<String, String> entry : inMappings.entrySet() ) {
result += " params.put(\"" + entry.getKey() + "\", " + entry.getValue() + ");\n";
}
result += " drools.getWorkingMemory().startProcess(\"" + process.getId() + "\", params);\n" + "end\n\n";
} else {
result += " drools.getWorkingMemory().startProcess(\"" + process.getId() + "\");\n" + "end\n\n";
}
return result;
}
private static class XSLTransformation {
public static String transform(String stylesheet,
String srcXMLString,
HashMap<String, String> params) throws Exception {
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult( writer );
Source src = new StreamSource( new StringReader( srcXMLString ) );
transform( stylesheet,
src,
result,
params );
return writer.toString();
}
public static void transform(String stylesheet,
Source src,
Result res,
HashMap<String, String> params) throws Exception {
Transformer transformer = getTransformer( stylesheet );
transformer.clearParameters();
if ( params != null && params.size() > 0 ) {
Iterator<String> itKeys = params.keySet().iterator();
while ( itKeys.hasNext() ) {
String key = itKeys.next();
String value = params.get( key );
transformer.setParameter( key,
value );
}
}
transformer.transform( src,
res );
}
private static Transformer getTransformer(String stylesheet) throws Exception {
Transformer transformer = null;
InputStream xslStream = null;
try {
InputStream in = XSLTransformation.class.getResourceAsStream( stylesheet );
xslStream = new BufferedInputStream( in );
StreamSource src = new StreamSource( xslStream );
src.setSystemId( stylesheet );
transformer = TransformerFactory.newInstance().newTransformer( src );
} finally {
if ( xslStream != null ) xslStream.close();
}
return transformer;
}
}
}