/* * 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. */ package org.drools.compiler; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.FactoryConfigurationError; import org.drools.RuntimeDroolsException; import org.drools.compiler.Dialect; import org.drools.compiler.DialectCompiletimeRegistry; import org.drools.compiler.DroolsError; import org.drools.compiler.DroolsParserException; import org.drools.compiler.PackageBuilder; import org.drools.compiler.PackageBuilderConfiguration; import org.drools.compiler.PackageRegistry; import org.drools.compiler.ParserError; import org.drools.compiler.ProcessBuilder; import org.drools.compiler.ProcessLoadError; import org.drools.compiler.xml.ProcessSemanticModule; import org.drools.compiler.xml.XmlProcessReader; import org.drools.compiler.xml.processes.RuleFlowMigrator; 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.io.Resource; import org.drools.io.internal.InternalResource; import org.drools.lang.descr.ActionDescr; import org.drools.lang.descr.ProcessDescr; import org.drools.process.builder.ProcessBuildContext; import org.drools.process.builder.ProcessNodeBuilder; import org.drools.process.builder.ProcessNodeBuilderRegistry; import org.drools.process.builder.dialect.ProcessDialect; import org.drools.process.builder.dialect.ProcessDialectRegistry; 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.impl.ProcessImpl; import org.drools.process.core.validation.ProcessValidationError; import org.drools.process.core.validation.ProcessValidator; import org.drools.rule.builder.dialect.java.JavaDialect; 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.ConnectionRef; 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.StateNode; import org.drools.workflow.core.node.Trigger; /** * 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 ProcessBuilderImpl implements ProcessBuilder { private PackageBuilder packageBuilder; private final List<DroolsError> errors = new ArrayList<DroolsError>(); private Map<String, ProcessValidator> processValidators = new HashMap<String, ProcessValidator>(); public ProcessBuilderImpl(PackageBuilder packageBuilder) { this.packageBuilder = packageBuilder; configurePackageBuilder(packageBuilder); this.processValidators.put(RuleFlowProcess.RULEFLOW_TYPE, RuleFlowProcessValidator.getInstance()); } public void configurePackageBuilder(PackageBuilder packageBuilder) { PackageBuilderConfiguration conf = packageBuilder .getPackageBuilderConfiguration(); if (conf.getSemanticModules().getSemanticModule( ProcessSemanticModule.URI) == null) { conf.addSemanticModule(new ProcessSemanticModule()); } } public List<DroolsError> getErrors() { return errors; } public void buildProcess(final Process process, Resource resource) { if (resource != null && ((InternalResource) resource).hasURL()) { ((org.drools.process.core.Process) process).setResource(resource); } 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); // System.out.println(rules); 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); } PackageRegistry pkgRegistry = this.packageBuilder .getPackageRegistry(process.getPackageName()); if (pkgRegistry != null) { org.drools.rule.Package p = pkgRegistry.getPackage(); if (p != null) { ProcessDescr processDescr = new ProcessDescr(); processDescr.setName(process.getPackageName()); processDescr.setResource(resource); DialectCompiletimeRegistry dialectRegistry = pkgRegistry .getDialectCompiletimeRegistry(); Dialect dialect = dialectRegistry.getDialect("java"); dialect.init(processDescr); ProcessBuildContext buildContext = new ProcessBuildContext( this.packageBuilder, p, process, processDescr, dialectRegistry, dialect); buildContexts((ContextContainer) process, buildContext); if (process instanceof WorkflowProcess) { buildNodes((WorkflowProcess) process, buildContext); } p.addProcess(process); pkgRegistry.compileAll(); pkgRegistry.getDialectRuntimeRegistry().onBeforeExecute(); } } else { // invalid package registry..there is an issue with the package // id of the process throw new RuntimeDroolsException("invalid package id"); } } } 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()); ProcessDialect dialect = ProcessDialectRegistry .getDialect(action.getDialect()); dialect.getActionBuilder().build(buildContext, action, actionDescr, (ProcessImpl) buildContext.getProcess()); } } } } } @SuppressWarnings("unchecked") public void buildNodes(WorkflowProcess process, ProcessBuildContext context) { processNodes(process.getNodes(), process, context.getProcessDescr(), context); if (!context.getErrors().isEmpty()) { this.errors.addAll(context.getErrors()); } ProcessDialectRegistry.getDialect(JavaDialect.ID).addProcess(context); } private void processNodes(Node[] nodes, Process process, ProcessDescr processDescr, ProcessBuildContext context) { for (Node node : nodes) { ProcessNodeBuilder builder = ProcessNodeBuilderRegistry.INSTANCE .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); } if (node instanceof ContextContainer) { buildContexts((ContextContainer) node, context); } } } public List<DroolsError> addProcessFromXml(final Resource resource) throws IOException { Reader reader = resource.getReader(); 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); if (process != null) { // it is possible an xml file could not be parsed, so we need to // stop null pointers buildProcess(process, resource); } else { // @TODO could we maybe add something a bit more informative // about what is wrong with the XML ? this.errors.add(new ProcessLoadError("unable to parse xml", null)); } } catch (FactoryConfigurationError e1) { this.errors.add(new ProcessLoadError("FactoryConfigurationError ", e1.getException())); } catch (Exception e2) { e2.printStackTrace(); this.errors.add(new ProcessLoadError("unable to parse xml", e2)); } finally { Thread.currentThread().setContextClassLoader(oldLoader); } reader.close(); return this.errors; } /************************************************************************* * Converts a drools version 4 .rf or .rfm ruleflow to a version 5 .rf. * Version 5 .rf ruleflows are allowed, but are not migrated. * * @param reader * containing any drools 4 .rf or .rfm ruleflow, or a version 5 * .rf * @return reader containing the input reader in the latest (5) .rf format * @throws Exception ************************************************************************/ private Reader portToCurrentVersion(final Reader reader) throws Exception { // Migrate v4 ruleflows to v5 String xml = RuleFlowMigrator.convertReaderToString(reader); if (RuleFlowMigrator.needToMigrateRFM(xml)) { // Not a current version RFM convert it. xml = RuleFlowMigrator.portRFMToCurrentVersion(xml); } else if (RuleFlowMigrator.needToMigrateRF(xml)) { // Not a current version RF convert it. xml = RuleFlowMigrator.portRFMToCurrentVersion(xml); } // // Note that we have also return any input v5 ruleflow as // a StringReader since the act of checking it using // convertReaderToString will have read the reader making it // appear empty if read later. As reset is not guaranteed on // all Reader implementation, it is safest to convert the v5 // ruleflow string representation to a StringReader as well. // return new StringReader(xml); } private String generateRules(final Process process) { StringBuffer builder = new StringBuffer(); 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(); generateRules(nodes, process, builder); } return builder.toString(); } private void generateRules(Node[] nodes, Process process, StringBuffer builder) { 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 StateNode) { StateNode state = (StateNode) nodes[i]; builder.append(createStateRules(process, state)); } 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)); } } } } else if (nodes[i] instanceof NodeContainer) { generateRules(((NodeContainer) nodes[i]).getNodes(), process, builder); } } } private String createSplitRule(Process process, Connection connection, String constraint) { return "rule \"RuleFlow-Split-" + process.getId() + "-" + ((org.drools.workflow.core.Node) connection.getFrom()) .getUniqueId() + "-" + ((org.drools.workflow.core.Node) connection.getTo()) .getUniqueId() + "-" + connection.getToType() + "\" \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.getUniqueId() + "\" \n" + " ruleflow-group \"DROOLS_SYSTEM\" \n" + " when \n" + " " + milestone.getConstraint() + "\n" + " then \n" + "end \n\n"; } private String createStateRule(Process process, StateNode state, ConnectionRef key, Constraint constraint) { if (constraint.getConstraint() == null || constraint.getConstraint().trim().length() == 0) { return ""; } else { return "rule \"RuleFlowStateNode-" + process.getId() + "-" + state.getUniqueId() + "-" + key.getNodeId() + "-" + key.getToType() + "\" \n" + " ruleflow-group \"DROOLS_SYSTEM\" \n" + " when \n" + " " + state.getConstraints().get(key).getConstraint() + "\n" + " then \n" + "end \n\n"; } } private String createStateRules(Process process, StateNode state) { String result = ""; for (Map.Entry<ConnectionRef, Constraint> entry : state .getConstraints().entrySet()) { result += createStateRule(process, state, entry.getKey(), entry.getValue()); } return result; } private String createStartConstraintRule(Process process, ConstraintTrigger trigger) { String result = "rule \"RuleFlow-Start-" + process.getId() + "\" \n" + (trigger.getHeader() == null ? "" : " " + trigger.getHeader() + " \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 += " kcontext.getKnowledgeRuntime().startProcess(\"" + process.getId() + "\", params);\n" + "end\n\n"; } else { result += " kcontext.getKnowledgeRuntime().startProcess(\"" + process.getId() + "\");\n" + "end\n\n"; } return result; } }