001: /*
002: * This file is part of the WfMOpen project.
003: * Copyright (C) 2001-2003 Danet GmbH (www.danet.de), GS-AN.
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: SOAPClient.java,v 1.5 2006/09/29 12:32:13 drmlipp Exp $
021: *
022: * $Log: SOAPClient.java,v $
023: * Revision 1.5 2006/09/29 12:32:13 drmlipp
024: * Consistently using WfMOpen as projct name now.
025: *
026: * Revision 1.4 2005/09/05 09:41:49 drmlipp
027: * Synchronized with 1.3.2.
028: *
029: * Revision 1.3 2005/08/25 13:24:22 drmlipp
030: * Synchronized with 1.3.1p6.
031: *
032: * Revision 1.2 2005/04/24 18:59:43 mlipp
033: * Fixed bug with scalar result.
034: *
035: * Revision 1.1.1.4 2004/08/18 15:17:39 drmlipp
036: * Update to 1.2
037: *
038: * Revision 1.12 2004/04/16 07:56:19 lipp
039: * Make sure that we use Axis (even on WLS).
040: *
041: * Revision 1.11 2004/04/01 15:21:34 lipp
042: * Using new tool helpers.
043: *
044: * Revision 1.10 2004/03/21 21:30:22 lipp
045: * Added Document for absolut paths.
046: *
047: * Revision 1.9 2004/03/21 20:35:36 lipp
048: * Using exception signalling.
049: *
050: * Revision 1.8 2004/02/21 21:31:01 lipp
051: * Some more refactoring to resolve cyclic dependencies.
052: *
053: * Revision 1.7 2003/09/09 08:33:33 lipp
054: * Fixed loop bug.
055: *
056: * Revision 1.6 2003/06/27 12:46:06 lipp
057: * Got SOAPClient running.
058: *
059: * Revision 1.5 2003/06/27 08:51:44 lipp
060: * Fixed copyright/license information.
061: *
062: * Revision 1.4 2003/06/25 15:50:29 lipp
063: * New SOAP client that makes wsif obsolete.
064: *
065: * Revision 1.3 2003/06/24 22:41:43 lipp
066: * Fixed loop bug.
067: *
068: * Revision 1.2 2003/06/24 16:08:12 lipp
069: * Implemented invocation.
070: *
071: * Revision 1.1 2003/06/23 22:06:20 lipp
072: * Started alternate SOAP client.
073: *
074: */
075: package de.danet.an.workflow.tools.soapclient;
076:
077: import java.io.Serializable;
078:
079: import java.util.ArrayList;
080: import java.util.Collection;
081: import java.util.HashMap;
082: import java.util.HashSet;
083: import java.util.Iterator;
084: import java.util.List;
085: import java.util.Map;
086: import java.util.Set;
087:
088: import java.net.ConnectException;
089: import java.rmi.RemoteException;
090:
091: import javax.wsdl.Definition;
092: import javax.wsdl.Operation;
093: import javax.wsdl.Part;
094: import javax.wsdl.Port;
095: import javax.wsdl.PortType;
096: import javax.wsdl.Service;
097: import javax.wsdl.extensions.ExtensibilityElement;
098: import javax.wsdl.extensions.soap.SOAPAddress;
099: import javax.wsdl.factory.WSDLFactory;
100: import javax.wsdl.xml.WSDLReader;
101: import javax.xml.rpc.Call;
102: import javax.xml.rpc.ParameterMode;
103: import javax.xml.rpc.encoding.DeserializerFactory;
104: import javax.xml.rpc.encoding.SerializerFactory;
105: import javax.xml.rpc.encoding.TypeMapping;
106: import javax.xml.rpc.encoding.TypeMappingRegistry;
107:
108: import org.w3c.dom.Element;
109: import org.w3c.dom.NodeList;
110:
111: import org.jaxen.JaxenException;
112: import org.jaxen.XPath;
113: import org.jaxen.jdom.JDOMXPath;
114: import org.jdom.Document;
115:
116: import de.danet.an.workflow.util.SAXEventBufferImpl;
117: import de.danet.an.workflow.util.XPDLUtil;
118:
119: import de.danet.an.workflow.api.Activity;
120: import de.danet.an.workflow.api.ActivityUniqueKey;
121: import de.danet.an.workflow.api.FormalParameter;
122:
123: import de.danet.an.workflow.spis.aii.ApplicationNotStoppedException;
124: import de.danet.an.workflow.spis.aii.CannotExecuteException;
125: import de.danet.an.workflow.spis.aii.ExceptionMappingProvider;
126: import de.danet.an.workflow.spis.aii.ResultProvider;
127: import de.danet.an.workflow.spis.aii.ToolAgent;
128: import de.danet.an.workflow.spis.aii.XMLArgumentTypeProvider;
129:
130: /**
131: * This class provides a client for RPC style SOAP invocations.
132: *
133: * @author <a href="mailto:mnl@mnl.de">Michael N. Lipp</a>
134: * @version $Revision: 1.5 $
135: */
136:
137: public class SOAPClient implements ToolAgent, XMLArgumentTypeProvider,
138: ResultProvider, ExceptionMappingProvider, Serializable {
139:
140: private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory
141: .getLog(SOAPClient.class);
142:
143: private String method = null;
144: // hold the mapping of return parameter name and eventually the XPath
145: // expression
146: private Map returnParamInfo = new HashMap();
147: private Definition wsdlDef = null;
148: private Service service = null;
149: private Port port = null;
150: private PortType portType = null;
151: private String portLocation = null;
152: private Operation operation = null;
153:
154: /** The result container. */
155: private ThreadLocal result = new ThreadLocal();
156:
157: /**
158: * Creates an instance of <code>SOAPClient</code>
159: * with all attributes initialized to default values.
160: */
161: public SOAPClient() {
162: }
163:
164: /**
165: * Return the requested type for XML arguments.
166: * @return one of <code>XML_AS_W3C_DOM</code>,
167: * <code>XML_AS_JDOM</code> or <code>XML_AS_SAX</code>
168: */
169: public int requestedXMLArgumentType() {
170: return XMLArgumentTypeProvider.XML_AS_SAX;
171: }
172:
173: /**
174: * Set the definition of WSDL. It is used to invoke the defined method
175: * dynamically.
176: *
177: * @param wsdlLocation the location of given WSDL definition.
178: */
179: public void setWSDL(String wsdlLocation) {
180: try {
181: if ((wsdlLocation == null) || wsdlLocation.equals("")) {
182: return;
183: }
184: WSDLReader rdr = WSDLFactory.newInstance().newWSDLReader();
185: wsdlDef = rdr.readWSDL(wsdlLocation);
186: } catch (Exception e) {
187: logger.error("Error setting WSDL: " + e.getMessage(), e);
188: }
189: }
190:
191: /**
192: * Set the definition of WSDL. It is used to invoke the defined method
193: * dynamically.
194: *
195: * @param wsdl the given WSDL definition in W3C DOM Element.
196: */
197: public void setWSDL(Element wsdl) {
198: try {
199: WSDLReader rdr = WSDLFactory.newInstance().newWSDLReader();
200: wsdlDef = rdr.readWSDL(null, wsdl);
201: } catch (Exception e) {
202: logger.error("Error setting WSDL: " + e.getMessage(), e);
203: }
204: }
205:
206: /**
207: * Set the xml definition of output mappings. It is used to convert the
208: * WSIF invocation response.
209: *
210: * @param outputMappings the given xml as JDOM Element.
211: */
212: public void setMappings(Element outputMappings) {
213: try {
214: // retrieve namespaces
215: NodeList namespacesNodeList = outputMappings
216: .getElementsByTagNameNS(XPDLUtil.XPDL_EXTN_NS,
217: "Namespaces");
218: NodeList nsNodeList = null;
219: if (namespacesNodeList.getLength() > 0) {
220: Element namespacesNode = (Element) namespacesNodeList
221: .item(0);
222: nsNodeList = namespacesNode.getElementsByTagNameNS(
223: XPDLUtil.XPDL_EXTN_NS, "Namespace");
224: }
225: NodeList paramNodeList = outputMappings
226: .getElementsByTagNameNS(XPDLUtil.XPDL_EXTN_NS,
227: "Parameter");
228: for (int i = 0; i < paramNodeList.getLength(); i++) {
229: Element param = (Element) paramNodeList.item(i);
230: String name = param.getAttribute("Name");
231: XPath xpath = new JDOMXPath(param
232: .getAttribute("Select"));
233: if (nsNodeList != null) {
234: for (int j = 0; j < nsNodeList.getLength(); j++) {
235: Element ns = (Element) nsNodeList.item(j);
236: String prefix = ns.getAttribute("Prefix");
237: String uri = ns.getAttribute("Uri");
238: xpath.addNamespace(prefix, uri);
239: }
240: }
241: returnParamInfo.put(name, xpath);
242: }
243: } catch (Exception e) {
244: // if any error ocurred, outputMappings is still null.
245: logger
246: .error("error in setting XML for output mappings!",
247: e);
248: }
249: }
250:
251: /**
252: * set the SOAP method to be called with WSIF.
253: *
254: * @param method the given method.
255: */
256: public void setMethod(String method) {
257: this .method = method;
258: }
259:
260: // Implementation of de.danet.an.workflow.spis.aii.ToolAgent
261:
262: /* Comment copied from interface. */
263: public void invoke(Activity activity, FormalParameter[] formPars,
264: Map map) throws RemoteException, CannotExecuteException {
265: // Do not attempt to perform any operation on the activity
266: // except key() and uniqueKey() before WSIF dynamic
267: // invocation. Else, the activity becomes part of the EJB
268: // transaction and is locked, i.e. all accesses (even display
269: // in the management client) are deferred until the invocation
270: // has completed. The time this takes is not controllable.
271: ActivityUniqueKey auk = null;
272: try {
273: if (wsdlDef == null) {
274: throw new IllegalStateException(
275: "WSDL definition not defined");
276: }
277: Operation calledOp = operation;
278: if (calledOp == null) {
279: if (method == null) {
280: calledOp = initOperation((String) map
281: .get(formPars[0].id()));
282: FormalParameter[] fps = new FormalParameter[formPars.length - 1];
283: System.arraycopy(formPars, 0, fps, 0, fps.length);
284: formPars = fps;
285: } else {
286: calledOp = initOperation(method);
287: operation = calledOp;
288: }
289: }
290: auk = activity.uniqueKey();
291: try {
292: result.set(invokeOperation(calledOp, formPars, map));
293: } catch (RemoteException e) {
294: if (logger.isDebugEnabled()) {
295: logger.debug("Cannot invoke: " + e.getMessage(), e);
296: }
297: throw new CannotExecuteException("Cannot invoke: "
298: + e.getMessage(), e);
299: }
300: } finally {
301: if (logger.isDebugEnabled()) {
302: logger.debug("Finished invocation of " + auk);
303: }
304: }
305: }
306:
307: private Operation initOperation(String method)
308: throws IllegalStateException {
309: if (service == null) {
310: Collection services = wsdlDef.getServices().values();
311: if (services.size() == 0) {
312: throw new IllegalStateException("No service defined.");
313: }
314: if (services.size() > 1) {
315: throw new IllegalStateException(
316: "More than one service defined.");
317: }
318: service = (Service) services.iterator().next();
319: Collection ports = service.getPorts().values();
320: if (ports.size() == 0) {
321: throw new IllegalStateException("No ports defined.");
322: }
323: if (ports.size() > 1) {
324: throw new IllegalStateException(
325: "More than one port defined.");
326: }
327: port = (Port) ports.iterator().next();
328: for (Iterator i = port.getExtensibilityElements()
329: .iterator(); i.hasNext();) {
330: ExtensibilityElement ee = (ExtensibilityElement) i
331: .next();
332: if (ee instanceof SOAPAddress) {
333: portLocation = ((SOAPAddress) ee).getLocationURI();
334: }
335: }
336: if (portLocation == null) {
337: throw new IllegalStateException(
338: "No port location defined.");
339: }
340: portType = port.getBinding().getPortType();
341: }
342: String opName = method;
343: String inName = null;
344: String outName = null;
345: int sepCol = opName.indexOf(':');
346: if (sepCol >= 0) {
347: inName = opName.substring(sepCol + 1);
348: opName = opName.substring(0, sepCol);
349: sepCol = inName.indexOf(':');
350: if (sepCol >= 0) {
351: outName = inName.substring(sepCol + 1);
352: inName = inName.substring(0, sepCol);
353: }
354: }
355: Operation operation = null;
356: for (Iterator i = portType.getOperations().iterator(); i
357: .hasNext();) {
358: Operation op = (Operation) i.next();
359: if (!opName.equals(op.getName())
360: || (inName != null && !inName.equals(op.getInput()
361: .getName()))
362: || (outName != null && !outName.equals(op
363: .getOutput().getName()))) {
364: continue;
365: }
366: if (operation != null) {
367: operation = null;
368: throw new IllegalStateException("Operation '" + method
369: + "' is overloaded. "
370: + "Please specify the operation in the form "
371: + "'operationName:inputMessageName:"
372: + "outputMessageName' to distinguish it");
373: }
374: operation = op;
375: }
376: if (operation == null) {
377: throw new IllegalStateException("Operation \"" + method
378: + "\" not found.");
379: }
380: return operation;
381: }
382:
383: /**
384: * Invokes the dedicated operation of web services. The Operation return
385: * value as Map must have a key named <code>Result</code>, its value is
386: * written in the new constructed process data using the key of the out
387: * formal parameters.
388: *
389: * @return the new process data with the result of web services operation
390: * included.
391: * @throws WSIFDynamicInvokerException if any errors in invoking web
392: * service occurred.
393: */
394: private Map invokeOperation(Operation operation,
395: FormalParameter[] formPars, Map map)
396: throws CannotExecuteException, RemoteException {
397: try {
398: // javax.xml.rpc.Service svc
399: // = javax.xml.rpc.ServiceFactory.newInstance()
400: // .createService (service.getQName());
401: javax.xml.rpc.Service svc = (new org.apache.axis.client.ServiceFactory())
402: .createService(service.getQName());
403: TypeMappingRegistry tmr = svc.getTypeMappingRegistry();
404: TypeMapping tm = tmr.getDefaultTypeMapping();
405: Call call = svc.createCall();
406: call.setTargetEndpointAddress(portLocation);
407: call.setOperationName(new javax.xml.namespace.QName(wsdlDef
408: .getTargetNamespace(), operation.getName()));
409: SerializerFactory mySer = new SAXEventBufferSerializerFactory();
410: DeserializerFactory myDes = new JDOMDeserializerFactory();
411: Set fpns = new HashSet();
412: for (int i = 0; i < formPars.length; i++) {
413: if (formPars[i].mode() != FormalParameter.Mode.OUT) {
414: fpns.add(formPars[i].id());
415: }
416: }
417: Map parts = operation.getInput().getMessage().getParts();
418: List args = new ArrayList();
419: for (Iterator pi = parts.keySet().iterator(); pi.hasNext();) {
420: String pn = (String) pi.next();
421: if (!fpns.contains(pn)) {
422: throw new IllegalStateException(
423: "Missing formal parameter for part \"" + pn
424: + "\".");
425: }
426: fpns.remove(pn);
427: Part part = (Part) parts.get(pn);
428: Object value = map.get(pn);
429: if (value instanceof SAXEventBufferImpl) {
430: tm.register(SAXEventBufferImpl.class, part
431: .getTypeName(), mySer, myDes);
432: }
433: call.addParameter(pn, part.getTypeName(),
434: ParameterMode.IN);
435: args.add(value);
436: }
437: if (fpns.size() > 0) {
438: String param = (String) fpns.iterator().next();
439: throw new IllegalStateException(
440: "Extra formal parameter \"" + param + "\".");
441: }
442: Map outParts = operation.getOutput().getMessage()
443: .getParts();
444: if (outParts.values().size() == 0) {
445: call
446: .setReturnType(org.apache.axis.encoding.XMLType.AXIS_VOID);
447: } else {
448: Part outPart = (Part) outParts.values().iterator()
449: .next();
450: if (tm.getDeserializer(null, outPart.getTypeName()) == null) {
451: tm.register(SAXEventBufferImpl.class, outPart
452: .getTypeName(), mySer, myDes);
453: }
454: call.setReturnType(outPart.getTypeName());
455: }
456: Object res = call.invoke(args.toArray());
457: // add root; else absolute XPath expressions don't work.
458: // But leave res assigne to root element for backward
459: // compatibility of relative paths.
460: if (res instanceof org.jdom.Element) {
461: new Document((org.jdom.Element) res);
462: }
463: Map resData = new HashMap();
464: for (int i = 0; i < formPars.length; i++) {
465: if (formPars[i].mode() == FormalParameter.Mode.IN) {
466: continue;
467: }
468: XPath path = (XPath) returnParamInfo.get(formPars[i]
469: .id());
470: if (path == null) {
471: resData.put(formPars[i].id(), res);
472: } else {
473: if (formPars[i].type().equals(String.class)) {
474: resData.put(formPars[i].id(), path
475: .stringValueOf(res));
476: } else if ((formPars[i].type() instanceof Class)
477: && Number.class
478: .isAssignableFrom((Class) formPars[i]
479: .type())) {
480: Number n = path.numberValueOf(res);
481: if (formPars[i].type().equals(Long.class)
482: && !(n instanceof Long)) {
483: n = new Long(n.longValue());
484: }
485: resData.put(formPars[i].id(), n);
486: } else {
487: resData.put(formPars[i].id(), path
488: .selectNodes(res));
489: }
490: }
491: }
492: return resData;
493: } catch (JaxenException e) {
494: throw new CannotExecuteException("Cannot execute: "
495: + e.getMessage(), e);
496: } catch (javax.xml.rpc.ServiceException e) {
497: logger.error(e.getMessage(), e);
498: throw new CannotExecuteException(e.getMessage());
499: }
500: }
501:
502: /**
503: * Return the result evaluated during {@link ToolAgent#invoke
504: * <code>invoke</code>}. The method will only be called once after
505: * each invoke, i.e. the attribute holding the result be be
506: * cleared in this method.
507: *
508: * @return the result data or <code>null</code> if the invocation
509: * does not return any data.
510: */
511: public Object result() {
512: Object res = result.get();
513: result.set(null);
514: return res;
515: }
516:
517: /* (non-Javadoc)
518: * @see ExceptionMappingProvider#exceptionMappings()
519: */
520: public Collection exceptionMappings() {
521: Collection mappings = new ArrayList();
522: mappings.add(new ExceptionMapping(RemoteException.class,
523: "RemoteException"));
524: return mappings;
525: }
526:
527: /* Comment copied from interface. */
528: public void terminate(Activity activity)
529: throws ApplicationNotStoppedException {
530: throw new ApplicationNotStoppedException(
531: "Terminate not implemented for WSIFDynamicInvocationTool.");
532: }
533:
534: }
|