001: /*
002: * $Id: WfProcessImpl.java,v 1.5 2003/11/26 07:24:17 ajzeneski Exp $
003: *
004: * Copyright (c) 2001 The Open For Business Project - www.ofbiz.org
005: *
006: * Permission is hereby granted, free of charge, to any person obtaining a
007: * copy of this software and associated documentation files (the "Software"),
008: * to deal in the Software without restriction, including without limitation
009: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
010: * and/or sell copies of the Software, and to permit persons to whom the
011: * Software is furnished to do so, subject to the following conditions:
012: *
013: * The above copyright notice and this permission notice shall be included
014: * in all copies or substantial portions of the Software.
015: *
016: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
017: * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
018: * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
019: * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
020: * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
021: * OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
022: * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
023: *
024: */
025: package org.ofbiz.workflow.impl;
026:
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import org.ofbiz.base.util.Debug;
036: import org.ofbiz.base.util.StringUtil;
037: import org.ofbiz.base.util.UtilDateTime;
038: import org.ofbiz.base.util.UtilMisc;
039: import org.ofbiz.entity.GenericDelegator;
040: import org.ofbiz.entity.GenericEntityException;
041: import org.ofbiz.entity.GenericValue;
042: import org.ofbiz.service.GenericResultWaiter;
043: import org.ofbiz.service.LocalDispatcher;
044: import org.ofbiz.service.job.Job;
045: import org.ofbiz.service.job.JobManager;
046: import org.ofbiz.service.job.JobManagerException;
047: import org.ofbiz.workflow.AlreadyRunning;
048: import org.ofbiz.workflow.CannotChangeRequester;
049: import org.ofbiz.workflow.CannotStart;
050: import org.ofbiz.workflow.CannotStop;
051: import org.ofbiz.workflow.InvalidData;
052: import org.ofbiz.workflow.InvalidPerformer;
053: import org.ofbiz.workflow.InvalidState;
054: import org.ofbiz.workflow.NotRunning;
055: import org.ofbiz.workflow.ResultNotAvailable;
056: import org.ofbiz.workflow.WfActivity;
057: import org.ofbiz.workflow.WfEventAudit;
058: import org.ofbiz.workflow.WfException;
059: import org.ofbiz.workflow.WfFactory;
060: import org.ofbiz.workflow.WfProcess;
061: import org.ofbiz.workflow.WfProcessMgr;
062: import org.ofbiz.workflow.WfRequester;
063: import org.ofbiz.workflow.WfUtil;
064: import org.ofbiz.workflow.client.StartActivityJob;
065:
066: /**
067: * WfProcessImpl - Workflow Process Object implementation
068: *
069: * @author <a href="mailto:jaz@ofbiz.org">Andy Zeneski</a>
070: * @author David Ostrovsky (d.ostrovsky@gmx.de)
071: * @version $Revision: 1.5 $
072: * @since 2.0
073: */
074: public class WfProcessImpl extends WfExecutionObjectImpl implements
075: WfProcess {
076:
077: public static final String module = WfProcessImpl.class.getName();
078:
079: protected WfRequester requester = null;
080: protected WfProcessMgr manager = null;
081:
082: public WfProcessImpl(GenericValue valueObject, WfProcessMgr manager)
083: throws WfException {
084: super (valueObject, null);
085: this .manager = manager;
086: this .requester = null;
087: init();
088: }
089:
090: /**
091: * @see org.ofbiz.workflow.impl.WfExecutionObjectImpl#WfExecutionObjectImpl(org.ofbiz.entity.GenericDelegator, java.lang.String)
092: */
093: public WfProcessImpl(GenericDelegator delegator, String workEffortId)
094: throws WfException {
095: super (delegator, workEffortId);
096: if (activityId != null && activityId.length() > 0)
097: throw new WfException(
098: "Execution object is not of type WfProcess.");
099: this .manager = WfFactory.getWfProcessMgr(delegator, packageId,
100: packageVersion, processId, processVersion);
101: this .requester = null;
102: }
103:
104: private void init() throws WfException {
105: // since we are a process we don't have a context yet
106: // get the context to use with parsing descriptions from the manager
107: Map context = manager.getInitialContext();
108: this .parseDescriptions(context);
109: }
110:
111: /**
112: * @see org.ofbiz.workflow.WfProcess#setRequester(org.ofbiz.workflow.WfRequester)
113: */
114: public void setRequester(WfRequester newValue) throws WfException,
115: CannotChangeRequester {
116: if (requester != null)
117: throw new CannotChangeRequester();
118: requester = newValue;
119: }
120:
121: /**
122: * @see org.ofbiz.workflow.WfProcess#getSequenceStep(int)
123: */
124: public List getSequenceStep(int maxNumber) throws WfException {
125: if (maxNumber > 0)
126: return new ArrayList(activeSteps()
127: .subList(0, maxNumber - 1));
128: return activeSteps();
129: }
130:
131: /**
132: * @see org.ofbiz.workflow.WfExecutionObject#abort()
133: */
134: public void abort() throws WfException, CannotStop, NotRunning {
135: super .abort();
136:
137: // cancel the active activities
138: Iterator activities = this .activeSteps().iterator();
139: while (activities.hasNext()) {
140: WfActivity activity = (WfActivity) activities.next();
141: activity.abort();
142: }
143: }
144:
145: /**
146: * @see org.ofbiz.workflow.WfProcess#start()
147: */
148: public void start() throws WfException, CannotStart, AlreadyRunning {
149: start(null);
150: }
151:
152: /**
153: * @see org.ofbiz.workflow.WfProcess#start()
154: */
155: public void start(String activityId) throws WfException,
156: CannotStart, AlreadyRunning {
157: if (state().equals("open.running"))
158: throw new AlreadyRunning("Process is already running");
159:
160: if (activityId == null
161: && getDefinitionObject().get("defaultStartActivityId") == null)
162: throw new CannotStart("Initial activity is not defined.");
163:
164: changeState("open.running");
165:
166: // start the first activity (using the defaultStartActivityId)
167: GenericValue start = null;
168:
169: try {
170: if (activityId != null) {
171: GenericValue processDef = getDefinitionObject();
172: Map fields = UtilMisc.toMap("packageId", processDef
173: .getString("packageId"), "packageVersion",
174: processDef.getString("packageVersion"),
175: "processId", processDef.getString("processId"),
176: "processVersion", processDef
177: .getString("processVersion"),
178: "activityId", activityId);
179: start = getDelegator().findByPrimaryKey(
180: "WorkflowActivity", fields);
181:
182: // here we must check and make sure this activity is defined to as a starting activity
183: if (!start.getBoolean("canStart").booleanValue())
184: throw new CannotStart(
185: "The specified activity cannot initiate the workflow process");
186: } else {
187: // this is either the first activity defined or specified as an ExtendedAttribute
188: // since this is defined in XPDL, we don't care if canStart is set.
189: start = getDefinitionObject().getRelatedOne(
190: "DefaultStartWorkflowActivity");
191: }
192: } catch (GenericEntityException e) {
193: throw new WfException(e.getMessage(), e.getNested());
194: }
195: if (start == null)
196: throw new CannotStart("No initial activity available");
197:
198: if (Debug.verboseOn())
199: Debug
200: .logVerbose(
201: "[WfProcess.start] : Started the workflow process.",
202: module);
203:
204: // set the actualStartDate
205: try {
206: GenericValue v = getRuntimeObject();
207: v.set("actualStartDate", UtilDateTime.nowTimestamp());
208: v.store();
209: } catch (GenericEntityException e) {
210: Debug
211: .logWarning("Could not set 'actualStartDate'.",
212: module);
213: e.printStackTrace();
214: }
215: startActivity(start);
216: }
217:
218: /**
219: * @see org.ofbiz.workflow.WfProcess#manager()
220: */
221: public WfProcessMgr manager() throws WfException {
222: return manager;
223: }
224:
225: /**
226: * @see org.ofbiz.workflow.WfProcess#requester()
227: */
228: public WfRequester requester() throws WfException {
229: return requester;
230: }
231:
232: /**
233: * @see org.ofbiz.workflow.WfProcess#getIteratorStep()
234: */
235: public Iterator getIteratorStep() throws WfException {
236: return activeSteps().iterator();
237: }
238:
239: /**
240: * @see org.ofbiz.workflow.WfProcess#isMemberOfStep(org.ofbiz.workflow.WfActivity)
241: */
242: public boolean isMemberOfStep(WfActivity member) throws WfException {
243: return activeSteps().contains(member);
244: }
245:
246: /**
247: * @see org.ofbiz.workflow.WfProcess#getActivitiesInState(java.lang.String)
248: */
249: public Iterator getActivitiesInState(String state)
250: throws WfException, InvalidState {
251: ArrayList res = new ArrayList();
252: Iterator i = getIteratorStep();
253:
254: while (i.hasNext()) {
255: WfActivity a = (WfActivity) i.next();
256:
257: if (a.state().equals(state))
258: res.add(a);
259: }
260: return res.iterator();
261: }
262:
263: /**
264: * @see org.ofbiz.workflow.WfProcess#result()
265: */
266: public Map result() throws WfException, ResultNotAvailable {
267: Map resultSig = manager().resultSignature();
268: Map results = new HashMap();
269: Map context = processContext();
270:
271: if (resultSig != null) {
272: Set resultKeys = resultSig.keySet();
273: Iterator i = resultKeys.iterator();
274:
275: while (i.hasNext()) {
276: Object key = i.next();
277:
278: if (context.containsKey(key))
279: results.put(key, context.get(key));
280: }
281: }
282: return results;
283: }
284:
285: /**
286: * @see org.ofbiz.workflow.WfProcess#howManyStep()
287: */
288: public int howManyStep() throws WfException {
289: return activeSteps().size();
290: }
291:
292: /**
293: * @see org.ofbiz.workflow.WfProcess#receiveResults(org.ofbiz.workflow.WfActivity, java.util.Map)
294: */
295: public synchronized void receiveResults(WfActivity activity,
296: Map results) throws WfException, InvalidData {
297: Map context = processContext();
298: context.putAll(results);
299: setSerializedData(context);
300: }
301:
302: /**
303: * @see org.ofbiz.workflow.WfProcess#activityComplete(org.ofbiz.workflow.WfActivity)
304: */
305: public synchronized void activityComplete(WfActivity activity)
306: throws WfException {
307: if (!activity.state().equals("closed.completed"))
308: throw new WfException("Activity state is not completed");
309: if (Debug.verboseOn())
310: Debug
311: .logVerbose(
312: "[WfProcess.activityComplete] : Activity ("
313: + activity.name() + ") is complete",
314: module);
315: queueNext(activity);
316: }
317:
318: /**
319: * @see org.ofbiz.workflow.impl.WfExecutionObjectImpl#executionObjectType()
320: */
321: public String executionObjectType() {
322: return "WfProcess";
323: }
324:
325: // Queues the next activities for processing
326: private void queueNext(WfActivity fromActivity) throws WfException {
327: List nextTrans = getTransFrom(fromActivity);
328:
329: if (nextTrans.size() > 0) {
330: Iterator i = nextTrans.iterator();
331:
332: while (i.hasNext()) {
333: GenericValue trans = (GenericValue) i.next();
334:
335: // Get the activity definition
336: GenericValue toActivity = null;
337:
338: try {
339: toActivity = trans
340: .getRelatedOne("ToWorkflowActivity");
341: } catch (GenericEntityException e) {
342: throw new WfException(e.getMessage(), e);
343: }
344:
345: // check for a join
346: String join = "WJT_AND"; // default join is AND
347:
348: if (toActivity.get("joinTypeEnumId") != null)
349: join = toActivity.getString("joinTypeEnumId");
350:
351: if (Debug.verboseOn())
352: Debug.logVerbose("[WfProcess.queueNext] : " + join
353: + " join.", module);
354:
355: // activate if XOR or test the join transition(s)
356: if (join.equals("WJT_XOR"))
357: startActivity(toActivity);
358: else
359: joinTransition(toActivity, trans);
360: }
361: } else {
362: if (Debug.verboseOn())
363: Debug
364: .logVerbose(
365: "[WfProcess.queueNext] : No transitions left to follow.",
366: module);
367: this .finishProcess();
368: }
369: }
370:
371: // Follows the and-join transition
372: private void joinTransition(GenericValue toActivity,
373: GenericValue transition) throws WfException {
374: // get all TO transitions to this activity
375: GenericValue dataObject = getRuntimeObject();
376: Collection toTrans = null;
377:
378: try {
379: toTrans = toActivity.getRelated("ToWorkflowTransition");
380: } catch (GenericEntityException e) {
381: throw new WfException(e.getMessage(), e);
382: }
383:
384: // get a list of followed transition to this activity
385: Collection followed = null;
386:
387: try {
388: Map fields = new HashMap();
389: fields.put("processWorkEffortId", dataObject
390: .getString("workEffortId"));
391: fields.put("toActivityId", toActivity
392: .getString("activityId"));
393: followed = getDelegator().findByAnd("WorkEffortTransBox",
394: fields);
395: } catch (GenericEntityException e) {
396: throw new WfException(e.getMessage(), e);
397: }
398:
399: if (Debug.verboseOn())
400: Debug.logVerbose("[WfProcess.joinTransition] : toTrans ("
401: + toTrans.size() + ") followed ("
402: + (followed.size() + 1) + ")", module);
403:
404: // check to see if all transition requirements are met
405: if (toTrans.size() == (followed.size() + 1)) {
406: Debug
407: .logVerbose(
408: "[WfProcess.joinTransition] : All transitions have followed.",
409: module);
410: startActivity(toActivity);
411: try {
412: Map fields = new HashMap();
413: fields.put("processWorkEffortId", dataObject
414: .getString("workEffortId"));
415: fields.put("toActivityId", toActivity
416: .getString("activityId"));
417: getDelegator()
418: .removeByAnd("WorkEffortTransBox", fields);
419: } catch (GenericEntityException e) {
420: throw new WfException(e.getMessage(), e);
421: }
422: } else {
423: Debug
424: .logVerbose(
425: "[WfProcess.joinTransition] : Waiting for transitions to finish.",
426: module);
427: try {
428: Map fields = new HashMap();
429: fields.put("processWorkEffortId", dataObject
430: .getString("workEffortId"));
431: fields.put("toActivityId", toActivity
432: .getString("activityId"));
433: fields.put("transitionId", transition
434: .getString("transitionId"));
435: GenericValue obj = getDelegator().makeValue(
436: "WorkEffortTransBox", fields);
437:
438: getDelegator().create(obj);
439: } catch (GenericEntityException e) {
440: throw new WfException(e.getMessage(), e);
441: }
442: }
443: }
444:
445: // Activates an activity object
446: private void startActivity(GenericValue value) throws WfException {
447: WfActivity activity = WfFactory.getWfActivity(value,
448: workEffortId);
449: GenericResultWaiter req = new GenericResultWaiter();
450:
451: if (Debug.verboseOn())
452: Debug.logVerbose(
453: "[WfProcess.startActivity] : Attempting to start activity ("
454: + activity.name() + ")", module);
455:
456: // locate the dispatcher to use
457: LocalDispatcher dispatcher = this .getDispatcher();
458:
459: // get the job manager
460: JobManager jm = dispatcher.getJobManager();
461: if (jm == null) {
462: throw new WfException(
463: "No job manager found on the service dispatcher; cannot start activity");
464: }
465:
466: // using the StartActivityJob class to run the activity within its own thread
467: try {
468: Job activityJob = new StartActivityJob(activity, req);
469: jm.runJob(activityJob);
470: } catch (JobManagerException e) {
471: throw new WfException("JobManager error", e);
472: }
473:
474: // the GenericRequester object will hold any exceptions; and report the job as failed
475: if (req.status() == GenericResultWaiter.SERVICE_FAILED) {
476: Throwable reqt = req.getThrowable();
477: if (reqt instanceof CannotStart)
478: Debug
479: .logVerbose(
480: "[WfProcess.startActivity] : Cannot start activity. Waiting for manual start.",
481: module);
482: else if (reqt instanceof AlreadyRunning)
483: throw new WfException("Activity already running", reqt);
484: else
485: throw new WfException("Activity error", reqt);
486: }
487: }
488:
489: // Determine the next activity or activities
490: private List getTransFrom(WfActivity fromActivity)
491: throws WfException {
492: List transList = new ArrayList();
493: // get the from transitions
494: Collection fromCol = null;
495:
496: try {
497: fromCol = fromActivity.getDefinitionObject().getRelated(
498: "FromWorkflowTransition");
499: } catch (GenericEntityException e) {
500: throw new WfException(e.getMessage(), e);
501: }
502:
503: // check for a split
504: String split = "WST_AND"; // default split is AND
505:
506: if (fromActivity.getDefinitionObject().get("splitTypeEnumId") != null)
507: split = fromActivity.getDefinitionObject().getString(
508: "splitTypeEnumId");
509:
510: // the default value is TRUE, so if no expression is supplied we evaluate as true
511: boolean transitionOk = true;
512:
513: // the otherwise condition (only used by XOR splits)
514: GenericValue otherwise = null;
515:
516: // iterate through the possible transitions
517: Iterator fromIt = fromCol.iterator();
518: while (fromIt.hasNext()) {
519: GenericValue transition = (GenericValue) fromIt.next();
520:
521: // if this transition is OTHERWISE store it for later and continue on
522: if (transition.get("conditionTypeEnumId") != null
523: && transition.getString("conditionTypeEnumId")
524: .equals("WTC_OTHERWISE")) {
525: // there should be only one of these, if there is more then one we will use the last one defined
526: otherwise = transition;
527: continue;
528: }
529:
530: // get the condition body from the condition tag
531: String conditionBody = transition
532: .getString("conditionExpr");
533:
534: // get the extended attributes for the transition
535: Map extendedAttr = StringUtil.strToMap(transition
536: .getString("extendedAttributes"));
537:
538: // check for a conditionClassName attribute if exists use it
539: if (extendedAttr != null
540: && extendedAttr.get("conditionClassName") != null) {
541: String conditionClassName = (String) extendedAttr
542: .get("conditionClassName");
543: transitionOk = this .evalConditionClass(
544: conditionClassName, conditionBody, this
545: .processContext(), extendedAttr);
546: } else {
547: // since no condition class is supplied, evaluate the expression using bsh
548: if (conditionBody != null) {
549: transitionOk = this .evalBshCondition(conditionBody,
550: this .processContext());
551: }
552: }
553:
554: if (transitionOk) {
555: transList.add(transition);
556: if (split.equals("WST_XOR"))
557: break;
558: }
559: }
560:
561: // we only use the otherwise transition for XOR splits
562: if (split.equals("WST_XOR") && transList.size() == 0
563: && otherwise != null) {
564: transList.add(otherwise);
565: Debug.logVerbose("Used OTHERWISE Transition.", module);
566: }
567:
568: if (Debug.verboseOn())
569: Debug.logVerbose("[WfProcess.getTransFrom] : Transitions: "
570: + transList.size(), module);
571: return transList;
572: }
573:
574: // Gets a specific activity by its key
575: private WfActivity getActivity(String key) throws WfException {
576: Iterator i = getIteratorStep();
577:
578: while (i.hasNext()) {
579: WfActivity a = (WfActivity) i.next();
580: if (a.key().equals(key))
581: return a;
582: }
583: throw new WfException(
584: "Activity not an active member of this process");
585: }
586:
587: // Complete this workflow
588: private void finishProcess() throws WfException {
589: changeState("closed.completed");
590: Debug
591: .logVerbose(
592: "[WfProcess.finishProcess] : Workflow Complete. Calling back to requester.",
593: module);
594: if (requester != null) {
595: WfEventAudit audit = WfFactory.getWfEventAudit(this , null); // this will need to be updated
596:
597: try {
598: requester.receiveEvent(audit);
599: } catch (InvalidPerformer e) {
600: throw new WfException(e.getMessage(), e);
601: }
602: }
603: }
604:
605: // Get the active process activities
606: private List activeSteps() throws WfException {
607: List steps = new ArrayList();
608: Collection c = null;
609:
610: try {
611: c = getDelegator().findByAnd("WorkEffort",
612: UtilMisc.toMap("workEffortParentId", runtimeKey()));
613: } catch (GenericEntityException e) {
614: throw new WfException(e.getMessage(), e);
615: }
616: if (c == null)
617: return steps;
618: Iterator i = c.iterator();
619:
620: while (i.hasNext()) {
621: GenericValue v = (GenericValue) i.next();
622:
623: if (v.get("currentStatusId") != null
624: && WfUtil.getOMGStatus(
625: v.getString("currentStatusId")).startsWith(
626: "open."))
627: steps.add(WfFactory.getWfActivity(getDelegator(), v
628: .getString("workEffortId")));
629: }
630: return steps;
631: }
632: }
|