/*
* This file is part of the WfMOpen project.
* Copyright (C) 2001-2006 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: ActivityResponseGenerator.java 2331 2007-03-29 11:46:54Z schnelle $
*
* $Log$
* Revision 1.9 2007/02/16 21:00:21 mlipp
* Fixed some null pointer problems.
*
* Revision 1.8 2007/02/01 13:44:44 schnelle
* Using namespace for factory schemas that do not contain '&'.
*
* Revision 1.7 2007/02/01 12:38:36 schnelle
* Use of encoded key for properties.
*
* Revision 1.6 2007/01/31 22:55:36 mlipp
* Some more refactoring and fixes of problems introduced by refactoring.
*
* Revision 1.5 2007/01/31 12:24:05 drmlipp
* Design revisited.
*
* Revision 1.4 2007/01/30 15:07:37 schnelle
* Filtering of all instances for the current factory in a list instances request.
*
* Revision 1.3 2007/01/30 12:47:45 drmlipp
* Added startdate property to response.
*
* Revision 1.2 2007/01/30 11:56:14 drmlipp
* Merged Wf-XML branch.
*
* Revision 1.1.2.19 2007/01/29 15:04:22 schnelle
* Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
*
* Revision 1.1.2.17 2007/01/29 13:40:31 schnelle
* Storing of the sender base in the servlet context.
*
* Revision 1.1.2.16 2007/01/29 13:10:26 drmlipp
* Using utilities method for timestamp convertion.
*
* Revision 1.1.2.15 2007/01/24 11:46:36 schnelle
* Moved wsdl files and xsd files intot resources subdirectory.
*
* Revision 1.1.2.14 2007/01/24 10:56:50 schnelle
* Prepared return of a result for aobservers.
*
* Revision 1.1.2.13 2007/01/19 12:34:56 schnelle
* Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
*
* Revision 1.1.2.12 2007/01/16 11:05:42 schnelle
* Refactoring: Moved subscription handling methods to own class.
*
* Revision 1.1.2.11 2007/01/09 15:53:37 schnelle
* Added implementation of activity set properties.
*
* Revision 1.1.2.10 2007/01/09 12:05:11 schnelle
* Implemented getProperties.
*
* Revision 1.1.2.9 2006/12/20 14:37:59 schnelle
* Implemented Factory GetDefinition.
*
* Revision 1.1.2.8 2006/12/20 13:32:24 schnelle
* Basic implementato of GetProperties for Instance and Activity.
*
* Revision 1.1.2.7 2006/12/18 14:41:03 schnelle
* Preparatation for individual schema definition for each getproperties request.
*
* Revision 1.1.2.6 2006/12/14 08:50:21 schnelle
* Implemented CompleteActivity.
*
* Revision 1.1.2.5 2006/12/13 11:23:48 schnelle
* Implemented instance ListActivities.
*
* Revision 1.1.2.4 2006/12/12 13:24:38 schnelle
* Introduction of ASAPException to provide a detailed mesage.
*
* Revision 1.1.2.3 2006/12/12 09:34:35 schnelle
* Implemented ChangeState for Instance.
*
* Revision 1.1.2.2 2006/12/11 11:05:34 schnelle
* Added template methods for all requests.
*
* Revision 1.1.2.1 2006/11/28 12:20:09 schnelle
* Creation of a separate class to handle the issues for a specific resource.
*
* Revision 1.1.2.2 2006/11/27 15:41:55 schnelle
* Introducing some constants for request and response identification.
*
* Revision 1.1.2.1 2006/11/24 12:19:13 schnelle
* Separtion of response generation into ResponseGenerator class.
*
*/
package de.danet.an.workflow.clients.wfxml;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.NoSuchElementException;
import javax.xml.soap.SOAPBodyElement;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import de.danet.an.util.XMLUtil;
import de.danet.an.workflow.api.Activity;
import de.danet.an.workflow.api.ActivityUniqueKey;
import de.danet.an.workflow.api.InvalidKeyException;
import de.danet.an.workflow.api.Process;
import de.danet.an.workflow.api.ProcessDirectory;
import de.danet.an.workflow.api.WorkflowService;
import de.danet.an.workflow.apix.ExtActivity;
import de.danet.an.workflow.omgcore.CannotCompleteException;
import de.danet.an.workflow.omgcore.NotRunningException;
/**
* This class provides the methods of an {@link AbstractResponseGenerator}
* that are relevant for the activity.
*
* <p>
* The <em>Activity</em> resource is an extension of ASAP for Wf-XML. The
* process instance will at any point in time be waiting for what it considers
* to be an external action to be completed. The activity represents this
* wait-point within the process. The process may be waiting for a human to
* interact with it, or it may be waiting for the result of an automated step in
* the process. The activity presents information about what the process is
* waiting for, such as the assignee, and possibly detail about how long it has
* been waiting, and how long it is willing to wait. One way of invoking an
* external action is through the use of ASAP or Wf-XML. In this case, the
* activity is acting as an observer of that remote process. The activity can
* provide the URL of the remote process instance that it is waiting on.
* </p>
*
* @author Dirk Schnelle
*
*/
class ActivityResponseGenerator extends AbstractResponseGenerator {
/** Logger instance. */
private static final org.apache.commons.logging.Log logger
= org.apache.commons.logging.LogFactory.getLog
(ActivityResponseGenerator.class);
/**
* Constructs a new object.
*
* @param observerRegistry the observer registry
* @param wfs Reference to the workflow engine.
* @param decoder the URI decoder.
*/
public ActivityResponseGenerator(ObserverRegistry observerRegistry,
WorkflowService wfs, ResourceReference decoder){
super(observerRegistry, wfs, decoder);
}
/* (non-Javadoc)
* @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator#evaluate(javax.xml.soap.SOAPMessage, javax.xml.soap.SOAPMessage)
*/
public void evaluate(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
SOAPBodyElement actionElement = getActionElement(reqMsg);
String actName = actionElement.getElementName().getLocalName();
if (actName.equals (Consts.GET_PROPERTIES_REQUEST)) {
getActivityProperties(reqMsg, respMsg);
} else if (actName.equals (Consts.SET_PROPERTIES_REQUEST)) {
setActivityProperties(actionElement, reqMsg, respMsg);
} else if (actName.equals (Consts.COMPLETE_ACTIVITY_REQUEST)) {
completeActivity(reqMsg, respMsg);
} else {
if (logger.isDebugEnabled()) {
logger.debug("unknown action '" + actName + "'");
}
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_OPERATION_SPECIFICATION,
getResourceName() + ": Unknown action \"" + actName
+ "\".");
}
}
/* (non-Javadoc)
* @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator#getSender()
*/
protected String getResourceName() {
return RESOURCE_ACTIVITY;
}
/**
* Creates a response that contains properties of the activity.
*
* <p>
* This method produces XML that fails validation, since the WFXML
* schema definition defines a group to return theses properties, but
* which is never referenced, so that it must not be a part of
* a <code>GetpropertiesRs</code> message.
* </p>
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void getActivityProperties(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
if (logger.isDebugEnabled()) {
logger.debug("get activity properties...");
}
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Activity activity = null;
try {
activity = getActivity(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
SOAPBodyElement propsNode
= createWfxmlResponseNode(respMsg, Consts.GET_PROPERTIES_RESPONSE);
appendActivityProperties(respMsg, receiverKey, propsNode, activity);
}
/**
* Sets properties for the activity.
*
* @param action the action element.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void setActivityProperties(SOAPElement action, SOAPMessage reqMsg,
SOAPMessage respMsg)
throws SOAPException, RemoteException {
if (logger.isDebugEnabled()) {
logger.debug("set activity properties...");
}
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Activity activity = null;
try {
activity = getActivity(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
String name = getChildsTextContent(action, "Name");
if (name != null) {
activity.setName(name);
}
String description = getChildsTextContent(action, "Description");
if (description != null) {
activity.setDescription(description);
}
SOAPBodyElement propsNode
= createWfxmlResponseNode(respMsg, Consts.SET_PROPERTIES_RESPONSE);
appendActivityProperties(respMsg, receiverKey, propsNode, activity);
}
/**
* Reads the properties from the activity and appends them to the parent.
* @param respMsg the response message.
* @param receiverKey this activity instance
* @param parent the parent node of the response message.
* @param activity the activity
* @throws SOAPException
* error appending to the parent node
* @throws RemoteException
* error accessing the activity
*/
private void appendActivityProperties(SOAPMessage respMsg,
String receiverKey, SOAPElement parent, Activity activity)
throws SOAPException, RemoteException {
SOAPElement keyNode
= parent.addChildElement("Key", Consts.WFXML_PREFIX);
keyNode.addTextNode (getResourceReference().getResourceKey());
String activityState = activity.state();
SOAPElement state
= parent.addChildElement("State", Consts.WFXML_PREFIX);
state.addTextNode(StateMapper.omg2asapState(activityState));
SOAPElement nameNode
= parent.addChildElement("Name", Consts.WFXML_PREFIX);
nameNode.addTextNode(activity.name());
SOAPElement descNode
= parent.addChildElement("Description", Consts.WFXML_PREFIX);
if (activity.description() != null) {
descNode.addTextNode(activity.description());
}
Collection states = activity.validStates();
Iterator stateIterator = states.iterator();
while (stateIterator.hasNext()) {
String validState = (String) stateIterator.next();
SOAPElement stateElement
= parent.addChildElement("ValidState", Consts.WFXML_PREFIX);
stateElement.addTextNode(StateMapper.omg2asapState(validState));
}
SOAPElement instanceKey
= parent.addChildElement("InstanceKey", Consts.WFXML_PREFIX);
ResourceReference procResRef = new ResourceReference
(getResourceReference().getBaseUrl(),
(Process)activity.container());
instanceKey.addTextNode (procResRef.getResourceKey());
SOAPElement remoteInstance
= parent.addChildElement("RemoteInstance", Consts.WFXML_PREFIX);
// TODO: Clarify the meaning of RemoteInstance.
try {
Date startTime = ((ExtActivity)activity).startTime();
SOAPElement started
= parent.addChildElement("StartedDate", Consts.WFXML_PREFIX);
started.addTextNode (XMLUtil.toXsdGMTDateTime(startTime));
} catch (NotRunningException e) {
// should not happen, only started activities are delivered
// to clients anyway.
}
SOAPElement due
= parent.addChildElement("DueDate", Consts.WFXML_PREFIX);
Date dueDate = new Date(Long.MAX_VALUE);
// TODO: This has to be adapted to the real deadline.
// Currently the deadline may consist of multiple conditions which
// may be independent of a date.
due.addTextNode(XMLUtil.toXsdGMTDateTime(dueDate));
SOAPElement lastmodified
= parent.addChildElement("LastModified", Consts.WFXML_PREFIX);
Activity.Info info = activity.activityInfo();
Date lastModifiedDate = info.lastStateTime();
lastmodified.addTextNode(XMLUtil.toXsdGMTDateTime(lastModifiedDate));
}
/**
* Completes the specified activity.
* @param reqMsg the request message.
* @param respMsg the response message
* @throws SOAPException
* error evaluating the request or constructing the response.
* @throws RemoteException
* error accessing the workflow engine.
*/
private void completeActivity(SOAPMessage reqMsg, SOAPMessage respMsg)
throws SOAPException, RemoteException {
String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
Activity activity = null;
try {
activity = getActivity(receiverKey);
} catch (InvalidKeyException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
} catch (NoSuchElementException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_INSTANCE_KEY, e.getMessage());
return;
}
try {
activity.complete();
} catch (CannotCompleteException e) {
FaultUtils.setFault(respMsg,
ASAPException.ASAP_INVALID_STATE_TRANSITION,
e.getMessage());
return;
}
createAsapResponseNode(respMsg, Consts.COMPLETE_ACTIVITY_RESPONSE);
if (logger.isDebugEnabled()) {
logger.debug("completed activity " + activity.toString());
}
}
/**
* Gets the activity that that can handle the package id and the
* process id that are encoded in the given uri.
* @param uri the uri with package and process id.
* @return process manager.
* @throws RemoteException
* error accessing the workflow engine
* @throws InvalidKeyException
* process manager or process does not exist
*/
private Activity getActivity(String uri)
throws RemoteException, InvalidKeyException {
String packageId = getResourceReference().getPackageId();
String processId = getResourceReference().getProcessId();
String processKey = getResourceReference().getProcessKey();
String activityKey = getResourceReference().getActivityKey();
if (logger.isDebugEnabled()) {
logger.debug("finding activity definition'" + packageId + "/"
+ processId + "/" + processKey + "/" + activityKey
+ "'...");
}
ActivityUniqueKey uniqueKey = new ActivityUniqueKey(packageId + "/"
+ processId, processKey, activityKey);
ProcessDirectory pd = getWorkflowService().processDirectory();
return pd.lookupActivity(uniqueKey);
}
}