001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 2002 The Apache Software Foundation. All rights
006: * reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "WSIF" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 2001, 2002, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.wsif.base;
059:
060: import java.lang.reflect.InvocationHandler;
061: import java.lang.reflect.Method;
062: import java.lang.reflect.Proxy;
063: import java.util.ArrayList;
064: import java.util.HashMap;
065: import java.util.Iterator;
066: import java.util.List;
067: import java.util.Map;
068:
069: import javax.activation.DataHandler;
070: import javax.wsdl.Definition;
071: import javax.wsdl.Input;
072: import javax.wsdl.Message;
073: import javax.wsdl.Operation;
074: import javax.wsdl.Output;
075: import javax.wsdl.Part;
076: import javax.wsdl.PortType;
077: import javax.xml.namespace.QName;
078:
079: import org.apache.wsif.WSIFConstants;
080: import org.apache.wsif.WSIFException;
081: import org.apache.wsif.WSIFMessage;
082: import org.apache.wsif.WSIFOperation;
083: import org.apache.wsif.WSIFPort;
084: import org.apache.wsif.compiler.util.TypeMapping;
085: import org.apache.wsif.logging.Trc;
086: import org.apache.wsif.providers.WSIFDynamicTypeMap;
087: import org.apache.wsif.providers.WSIFDynamicTypeMapping;
088: import org.apache.wsif.util.WSIFUtils;
089:
090: /**
091: * WSIFClientProxy is a dynamic proxy (or stub) used by the
092: * WSIFServiceImpl when the application is using the stubs to
093: * invoke the web service. A WSIFClientProxy is created using the
094: * static newInstance method. A WSIFClientProxy dynamically implements
095: * exactly one interface passed by the application. This class
096: * invokes the web service using the WSIFOperation and WSIFPort
097: * interfaces and so is independent of any provider implementation.
098: * Operation overloading is supported.
099: *
100: * @author Owen Burroughs <owenb@apache.org>
101: * @author Ant Elder <antelder@apache.org>
102: * @author Jeremy Hughes <hughesj@apache.org>
103: * @author Mark Whitlock <whitlock@apache.org>
104: * @author Nirmal Mukhi <nmukhi@apache.org>
105: */
106: public class WSIFClientProxy implements InvocationHandler {
107: protected Class iface = null;
108: protected Definition def = null;
109: protected String serviceNS = null;
110: protected String serviceName = null;
111: protected String portTypeNS = null;
112: protected String portTypeName = null;
113: protected WSIFDynamicTypeMap typeMap = null;
114: protected Map simpleTypeReg = null;
115: protected PortType portType = null;
116: protected WSIFPort wsifport = null;
117: protected Object proxy = null;
118: private Map wsdlOperationTable = null;
119:
120: /**
121: * Factory method to create a new dynamic proxy.
122: * @param iface the user interface that is to be dynamically implemented
123: * @param def the WSDL definition
124: * @param serviceNS WSDL service namespace
125: * @param serviceName WSDL service name
126: * @param portTypeNS WSDL port type namespace
127: * @param portTypeName WSDL port type name
128: * @param typeMap table of mappings between XML and Java types
129: * @return the new WSIFClientProxy
130: * @exception WSIFException
131: */
132: public static WSIFClientProxy newInstance(Class iface,
133: Definition def, String serviceNS, String serviceName,
134: String portTypeNS, String portTypeName,
135: WSIFDynamicTypeMap typeMap) throws WSIFException {
136: Trc.entry(null, iface, def, serviceNS, serviceName, portTypeNS,
137: portTypeName, typeMap);
138:
139: if (!iface.isInterface())
140: throw new WSIFException("Cannot get a stub for " + iface
141: + " because it is not an interface");
142:
143: WSIFClientProxy clientProxy = new WSIFClientProxy(iface, def,
144: serviceNS, serviceName, portTypeNS, portTypeName,
145: typeMap);
146:
147: Object proxy = Proxy.newProxyInstance(iface.getClassLoader(),
148: new Class[] { iface }, clientProxy);
149:
150: clientProxy.setProxy(proxy);
151:
152: if (Trc.ON)
153: Trc.exit(clientProxy.deep());
154: return clientProxy;
155: }
156:
157: /**
158: * Private constructor because newInstance() should be used instead.
159: */
160: private WSIFClientProxy(Class iface, Definition def,
161: String serviceNS, String serviceName, String portTypeNS,
162: String portTypeName, WSIFDynamicTypeMap typeMap)
163: throws WSIFException {
164: Trc.entry(this , iface, def, serviceNS, serviceName, portTypeNS,
165: portTypeName, typeMap);
166: this .iface = iface;
167: this .def = def;
168: this .serviceNS = serviceNS;
169: this .serviceName = serviceName;
170: this .portTypeNS = portTypeNS;
171: this .portTypeName = portTypeName;
172: this .typeMap = typeMap;
173: this .portType = WSIFUtils.selectPortType(def, portTypeNS,
174: portTypeName);
175:
176: simpleTypeReg = WSIFUtils.getSimpleTypesMap();
177: wsdlOperationTable = new HashMap();
178: Trc.exit();
179: }
180:
181: private void setProxy(Object proxy) {
182: this .proxy = proxy;
183: }
184:
185: public Object getProxy() {
186: return this .proxy;
187: }
188:
189: /**
190: * Select which port to use for this proxy.
191: */
192: public void setPort(WSIFPort wsifport) {
193: Trc.entry(this , wsifport);
194: this .wsifport = wsifport;
195: Trc.exit();
196: }
197:
198: /**
199: * Invoke a user method. The java proxy support calls this method.
200: *
201: * The fault from the fault message is not passed back to caller
202: * (but it should be). However none of the existing providers set
203: * the fault message. I'm not sure what to do with the fault message
204: * anyhow. I guess raise a WSIFException which is what the current
205: * providers do with faults already.
206: */
207: public Object invoke(Object proxy, Method method, Object[] args)
208: throws WSIFException {
209: Trc.entry(this , method, args); // Tracing proxy cause a hang
210:
211: Operation operation = findMatchingOperation(method, args);
212:
213: // Now set up the input and output messages.
214: Input input = operation.getInput();
215: Output output = operation.getOutput();
216: String inputName = (input == null) ? null : input.getName();
217: String outputName = (output == null) ? null : output.getName();
218: Message inputMessage = (input == null) ? null : input
219: .getMessage();
220: Message outputMessage = (output == null) ? null : output
221: .getMessage();
222:
223: WSIFOperation wsifOperation = wsifport.createOperation(method
224: .getName(), inputName, outputName);
225:
226: // make the msg names for compiled msgs xxxAnt ask Mark why diff from inputName
227: String inputMsgName = "";
228: if (input != null) {
229: Message m = input.getMessage();
230: if (m != null) {
231: QName qn = m.getQName();
232: inputMsgName = (qn == null) ? "" : qn.getLocalPart();
233: }
234: }
235:
236: String outputMsgName = "";
237: if (output != null) {
238: Message m = output.getMessage();
239: if (m != null) {
240: QName qn = m.getQName();
241: outputMsgName = (qn == null) ? "" : qn.getLocalPart();
242: }
243: }
244:
245: // There must be an inputMessage.
246: WSIFMessage wsifInputMessage = wsifOperation
247: .createInputMessage(inputMsgName);
248:
249: // There may not be an output message.
250: WSIFMessage wsifOutputMessage = null;
251: WSIFMessage wsifFaultMessage = null;
252: if (output != null) {
253: wsifOutputMessage = wsifOperation
254: .createOutputMessage(inputMsgName);
255: wsifFaultMessage = wsifOperation
256: .createFaultMessage(inputMsgName);
257: }
258:
259: List inputParts = (inputMessage == null) ? new ArrayList()
260: : inputMessage.getOrderedParts(null);
261: if (args != null) {
262: if (inputParts.size() != args.length) {
263: unWrapIfWrappedDocLit(inputParts, operation.getName());
264: }
265: Iterator partIt = inputParts.iterator();
266: for (int argIndex = 0; partIt.hasNext(); argIndex++) {
267: Part part = (Part) partIt.next();
268: String partName;
269: if (isWrappedInContext()) {
270: QName qn = part.getElementName();
271: partName = (qn == null) ? "" : qn.getLocalPart();
272: } else {
273: partName = part.getName();
274: }
275: wsifInputMessage
276: .setObjectPart(partName, args[argIndex]);
277: }
278: }
279:
280: if (output == null)
281: wsifOperation.executeInputOnlyOperation(wsifInputMessage);
282: else {
283: boolean success = wsifOperation
284: .executeRequestResponseOperation(wsifInputMessage,
285: wsifOutputMessage, wsifFaultMessage);
286: if (!success) {
287: StringBuffer sb = new StringBuffer();
288: Iterator it = wsifFaultMessage.getPartNames();
289: while (it.hasNext()) {
290: String name = (String) it.next();
291: sb.append(name).append(":").append(
292: wsifFaultMessage.getObjectPart(name))
293: .append(" ");
294: }
295: throw new WSIFException(sb.toString());
296: }
297: }
298:
299: // Copy the output part out of the message.
300: Object result = null;
301: if (outputMessage != null) {
302: List outputParts = outputMessage.getOrderedParts(null);
303: if (outputParts != null && outputParts.size() > 0) {
304: // The return value is always the first output part
305: Iterator outPartIt = outputParts.iterator();
306: Part returnPart = (Part) outPartIt.next();
307: try {
308: result = wsifOutputMessage.getObjectPart(returnPart
309: .getName());
310: } catch (WSIFException e) {
311: Trc.ignoredException(e);
312: unWrapIfWrappedDocLit(outputParts, operation
313: .getName()
314: + "Response");
315: outPartIt = outputParts.iterator();
316: returnPart = (Part) outPartIt.next();
317: result = wsifOutputMessage.getObjectPart(returnPart
318: .getName());
319: }
320: // Are there any inout parts? Multiple output-only parts
321: // are not allowed in java. Skip over input-only parts in the message.
322: if (outPartIt.hasNext()) {
323: Object[] inPartArr = inputMessage.getOrderedParts(
324: null).toArray();
325: Part nextOutPart = (Part) outPartIt.next();
326:
327: for (int argIndex = 0; argIndex < args.length; argIndex++) {
328: if (((Part) (inPartArr[argIndex])).getName()
329: .equals(nextOutPart.getName())) {
330: args[argIndex] = wsifOutputMessage
331: .getObjectPart(nextOutPart
332: .getName());
333: if (outPartIt.hasNext())
334: nextOutPart = (Part) outPartIt.next();
335: else
336: break; // No more output parameters
337: }
338: } // end for
339: }
340: }
341: }
342: Trc.exit(result);
343: return result;
344: }
345:
346: /**
347: * Find an operation in the list that matches the method and arguments.
348: *
349: * Java only allows one output-only parameter and that is the return
350: * value. Java also does not allow overloading based on the return value.
351: * Consequently we only look at the input parameters when deciding
352: * which overloaded operation to pick.
353: *
354: * If the user invoked an overloaded method, MyMethod(null) seems to be
355: * ambiguous. However it is not since java forces the user to cast the
356: * null to one of the types that are valid on the method. So the invoke
357: * method on our client proxy gets passed args[0]==null which is not typed.
358: * However method.getParameterTypes()[0] is the type that java picked to
359: * invoke. So we use getParameterTypes() to choose the operation, as well
360: * as args[i].getClass().
361: *
362: * We also use args[i].getClass() to choose the operation in case
363: * getParameterTypes()[i].equals(Object.class) (no match) but
364: * args[i].getClass() is the class specified in the operation.
365: *
366: * The typeMap only contains complexTypes, so this class also uses the
367: * simpleTypeReg for simple types (int, string, etc).
368: *
369: * We compare the class in the mapping with the one from types using
370: * isAssignableFrom() not equals() because we allow the user to pass
371: * a subclass.
372: *
373: * If there are two methods MyMethod(Address) and MyMethod(SubAddress)
374: * then MyMethod(new SubAddress()) would match both methods. So if we
375: * find an operation which exactly matches the method we return it.
376: * But if we find an operation whose types are assignable from the
377: * method's types, we carry on searching for an exact match. If we fail
378: * to find an exact match then we return the "assignable" match. There
379: * is a problem if there are multiple "assignable" matches and no exact
380: * match as would happen if MyMethod(SubSubAddress) where SubSubAddress
381: * extends Address. This code does not cope with that case and it is a
382: * known restriction (bug).
383: *
384: * If the WSDL is correct, we do not expect that there will be multiple
385: * exact matches, so we do not test for this.
386: */
387: private Operation findMatchingOperation(Method method, Object[] args)
388: throws WSIFException {
389:
390: // create a key consisting of the method and these args
391: String key = createWSDLOperationKey(method, args);
392:
393: // check if we have found an operation matching the signature of this
394: // invocation before
395: Operation previousOp = (Operation) wsdlOperationTable.get(key);
396: if (previousOp != null) {
397: return previousOp;
398: }
399:
400: // Check here that the method is in the interface iface
401: Method[] allMethods = iface.getMethods();
402: int i;
403: for (i = 0; i < allMethods.length; i++)
404: if (allMethods[i].equals(method))
405: break;
406: if (i >= allMethods.length || !method.equals(allMethods[i]))
407: throw new WSIFException("Method " + method.getName()
408: + " is not in interface " + iface.getName());
409:
410: String methodName = method.getName();
411: Class[] types = method.getParameterTypes();
412: List opList = portType.getOperations();
413: Iterator opIt = opList.iterator();
414: Operation matchingOperation = null;
415:
416: // First try to find this method in the portType's list of operations.
417: // Be careful of overloaded operations.
418: while (opIt.hasNext()) {
419: Operation operation = (Operation) opIt.next();
420: // If the method name doesn't match the operation name this isn't the operation
421: if (!methodName.equalsIgnoreCase(operation.getName()))
422: continue;
423:
424: Input input = operation.getInput();
425: Message inputMessage = (input == null) ? null : input
426: .getMessage();
427: List inputParts = (inputMessage == null) ? new ArrayList()
428: : inputMessage.getOrderedParts(null);
429:
430: int numInputParts = inputParts.size();
431:
432: // Check for a match if neither args nor the operation has any parameters
433: if (numInputParts == 0 && types.length == 0) {
434: wsdlOperationTable.put(key, operation);
435: return operation;
436: }
437:
438: // No match if there are different numbers of parameters
439: if (types != null && (numInputParts != types.length)) {
440: unWrapIfWrappedDocLit(inputParts, operation.getName());
441: numInputParts = inputParts.size();
442: if (numInputParts != types.length) {
443: continue;
444: }
445: }
446:
447: // Go through all the parameters making sure all their datatypes match
448: Iterator partIt = inputParts.iterator();
449: boolean foundAllArgs = true;
450: boolean exactMatchAllArgs = true;
451: for (int argIndex = 0; partIt.hasNext() && foundAllArgs; argIndex++) {
452:
453: Part part = (Part) partIt.next();
454: QName partTypeName = part.getTypeName();
455: if (partTypeName == null) {
456: partTypeName = part.getElementName();
457: }
458:
459: /* for wrapped document literal operations AXIS uses a wrapper
460: * element class with ">" prefixed to the namespace local part
461: */
462: QName partTypeNameWrapped = new QName(partTypeName
463: .getNamespaceURI(), ">"
464: + partTypeName.getLocalPart());
465:
466: boolean foundThisArg = false;
467: boolean exactMatchThisArg = false;
468:
469: // Look this parameter up in the typeMap.
470: for (Iterator mapIt = typeMap.iterator(); mapIt
471: .hasNext()
472: && !foundThisArg;) {
473: WSIFDynamicTypeMapping mapping = (WSIFDynamicTypeMapping) mapIt
474: .next();
475: if (mapping.getXmlType().equals(partTypeName)
476: || (mapping.getXmlType()
477: .equals(partTypeNameWrapped))) {
478: if (mapping.getJavaType().isAssignableFrom(
479: types[argIndex])
480: || (args[argIndex] != null && mapping
481: .getJavaType()
482: .isAssignableFrom(
483: args[argIndex]
484: .getClass()))) {
485: foundThisArg = true;
486: if (mapping.getJavaType().equals(
487: types[argIndex])
488: || (args[argIndex] != null && mapping
489: .getJavaType()
490: .equals(
491: args[argIndex]
492: .getClass())))
493: exactMatchThisArg = true;
494: } else
495: break;
496: }
497: }
498:
499: // Look for a simple type that matches
500: TypeMapping tm = (TypeMapping) (simpleTypeReg
501: .get(partTypeName));
502: if (!foundThisArg) {
503: if (tm != null) {
504: String simpleType = tm.javaType;
505: if (types[argIndex].toString().equals(
506: simpleType)) {
507: // this works for simple types (float, int)
508: foundThisArg = true;
509: exactMatchThisArg = true;
510: } else
511: try // this works for String, Date
512: {
513: Class simpleClass = Class
514: .forName(
515: simpleType,
516: true,
517: Thread
518: .currentThread()
519: .getContextClassLoader());
520: if (simpleClass
521: .isAssignableFrom(types[argIndex])) {
522: foundThisArg = true;
523: if (simpleClass
524: .equals(types[argIndex]))
525: exactMatchThisArg = true;
526: }
527: } catch (ClassNotFoundException ignored) {
528: Trc.ignoredException(ignored);
529: }
530: } else if (types[argIndex]
531: .equals(DataHandler.class))
532: // There is no (simple or complex) type mapping for
533: // this argument. If it's a DataHandler, then assume
534: // it's a mime type, since we do automatic registering
535: // of DataHandlers for Mime types. We should really look
536: // in the WSDL binding to make sure it is a mime part.
537: foundThisArg = true;
538: }
539:
540: if (!foundThisArg)
541: foundAllArgs = false;
542: if (!exactMatchThisArg)
543: exactMatchAllArgs = false;
544: }
545:
546: if (foundAllArgs) {
547: if (exactMatchAllArgs) {
548: wsdlOperationTable.put(key, operation);
549: return operation;
550: }
551:
552: // if matchingOperation!=null then write trace statement.
553: matchingOperation = operation;
554: }
555: } // end while
556:
557: if (matchingOperation != null) {
558: wsdlOperationTable.put(key, matchingOperation);
559: return matchingOperation;
560: }
561:
562: // if we get here then we haven't found a matching operation
563: String argString = new String();
564: if (types != null)
565: for (i = 0; i < types.length; i++) {
566: if (i != 0)
567: argString += ", ";
568: argString += types[i];
569: }
570:
571: throw new WSIFException("Method " + methodName + "("
572: + argString + ") was not found in portType "
573: + portType.getQName());
574: }
575:
576: /**
577: * Create a key consisting of the method name and the types of all the args
578: */
579: private String createWSDLOperationKey(Method method, Object[] args) {
580: Trc.entry(this , method, args);
581:
582: StringBuffer sb = new StringBuffer();
583: sb.append(method.getName()).append(":");
584:
585: Class[] types = method.getParameterTypes();
586: for (int i = 0; i < types.length; i++)
587: sb.append(types[i].getName()).append(":");
588:
589: if (args != null)
590: for (int i = 0; i < args.length; i++) {
591: if (args[i] == null)
592: sb.append("null");
593: else
594: sb.append(args[i].getClass().getName());
595: sb.append(":");
596: }
597:
598: Trc.exit(sb.toString());
599: return sb.toString();
600: }
601:
602: /**
603: * Create a key consisting of all names concatenated
604: */
605: private String createWSIFOperationKey(String operationName,
606: String inputName, String outputName) {
607: Trc.entry(this , operationName, inputName, outputName);
608:
609: StringBuffer sb = new StringBuffer();
610: sb.append(operationName).append(inputName).append(outputName);
611:
612: Trc.exit(sb.toString());
613: return sb.toString();
614: }
615:
616: /**
617: * Unwraps the top level element if this a wrapped message.
618: */
619: private void unWrapIfWrappedDocLit(List parts, String operationName)
620: throws WSIFException {
621: Part p = WSIFUtils.getWrappedDocLiteralPart(parts,
622: operationName);
623: if (p != null) {
624: List unWrappedParts = WSIFUtils.unWrapPart(p, def);
625: parts.remove(p);
626: parts.addAll(unWrappedParts);
627: }
628: }
629:
630: private boolean isWrappedInContext() throws WSIFException {
631: WSIFMessage context = wsifport.getContext();
632: String style = null;
633: try {
634: style = (String) context
635: .getObjectPart(WSIFConstants.CONTEXT_OPERATION_STYLE);
636: } catch (WSIFException e) {
637: Trc.ignoredException(e);
638: }
639: boolean wrappedInContext;
640: if (WSIFConstants.CONTEXT_OPERATION_STYLE_WRAPPED.equals(style)) {
641: wrappedInContext = true;
642: } else {
643: wrappedInContext = false;
644: }
645: return wrappedInContext;
646: }
647:
648: public String deep() {
649: String buff = "";
650: try {
651: buff = new String(this .toString() + "\n");
652: buff += "iface: " + iface;
653: buff += " def: " + Trc.brief(def);
654: buff += " serviceNS: " + serviceNS;
655: buff += " serviceName: " + serviceName;
656: buff += " portTypeNS: " + portTypeNS;
657: buff += " portTypeName: " + portTypeName;
658: buff += " typeMap: " + typeMap;
659: buff += "\nsimpleTypeReg: " + simpleTypeReg;
660: buff += "\nportType: " + Trc.brief(portType);
661: buff += " wsifport: " + wsifport;
662:
663: // Can't trace proxy here because it causes a hang.
664: //buff += "proxy: " + proxy;
665: } catch (Exception e) {
666: Trc.exceptionInTrace(e);
667: }
668: return buff;
669: }
670: }
|