/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2005 Danet GmbH (www.danet.de), BU BTS.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Id: ActivityWrapper.java 2326 2007-03-27 21:59:44Z mlipp $
*
* $Log$
* Revision 1.26 2007/02/09 10:03:50 mlipp
* Improved exception handling.
*
* Revision 1.25 2007/02/02 08:48:35 drmlipp
* Fixed sorting, added column in assignments view.
*
* Revision 1.24 2006/12/04 14:18:42 drmlipp
* Added navigation between invoking and subprocess and fixed multiple deletes.
*
* Revision 1.23 2006/11/22 12:49:47 drmlipp
* Improved error handling.
*
* Revision 1.22 2006/11/21 18:38:29 drmlipp
* Improving exception handling.
*
* Revision 1.21 2006/11/17 10:52:23 drmlipp
* Added display of keys.
*
* Revision 1.20 2006/10/26 13:24:53 drmlipp
* Extended exception handling.
*
* Revision 1.19 2006/09/29 12:32:11 drmlipp
* Consistently using WfMOpen as projct name now.
*
* Revision 1.18 2006/09/15 11:43:04 drmlipp
* Minor improvements
*
* Revision 1.17 2006/09/14 08:05:14 drmlipp
* Use Activity.Info for storing attributes.
*
* Revision 1.15 2006/09/13 13:55:16 drmlipp
* Proceeding with assignments display.
*
* Revision 1.14 2006/09/06 09:31:06 drmlipp
* Cleaned up event display.
*
* Revision 1.13 2005/11/07 14:36:11 drmlipp
* Adapted to revised request attribute handling.
*
* Revision 1.12 2005/11/03 20:50:17 mlipp
* Simplified a bit.
*
* Revision 1.11 2005/10/31 16:38:02 drmlipp
* Implementation of debug features continued.
*
* Revision 1.10 2005/10/28 11:00:29 drmlipp
* Continued exception display implementation.
*
* Revision 1.9 2005/10/27 15:10:11 drmlipp
* Started exception display.
*
* Revision 1.8 2005/10/25 11:36:31 drmlipp
* Using extended column tag.
*
* Revision 1.7 2005/10/24 15:30:49 drmlipp
* Implemented context data change display.
*
* Revision 1.6 2005/10/21 15:05:51 drmlipp
* Continued audit event display and cleaned up some things.
*
* Revision 1.5 2005/10/17 15:26:17 drmlipp
* Added activity state actions.
*
* Revision 1.4 2005/10/14 12:45:35 drmlipp
* Added information.
*
* Revision 1.3 2005/10/02 21:04:03 mlipp
* Added activity list sorting.
*
* Revision 1.2 2005/10/01 20:53:55 mlipp
* Improved status display.
*
* Revision 1.1 2005/09/30 21:48:58 mlipp
* Basic process detail display working.
*
*/
package de.danet.an.workflow.clients.mgmtportlets.process;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.faces.model.ArrayDataModel;
import javax.faces.model.DataModel;
import de.danet.an.util.jsf.JSFUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.MethodInvocationBatch;
import de.danet.an.workflow.api.ProcessDefinition;
import de.danet.an.workflow.api.WorkflowService;
import de.danet.an.workflow.clients.mgmtportlets.WorkflowServiceConnection;
import de.danet.an.workflow.omgcore.InvalidControlOperationException;
import de.danet.an.workflow.omgcore.InvalidStateException;
import de.danet.an.workflow.omgcore.TransitionNotAllowedException;
import de.danet.an.workflow.omgcore.WfAssignment;
/**
* A wrapper class that makes activities look like JavaBeans.
* @author mnl
*/
public class ActivityWrapper implements Serializable {
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog (ActivityWrapper.class);
private static String L10N_MSGS
= "de.danet.an.workflow.clients.mgmtportlets.process.L10n";
private Activity.Info info;
private String uniqueDispKey;
private String state;
private List assignees;
private String executor;
private boolean executorIsSubprocess = false;
private String invokedManagerName;
private String invokedKey;
private Collection validStates;
private Map validStatesMap;
private Activity.DeadlineInfo[] deadlineInfos;
private Map deadlineInfosByException;
private boolean debugEnabled;
private Date lastStateTime;
private String[] handledExceptions;
private transient DataModel handledExceptionsModel = null;
/**
* Create a new instance for the given activity.
* @param wfs the workflow service
* @param act the activity
*/
public ActivityWrapper (WorkflowService wfs, Activity act)
throws RemoteException {
MethodInvocationBatch mib = new MethodInvocationBatch ();
mib.addInvocation(act, "activityInfo", null, null);
mib.addInvocation(act, "state", null, null);
mib.addInvocation(act, "assignments", null, null);
mib.addInvocation(act, "executor", null, null);
mib.addInvocation(act, "validStates", null, null);
mib.addInvocation(act, "handledExceptions", null, null);
mib.addInvocation(act, "deadlines", null, null);
mib.addInvocation(act, "debugEnabled", null, null);
mib.addInvocation(act, "lastStateTime", null, null);
MethodInvocationBatch.Result mir = null;
try {
mir = (MethodInvocationBatch.Result)wfs.executeBatch(mib);
} catch (InvocationTargetException e) {
throw (IllegalStateException)
(new IllegalStateException (e.getMessage())).initCause(e);
}
if (mir.hasExceptions ()) {
Exception e = mir.firstException ();
if (e instanceof RemoteException) {
throw (RemoteException)e;
}
throw (IllegalStateException)
(new IllegalStateException(e.getMessage())).initCause(e);
}
info = (Activity.Info)mir.result(0);
uniqueDispKey = info.uniqueKey().managerName() + "/" + info.name();
state = mir.resultAsString(1);
Collection assignments = (Collection)mir.result(2);
assignees = new ArrayList ();
for (Iterator i = assignments.iterator(); i.hasNext ();) {
WfAssignment a = (WfAssignment)i.next();
assignees.add (new ResourceWrapper(a.assignee()));
}
Activity.Implementation executing
= (Activity.Implementation)mir.result(3);
if (executing == null) {
executor = null;
}
if (executing instanceof Activity.ToolImplementation) {
executor = ((Activity.ToolImplementation)executing).id();
} else if (executing instanceof Activity.SubFlowImplementation) {
try {
ProcessDefinition procDef
= wfs.processDefinitionDirectory().lookupProcessDefinition
(((Activity.SubFlowImplementation)executing).packageId(),
((Activity.SubFlowImplementation)executing).processId());
invokedManagerName = procDef.mgrName();
invokedKey
= ((Activity.SubFlowImplementation)executing).processKey();
executor = wfs.processDirectory()
.lookupProcess(invokedManagerName, invokedKey).name();
executorIsSubprocess = true;
if (executor == null) {
executor = invokedManagerName;
}
} catch (InvalidKeyException e) {
// Probably no longer exists
executor
= ((Activity.SubFlowImplementation)executing).packageId()
+ "/"
+ ((Activity.SubFlowImplementation)executing).processId();
}
}
validStates = (Collection)mir.result(4);
validStatesMap = new HashMap ();
for (Iterator i = validStates.iterator(); i.hasNext();) {
String state = (String)i.next();
validStatesMap.put(state, Boolean.TRUE);
}
handledExceptions = (String[])mir.result(5);
deadlineInfos = (Activity.DeadlineInfo[])mir.result(6);
deadlineInfosByException = new HashMap ();
for (int i = 0; i < deadlineInfos.length; i++) {
deadlineInfosByException.put
(deadlineInfos[i].getExceptionName(), deadlineInfos[i]);
}
debugEnabled = ((Boolean)mir.result(7)).booleanValue();
lastStateTime = mir.resultAsDate(8);
}
private Activity activity() throws RemoteException, InvalidKeyException {
FacesContext fc = FacesContext.getCurrentInstance();
Activity act = (Activity)fc.getExternalContext()
.getRequestMap().get(uniqueDispKey);
if (act == null) {
WorkflowService wfs = WorkflowServiceConnection
.instance("workflowServiceConnection").getWorkflowService();
act = wfs.processDirectory().lookupActivity(info.uniqueKey());
fc.getExternalContext().getRequestMap().put(uniqueDispKey, act);
}
return act;
}
/**
* Show the associated process.
*/
public String showProcess () {
FacesContext fc = FacesContext.getCurrentInstance();
fc.getExternalContext().getSessionMap().put
("processSelection",
new ProcessSelection
(info.uniqueKey().managerName(),
info.uniqueKey().processKey()));
return "showProcessDetail";
}
/**
* Show the associated process.
*/
public String showInvokedProcess () {
FacesContext fc = FacesContext.getCurrentInstance();
fc.getExternalContext().getSessionMap().put
("processSelection",
new ProcessSelection (invokedManagerName, invokedKey));
return "showProcessDetail";
}
/**
* @return Returns the activityKey.
*/
public String getProcessKey() {
return info.uniqueKey().processKey();
}
/**
* @return Returns the activityKey.
*/
public String getActivityKey() {
return info.uniqueKey().activityKey();
}
/**
* @return Returns the uniqueKey.
*/
public String getUniqueKey() {
return uniqueDispKey;
}
/**
* @return Returns the process name.
*/
public String getProcessName() {
return info.processName();
}
/**
* @return Returns the process name and key.
*/
public String getProcessNameAndKey() {
return info.processName() + "/" + info.uniqueKey().processKey();
}
/**
* @return Returns the activityName.
*/
public String getActivityName() {
return info.name();
}
/**
* @return Returns the priority.
*/
public int getPriority() {
return info.priority();
}
/**
* @return Returns the lastStateTime.
*/
public Date getLastStateTime() {
return lastStateTime;
}
/**
* @return Returns the state name.
*/
public String getState() {
return state;
}
/**
* Check if activity is running.
* @return result
*/
public boolean isRunning() {
return state.startsWith("open.running");
}
/**
* Checks if activity is being debugged.
* @return result
*/
public boolean isInDebugMode () {
return debugEnabled;
}
/**
* @return Returns the state name.
*/
public String getStateName() {
return StateNameMapper.mapState(state);
}
/**
* @return URL of state icon or null
*/
public String getStateIconUrl () {
return StateNameMapper.stateIconUrl (state);
}
/**
* @return Returns the executor.
*/
public String getExecutor() {
return executor;
}
/**
* @return if executor is subprocess
*/
public boolean isExecutorSubprocess() {
return executorIsSubprocess;
}
/**
* @return the current assignee
*/
public List getAssignees () {
return assignees;
}
/**
* Check if the given state is valid (i.e. may be used in setState).
* @param state the checkState
*/
public Map getValidStatesMap () {
return validStatesMap;
}
/**
* Suspend the activity
*/
public String suspend () throws RemoteException {
try {
activity().suspend();
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidControlOperationException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Resume the activity
*/
public String resume () throws RemoteException {
try {
activity().resume();
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidControlOperationException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Resume the activity
*/
public String clearExceptionAndResume () throws RemoteException {
try {
activity().changeState("open.not_running.suspended.clearing_exception");
activity().resume();
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidStateException e) {
logger.debug ("Invalid state:" + e.getMessage());
} catch (TransitionNotAllowedException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
} catch (InvalidControlOperationException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Terminate the activity
*/
public String terminate () throws RemoteException {
try {
activity().terminate();
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidControlOperationException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Abort the activity
*/
public String abort () throws RemoteException {
try {
activity().abort();
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidControlOperationException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Continue the activity
*/
public String continueExecution () throws RemoteException {
try {
activity().changeState("open.running");
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidStateException e) {
// cannot happen, state name hard-coded
} catch (TransitionNotAllowedException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Continue the activity
*/
public String skip () throws RemoteException {
try {
activity().changeState("open.running.debug.skipping");
} catch (InvalidKeyException e) {
logger.debug ("Referenced activity does not exist (ignored):"
+ e.getMessage());
} catch (InvalidStateException e) {
// cannot happen, state name hard-coded
} catch (TransitionNotAllowedException e) {
JSFUtil.addMessage (FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotChangeState", null, e);
}
return null;
}
/**
* Send an exception.
*/
public String sendException () throws RemoteException {
Activity.DeadlineInfo dl = null;
DataModel he = getHandledExceptions();
if (he == null) {
return null;
}
String exception = ((String[])he.getWrappedData())[he.getRowIndex()];
for (int i = 0; i < deadlineInfos.length; i++) {
if (deadlineInfos[i].getExceptionName().equals(exception)) {
dl = deadlineInfos[i];
break;
}
}
Activity activity = null;
try {
activity = activity();
} catch (InvalidKeyException e) {
// TODO:
}
MethodInvocationBatch mib = new MethodInvocationBatch(true);
if (dl == null
|| (dl.getExecutionMode() == Activity.DeadlineInfo.SYNCHR)) {
// not an asynchronous deadline, abandon if not aborted
if (!state.startsWith("open.running.debug.abandoning")) {
mib.addInvocation(activity, "abandon",
new String[] { "java.lang.String" },
new Object[] { exception });
}
mib.addInvocation(activity, "changeState",
new String[] { "java.lang.String" },
new Object[] { "open.running.debug.awaiting_exception" });
mib.addInvocation(activity, "abandon",
new String[] { "java.lang.String" },
new Object[] { exception });
} else {
// asynchronous deadline, simply have it sent
mib.addInvocation(activity, "changeState",
new String[] { "java.lang.String" },
new Object[] { "open.running.debug.forwarding_exception" });
mib.addInvocation(activity, "abandon",
new String[] { "java.lang.String" },
new Object[] { exception });
}
try {
MethodInvocationBatch.Result mir = (MethodInvocationBatch.Result)
WorkflowServiceConnection.instance("workflowServiceConnection")
.getWorkflowService().executeBatch(mib);
if (mir.hasExceptions()) {
JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"cannotSendException", null, mir.firstException());
}
} catch (InvocationTargetException e) {
logger.error
("Unexpected exception: " + e.getCause().getMessage(), e);
JSFUtil.addMessage(FacesMessage.SEVERITY_ERROR, L10N_MSGS,
"resourceCurrentlyUnavailable", null);
}
return null;
}
/**
* The show events action for the activity events
*
* @return outcome
*/
public String showEvents () {
FacesContext fc = FacesContext.getCurrentInstance();
((ProcessSelection)fc.getExternalContext().getSessionMap()
.get("processSelection")).setEventSelection(getActivityKey());
return "showAuditEvents";
}
/**
* Return the map for storing the expanded state.
* @return result
*/
private Map expandedActivities() {
FacesContext fc = FacesContext.getCurrentInstance();
Map xm = (Map)fc.getExternalContext()
.getSessionMap().get("expandedActs");
if (xm == null) {
xm = new HashMap ();
fc.getExternalContext().getSessionMap().put ("expandedActs", xm);
}
return xm;
}
/**
* Check if the display can be expanded.
* @return result
*/
public boolean isDisplayExpandable () {
return getHandledExceptions() != null
&& getHandledExceptions().getRowCount() > 0;
}
/**
* Check if activity is to be displayed as expanded.
* @return result
*/
public boolean isDisplayExpanded () {
return expandedActivities().containsKey(getUniqueKey());
}
/**
* Toggle the display expanded state.
*/
public String toggleDisplayExpanded () {
if (isDisplayExpanded()) {
expandedActivities().remove(getUniqueKey());
} else {
expandedActivities().put(getUniqueKey(), Boolean.TRUE);
}
return null;
}
/**
* Return the list of handled exceptions.
* @return the result
*/
public DataModel getHandledExceptions() {
if (handledExceptionsModel == null) {
handledExceptionsModel = new ArrayDataModel(handledExceptions);
}
return handledExceptionsModel;
}
/**
* Return the deadline infos.
* @return the result
*/
public Map getDeadlineInfos() {
return deadlineInfosByException;
}
}