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: AbstractResponseGenerator.java,v 1.12 2007/03/29 11:46:54 schnelle Exp $
021: *
022: * $Log: AbstractResponseGenerator.java,v $
023: * Revision 1.12 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.11 2007/03/27 21:59:42 mlipp
027: * Fixed lots of checkstyle warnings.
028: *
029: * Revision 1.10 2007/03/01 12:32:57 schnelle
030: * Enhanced Instance.SetProperties to process ContextData.
031: *
032: * Revision 1.9 2007/02/28 13:19:36 drmlipp
033: * Fixed charset problem.
034: *
035: * Revision 1.8 2007/02/16 21:00:21 mlipp
036: * Fixed some null pointer problems.
037: *
038: * Revision 1.7 2007/02/01 10:08:36 drmlipp
039: * Removed no longer used observer resource.
040: *
041: * Revision 1.6 2007/01/31 22:55:36 mlipp
042: * Some more refactoring and fixes of problems introduced by refactoring.
043: *
044: * Revision 1.5 2007/01/31 14:53:06 schnelle
045: * Small corrections wvaluating the resource reference.
046: *
047: * Revision 1.4 2007/01/31 13:42:02 schnelle
048: * Added convenient method to retrieve a boolean parameter.
049: *
050: * Revision 1.3 2007/01/31 12:24:04 drmlipp
051: * Design revisited.
052: *
053: * Revision 1.2 2007/01/30 11:56:14 drmlipp
054: * Merged Wf-XML branch.
055: *
056: * Revision 1.1.2.26 2007/01/29 15:04:20 schnelle
057: * Renaming of Observer to ObserverRegistry and URIDecoder to ResourceReference.
058: *
059: * Revision 1.1.2.25 2007/01/29 13:50:35 drmlipp
060: * Minor cleanup.
061: *
062: * Revision 1.1.2.24 2007/01/29 13:40:32 schnelle
063: * Storing of the sender base in the servlet context.
064: *
065: * Revision 1.1.2.23 2007/01/29 13:10:26 drmlipp
066: * Using utilities method for timestamp convertion.
067: *
068: * Revision 1.1.2.22 2007/01/26 15:50:28 schnelle
069: * Added encoding for process id and package id.
070: *
071: * Revision 1.1.2.21 2007/01/24 10:56:50 schnelle
072: * Prepared return of a result for aobservers.
073: *
074: * Revision 1.1.2.20 2007/01/19 12:34:56 schnelle
075: * Moved generation and decoding of the URI that is used as the receiver key to new class URIDecoder.
076: *
077: * Revision 1.1.2.19 2007/01/19 07:59:35 schnelle
078: * Corrected return value for factory list instances.
079: *
080: * Revision 1.1.2.18 2007/01/16 11:05:42 schnelle
081: * Refactoring: Moved subscription handling methods to own class.
082: *
083: * Revision 1.1.2.17 2007/01/11 11:37:10 schnelle
084: * Added subscription if an oberver key is given in the creation of a process.
085: *
086: * Revision 1.1.2.16 2007/01/11 10:23:52 schnelle
087: * Creation of StateChanged notifications.
088: *
089: * Revision 1.1.2.15 2006/12/20 14:37:59 schnelle
090: * Implemented Factory GetDefinition.
091: *
092: * Revision 1.1.2.14 2006/12/20 13:32:25 schnelle
093: * Basic implementato of GetProperties for Instance and Activity.
094: *
095: * Revision 1.1.2.13 2006/12/19 14:43:29 schnelle
096: * Implementation of GetProperties for ServiceRegistry and Factory.
097: *
098: * Revision 1.1.2.12 2006/12/18 14:41:03 schnelle
099: * Preparatation for individual schema definition for each getproperties request.
100: *
101: * Revision 1.1.2.11 2006/12/18 11:56:51 schnelle
102: * Returning the XPDL node after a NewDefiniton to the ServiceRegistry.
103: *
104: * Revision 1.1.2.10 2006/12/14 08:50:21 schnelle
105: * Implemented CompleteActivity.
106: *
107: * Revision 1.1.2.9 2006/12/13 11:23:48 schnelle
108: * Implemented instance ListActivities.
109: *
110: * Revision 1.1.2.8 2006/12/12 13:24:38 schnelle
111: * Introduction of ASAPException to provide a detailed mesage.
112: *
113: * Revision 1.1.2.7 2006/12/11 11:05:34 schnelle
114: * Added template methods for all requests.
115: *
116: * Revision 1.1.2.6 2006/12/01 12:49:54 schnelle
117: * Basic import of context data for process creation.
118: *
119: * Revision 1.1.2.5 2006/11/30 12:45:08 schnelle
120: * Basic implementation of Factory CreateInstance.
121: *
122: * Revision 1.1.2.4 2006/11/29 14:12:37 schnelle
123: * Take respect to namespaces of asap requests and responses.
124: *
125: * Revision 1.1.2.3 2006/11/29 11:05:22 schnelle
126: * Full implementation of the request and response headers.
127: *
128: * Revision 1.1.2.2 2006/11/28 15:31:51 schnelle
129: * Proper selection of the response generator.
130: *
131: * Revision 1.1.2.1 2006/11/28 12:20:09 schnelle
132: * Creation of a separate class to handle the issues for a specific resource.
133: *
134: */
135: package de.danet.an.workflow.clients.wfxml;
136:
137: import java.io.ByteArrayOutputStream;
138: import java.rmi.RemoteException;
139: import java.util.Iterator;
140:
141: import javax.xml.soap.Name;
142: import javax.xml.soap.Node;
143: import javax.xml.soap.SOAPBody;
144: import javax.xml.soap.SOAPBodyElement;
145: import javax.xml.soap.SOAPElement;
146: import javax.xml.soap.SOAPEnvelope;
147: import javax.xml.soap.SOAPException;
148: import javax.xml.soap.SOAPFactory;
149: import javax.xml.soap.SOAPHeader;
150: import javax.xml.soap.SOAPHeaderElement;
151: import javax.xml.soap.SOAPMessage;
152: import javax.xml.soap.SOAPPart;
153: import javax.xml.soap.Text;
154: import javax.xml.transform.Transformer;
155: import javax.xml.transform.TransformerException;
156: import javax.xml.transform.TransformerFactory;
157: import javax.xml.transform.dom.DOMSource;
158: import javax.xml.transform.stream.StreamResult;
159:
160: import org.xml.sax.SAXException;
161:
162: import de.danet.an.util.XMLUtil;
163: import de.danet.an.util.sax.HandlerStack;
164: import de.danet.an.util.soap.SOAPBuilder;
165: import de.danet.an.workflow.api.SAXEventBuffer;
166: import de.danet.an.workflow.api.WorkflowService;
167:
168: /**
169: * This class provides the basic functionality of a SOAP based interface between
170: * the workflow engine (i.e. its processes) and any SOAP based client.
171: *
172: * @author Dirk Schnelle
173: */
174: abstract class AbstractResponseGenerator {
175:
176: /** Connection to the workflow service. */
177: private WorkflowService wfs = null;
178: /** The URI decoder. */
179: private ResourceReference resourceReference;
180:
181: public static final String RESOURCE_SERVICE_REGISTRY = "ServiceRegistry";
182: public static final String RESOURCE_FACTORY = "Factory";
183: public static final String RESOURCE_INSTANCE = "Instance";
184: public static final String RESOURCE_ACTIVITY = "Activity";
185:
186: private final ObserverRegistry observerRegistry;
187:
188: /**
189: * Constructs a new object.
190: *
191: * @param observerRegistry the observer registry
192: * @param wfs Reference to the workflow engine.
193: * @param resRef the resource reference.
194: */
195: public AbstractResponseGenerator(ObserverRegistry observerRegistry,
196: WorkflowService wfs, ResourceReference resRef) {
197: this .observerRegistry = observerRegistry;
198: this .wfs = wfs;
199: this .resourceReference = resRef;
200: }
201:
202: /**
203: * Retrieves the observer registry.
204: * @return the observer registry.
205: */
206: protected ObserverRegistry getObserverRegistry() {
207: return observerRegistry;
208: }
209:
210: /**
211: * @return Returns the resourceReference.
212: */
213: public ResourceReference getResourceReference() {
214: return resourceReference;
215: }
216:
217: /**
218: * @return Returns the workflow service.
219: */
220: public WorkflowService getWorkflowService() {
221: return wfs;
222: }
223:
224: /**
225: * Retrieves the text content of the child node with the specified name.
226: * @param message the message to inspect.
227: * @param element the parent node.
228: * @param prefix prefix of the searched child node.
229: * @param name name of the searched child node.
230: * @return text content, or <code>null</code> if there is no such child
231: * node.
232: * @throws SOAPException
233: * error inspecting the SOAP message.
234: */
235: protected String getChildTextContent(SOAPMessage message,
236: SOAPElement element, String prefix, String name)
237: throws SOAPException {
238: SOAPPart part = message.getSOAPPart();
239: SOAPEnvelope envelope = part.getEnvelope();
240: Name nm = envelope.createName(name, prefix, Consts.ASAP_NS);
241: Iterator i = element.getChildElements(nm);
242: if (!i.hasNext()) {
243: return null;
244: }
245: SOAPElement child = (SOAPElement) i.next();
246:
247: return XMLUtil.getFirstLevelTextContent(child);
248: }
249:
250: /**
251: * Retrieves the first element of the given request body that contains the
252: * requested action to perform. It is guaranteed that the element has the
253: * correct namespace.
254: * @param request the current request.
255: * @return action element
256: * @throws SOAPException
257: * error evaluating the request.
258: */
259: protected SOAPBodyElement getActionElement(SOAPMessage request)
260: throws SOAPException {
261: SOAPBody body = request.getSOAPBody();
262: for (Iterator i = body.getChildElements(); i.hasNext();) {
263: Node node = (Node) i.next();
264: if (node instanceof SOAPBodyElement) {
265: SOAPBodyElement bodyElement = (SOAPBodyElement) node;
266:
267: Name bodyElementName = bodyElement.getElementName();
268: String headerUri = bodyElementName.getURI();
269: if (headerUri.equals(Consts.ASAP_NS)
270: || headerUri.equals(Consts.WFXML_NS)) {
271: return bodyElement;
272: }
273: }
274: }
275:
276: throw new SOAPException("Missing body.");
277: }
278:
279: /**
280: * Handles the given request.
281: * @param reqMsg the request message.
282: * @param respMsg the response message.
283: * @throws SOAPException
284: * Error in the underlying SOAP protocol.
285: * @throws RemoteException
286: * Error accessing the workflow engine.
287: */
288: public abstract void evaluate(SOAPMessage reqMsg,
289: SOAPMessage respMsg) throws SOAPException, RemoteException;
290:
291: /**
292: * Retrieves the name of this resource.
293: * @return Name of the sender instance.
294: */
295: protected abstract String getResourceName();
296:
297: /**
298: * Retrieves the ASAP <code>key</code> from the header of the message.
299: * @param message the message to inspect.
300: * @param key the key to find.
301: * @return value for the <code>key</code>, <code>null</code> if the key
302: * could not be found.
303: * @throws SOAPException
304: * error inspecting the SOAP message.
305: */
306: protected String getHeaderValue(SOAPMessage message, String key)
307: throws SOAPException {
308: SOAPHeaderElement headerElement = null;
309:
310: SOAPHeader header = message.getSOAPHeader();
311: for (Iterator i = header.getChildElements(); i.hasNext();) {
312: Node node = (Node) i.next();
313: if (node instanceof SOAPHeaderElement) {
314: headerElement = (SOAPHeaderElement) node;
315:
316: Name headerElementName = headerElement.getElementName();
317: String localName = headerElementName.getLocalName();
318: if (localName.equals(Consts.REQUEST_HEADER)
319: || localName.equals(Consts.RESPONSE_HEADER)) {
320: String headerUri = headerElementName.getURI();
321: if (headerUri.equals(Consts.ASAP_NS)
322: || headerUri.equals(Consts.WFXML_NS)) {
323: break;
324: }
325: }
326: }
327: }
328:
329: if (headerElement == null) {
330: throw new SOAPException("ASAP request header not found.");
331: }
332:
333: return getChildsTextContent(headerElement, key);
334: }
335:
336: /**
337: * Tries to find a child node with the given name. If there is such a child
338: * node, the first child is returned.
339: * @param element the parent element
340: * @param key the name of the tag to find
341: * @return first child, if the tag could be found, <code>null</code> else.
342: */
343: protected SOAPElement findChildNode(SOAPElement element, String key) {
344: for (Iterator i = element.getChildElements(); i.hasNext();) {
345: Node node = (Node) i.next();
346: if (node instanceof SOAPElement) {
347: SOAPElement child = (SOAPElement) node;
348: String name = child.getElementName().getLocalName();
349: if (name.equals(key)) {
350: return child;
351: }
352: }
353: }
354:
355: return null;
356: }
357:
358: /**
359: * Retrieves the contents of the child node with the given name as text.
360: * @param element the parent node.
361: * @param key name of the child node.
362: * @return Contents of the child node, or <code>null</code>, if the
363: * key is no child of element.
364: */
365: protected String getChildsTextContent(SOAPElement element,
366: String key) {
367: for (Iterator i = element.getChildElements(); i.hasNext();) {
368: Node node = (Node) i.next();
369: if (node instanceof SOAPElement) {
370: SOAPElement child = (SOAPElement) node;
371: String name = child.getElementName().getLocalName();
372: if (name.equals(key)) {
373: return XMLUtil.getFirstLevelTextContent(child);
374: }
375: }
376: }
377:
378: return null;
379: }
380:
381: /**
382: * Fills the response header.
383: * @param reqMsg the request message.
384: * @param respMsg the response message.
385: * @exception SOAPException
386: * Error in the SOAP message.
387: */
388: void fillResponseHeader(SOAPMessage reqMsg, SOAPMessage respMsg)
389: throws SOAPException {
390:
391: SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
392: SOAPHeader respHeader = respEnv.getHeader();
393: SOAPHeaderElement respNode = respHeader
394: .addHeaderElement(respEnv.createName(
395: Consts.RESPONSE_HEADER, Consts.ASAP_PREFIX,
396: Consts.ASAP_NS));
397:
398: // Set the sender to me. This might differ from the value, given in
399: // the request as ReceiverKey. We set it to the URL under which we were
400: // called.
401: SOAPElement skNode = respNode.addChildElement(
402: Consts.SENDER_KEY, Consts.ASAP_PREFIX);
403: skNode.addTextNode(resourceReference.getResourceKey());
404:
405: // Set the receiver if it was specified in the request.
406: String receiver = getHeaderValue(reqMsg, Consts.SENDER_KEY);
407: if (receiver != null) {
408: SOAPElement node = respNode.addChildElement(
409: Consts.RECEIVER_KEY, Consts.ASAP_PREFIX,
410: Consts.ASAP_NS);
411: node.addTextNode(receiver);
412: }
413:
414: // Retain the RequestId if given.
415: String requestId = getHeaderValue(reqMsg, Consts.REQUEST_ID);
416: if (requestId != null) {
417: SOAPElement node = respNode.addChildElement(
418: Consts.REQUEST_ID, Consts.ASAP_PREFIX,
419: Consts.ASAP_NS);
420: node.addTextNode(requestId);
421: }
422: }
423:
424: /**
425: * Adds the text as a child node, if it is neither <code>null</code> nor
426: * empty.
427: * @param element the parent node.
428: * @param text the text to add.
429: * @throws SOAPException
430: * error appending the text node.
431: */
432: protected void maybeAddTextNode(SOAPElement element, String text)
433: throws SOAPException {
434: if ((text == null) || (text.length() == 0)) {
435: return;
436: }
437:
438: element.addTextNode(text);
439: }
440:
441: /**
442: * Convenience method to create response node for WfXML requests.
443: * @param respMsg the response message.
444: * @param nodeName name of the response node.
445: * @return created response node.
446: * @throws SOAPException
447: * error creating the SOAP nodes.
448: */
449: protected SOAPBodyElement createWfxmlResponseNode(
450: SOAPMessage respMsg, String nodeName) throws SOAPException {
451: SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
452: SOAPBody respBody = respEnv.getBody();
453: Name respName = respEnv.createName(nodeName,
454: Consts.WFXML_PREFIX, Consts.WFXML_NS);
455:
456: SOAPBodyElement node = respBody.addBodyElement(respName);
457: node
458: .addNamespaceDeclaration(Consts.ASAP_PREFIX,
459: Consts.ASAP_NS);
460:
461: return node;
462: }
463:
464: /**
465: * Convenience method to create response node for WfXML requests.
466: * @param respMsg the repsonse message.
467: * @param nodeName name of the response node.
468: * @return created response node.
469: * @throws SOAPException
470: * error creating the SOAP nodes.
471: */
472: protected SOAPBodyElement createAsapResponseNode(
473: SOAPMessage respMsg, String nodeName) throws SOAPException {
474: SOAPEnvelope respEnv = respMsg.getSOAPPart().getEnvelope();
475: SOAPBody respBody = respEnv.getBody();
476: Name respName = respEnv.createName(nodeName,
477: Consts.ASAP_PREFIX, Consts.ASAP_NS);
478:
479: SOAPBodyElement node = respBody.addBodyElement(respName);
480:
481: return node;
482: }
483:
484: /**
485: * Creates a byte array representation for the given SOAP element.
486: * @param node the node to transform
487: * @return bytes for the given SOAP element.
488: * @throws TransformerException
489: * error transforming the node into a string.
490: */
491: protected byte[] nodeToBytes(SOAPElement node)
492: throws TransformerException {
493: TransformerFactory factory = TransformerFactory.newInstance();
494: Transformer transformer = factory.newTransformer();
495:
496: DOMSource source = new DOMSource(node);
497: ByteArrayOutputStream out = new ByteArrayOutputStream();
498: StreamResult result = new StreamResult(out);
499:
500: transformer.transform(source, result);
501:
502: return out.toByteArray();
503: }
504:
505: /**
506: * Imports a SOAP node as a child of another node in another document.
507: *
508: * <p>
509: * Note that this might be not namespace aware. make sure to have all
510: * namespaces resolved before calling this method.
511: * </p>
512: *
513: * @param parent the parent node
514: * @param imported the node to import
515: * @return the imported node
516: * @throws SOAPException
517: * error navigating through the SOAP stack.
518: */
519: protected SOAPElement importAsChild(SOAPElement parent,
520: SOAPElement imported) throws SOAPException {
521: SOAPFactory factory = SOAPFactory.newInstance();
522:
523: Name name = imported.getElementName();
524:
525: SOAPElement newChild = parent.addChildElement(name
526: .getLocalName(), name.getPrefix(), name.getURI());
527:
528: Iterator attributes = imported.getAllAttributes();
529: while (attributes.hasNext()) {
530: Name attribute = (Name) attributes.next();
531: String value = imported.getAttributeValue(attribute);
532: Name newName = factory.createName(attribute.getLocalName(),
533: attribute.getPrefix(), attribute.getURI());
534: newChild.addAttribute(newName, value);
535: }
536:
537: Iterator children = imported.getChildElements();
538: while (children.hasNext()) {
539: Object o = children.next();
540: if (o instanceof Text) {
541: String text = imported.getNodeValue();
542: if (text != null) {
543: newChild.addTextNode(text);
544: }
545: } else {
546: importAsChild(newChild, (SOAPElement) o);
547: }
548: }
549:
550: return newChild;
551: }
552:
553: /**
554: * Imports a SOAP node as a child of another node in another document.
555: *
556: * <p>
557: * Note that this might be not namespace aware. make sure to have all
558: * namespaces resolved before calling this method.
559: * </p>
560: *
561: * @param parent the parent node
562: * @param imported the node to import
563: * @return the imported node
564: * @throws SOAPException
565: * error navigating through the SOAP stack.
566: */
567: protected static SOAPElement importW3CNodeAsChild(
568: SOAPElement parent, org.w3c.dom.Node imported)
569: throws SOAPException {
570: SOAPFactory factory = SOAPFactory.newInstance();
571:
572: SOAPElement newChild = parent.addChildElement(imported
573: .getLocalName(), imported.getPrefix(), imported
574: .getNamespaceURI());
575:
576: org.w3c.dom.NamedNodeMap attributes = imported.getAttributes();
577: for (int i = 0; i < attributes.getLength(); i++) {
578: org.w3c.dom.Node attribute = attributes.item(i);
579: Name newName = factory.createName(attribute.getLocalName(),
580: attribute.getPrefix(), attribute.getNamespaceURI());
581: newChild.addAttribute(newName, attribute.getNodeValue());
582: }
583:
584: org.w3c.dom.NodeList children = imported.getChildNodes();
585: for (int i = 0; i < children.getLength(); i++) {
586: Object o = children.item(i);
587: if (o instanceof org.w3c.dom.Text) {
588: String text = imported.getNodeValue();
589: newChild.addTextNode(text);
590: } else {
591: importW3CNodeAsChild(newChild, (org.w3c.dom.Node) o);
592: }
593: }
594:
595: return newChild;
596: }
597:
598: /**
599: * Append the given SAX event buffer as a child to the parent SOAP element.
600: * @param message the message containing the parent
601: * @param parent the parent SOAP element
602: * @param sax the sax buffer to import.
603: * @throws TransformerException
604: * @throws SOAPException
605: * Error in the SOAP message.
606: */
607: protected static void importSAXAsChild(SOAPMessage message,
608: SOAPElement parent, SAXEventBuffer sax)
609: throws SOAPException {
610: SOAPEnvelope env = message.getSOAPPart().getEnvelope();
611:
612: HandlerStack hs = new HandlerStack(new SOAPBuilder(parent));
613: hs.setContextData("envelope", env);
614: try {
615: sax.emit(hs.contentHandler());
616: } catch (SAXException e) {
617: throw (IllegalArgumentException) (new IllegalArgumentException(
618: e.getMessage()).initCause(e));
619: }
620: }
621: }
|