0001: /*--
0002:
0003: Copyright (C) 2002-2005 Adrian Price.
0004: All rights reserved.
0005:
0006: Redistribution and use in source and binary forms, with or without
0007: modification, are permitted provided that the following conditions
0008: are met:
0009:
0010: 1. Redistributions of source code must retain the above copyright
0011: notice, this list of conditions, and the following disclaimer.
0012:
0013: 2. Redistributions in binary form must reproduce the above copyright
0014: notice, this list of conditions, and the disclaimer that follows
0015: these conditions in the documentation and/or other materials
0016: provided with the distribution.
0017:
0018: 3. The names "OBE" and "Open Business Engine" must not be used to
0019: endorse or promote products derived from this software without prior
0020: written permission. For written permission, please contact
0021: adrianprice@sourceforge.net.
0022:
0023: 4. Products derived from this software may not be called "OBE" or
0024: "Open Business Engine", nor may "OBE" or "Open Business Engine"
0025: appear in their name, without prior written permission from
0026: Adrian Price (adrianprice@users.sourceforge.net).
0027:
0028: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0029: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0030: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0031: DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
0032: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0033: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
0034: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0035: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
0036: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
0037: IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0038: POSSIBILITY OF SUCH DAMAGE.
0039:
0040: For more information on OBE, please see
0041: <http://obe.sourceforge.net/>.
0042:
0043: */
0044:
0045: package org.obe.engine;
0046:
0047: import org.apache.commons.logging.Log;
0048: import org.apache.commons.logging.LogFactory;
0049: import org.obe.client.api.model.MIMETypes;
0050: import org.obe.client.api.model.ProcessInstanceAttributes;
0051: import org.obe.client.api.repository.*;
0052: import org.obe.client.api.tool.Parameter;
0053: import org.obe.client.api.tool.ToolAgent;
0054: import org.obe.client.api.tool.ToolInvocation;
0055: import org.obe.client.api.xpdl.InvalidPackageException;
0056: import org.obe.client.api.xpdl.PackageValidator;
0057: import org.obe.runtime.tool.NoImplementationToolAgent;
0058: import org.obe.spi.evaluator.Evaluator;
0059: import org.obe.spi.evaluator.EvaluatorException;
0060: import org.obe.spi.event.ApplicationEvent;
0061: import org.obe.spi.model.*;
0062: import org.obe.spi.runtime.CompletionStrategy;
0063: import org.obe.spi.service.*;
0064: import org.obe.util.ClassUtils;
0065: import org.obe.util.WorkflowUtilities;
0066: import org.obe.xpdl.model.activity.*;
0067: import org.obe.xpdl.model.application.Application;
0068: import org.obe.xpdl.model.data.*;
0069: import org.obe.xpdl.model.ext.AssignmentStrategyDef;
0070: import org.obe.xpdl.model.ext.ToolMode;
0071: import org.obe.xpdl.model.misc.*;
0072: import org.obe.xpdl.model.participant.Participant;
0073: import org.obe.xpdl.model.pkg.XPDLPackage;
0074: import org.obe.xpdl.model.workflow.ProcessHeader;
0075: import org.obe.xpdl.model.workflow.WorkflowProcess;
0076: import org.wfmc.wapi.*;
0077:
0078: import java.beans.PropertyDescriptor;
0079: import java.util.*;
0080:
0081: /**
0082: * Utility class to support the engine run-time.
0083: *
0084: * @author Anthony Eden
0085: * @author Adrian Price
0086: */
0087: public class WorkflowEngineUtilities {
0088: private static final Log _logger = LogFactory
0089: .getLog(WorkflowEngineUtilities.class);
0090: private static final Comparator _workflowValidityComparator;
0091: private static final Map _propDescs = new HashMap();
0092: private static final ToolInvocation[] EMPTY_TOOL_INVOCATIONS = {};
0093:
0094: public static void validatePackage(XPDLPackage pkg)
0095: throws InvalidPackageException {
0096:
0097: // Unless the package is under development, validate it.
0098: RedefinableHeader hdr = pkg.getRedefinableHeader();
0099: if (hdr == null
0100: || hdr.getPublicationStatus() != PublicationStatus.UNDER_REVISION) {
0101:
0102: PackageValidator validator = new PackageValidator();
0103: validator.validate(pkg, true);
0104: }
0105: }
0106:
0107: static {
0108: _workflowValidityComparator = new Comparator() {
0109: // ORDER BY validFrom DESC, validTo DESC, created DESC
0110: public int compare(Object o1, Object o2) {
0111: ProcessHeader ph1 = ((WorkflowProcess) o1)
0112: .getProcessHeader();
0113: ProcessHeader ph2 = ((WorkflowProcess) o2)
0114: .getProcessHeader();
0115: Date d1 = ph1.getValidFrom();
0116: Date d2 = ph2.getValidFrom();
0117: int order = compareDates(d1, d2);
0118: if (order == 0) {
0119: d1 = ph1.getValidTo();
0120: d2 = ph2.getValidTo();
0121: order = compareDates(d1, d2);
0122: if (order == 0) {
0123: d1 = ph1.getCreated();
0124: d2 = ph2.getCreated();
0125: order = compareDates(d1, d2);
0126: }
0127: }
0128: return order;
0129: }
0130:
0131: private int compareDates(Date d1, Date d2) {
0132: int order;
0133: if (d1 == null && d2 == null) {
0134: order = 0;
0135: } else if (d1 == null) {
0136: order = -1;
0137: } else if (d2 == null) {
0138: order = +1;
0139: } else {
0140: order = d1.compareTo(d2);
0141: }
0142: return order;
0143: }
0144: };
0145: }
0146:
0147: public static class AttributeDescription {
0148: public final String id;
0149: public final String name;
0150: public final String description;
0151: public final DataType dataType;
0152:
0153: private AttributeDescription(String id, String name,
0154: String description, DataType dataType) {
0155:
0156: this .id = id;
0157: this .name = name;
0158: this .description = description;
0159: this .dataType = dataType;
0160: }
0161: }
0162:
0163: /**
0164: * Maps activity state to process state. When an activity instance with a
0165: * SubFlow implementation transitions to the <code>newState</code> state,
0166: * any child process belonging to that activity instance for which it would
0167: * be a legal transition, should transition to the state returned by this
0168: * method.
0169: *
0170: * @param newState The new process instance state.
0171: * @return The required activity instance state.
0172: */
0173: public static int activityStateToProcessState(int newState) {
0174: // The states actually have the same ordinals.
0175: return newState;
0176: }
0177:
0178: /**
0179: * Maps activity state to work item state. When an activity instance
0180: * transitions to the <code>newState</code> state, work items belonging
0181: * to that activity instance and for which it would be a legal transition,
0182: * should transition to the state returned by this method.
0183: *
0184: * @param newState The new activity state.
0185: * @return The required work item state.
0186: */
0187: public static int activityStateToWorkItemState(int newState) {
0188: // The states actually have the same ordinals.
0189: return newState;
0190: }
0191:
0192: /**
0193: * Applies the results of a tool invocation to a process instance. INOUT-
0194: * and OUT-type process variables are updated with the values provided by
0195: * the application or procedure. IN-type parameters are ignored.
0196: *
0197: * @param parms Parameter values returned by the tool invocation.
0198: * @param processInstance The process instance to which the values are to be
0199: * applied.
0200: * @param ignoreNullSchemaTypeValues <code>true</code> to ignore (i.e.,
0201: * not store) null values for attributes whose type is defined by an
0202: * {@link SchemaType XML schema} and which does not map to a
0203: * {@link BasicType}. The reason for this is to enable worklist handler
0204: * clients to support optional file uploads to such attributes.
0205: * @throws RepositoryException If the value could not be stored in the
0206: * attribute.
0207: * @throws AttributeReadOnlyException If the attribute in question is read-
0208: * only (applies to most system attributes).
0209: */
0210: public static void applyResults(Parameter[] parms,
0211: ProcessInstance processInstance,
0212: boolean ignoreNullSchemaTypeValues)
0213: throws RepositoryException, AttributeReadOnlyException {
0214:
0215: // Copy the return values into the workflow data.
0216: for (int i = 0; i < parms.length; i++) {
0217: Parameter parm = parms[i];
0218:
0219: // Skip input parameters.
0220: if (parm.getMode() == ParameterMode.IN)
0221: continue;
0222:
0223: // Do not clear schema type attributes if an application
0224: // returned null - it just means the user didn't upload a file.
0225: if (parm.getDataType().getType().getImpliedType().value() == Type.SCHEMA_TYPE
0226: && parm.getValue() == null
0227: && ignoreNullSchemaTypeValues) {
0228:
0229: if (_logger.isDebugEnabled()) {
0230: _logger
0231: .debug("Ignoring null value for schema-typed attribute '"
0232: + parm.getId() + '\'');
0233: }
0234:
0235: continue;
0236: }
0237:
0238: if (_logger.isDebugEnabled()) {
0239: _logger.debug("Updating process attribute '"
0240: + parm.getId() + "' with value: "
0241: + parm.getValue());
0242: }
0243:
0244: processInstance.getAttributeInstance(parm.getId())
0245: .setValue(Type.DEFAULT_TYPE, parm.getValue());
0246: }
0247: }
0248:
0249: /**
0250: * Applies data from an application event to a process instance.
0251: *
0252: * @param actualParms The actual parameters passed by the event
0253: * subscription.
0254: * @param processInstance The process instance to update.
0255: * @param event The application event.
0256: * @param broker The event broker to use.
0257: * @throws WMWorkflowException If the event type could not be found or if
0258: * an attribute could not be updated.
0259: */
0260: public static void applyResults(ActualParameter[] actualParms,
0261: ProcessInstance processInstance, ApplicationEvent event,
0262: ApplicationEventBroker broker) throws WMWorkflowException {
0263:
0264: try {
0265: // Extract the parameter values from the inbound event.
0266: EventTypeMetaData metaData = broker
0267: .findEventTypeMetaData(event.getEventType());
0268: FormalParameter[] formalParms = metaData
0269: .getFormalParameter();
0270: Object[] eventKeys = event.getKeys();
0271: Map attrs = processInstance.getAttributeInstances();
0272: for (int i = 0, m = formalParms.length, n = actualParms.length; i < n; i++) {
0273:
0274: // varargs pattern: reuse the last formal parameter as necessary.
0275: FormalParameter formalParm = formalParms[i < m ? i
0276: : m - 1];
0277: ActualParameter actualParm = actualParms[i];
0278: ParameterMode mode = formalParm.getMode();
0279: if (mode == ParameterMode.INOUT
0280: || mode == ParameterMode.OUT) {
0281: String attrName = actualParm.getText();
0282: AttributeInstance attr = (AttributeInstance) attrs
0283: .get(attrName);
0284: if (attr == null)
0285: throw new WMInvalidAttributeException(attrName);
0286: attr.setValue(Type.DEFAULT_TYPE, eventKeys[i]);
0287: }
0288: }
0289: } catch (RepositoryException e) {
0290: throw new WMWorkflowException(e);
0291: } catch (AttributeReadOnlyException e) {
0292: throw new WMWorkflowException(e);
0293: }
0294: }
0295:
0296: /**
0297: * Calculates the expiration date for an application event.
0298: *
0299: * @param expiration The duration for which the event will be valid.
0300: * @param calendar The name of the business calendar to use.
0301: * @param calendarFactory The calendar factory to use.
0302: * @return The expiration date.
0303: * @throws RepositoryException If the calendar could not be found.
0304: */
0305: public static Date calculateExpiryDate(Duration expiration,
0306: String calendar, CalendarFactory calendarFactory)
0307: throws RepositoryException {
0308:
0309: return calendarFactory.findCalendar(calendar).add(new Date(),
0310: expiration.getUnit(), expiration.getValue(), null);
0311: }
0312:
0313: /**
0314: * Calculates the deadline date for an activity.
0315: *
0316: * @param activity
0317: * @param deadline
0318: * @param startedDate
0319: * @param calendarFactory
0320: * @return
0321: * @throws RepositoryException
0322: */
0323: public static Date calculateDeadlineDate(Activity activity,
0324: Deadline deadline, Date startedDate,
0325: CalendarFactory calendarFactory) throws RepositoryException {
0326:
0327: try {
0328: return dateAdd(activity, null, null, startedDate, deadline
0329: .getDuration(), calendarFactory);
0330: } catch (WMInvalidTargetUserException e) {
0331: // Can't happen since we're not passing a performer.
0332: throw new RepositoryException(e);
0333: }
0334: }
0335:
0336: /**
0337: * Calculates the due date for a workflow.
0338: *
0339: * @param workflow
0340: * @param startedDate
0341: * @param calendarFactory
0342: * @return The due date, or null if the workflow does not specify a limit.
0343: */
0344: public static Date calculateDueDate(WorkflowProcess workflow,
0345: Date startedDate, CalendarFactory calendarFactory)
0346: throws RepositoryException {
0347:
0348: return dateAdd(workflow, startedDate, workflow
0349: .getProcessHeader().getLimit(), calendarFactory);
0350: }
0351:
0352: /**
0353: * Calculates the due date for an activity.
0354: *
0355: * @param activity
0356: * @param performer
0357: * @param user
0358: * @param startedDate
0359: * @param calendarFactory
0360: * @return The due date, or null if the activity does not specify a limit.
0361: */
0362: public static Date calculateDueDate(Activity activity,
0363: String performer, String user, Date startedDate,
0364: CalendarFactory calendarFactory)
0365: throws RepositoryException, WMInvalidTargetUserException {
0366:
0367: return dateAdd(activity, performer, user, startedDate, activity
0368: .getLimit(), calendarFactory);
0369: }
0370:
0371: /**
0372: * Calculates the target date for a workflow.
0373: *
0374: * @param workflow
0375: * @param startedDate
0376: * @param calendarFactory
0377: * @return The target date, or null if the workflow does not specify a
0378: * duration.
0379: */
0380: public static Date calculateTargetDate(WorkflowProcess workflow,
0381: Date startedDate, CalendarFactory calendarFactory)
0382: throws RepositoryException {
0383:
0384: TimeEstimation te = workflow.getProcessHeader()
0385: .getTimeEstimation();
0386: Duration duration = te == null ? null : te.getDuration();
0387: return dateAdd(workflow, startedDate, duration, calendarFactory);
0388: }
0389:
0390: /**
0391: * Calculates the target date for an activity.
0392: *
0393: * @param activity
0394: * @param performer
0395: * @param user
0396: * @param startedDate
0397: * @param calendarFactory
0398: * @return The due date, or null if the activity does not specify a limit.
0399: */
0400: public static Date calculateTargetDate(Activity activity,
0401: String performer, String user, Date startedDate,
0402: CalendarFactory calendarFactory)
0403: throws RepositoryException, WMInvalidTargetUserException {
0404:
0405: Duration duration = null;
0406: SimulationInformation si = activity.getSimulationInformation();
0407: if (si != null) {
0408: TimeEstimation te = si.getTimeEstimation();
0409: if (te != null)
0410: duration = te.getDuration();
0411: }
0412: return dateAdd(activity, performer, user, startedDate,
0413: duration, calendarFactory);
0414: }
0415:
0416: /**
0417: * Synthesizes a conditional expression from a set of formal and actual
0418: * parameter specifications. The key/value pairs for all parameters are
0419: * ANDed together in the resulting conditional expression.
0420: *
0421: * @param metaData The event definition contains the formal parameters which
0422: * define the keys referenced in the resulting conditional expression.
0423: * @param actualParms The actual parameters define the values to match
0424: * against their corresponding keys.
0425: * @param ctx The workflow context in which to evaluate the actual
0426: * parameters.
0427: * @return A conditional expression in the specified scripting language.
0428: * @throws WMWorkflowException
0429: */
0430: public static String createEventCondition(
0431: EventTypeMetaData metaData, ActualParameter[] actualParms,
0432: EngineContext ctx) throws WMWorkflowException {
0433:
0434: StringBuffer sb = new StringBuffer();
0435: try {
0436: // Find the event definition.
0437: ServiceManager svcMgr = ctx.getServiceManager();
0438:
0439: // TODO: support other scripting languages in addition to XPath.
0440: String scriptType = metaData.getScriptType();
0441: if (!scriptType.equals(MIMETypes.XPATH)) {
0442: throw new IllegalArgumentException(
0443: "Unsupported condition scripting language: "
0444: + scriptType);
0445: }
0446:
0447: WorkflowProcess workflow = ctx.getWorkflow();
0448: ProcessInstance processInstance = ctx.getProcessInstance();
0449: Map attrs = processInstance == null ? null
0450: : processInstance.getAttributeInstances();
0451: Evaluator evaluator = null;
0452:
0453: // Iterate through the parameters and build the expression.
0454: EventParameter[] formalParms = metaData
0455: .getFormalParameter();
0456: for (int i = 0, n = formalParms.length; i < n; i++) {
0457: EventParameter fp = formalParms[i];
0458: ActualParameter ap = actualParms[i];
0459: String text = ap.getText();
0460: Object value = null;
0461: ParameterMode mode = fp.getMode();
0462: if (mode == null)
0463: mode = ParameterMode.IN;
0464: switch (mode.value()) {
0465: case ParameterMode.IN_INT:
0466: // IN parameter: text is an expression to evaluate.
0467: if (text != null && text.length() > 0) {
0468: if (evaluator == null) {
0469: evaluator = getEvaluator(workflow
0470: .getPackage(), svcMgr);
0471: }
0472: value = evaluator.evaluateExpression(text, ctx);
0473: }
0474: break;
0475: case ParameterMode.INOUT_INT:
0476: // INOUT parameter: text is the name of the attr to pass.
0477: if (attrs != null) {
0478: AttributeInstance attributeInstance = (AttributeInstance) attrs
0479: .get(text);
0480: if (attributeInstance == null)
0481: throw new WMInvalidAttributeException(text);
0482: value = attributeInstance.getValue();
0483: }
0484: break;
0485: case ParameterMode.OUT_INT:
0486: return sb.toString();
0487: }
0488: // Test the key value expression for equality with the supplied
0489: // actual parameter value.
0490: sb.append(fp.getKeyExpression());
0491: sb.append(" = ");
0492: Type dataType = fp.getDataType().getType();
0493: if (dataType.equals(BasicType.STRING)) {
0494: sb.append('\'');
0495: sb.append(value);
0496: sb.append('\'');
0497: } else if (dataType.equals(BasicType.BOOLEAN)) {
0498: sb.append(value);
0499: sb.append("()");
0500: } else {
0501: sb.append(value);
0502: }
0503: if (i < n - 1)
0504: sb.append(" and ");
0505: }
0506: } catch (EvaluatorException e) {
0507: throw new WMWorkflowException(e);
0508: } catch (RepositoryException e) {
0509: throw new WMWorkflowException(e);
0510: }
0511: return sb.toString();
0512: }
0513:
0514: /**
0515: * Creates the keys for a key-based event subscription.
0516: *
0517: * @param metaData Event type meta data.
0518: * @param actualParms The actual parameters from the
0519: * <code><obe:Event></code> element in the workflow process.
0520: * @param ctx The engine context.
0521: * @return Event keys for the IN- and INOUT-type formal event parameters.
0522: * @throws WMWorkflowException
0523: */
0524: public static Object[] createEventKeys(EventTypeMetaData metaData,
0525: ActualParameter[] actualParms, EngineContext ctx)
0526: throws WMWorkflowException {
0527:
0528: EventParameter[] formalParms = metaData.getFormalParameter();
0529: List eventKeys = new ArrayList(actualParms.length);
0530: try {
0531: ServiceManager svcMgr = ctx.getServiceManager();
0532: DataConverter dataConverter = svcMgr.getDataConverter();
0533: WorkflowProcess workflow = ctx.getWorkflow();
0534: ProcessInstance processInstance = ctx.getProcessInstance();
0535: Map attrs = processInstance == null ? null
0536: : processInstance.getAttributeInstances();
0537: Evaluator evaluator = null;
0538: for (int i = 0, m = formalParms.length, n = actualParms.length; i < n; i++) {
0539:
0540: // varargs pattern: reuse the last formal parameter as necessary.
0541: FormalParameter formalParm = formalParms[i < m ? i
0542: : m - 1];
0543: ActualParameter actualParm = actualParms[i];
0544: String text = actualParm.getText();
0545: Object value = null;
0546: ParameterMode mode = formalParm.getMode();
0547: if (mode == null)
0548: mode = ParameterMode.IN;
0549: switch (mode.value()) {
0550: case ParameterMode.IN_INT:
0551: // IN parameter: text is an expression to evaluate.
0552: if (text != null && text.length() > 0) {
0553: if (evaluator == null) {
0554: evaluator = getEvaluator(workflow
0555: .getPackage(), svcMgr);
0556: }
0557: value = evaluator.evaluateExpression(text, ctx);
0558: } else {
0559: value = null;
0560: }
0561: break;
0562: case ParameterMode.INOUT_INT:
0563: // INOUT parameter: text is the name of the attr to pass.
0564: if (attrs != null) {
0565: AttributeInstance attributeInstance = (AttributeInstance) attrs
0566: .get(text);
0567: if (attributeInstance == null)
0568: throw new WMInvalidAttributeException(text);
0569: value = attributeInstance.getValue();
0570: }
0571: break;
0572: case ParameterMode.OUT_INT:
0573: // Event parameters must declare OUT parameters last.
0574: return eventKeys.toArray();
0575: }
0576:
0577: // Ensure that the parameter value is of the required class.
0578: // N.B. If the FormalParameters came from an external reference
0579: // (e.g., defined in a repository), data types may not be
0580: // specified, in which case just use the actual data type.
0581: DataType formalDataType = formalParm.getDataType();
0582: if (formalDataType != null) {
0583: value = dataConverter.convertValue(value, DataTypes
0584: .classForDataType(formalDataType));
0585: }
0586:
0587: eventKeys.add(value);
0588: }
0589: } catch (EvaluatorException e) {
0590: throw new WMWorkflowException(e);
0591: } catch (RepositoryException e) {
0592: throw new WMWorkflowException(e);
0593: }
0594: return eventKeys.toArray();
0595: }
0596:
0597: public static Parameter[] createParameters(
0598: FormalParameter[] formalParms,
0599: ActualParameter[] actualParms, EngineContext ctx)
0600: throws WMWorkflowException {
0601:
0602: Parameter[] parms = new Parameter[actualParms.length];
0603: try {
0604: ServiceManager svcMgr = ctx.getServiceManager();
0605: DataConverter dataConverter = svcMgr.getDataConverter();
0606: WorkflowProcess workflow = ctx.getWorkflow();
0607: ProcessInstance processInstance = ctx.getProcessInstance();
0608: Map attrs = processInstance.getAttributeInstances();
0609: Evaluator evaluator = null;
0610: for (int i = 0, m = formalParms.length, n = actualParms.length; i < n; i++) {
0611:
0612: // varargs pattern: reuse the last formal parameter as necessary.
0613: FormalParameter formalParm = formalParms[i < m ? i
0614: : m - 1];
0615: ActualParameter actualParm = actualParms[i];
0616: AttributeDescription attrDesc;
0617: String id;
0618: String name;
0619: String description;
0620: DataType dataType;
0621: String text = actualParm.getText();
0622: Object value;
0623: ParameterMode mode = formalParm.getMode();
0624: if (mode == null)
0625: mode = ParameterMode.IN;
0626: switch (mode.value()) {
0627: case ParameterMode.IN_INT:
0628: // IN parameter: text is an expression to evaluate.
0629: id = null;
0630: name = null;
0631: description = null;
0632: dataType = null;
0633: if (text != null && text.length() > 0) {
0634: if (evaluator == null) {
0635: evaluator = getEvaluator(workflow
0636: .getPackage(), svcMgr);
0637: }
0638: value = evaluator.evaluateExpression(text, ctx);
0639: } else {
0640: value = null;
0641: }
0642: break;
0643: case ParameterMode.INOUT_INT:
0644: id = text;
0645: attrDesc = describeAttribute(workflow, id, true);
0646: name = attrDesc.name;
0647: description = attrDesc.description;
0648: dataType = attrDesc.dataType;
0649: // INOUT parameter: text is the name of the attr to pass.
0650: value = ((AttributeInstance) attrs.get(text))
0651: .getValue();
0652: break;
0653: case ParameterMode.OUT_INT:
0654: id = text;
0655: attrDesc = describeAttribute(workflow, id, true);
0656: name = attrDesc.name;
0657: description = attrDesc.description;
0658: dataType = attrDesc.dataType;
0659: // OUT parameter: no value to pass.
0660: value = null;
0661: break;
0662: default:
0663: continue; // (Can't actually happen)
0664: }
0665:
0666: // Ensure that the parameter value is of the required class.
0667: // N.B. If the FormalParameters came from an external reference
0668: // (e.g., defined in a repository), data types may not be
0669: // specified, in which case just use the actual data type.
0670: DataType formalDataType = formalParm.getDataType();
0671: Class formalClass = null;
0672: if (formalDataType != null) {
0673: formalClass = DataTypes
0674: .classForDataType(formalDataType);
0675: value = dataConverter.convertValue(value,
0676: formalClass);
0677: }
0678:
0679: parms[i] = new Parameter(formalParm, formalClass, id,
0680: name, description, dataType, mode, value);
0681: }
0682: } catch (EvaluatorException e) {
0683: throw new WMWorkflowException(e);
0684: } catch (RepositoryException e) {
0685: throw new WMWorkflowException(e);
0686: }
0687: return parms;
0688: }
0689:
0690: public static Parameter[] createParameters(Application toolDef,
0691: ActualParameter[] actualParms, EngineContext ctx)
0692: throws WMWorkflowException {
0693:
0694: FormalParameter[] formalParms;
0695:
0696: ExternalReference extRef = toolDef.getExternalReference();
0697: if (extRef == null) {
0698: formalParms = toolDef.getFormalParameter();
0699: } else {
0700: // If the tool is described by an external reference which we haven't
0701: // yet analysed, introspect the external reference to determine the
0702: // formal parameter types.
0703: try {
0704: ToolAgentMetaData metaData = ctx.getServiceManager()
0705: .getToolAgentFactory()
0706: .findToolMetaData(toolDef);
0707: formalParms = metaData.getFormalParameter();
0708: } catch (ObjectNotFoundException e) {
0709: throw new WMInvalidToolException(extRef.toString());
0710: } catch (RepositoryException e) {
0711: throw new WMWorkflowException(e);
0712: }
0713: }
0714:
0715: return createParameters(formalParms, actualParms, ctx);
0716: }
0717:
0718: private static Date dateAdd(WorkflowProcess workflow, Date date,
0719: Duration duration, CalendarFactory calendarFactory)
0720: throws RepositoryException {
0721:
0722: Date result = null;
0723: if (duration != null) {
0724: int amount = duration.getValue();
0725: if (amount != 0) {
0726: DurationUnit unit = duration.getUnit();
0727: if (unit == null) {
0728: unit = workflow.getProcessHeader()
0729: .getDurationUnit();
0730: if (unit == null)
0731: unit = DurationUnit.DEFAULT;
0732: }
0733: String calendar = findCalendar(workflow);
0734: result = calendarFactory.findCalendar(calendar).add(
0735: date, unit, amount, null);
0736: }
0737: }
0738: return result;
0739: }
0740:
0741: private static Date dateAdd(Activity activity, String performer,
0742: String user, Date date, Duration duration,
0743: CalendarFactory calendarFactory)
0744: throws RepositoryException, WMInvalidTargetUserException {
0745:
0746: Date result = null;
0747: if (duration != null) {
0748: int amount = duration.getValue();
0749: if (amount != 0) {
0750: DurationUnit unit = duration.getUnit();
0751: if (unit == null) {
0752: unit = activity.getWorkflowProcess()
0753: .getProcessHeader().getDurationUnit();
0754: if (unit == null)
0755: unit = DurationUnit.DEFAULT;
0756: }
0757: String calendar = findCalendar(performer, activity);
0758: result = calendarFactory.findCalendar(calendar).add(
0759: date, unit, amount, user);
0760: }
0761: }
0762: return result;
0763: }
0764:
0765: public static AssignmentStrategyDef findAssignmentStrategy(
0766: Activity activity) {
0767:
0768: AssignmentStrategyDef strategy = activity
0769: .getAssignmentStrategy();
0770: if (strategy == null) {
0771: WorkflowProcess workflow = activity.getWorkflowProcess();
0772: strategy = workflow.getAssignmentStrategy();
0773: if (strategy == null) {
0774: strategy = workflow.getPackage()
0775: .getAssignmentStrategy();
0776: if (strategy == null)
0777: strategy = AssignmentStrategyDef.DEFAULT;
0778: }
0779: }
0780: return strategy;
0781: }
0782:
0783: /**
0784: * Finds the name of the business calendar to use.
0785: *
0786: * @param workflow Workflow definition to search.
0787: * @return The business calendar name, or <code>"default"</code>
0788: * if a calendar was not specified by the WorkflowProcess or Package.
0789: */
0790: public static String findCalendar(WorkflowProcess workflow) {
0791: String calendar = workflow.getCalendar();
0792: if (calendar == null) {
0793: calendar = workflow.getPackage().getCalendar();
0794: if (calendar == null)
0795: calendar = "default";
0796: }
0797: return calendar;
0798: }
0799:
0800: /**
0801: * Finds the name of the business calendar to use.
0802: *
0803: * @param performer The ID of the performer for whom the computation is
0804: * being made. The corresponding participant can have its own calendar,
0805: * which will over-ride any activity-, workflow- or package-level calendar.
0806: * @param activity The activity definition to search.
0807: * @return The business calendar ID, or <code>"default"</code> if
0808: * a calendar was not specified by the Activity, WorkflowProcess or Package.
0809: */
0810: public static String findCalendar(String performer,
0811: Activity activity) throws WMInvalidTargetUserException {
0812:
0813: String calendar = null;
0814:
0815: WorkflowProcess workflow = activity.getWorkflowProcess();
0816: if (performer != null) {
0817: Participant participant = WorkflowUtilities
0818: .findParticipant(workflow, performer);
0819: calendar = participant.getCalendar();
0820: }
0821: if (calendar == null) {
0822: calendar = activity.getCalendar();
0823: if (calendar == null)
0824: calendar = findCalendar(workflow);
0825: }
0826:
0827: return calendar;
0828: }
0829:
0830: public static CompletionStrategy findCompletionStrategy(
0831: Activity activity, ServiceManager svcMgr)
0832: throws WMWorkflowException {
0833:
0834: try {
0835: String strategy = activity.getCompletionStrategy();
0836: if (strategy == null) {
0837: WorkflowProcess workflow = activity
0838: .getWorkflowProcess();
0839: strategy = workflow.getCompletionStrategy();
0840: if (strategy == null) {
0841: strategy = workflow.getPackage()
0842: .getCompletionStrategy();
0843: if (strategy == null)
0844: strategy = "ALL";
0845: }
0846: }
0847: return svcMgr.getCompletionStrategyFactory().findStrategy(
0848: strategy);
0849: } catch (RepositoryException e) {
0850: throw new WMWorkflowException(e);
0851: }
0852: }
0853:
0854: /**
0855: * Finds the definition of a process attribute.
0856: *
0857: * @param id The attribute ID.
0858: * @return The attribute definition.
0859: * @throws ObjectNotFoundException if the attribute could not be found.
0860: */
0861: public static DataField findDataField(WorkflowProcess workflow,
0862: String id) throws ObjectNotFoundException {
0863:
0864: DataField field = ProcessInstanceAttributes
0865: .findSystemDataField(id);
0866: if (field != null)
0867: return field;
0868: DataField[] dataFields = workflow.getDataField();
0869: for (int j = 0, n = dataFields.length; j < n; j++) {
0870: field = dataFields[j];
0871: if (field.getId().equals(id))
0872: return field;
0873: }
0874: dataFields = workflow.getPackage().getDataField();
0875: for (int j = 0, n = dataFields.length; j < n; j++) {
0876: field = dataFields[j];
0877: if (field.getId().equals(id))
0878: return field;
0879: }
0880: throw new ObjectNotFoundException(id);
0881: }
0882:
0883: /**
0884: * Describes a process attribute.
0885: *
0886: * @param workflow The workflow definition.
0887: * @param id The attribute ID.
0888: * @param includeSystemAttrs <code>true</code> to include system attributes.
0889: * @return The corresponding DataType.
0890: */
0891: public static DataType findDataType(WorkflowProcess workflow,
0892: String id, boolean includeSystemAttrs)
0893: throws WMInvalidAttributeException {
0894:
0895: return describeAttribute(workflow, id, includeSystemAttrs).dataType;
0896: }
0897:
0898: /**
0899: * Describes a process attribute.
0900: *
0901: * @param workflow The workflow definition.
0902: * @param id The attribute ID.
0903: * @param includeSystemAttrs <code>true</code> to include system attributes.
0904: * @return A description of the attribute.
0905: */
0906: public static AttributeDescription describeAttribute(
0907: WorkflowProcess workflow, String id,
0908: boolean includeSystemAttrs)
0909: throws WMInvalidAttributeException {
0910:
0911: String name = null;
0912: String description = null;
0913: DataField dataField = includeSystemAttrs ? ProcessInstanceAttributes
0914: .findSystemDataField(id)
0915: : null;
0916: if (dataField == null) {
0917: dataField = workflow.getDataField(id);
0918: if (dataField == null)
0919: dataField = workflow.getPackage().getDataField(id);
0920: }
0921: DataType dataType = null;
0922: if (dataField != null) {
0923: dataType = dataField.getDataType();
0924: name = dataField.getName();
0925: description = dataField.getDescription();
0926: } else {
0927: FormalParameter fp = workflow.getFormalParameter(id);
0928: if (fp != null) {
0929: dataType = fp.getDataType();
0930: name = id;
0931: description = fp.getDescription();
0932: }
0933: }
0934: if (dataType == null)
0935: throw new WMInvalidAttributeException(id);
0936: if (name == null)
0937: name = id;
0938:
0939: return new AttributeDescription(id, name, description, dataType);
0940: }
0941:
0942: public static Evaluator getEvaluator(XPDLPackage pkg,
0943: ServiceManager svcMgr) throws RepositoryException {
0944:
0945: // Determine script type; default is text/x-xpath.
0946: Script script = pkg.getScript();
0947: String contentType = script == null ? ServerConfig
0948: .getScriptType() : script.getType();
0949: return svcMgr.getEvaluatorFactory().findEvaluator(contentType);
0950: }
0951:
0952: /**
0953: * Introspects properties of the specified class(es) into a map. The
0954: * results are cached internally on <code>beanClass<code>, with the result
0955: * that sequential calls with the same <code>beanClass<code> but different
0956: * <code>stopClass<code> will yield property information generated from the
0957: * first such call.
0958: *
0959: * @param beanClass
0960: * @param stopClass
0961: * @return A map of <code>PropertyDescriptor</code>s, keyed on property
0962: * name.
0963: */
0964: public static Map introspectToMap(Class beanClass, Class stopClass) {
0965: Map map = (Map) _propDescs.get(beanClass);
0966: if (map == null) {
0967: map = new HashMap();
0968: PropertyDescriptor[] propDescs = ClassUtils.introspect(
0969: beanClass, stopClass);
0970: for (int i = 0; i < propDescs.length; i++) {
0971: PropertyDescriptor propDesc = propDescs[i];
0972: map.put(propDesc.getName(), propDesc);
0973: }
0974: _propDescs.put(beanClass, map);
0975: }
0976: return map;
0977: }
0978:
0979: public static String mapPerformerId(Participant performerDef)
0980: throws WMInvalidTargetUserException {
0981:
0982: String performerId = performerDef.getId();
0983:
0984: // An external reference can map Role:Group or Human:Principal.
0985: ExternalReference extRef = performerDef.getExternalReference();
0986: if (extRef != null) {
0987: // If the external reference location is the OBE realm, the
0988: // mapping is given by the xref.
0989: if (SecurityRealm.XPDL_NAME.equals(extRef.getLocation())) {
0990: String xref = extRef.getXref();
0991: if (xref == null || xref.length() == 0)
0992: throw new WMInvalidTargetUserException(performerId);
0993:
0994: if (_logger.isDebugEnabled()) {
0995: _logger.debug("resolved participant '"
0996: + performerId + "' to external reference: "
0997: + xref);
0998: }
0999: performerId = xref;
1000: } else {
1001: _logger.info("Unknown external reference " + extRef
1002: + " ignored");
1003: }
1004: }
1005: return performerId;
1006: }
1007:
1008: /**
1009: * Returns everything necessary to invoke the tool(s) associated with a work
1010: * item.
1011: *
1012: * @param workItem The work item corresponding to the tool.
1013: * @param toolIndex The index of the tool to prepare (ignored in favour of
1014: * <code>workItem.getToolIndex()</code> if <code>workItem</code> is
1015: * non-null or if the toolset mode is PARALLEL.
1016: * @param ctx The workflow context in which to prepare the tool invocation.
1017: * @return Tool invocation details.
1018: * @throws WMInvalidToolException if the tool cannot be found.
1019: */
1020: public static ToolInvocation[] prepareToolInvocations(
1021: WorkItem workItem, int toolIndex, EngineContext ctx)
1022: throws RepositoryException, WMWorkflowException {
1023:
1024: ToolInvocation[] ti;
1025: Activity activity = ctx.getActivity();
1026: Implementation implementation = activity.getImplementation();
1027: if (implementation instanceof ToolSet) {
1028: // If the tool execution mode is PARALLEL, prepare all tool
1029: // invocations. Otherwise, just prepare the tool invocation for the
1030: // work item's current tool index.
1031: boolean parallel = activity.getToolMode() == ToolMode.PARALLEL;
1032: boolean serverSide = workItem == null;
1033: ToolSet toolSet = (ToolSet) implementation;
1034: Tool[] tools = toolSet.getTool();
1035: List invocations = new ArrayList(parallel ? tools.length
1036: : 1);
1037:
1038: ToolAgentFactory factory = ctx.getServiceManager()
1039: .getToolAgentFactory();
1040: WorkflowProcess workflow = ctx.getWorkflow();
1041: ActivityInstance activityInstance = ctx
1042: .getActivityInstance();
1043: String processInstanceId;
1044: String workItemId;
1045: if (workItem != null) {
1046: processInstanceId = workItem.getProcessInstanceId();
1047: workItemId = workItem.getWorkItemId();
1048: toolIndex = workItem.getToolIndex();
1049: } else {
1050: processInstanceId = ctx.getProcessInstance()
1051: .getProcessInstanceId();
1052: workItemId = null;
1053: }
1054: ctx.setWorkItem(workItem);
1055:
1056: // Prepare an invocation for each of the tools to be executed.
1057: for (int i = parallel ? 0 : toolIndex, n = tools.length; i < n; i++) {
1058: // Build the parameter list.
1059: Tool tool = tools[i];
1060:
1061: // The server only ever invokes PROCEDURES.
1062: if (serverSide
1063: && tool.getToolType() == ToolType.APPLICATION) {
1064: _logger.warn("Ignoring APPLICATION-type tool '"
1065: + tool.getId() + "' in activity '"
1066: + activity + "' of workflow '" + workflow
1067: + '\'');
1068: continue;
1069: }
1070:
1071: // Evaluate the tool invocation parameters.
1072: String toolId = tool.getId();
1073: Application toolDef = WorkflowUtilities
1074: .findToolDefinition(workflow, toolId);
1075: ctx.setTool(tool);
1076: Parameter[] parms = createParameters(toolDef, tool
1077: .getActualParameter(), ctx);
1078: ctx.setTool(null);
1079:
1080: // Find the tool meta-data and the tool agent.
1081: ToolAgentMetaData metaData = factory
1082: .findToolMetaData(toolDef);
1083: ToolAgent agent = factory.findToolAgent(toolDef, parms);
1084:
1085: // Create the tool invocation.
1086: invocations.add(new ToolInvocation(processInstanceId,
1087: activityInstance.getName(), workItemId, toolId,
1088: i, metaData, tool.getToolType(), agent, parms,
1089: activity.getDescription()));
1090:
1091: // Work items invoke sequentially executed tools one at a time.
1092: if (!parallel)
1093: break;
1094: }
1095:
1096: ti = (ToolInvocation[]) invocations
1097: .toArray(new ToolInvocation[invocations.size()]);
1098: } else if (implementation instanceof NoImplementation) {
1099: ti = new ToolInvocation[] { new ToolInvocation(workItem
1100: .getProcessInstanceId(), workItem.getName(),
1101: workItem.getWorkItemId(), null, 0,
1102: NoImplementationToolAgent.META_DATA, null,
1103: NoImplementationToolAgent.INSTANCE, null, activity
1104: .getDescription()) };
1105: } else {
1106: ti = EMPTY_TOOL_INVOCATIONS;
1107: }
1108:
1109: return ti;
1110: }
1111:
1112: /**
1113: * Maps process state to activity state. When a process instance
1114: * transitions to the <code>newState</code> state, activities belonging
1115: * to that process instance and for which it would be a legal transition,
1116: * should transition to the state returned by this method.
1117: *
1118: * @param newState The new process instance state.
1119: * @return The required activity instance state.
1120: */
1121: public static int processStateToActivityState(int newState) {
1122: // The states actually have the same ordinals.
1123: return newState;
1124: }
1125:
1126: /**
1127: * Validates that a WorkflowProcess is available for instantiation.
1128: *
1129: * @param workflow The WorkflowProcess
1130: * @param internal <code>true</code> if instantiating internally.
1131: * @throws WMInvalidProcessDefinitionException
1132: * if the process definition is
1133: * disabled, under revision, private and non-subflow, or has not yet come
1134: * into force, or has expired.
1135: */
1136: public static void checkValid(WorkflowProcess workflow,
1137: boolean internal)
1138: throws WMInvalidProcessDefinitionException {
1139:
1140: RedefinableHeader rhdr = workflow.getRedefinableHeader();
1141: if (rhdr == null)
1142: rhdr = workflow.getPackage().getRedefinableHeader();
1143: String procDefId = workflow.getId();
1144:
1145: // Cannot instantiate if disabled.
1146: if (workflow.getState() != WMProcessDefinitionState.ENABLED_INT) {
1147: throw new WMInvalidProcessDefinitionException("Workflow "
1148: + procDefId
1149: + " is disabled and cannot be instantiated.");
1150: }
1151:
1152: // Cannot instantiate if UNDER_REVISISION.
1153: PublicationStatus status = rhdr == null ? null : rhdr
1154: .getPublicationStatus();
1155: if (status == PublicationStatus.UNDER_REVISION) {
1156: throw new WMInvalidProcessDefinitionException("Workflow "
1157: + procDefId
1158: + " is under revision and cannot be instantiated.");
1159: }
1160:
1161: // Private workflows can only be instantiated as subflows.
1162: if (!internal
1163: && workflow.getAccessLevel() == AccessLevel.PRIVATE) {
1164: throw new WMInvalidProcessDefinitionException("Workflow "
1165: + procDefId
1166: + " is private and cannot be instantiated.");
1167: }
1168:
1169: ProcessHeader procHdr = workflow.getProcessHeader();
1170: long now = System.currentTimeMillis();
1171:
1172: // Cannot instantiate if it isn't yet valid.
1173: Date validFrom = procHdr.getValidFrom();
1174: if (validFrom != null && now < validFrom.getTime()) {
1175: throw new WMInvalidProcessDefinitionException("Workflow "
1176: + procDefId
1177: + " is not yet valid and cannot be instantiated.");
1178: }
1179:
1180: // Cannot instantiate if it's expired.
1181: Date validTo = procHdr.getValidTo();
1182: if (validTo != null && now > validTo.getTime()) {
1183: throw new WMInvalidProcessDefinitionException("Workflow "
1184: + procDefId
1185: + " has expired and cannot be instantiated.");
1186: }
1187: }
1188:
1189: /**
1190: * Sorts the workflows collection in ordered of descending validity,
1191: * namely: validFrom DESC, validTo DESC, created DESC. The 'most valid'
1192: * workflow is therefore always at index 0.
1193: *
1194: * @param workflows
1195: */
1196: public static void sortForValidity(WorkflowProcess[] workflows) {
1197: Arrays.sort(workflows, _workflowValidityComparator);
1198: }
1199:
1200: private WorkflowEngineUtilities() {
1201: }
1202: }
|