001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2006 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: ActivityResponseGenerator.java,v 1.10 2007/03/29 11:46:54 schnelle Exp $
021: *
022: * $Log: ActivityResponseGenerator.java,v $
023: * Revision 1.10 2007/03/29 11:46:54 schnelle
024: * Reactivated ASAPException to propagate ASAP error messages in cases of an invalid key, a missing resource or an invalid factory.
025: *
026: * Revision 1.9 2007/02/16 21:00:21 mlipp
027: * Fixed some null pointer problems.
028: *
029: * Revision 1.8 2007/02/01 13:44:44 schnelle
030: * Using namespace for factory schemas that do not contain '&'.
031: *
032: * Revision 1.7 2007/02/01 12:38:36 schnelle
033: * Use of encoded key for properties.
034: *
035: * Revision 1.6 2007/01/31 22:55:36 mlipp
036: * Some more refactoring and fixes of problems introduced by refactoring.
037: *
038: * Revision 1.5 2007/01/31 12:24:05 drmlipp
039: * Design revisited.
040: *
041: * Revision 1.4 2007/01/30 15:07:37 schnelle
042: * Filtering of all instances for the current factory in a list instances request.
043: *
044: * Revision 1.3 2007/01/30 12:47:45 drmlipp
045: * Added startdate property to response.
046: *
047: * Revision 1.2 2007/01/30 11:56:14 drmlipp
048: * Merged Wf-XML branch.
049: *
050: * Revision 1.1.2.19 2007/01/29 15:04:22 schnelle
051: * Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
052: *
053: * Revision 1.1.2.17 2007/01/29 13:40:31 schnelle
054: * Storing of the sender base in the servlet context.
055: *
056: * Revision 1.1.2.16 2007/01/29 13:10:26 drmlipp
057: * Using utilities method for timestamp convertion.
058: *
059: * Revision 1.1.2.15 2007/01/24 11:46:36 schnelle
060: * Moved wsdl files and xsd files intot resources subdirectory.
061: *
062: * Revision 1.1.2.14 2007/01/24 10:56:50 schnelle
063: * Prepared return of a result for aobservers.
064: *
065: * Revision 1.1.2.13 2007/01/19 12:34:56 schnelle
066: * Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
067: *
068: * Revision 1.1.2.12 2007/01/16 11:05:42 schnelle
069: * Refactoring: Moved subscription handling methods to own class.
070: *
071: * Revision 1.1.2.11 2007/01/09 15:53:37 schnelle
072: * Added implementation of activity set properties.
073: *
074: * Revision 1.1.2.10 2007/01/09 12:05:11 schnelle
075: * Implemented getProperties.
076: *
077: * Revision 1.1.2.9 2006/12/20 14:37:59 schnelle
078: * Implemented Factory GetDefinition.
079: *
080: * Revision 1.1.2.8 2006/12/20 13:32:24 schnelle
081: * Basic implementato of GetProperties for Instance and Activity.
082: *
083: * Revision 1.1.2.7 2006/12/18 14:41:03 schnelle
084: * Preparatation for individual schema definition for each getproperties request.
085: *
086: * Revision 1.1.2.6 2006/12/14 08:50:21 schnelle
087: * Implemented CompleteActivity.
088: *
089: * Revision 1.1.2.5 2006/12/13 11:23:48 schnelle
090: * Implemented instance ListActivities.
091: *
092: * Revision 1.1.2.4 2006/12/12 13:24:38 schnelle
093: * Introduction of ASAPException to provide a detailed mesage.
094: *
095: * Revision 1.1.2.3 2006/12/12 09:34:35 schnelle
096: * Implemented ChangeState for Instance.
097: *
098: * Revision 1.1.2.2 2006/12/11 11:05:34 schnelle
099: * Added template methods for all requests.
100: *
101: * Revision 1.1.2.1 2006/11/28 12:20:09 schnelle
102: * Creation of a separate class to handle the issues for a specific resource.
103: *
104: * Revision 1.1.2.2 2006/11/27 15:41:55 schnelle
105: * Introducing some constants for request and response identification.
106: *
107: * Revision 1.1.2.1 2006/11/24 12:19:13 schnelle
108: * Separtion of response generation into ResponseGenerator class.
109: *
110: */
111: package de.danet.an.workflow.clients.wfxml;
112:
113: import java.rmi.RemoteException;
114: import java.util.Collection;
115: import java.util.Date;
116: import java.util.Iterator;
117: import java.util.NoSuchElementException;
118:
119: import javax.xml.soap.SOAPBodyElement;
120: import javax.xml.soap.SOAPElement;
121: import javax.xml.soap.SOAPException;
122: import javax.xml.soap.SOAPMessage;
123:
124: import de.danet.an.util.XMLUtil;
125: import de.danet.an.workflow.api.Activity;
126: import de.danet.an.workflow.api.ActivityUniqueKey;
127: import de.danet.an.workflow.api.InvalidKeyException;
128: import de.danet.an.workflow.api.Process;
129: import de.danet.an.workflow.api.ProcessDirectory;
130: import de.danet.an.workflow.api.WorkflowService;
131: import de.danet.an.workflow.apix.ExtActivity;
132: import de.danet.an.workflow.omgcore.CannotCompleteException;
133: import de.danet.an.workflow.omgcore.NotRunningException;
134:
135: /**
136: * This class provides the methods of an {@link AbstractResponseGenerator}
137: * that are relevant for the activity.
138: *
139: * <p>
140: * The <em>Activity</em> resource is an extension of ASAP for Wf-XML. The
141: * process instance will at any point in time be waiting for what it considers
142: * to be an external action to be completed. The activity represents this
143: * wait-point within the process. The process may be waiting for a human to
144: * interact with it, or it may be waiting for the result of an automated step in
145: * the process. The activity presents information about what the process is
146: * waiting for, such as the assignee, and possibly detail about how long it has
147: * been waiting, and how long it is willing to wait. One way of invoking an
148: * external action is through the use of ASAP or Wf-XML. In this case, the
149: * activity is acting as an observer of that remote process. The activity can
150: * provide the URL of the remote process instance that it is waiting on.
151: * </p>
152: *
153: * @author Dirk Schnelle
154: *
155: */
156: class ActivityResponseGenerator extends AbstractResponseGenerator {
157: /** Logger instance. */
158: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
159: .getLog(ActivityResponseGenerator.class);
160:
161: /**
162: * Constructs a new object.
163: *
164: * @param observerRegistry the observer registry
165: * @param wfs Reference to the workflow engine.
166: * @param decoder the URI decoder.
167: */
168: public ActivityResponseGenerator(ObserverRegistry observerRegistry,
169: WorkflowService wfs, ResourceReference decoder) {
170: super (observerRegistry, wfs, decoder);
171: }
172:
173: /* (non-Javadoc)
174: * @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator#evaluate(javax.xml.soap.SOAPMessage, javax.xml.soap.SOAPMessage)
175: */
176: public void evaluate(SOAPMessage reqMsg, SOAPMessage respMsg)
177: throws SOAPException, RemoteException {
178: SOAPBodyElement actionElement = getActionElement(reqMsg);
179:
180: String actName = actionElement.getElementName().getLocalName();
181:
182: if (actName.equals(Consts.GET_PROPERTIES_REQUEST)) {
183: getActivityProperties(reqMsg, respMsg);
184: } else if (actName.equals(Consts.SET_PROPERTIES_REQUEST)) {
185: setActivityProperties(actionElement, reqMsg, respMsg);
186: } else if (actName.equals(Consts.COMPLETE_ACTIVITY_REQUEST)) {
187: completeActivity(reqMsg, respMsg);
188: } else {
189: if (logger.isDebugEnabled()) {
190: logger.debug("unknown action '" + actName + "'");
191: }
192:
193: FaultUtils.setFault(respMsg,
194: ASAPException.ASAP_INVALID_OPERATION_SPECIFICATION,
195: getResourceName() + ": Unknown action \"" + actName
196: + "\".");
197: }
198: }
199:
200: /* (non-Javadoc)
201: * @see de.danet.an.workflow.clients.wfxml.AbstractResponseGenerator#getSender()
202: */
203: protected String getResourceName() {
204: return RESOURCE_ACTIVITY;
205: }
206:
207: /**
208: * Creates a response that contains properties of the activity.
209: *
210: * <p>
211: * This method produces XML that fails validation, since the WFXML
212: * schema definition defines a group to return theses properties, but
213: * which is never referenced, so that it must not be a part of
214: * a <code>GetpropertiesRs</code> message.
215: * </p>
216:
217: * @param reqMsg the request message.
218: * @param respMsg the response message
219: * @throws SOAPException
220: * error evaluating the request or constructing the response.
221: * @throws RemoteException
222: * error accessing the workflow engine.
223: */
224: private void getActivityProperties(SOAPMessage reqMsg,
225: SOAPMessage respMsg) throws SOAPException, RemoteException {
226: if (logger.isDebugEnabled()) {
227: logger.debug("get activity properties...");
228: }
229:
230: String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
231:
232: Activity activity = null;
233: try {
234: activity = getActivity(receiverKey);
235: } catch (InvalidKeyException e) {
236: FaultUtils.setFault(respMsg,
237: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
238: .getMessage());
239:
240: return;
241: } catch (NoSuchElementException e) {
242: FaultUtils.setFault(respMsg,
243: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
244: .getMessage());
245:
246: return;
247: }
248:
249: SOAPBodyElement propsNode = createWfxmlResponseNode(respMsg,
250: Consts.GET_PROPERTIES_RESPONSE);
251:
252: appendActivityProperties(respMsg, receiverKey, propsNode,
253: activity);
254: }
255:
256: /**
257: * Sets properties for the activity.
258: *
259: * @param action the action element.
260: * @param reqMsg the request message.
261: * @param respMsg the response message
262: * @throws SOAPException
263: * error evaluating the request or constructing the response.
264: * @throws RemoteException
265: * error accessing the workflow engine.
266: */
267: private void setActivityProperties(SOAPElement action,
268: SOAPMessage reqMsg, SOAPMessage respMsg)
269: throws SOAPException, RemoteException {
270: if (logger.isDebugEnabled()) {
271: logger.debug("set activity properties...");
272: }
273:
274: String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
275:
276: Activity activity = null;
277: try {
278: activity = getActivity(receiverKey);
279: } catch (InvalidKeyException e) {
280: FaultUtils.setFault(respMsg,
281: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
282: .getMessage());
283:
284: return;
285: } catch (NoSuchElementException e) {
286: FaultUtils.setFault(respMsg,
287: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
288: .getMessage());
289:
290: return;
291: }
292:
293: String name = getChildsTextContent(action, "Name");
294: if (name != null) {
295: activity.setName(name);
296: }
297: String description = getChildsTextContent(action, "Description");
298: if (description != null) {
299: activity.setDescription(description);
300: }
301:
302: SOAPBodyElement propsNode = createWfxmlResponseNode(respMsg,
303: Consts.SET_PROPERTIES_RESPONSE);
304:
305: appendActivityProperties(respMsg, receiverKey, propsNode,
306: activity);
307: }
308:
309: /**
310: * Reads the properties from the activity and appends them to the parent.
311: * @param respMsg the response message.
312: * @param receiverKey this activity instance
313: * @param parent the parent node of the response message.
314: * @param activity the activity
315: * @throws SOAPException
316: * error appending to the parent node
317: * @throws RemoteException
318: * error accessing the activity
319: */
320: private void appendActivityProperties(SOAPMessage respMsg,
321: String receiverKey, SOAPElement parent, Activity activity)
322: throws SOAPException, RemoteException {
323:
324: SOAPElement keyNode = parent.addChildElement("Key",
325: Consts.WFXML_PREFIX);
326: keyNode.addTextNode(getResourceReference().getResourceKey());
327: String activityState = activity.state();
328: SOAPElement state = parent.addChildElement("State",
329: Consts.WFXML_PREFIX);
330: state.addTextNode(StateMapper.omg2asapState(activityState));
331: SOAPElement nameNode = parent.addChildElement("Name",
332: Consts.WFXML_PREFIX);
333: nameNode.addTextNode(activity.name());
334: SOAPElement descNode = parent.addChildElement("Description",
335: Consts.WFXML_PREFIX);
336: if (activity.description() != null) {
337: descNode.addTextNode(activity.description());
338: }
339: Collection states = activity.validStates();
340: Iterator stateIterator = states.iterator();
341: while (stateIterator.hasNext()) {
342: String validState = (String) stateIterator.next();
343: SOAPElement stateElement = parent.addChildElement(
344: "ValidState", Consts.WFXML_PREFIX);
345: stateElement.addTextNode(StateMapper
346: .omg2asapState(validState));
347: }
348:
349: SOAPElement instanceKey = parent.addChildElement("InstanceKey",
350: Consts.WFXML_PREFIX);
351: ResourceReference procResRef = new ResourceReference(
352: getResourceReference().getBaseUrl(), (Process) activity
353: .container());
354: instanceKey.addTextNode(procResRef.getResourceKey());
355: SOAPElement remoteInstance = parent.addChildElement(
356: "RemoteInstance", Consts.WFXML_PREFIX);
357: // TODO: Clarify the meaning of RemoteInstance.
358: try {
359: Date startTime = ((ExtActivity) activity).startTime();
360: SOAPElement started = parent.addChildElement("StartedDate",
361: Consts.WFXML_PREFIX);
362: started.addTextNode(XMLUtil.toXsdGMTDateTime(startTime));
363: } catch (NotRunningException e) {
364: // should not happen, only started activities are delivered
365: // to clients anyway.
366: }
367: SOAPElement due = parent.addChildElement("DueDate",
368: Consts.WFXML_PREFIX);
369: Date dueDate = new Date(Long.MAX_VALUE);
370: // TODO: This has to be adapted to the real deadline.
371: // Currently the deadline may consist of multiple conditions which
372: // may be independent of a date.
373: due.addTextNode(XMLUtil.toXsdGMTDateTime(dueDate));
374: SOAPElement lastmodified = parent.addChildElement(
375: "LastModified", Consts.WFXML_PREFIX);
376: Activity.Info info = activity.activityInfo();
377: Date lastModifiedDate = info.lastStateTime();
378: lastmodified.addTextNode(XMLUtil
379: .toXsdGMTDateTime(lastModifiedDate));
380: }
381:
382: /**
383: * Completes the specified activity.
384: * @param reqMsg the request message.
385: * @param respMsg the response message
386: * @throws SOAPException
387: * error evaluating the request or constructing the response.
388: * @throws RemoteException
389: * error accessing the workflow engine.
390: */
391: private void completeActivity(SOAPMessage reqMsg,
392: SOAPMessage respMsg) throws SOAPException, RemoteException {
393: String receiverKey = getHeaderValue(reqMsg, Consts.RECEIVER_KEY);
394:
395: Activity activity = null;
396: try {
397: activity = getActivity(receiverKey);
398: } catch (InvalidKeyException e) {
399: FaultUtils.setFault(respMsg,
400: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
401: .getMessage());
402:
403: return;
404: } catch (NoSuchElementException e) {
405: FaultUtils.setFault(respMsg,
406: ASAPException.ASAP_INVALID_INSTANCE_KEY, e
407: .getMessage());
408:
409: return;
410: }
411:
412: try {
413: activity.complete();
414: } catch (CannotCompleteException e) {
415: FaultUtils.setFault(respMsg,
416: ASAPException.ASAP_INVALID_STATE_TRANSITION, e
417: .getMessage());
418:
419: return;
420: }
421:
422: createAsapResponseNode(respMsg,
423: Consts.COMPLETE_ACTIVITY_RESPONSE);
424:
425: if (logger.isDebugEnabled()) {
426: logger.debug("completed activity " + activity.toString());
427: }
428: }
429:
430: /**
431: * Gets the activity that that can handle the package id and the
432: * process id that are encoded in the given uri.
433: * @param uri the uri with package and process id.
434: * @return process manager.
435: * @throws RemoteException
436: * error accessing the workflow engine
437: * @throws InvalidKeyException
438: * process manager or process does not exist
439: */
440: private Activity getActivity(String uri) throws RemoteException,
441: InvalidKeyException {
442: String packageId = getResourceReference().getPackageId();
443: String processId = getResourceReference().getProcessId();
444: String processKey = getResourceReference().getProcessKey();
445: String activityKey = getResourceReference().getActivityKey();
446:
447: if (logger.isDebugEnabled()) {
448: logger.debug("finding activity definition'" + packageId
449: + "/" + processId + "/" + processKey + "/"
450: + activityKey + "'...");
451: }
452:
453: ActivityUniqueKey uniqueKey = new ActivityUniqueKey(packageId
454: + "/" + processId, processKey, activityKey);
455:
456: ProcessDirectory pd = getWorkflowService().processDirectory();
457:
458: return pd.lookupActivity(uniqueKey);
459: }
460: }
|