001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2005 Danet GmbH (www.danet.de), BU BTS.
004: * All rights reserved.
005: *
006: * This program is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation; either version 2 of the License, or
009: * (at your option) any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: *
020: * $Id: ActivityWrapper.java,v 1.27 2007/03/27 21:59:44 mlipp Exp $
021: *
022: * $Log: ActivityWrapper.java,v $
023: * Revision 1.27 2007/03/27 21:59:44 mlipp
024: * Fixed lots of checkstyle warnings.
025: *
026: * Revision 1.26 2007/02/09 10:03:50 mlipp
027: * Improved exception handling.
028: *
029: * Revision 1.25 2007/02/02 08:48:35 drmlipp
030: * Fixed sorting, added column in assignments view.
031: *
032: * Revision 1.24 2006/12/04 14:18:42 drmlipp
033: * Added navigation between invoking and subprocess and fixed multiple deletes.
034: *
035: * Revision 1.23 2006/11/22 12:49:47 drmlipp
036: * Improved error handling.
037: *
038: * Revision 1.22 2006/11/21 18:38:29 drmlipp
039: * Improving exception handling.
040: *
041: * Revision 1.21 2006/11/17 10:52:23 drmlipp
042: * Added display of keys.
043: *
044: * Revision 1.20 2006/10/26 13:24:53 drmlipp
045: * Extended exception handling.
046: *
047: * Revision 1.19 2006/09/29 12:32:11 drmlipp
048: * Consistently using WfMOpen as projct name now.
049: *
050: * Revision 1.18 2006/09/15 11:43:04 drmlipp
051: * Minor improvements
052: *
053: * Revision 1.17 2006/09/14 08:05:14 drmlipp
054: * Use Activity.Info for storing attributes.
055: *
056: * Revision 1.15 2006/09/13 13:55:16 drmlipp
057: * Proceeding with assignments display.
058: *
059: * Revision 1.14 2006/09/06 09:31:06 drmlipp
060: * Cleaned up event display.
061: *
062: * Revision 1.13 2005/11/07 14:36:11 drmlipp
063: * Adapted to revised request attribute handling.
064: *
065: * Revision 1.12 2005/11/03 20:50:17 mlipp
066: * Simplified a bit.
067: *
068: * Revision 1.11 2005/10/31 16:38:02 drmlipp
069: * Implementation of debug features continued.
070: *
071: * Revision 1.10 2005/10/28 11:00:29 drmlipp
072: * Continued exception display implementation.
073: *
074: * Revision 1.9 2005/10/27 15:10:11 drmlipp
075: * Started exception display.
076: *
077: * Revision 1.8 2005/10/25 11:36:31 drmlipp
078: * Using extended column tag.
079: *
080: * Revision 1.7 2005/10/24 15:30:49 drmlipp
081: * Implemented context data change display.
082: *
083: * Revision 1.6 2005/10/21 15:05:51 drmlipp
084: * Continued audit event display and cleaned up some things.
085: *
086: * Revision 1.5 2005/10/17 15:26:17 drmlipp
087: * Added activity state actions.
088: *
089: * Revision 1.4 2005/10/14 12:45:35 drmlipp
090: * Added information.
091: *
092: * Revision 1.3 2005/10/02 21:04:03 mlipp
093: * Added activity list sorting.
094: *
095: * Revision 1.2 2005/10/01 20:53:55 mlipp
096: * Improved status display.
097: *
098: * Revision 1.1 2005/09/30 21:48:58 mlipp
099: * Basic process detail display working.
100: *
101: */
102: package de.danet.an.workflow.clients.mgmtportlets.process;
103:
104: import java.io.Serializable;
105: import java.lang.reflect.InvocationTargetException;
106: import java.rmi.RemoteException;
107: import java.util.ArrayList;
108: import java.util.Collection;
109: import java.util.Date;
110: import java.util.HashMap;
111: import java.util.Iterator;
112: import java.util.List;
113: import java.util.Map;
114:
115: import javax.faces.application.FacesMessage;
116: import javax.faces.context.FacesContext;
117: import javax.faces.model.ArrayDataModel;
118: import javax.faces.model.DataModel;
119:
120: import de.danet.an.util.jsf.JSFUtil;
121: import de.danet.an.workflow.api.Activity;
122: import de.danet.an.workflow.api.InvalidKeyException;
123: import de.danet.an.workflow.api.MethodInvocationBatch;
124: import de.danet.an.workflow.api.ProcessDefinition;
125: import de.danet.an.workflow.api.WorkflowService;
126: import de.danet.an.workflow.clients.mgmtportlets.WorkflowServiceConnection;
127: import de.danet.an.workflow.omgcore.InvalidControlOperationException;
128: import de.danet.an.workflow.omgcore.InvalidStateException;
129: import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
130: import de.danet.an.workflow.omgcore.WfAssignment;
131:
132: /**
133: * A wrapper class that makes activities look like JavaBeans.
134: * @author mnl
135: */
136: public class ActivityWrapper implements Serializable {
137:
138: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
139: .getLog(ActivityWrapper.class);
140:
141: private static String L10N_MSGS = "de.danet.an.workflow.clients.mgmtportlets.process.L10n";
142:
143: private Activity.Info info;
144: private String uniqueDispKey;
145: private String state;
146: private List assignees;
147: private String executor;
148: private boolean executorIsSubprocess = false;
149: private String invokedManagerName;
150: private String invokedKey;
151: private Collection validStates;
152: private Map validStatesMap;
153: private Activity.DeadlineInfo[] deadlineInfos;
154: private Map deadlineInfosByException;
155: private boolean debugEnabled;
156: private Date lastStateTime;
157: private String[] handledExceptions;
158: private transient DataModel handledExceptionsModel = null;
159:
160: /**
161: * Create a new instance for the given activity.
162: * @param wfs the workflow service
163: * @param act the activity
164: */
165: public ActivityWrapper(WorkflowService wfs, Activity act)
166: throws RemoteException {
167: MethodInvocationBatch mib = new MethodInvocationBatch();
168: mib.addInvocation(act, "activityInfo", null, null);
169: mib.addInvocation(act, "state", null, null);
170: mib.addInvocation(act, "assignments", null, null);
171: mib.addInvocation(act, "executor", null, null);
172: mib.addInvocation(act, "validStates", null, null);
173: mib.addInvocation(act, "handledExceptions", null, null);
174: mib.addInvocation(act, "deadlines", null, null);
175: mib.addInvocation(act, "debugEnabled", null, null);
176: mib.addInvocation(act, "lastStateTime", null, null);
177: MethodInvocationBatch.Result mir = null;
178: try {
179: mir = (MethodInvocationBatch.Result) wfs.executeBatch(mib);
180: } catch (InvocationTargetException e) {
181: throw (IllegalStateException) (new IllegalStateException(e
182: .getMessage())).initCause(e);
183: }
184: if (mir.hasExceptions()) {
185: Exception e = mir.firstException();
186: if (e instanceof RemoteException) {
187: throw (RemoteException) e;
188: }
189: throw (IllegalStateException) (new IllegalStateException(e
190: .getMessage())).initCause(e);
191: }
192: info = (Activity.Info) mir.result(0);
193: uniqueDispKey = info.uniqueKey().managerName() + "/"
194: + info.name();
195: state = mir.resultAsString(1);
196: Collection assignments = (Collection) mir.result(2);
197: assignees = new ArrayList();
198: for (Iterator i = assignments.iterator(); i.hasNext();) {
199: WfAssignment a = (WfAssignment) i.next();
200: assignees.add(new ResourceWrapper(a.assignee()));
201: }
202: Activity.Implementation executing = (Activity.Implementation) mir
203: .result(3);
204: if (executing == null) {
205: executor = null;
206: }
207: if (executing instanceof Activity.ToolImplementation) {
208: executor = ((Activity.ToolImplementation) executing).id();
209: } else if (executing instanceof Activity.SubFlowImplementation) {
210: try {
211: ProcessDefinition procDef = wfs
212: .processDefinitionDirectory()
213: .lookupProcessDefinition(
214: ((Activity.SubFlowImplementation) executing)
215: .packageId(),
216: ((Activity.SubFlowImplementation) executing)
217: .processId());
218: invokedManagerName = procDef.mgrName();
219: invokedKey = ((Activity.SubFlowImplementation) executing)
220: .processKey();
221: executor = wfs.processDirectory().lookupProcess(
222: invokedManagerName, invokedKey).name();
223: executorIsSubprocess = true;
224: if (executor == null) {
225: executor = invokedManagerName;
226: }
227: } catch (InvalidKeyException e) {
228: // Probably no longer exists
229: executor = ((Activity.SubFlowImplementation) executing)
230: .packageId()
231: + "/"
232: + ((Activity.SubFlowImplementation) executing)
233: .processId();
234: }
235: }
236: validStates = (Collection) mir.result(4);
237: validStatesMap = new HashMap();
238: for (Iterator i = validStates.iterator(); i.hasNext();) {
239: String state = (String) i.next();
240: validStatesMap.put(state, Boolean.TRUE);
241: }
242: handledExceptions = (String[]) mir.result(5);
243: deadlineInfos = (Activity.DeadlineInfo[]) mir.result(6);
244: deadlineInfosByException = new HashMap();
245: for (int i = 0; i < deadlineInfos.length; i++) {
246: deadlineInfosByException.put(deadlineInfos[i]
247: .getExceptionName(), deadlineInfos[i]);
248: }
249: debugEnabled = ((Boolean) mir.result(7)).booleanValue();
250: lastStateTime = mir.resultAsDate(8);
251: }
252:
253: private Activity activity() throws RemoteException,
254: InvalidKeyException {
255: FacesContext fc = FacesContext.getCurrentInstance();
256: Activity act = (Activity) fc.getExternalContext()
257: .getRequestMap().get(uniqueDispKey);
258: if (act == null) {
259: WorkflowService wfs = WorkflowServiceConnection.instance(
260: "workflowServiceConnection").getWorkflowService();
261: act = wfs.processDirectory().lookupActivity(
262: info.uniqueKey());
263: fc.getExternalContext().getRequestMap().put(uniqueDispKey,
264: act);
265: }
266: return act;
267: }
268:
269: /**
270: * Show the associated process.
271: */
272: public String showProcess() {
273: FacesContext fc = FacesContext.getCurrentInstance();
274: fc.getExternalContext().getSessionMap().put(
275: "processSelection",
276: new ProcessSelection(info.uniqueKey().managerName(),
277: info.uniqueKey().processKey()));
278: return "showProcessDetail";
279: }
280:
281: /**
282: * Show the associated process.
283: */
284: public String showInvokedProcess() {
285: FacesContext fc = FacesContext.getCurrentInstance();
286: fc.getExternalContext().getSessionMap().put("processSelection",
287: new ProcessSelection(invokedManagerName, invokedKey));
288: return "showProcessDetail";
289: }
290:
291: /**
292: * @return Returns the activityKey.
293: */
294: public String getProcessKey() {
295: return info.uniqueKey().processKey();
296: }
297:
298: /**
299: * @return Returns the activityKey.
300: */
301: public String getActivityKey() {
302: return info.uniqueKey().activityKey();
303: }
304:
305: /**
306: * @return Returns the uniqueKey.
307: */
308: public String getUniqueKey() {
309: return uniqueDispKey;
310: }
311:
312: /**
313: * @return Returns the process name.
314: */
315: public String getProcessName() {
316: return info.processName();
317: }
318:
319: /**
320: * @return Returns the process name and key.
321: */
322: public String getProcessNameAndKey() {
323: return info.processName() + "/" + info.uniqueKey().processKey();
324: }
325:
326: /**
327: * @return Returns the activityName.
328: */
329: public String getActivityName() {
330: return info.name();
331: }
332:
333: /**
334: * @return Returns the priority.
335: */
336: public int getPriority() {
337: return info.priority();
338: }
339:
340: /**
341: * @return Returns the lastStateTime.
342: */
343: public Date getLastStateTime() {
344: return lastStateTime;
345: }
346:
347: /**
348: * @return Returns the state name.
349: */
350: public String getState() {
351: return state;
352: }
353:
354: /**
355: * Check if activity is running.
356: * @return result
357: */
358: public boolean isRunning() {
359: return state.startsWith("open.running");
360: }
361:
362: /**
363: * Checks if activity is being debugged.
364: * @return result
365: */
366: public boolean isInDebugMode() {
367: return debugEnabled;
368: }
369:
370: /**
371: * @return Returns the state name.
372: */
373: public String getStateName() {
374: return StateNameMapper.mapState(state);
375: }
376:
377: /**
378: * @return URL of state icon or null
379: */
380: public String getStateIconUrl() {
381: return StateNameMapper.stateIconUrl(state);
382: }
383:
384: /**
385: * @return Returns the executor.
386: */
387: public String getExecutor() {
388: return executor;
389: }
390:
391: /**
392: * @return if executor is subprocess
393: */
394: public boolean isExecutorSubprocess() {
395: return executorIsSubprocess;
396: }
397:
398: /**
399: * @return the current assignee
400: */
401: public List getAssignees() {
402: return assignees;
403: }
404:
405: /**
406: * Check if the given state is valid (i.e. may be used in setState).
407: * @param state the checkState
408: */
409: public Map getValidStatesMap() {
410: return validStatesMap;
411: }
412:
413: /**
414: * Suspend the activity
415: */
416: public String suspend() throws RemoteException {
417: try {
418: activity().suspend();
419: } catch (InvalidKeyException e) {
420: logger
421: .debug("Referenced activity does not exist (ignored):"
422: + e.getMessage());
423: } catch (InvalidControlOperationException e) {
424: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
425: "cannotChangeState", null, e);
426: }
427: return null;
428: }
429:
430: /**
431: * Resume the activity
432: */
433: public String resume() throws RemoteException {
434: try {
435: activity().resume();
436: } catch (InvalidKeyException e) {
437: logger
438: .debug("Referenced activity does not exist (ignored):"
439: + e.getMessage());
440: } catch (InvalidControlOperationException e) {
441: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
442: "cannotChangeState", null, e);
443: }
444: return null;
445: }
446:
447: /**
448: * Resume the activity
449: */
450: public String clearExceptionAndResume() throws RemoteException {
451: try {
452: activity().changeState(
453: "open.not_running.suspended.clearing_exception");
454: activity().resume();
455: } catch (InvalidKeyException e) {
456: logger
457: .debug("Referenced activity does not exist (ignored):"
458: + e.getMessage());
459: } catch (InvalidStateException e) {
460: logger.debug("Invalid state:" + e.getMessage());
461: } catch (TransitionNotAllowedException e) {
462: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
463: "cannotChangeState", null, e);
464: } catch (InvalidControlOperationException e) {
465: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
466: "cannotChangeState", null, e);
467: }
468: return null;
469: }
470:
471: /**
472: * Terminate the activity
473: */
474: public String terminate() throws RemoteException {
475: try {
476: activity().terminate();
477: } catch (InvalidKeyException e) {
478: logger
479: .debug("Referenced activity does not exist (ignored):"
480: + e.getMessage());
481: } catch (InvalidControlOperationException e) {
482: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
483: "cannotChangeState", null, e);
484: }
485: return null;
486: }
487:
488: /**
489: * Abort the activity
490: */
491: public String abort() throws RemoteException {
492: try {
493: activity().abort();
494: } catch (InvalidKeyException e) {
495: logger
496: .debug("Referenced activity does not exist (ignored):"
497: + e.getMessage());
498: } catch (InvalidControlOperationException e) {
499: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
500: "cannotChangeState", null, e);
501: }
502: return null;
503: }
504:
505: /**
506: * Continue the activity
507: */
508: public String continueExecution() throws RemoteException {
509: try {
510: activity().changeState("open.running");
511: } catch (InvalidKeyException e) {
512: logger
513: .debug("Referenced activity does not exist (ignored):"
514: + e.getMessage());
515: } catch (InvalidStateException e) {
516: // cannot happen, state name hard-coded
517: } catch (TransitionNotAllowedException e) {
518: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
519: "cannotChangeState", null, e);
520: }
521: return null;
522: }
523:
524: /**
525: * Continue the activity
526: */
527: public String skip() throws RemoteException {
528: try {
529: activity().changeState("open.running.debug.skipping");
530: } catch (InvalidKeyException e) {
531: logger
532: .debug("Referenced activity does not exist (ignored):"
533: + e.getMessage());
534: } catch (InvalidStateException e) {
535: // cannot happen, state name hard-coded
536: } catch (TransitionNotAllowedException e) {
537: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
538: "cannotChangeState", null, e);
539: }
540: return null;
541: }
542:
543: /**
544: * Send an exception.
545: */
546: public String sendException() throws RemoteException {
547: Activity.DeadlineInfo dl = null;
548: DataModel he = getHandledExceptions();
549: if (he == null) {
550: return null;
551: }
552: String exception = ((String[]) he.getWrappedData())[he
553: .getRowIndex()];
554: for (int i = 0; i < deadlineInfos.length; i++) {
555: if (deadlineInfos[i].getExceptionName().equals(exception)) {
556: dl = deadlineInfos[i];
557: break;
558: }
559: }
560: Activity activity = null;
561: try {
562: activity = activity();
563: } catch (InvalidKeyException e) {
564: // TODO:
565: }
566: MethodInvocationBatch mib = new MethodInvocationBatch(true);
567: if (dl == null
568: || (dl.getExecutionMode() == Activity.DeadlineInfo.SYNCHR)) {
569: // not an asynchronous deadline, abandon if not aborted
570: if (!state.startsWith("open.running.debug.abandoning")) {
571: mib.addInvocation(activity, "abandon",
572: new String[] { "java.lang.String" },
573: new Object[] { exception });
574: }
575: mib
576: .addInvocation(
577: activity,
578: "changeState",
579: new String[] { "java.lang.String" },
580: new Object[] { "open.running.debug.awaiting_exception" });
581: mib.addInvocation(activity, "abandon",
582: new String[] { "java.lang.String" },
583: new Object[] { exception });
584: } else {
585: // asynchronous deadline, simply have it sent
586: mib
587: .addInvocation(
588: activity,
589: "changeState",
590: new String[] { "java.lang.String" },
591: new Object[] { "open.running.debug.forwarding_exception" });
592: mib.addInvocation(activity, "abandon",
593: new String[] { "java.lang.String" },
594: new Object[] { exception });
595: }
596: try {
597: MethodInvocationBatch.Result mir = (MethodInvocationBatch.Result) WorkflowServiceConnection
598: .instance("workflowServiceConnection")
599: .getWorkflowService().executeBatch(mib);
600: if (mir.hasExceptions()) {
601: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR,
602: L10N_MSGS, "cannotSendException", null, mir
603: .firstException());
604: }
605: } catch (InvocationTargetException e) {
606: logger.error("Unexpected exception: "
607: + e.getCause().getMessage(), e);
608: JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
609: "resourceCurrentlyUnavailable", null);
610: }
611: return null;
612: }
613:
614: /**
615: * The show events action for the activity events
616: *
617: * @return outcome
618: */
619: public String showEvents() {
620: FacesContext fc = FacesContext.getCurrentInstance();
621: ((ProcessSelection) fc.getExternalContext().getSessionMap()
622: .get("processSelection"))
623: .setEventSelection(getActivityKey());
624: return "showAuditEvents";
625: }
626:
627: /**
628: * Return the map for storing the expanded state.
629: * @return result
630: */
631: private Map expandedActivities() {
632: FacesContext fc = FacesContext.getCurrentInstance();
633: Map xm = (Map) fc.getExternalContext().getSessionMap().get(
634: "expandedActs");
635: if (xm == null) {
636: xm = new HashMap();
637: fc.getExternalContext().getSessionMap().put("expandedActs",
638: xm);
639: }
640: return xm;
641: }
642:
643: /**
644: * Check if the display can be expanded.
645: * @return result
646: */
647: public boolean isDisplayExpandable() {
648: return getHandledExceptions() != null
649: && getHandledExceptions().getRowCount() > 0;
650: }
651:
652: /**
653: * Check if activity is to be displayed as expanded.
654: * @return result
655: */
656: public boolean isDisplayExpanded() {
657: return expandedActivities().containsKey(getUniqueKey());
658: }
659:
660: /**
661: * Toggle the display expanded state.
662: */
663: public String toggleDisplayExpanded() {
664: if (isDisplayExpanded()) {
665: expandedActivities().remove(getUniqueKey());
666: } else {
667: expandedActivities().put(getUniqueKey(), Boolean.TRUE);
668: }
669: return null;
670: }
671:
672: /**
673: * Return the list of handled exceptions.
674: * @return the result
675: */
676: public DataModel getHandledExceptions() {
677: if (handledExceptionsModel == null) {
678: handledExceptionsModel = new ArrayDataModel(
679: handledExceptions);
680: }
681: return handledExceptionsModel;
682: }
683:
684: /**
685: * Return the deadline infos.
686: * @return the result
687: */
688: public Map getDeadlineInfos() {
689: return deadlineInfosByException;
690: }
691:
692: }
|