001: /* *****************************************************************************
002: * LZSOAPService.java
003: * ****************************************************************************/
004:
005: /* J_LZ_COPYRIGHT_BEGIN *******************************************************
006: * Copyright 2001-2007 Laszlo Systems, Inc. All Rights Reserved. *
007: * Use is subject to license terms. *
008: * J_LZ_COPYRIGHT_END *********************************************************/
009:
010: package org.openlaszlo.remote.swf.soap;
011:
012: import org.openlaszlo.iv.flash.api.action.Program;
013: import org.openlaszlo.remote.swf.soap.encoding.SWFSimpleDeserializerFactory;
014: import org.openlaszlo.remote.swf.soap.encoding.SWFObjectDeserializerFactory;
015: import org.openlaszlo.remote.swf.soap.encoding.LZObjectSerializerFactory;
016: import org.openlaszlo.server.LPS;
017: import java.io.IOException;
018: import java.net.URLDecoder;
019: import java.rmi.RemoteException;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.ArrayList;
023: import java.util.Hashtable;
024: import java.util.Map;
025: import javax.xml.namespace.QName;
026: import javax.xml.rpc.JAXRPCException;
027: import javax.xml.soap.SOAPHeader;
028: import org.apache.axis.AxisFault;
029: import org.apache.axis.client.Service;
030: import org.apache.axis.client.Call;
031: import javax.xml.rpc.ServiceException;
032: import javax.xml.rpc.encoding.DeserializerFactory;
033: import javax.xml.rpc.encoding.SerializerFactory;
034: import javax.xml.rpc.encoding.TypeMappingRegistry;
035: import javax.xml.rpc.encoding.TypeMapping;
036: import javax.xml.rpc.handler.HandlerInfo;
037: import javax.xml.rpc.handler.HandlerRegistry;
038: import javax.xml.soap.SOAPException;
039: import org.apache.axis.Constants;
040: import org.apache.axis.message.SOAPBodyElement;
041: import org.apache.axis.message.SOAPHeaderElement;
042: import org.apache.log4j.Logger;
043: import org.w3c.dom.DOMException;
044: import org.w3c.dom.Element;
045: import org.w3c.dom.Node;
046: import org.w3c.dom.NodeList;
047: import org.w3c.dom.Text;
048: import org.xml.sax.SAXException;
049:
050: public class LZSOAPService {
051: private static Logger mLogger = Logger
052: .getLogger(LZSOAPService.class);
053:
054: String mServiceName;
055: String mPort;
056: String mEndpointAddress;
057: String mTransport;
058: String mTargetNS;
059: String mXMLSchemaNS;
060: String mSOAPEncodingNS;
061: Service mService;
062: TypeMappingRegistry mTypeMappingRegistry;
063: HandlerRegistry mHandlerRegistry;
064: TypeMapping mDefaultTypeMapping;
065: TypeMapping mDefaultSOAPEncodingTypeMapping;
066:
067: /** URL for wsdl. This gets set in SOAPDataSource. */
068: String mWSDL;
069:
070: /** Map of SWF of services (synchronized) (key is version string like swf6,
071: * swf7, etc.) */
072: Hashtable mClientSOAPServiceMap = new Hashtable();
073:
074: /** Map of LZSOAPOperations. */
075: Map mOperations = null;
076:
077: /** Map of schema complex types. */
078: Map mSchemaComplexTypes = null;
079:
080: /** Keep one DeserializerFactory around. */
081: DeserializerFactory mSWFObjectDeserializerFactory = new SWFObjectDeserializerFactory();
082: SerializerFactory mObjectSerializerFactory = new LZObjectSerializerFactory();
083:
084: public LZSOAPService(String wsdl, String serviceName, String port,
085: String endpointAddress, String transport, String targetNS,
086: String xmlSchemaNS, String soapEncodingNS)
087: throws ServiceException {
088:
089: mWSDL = wsdl;
090: mServiceName = serviceName;
091: mPort = port;
092: mEndpointAddress = endpointAddress;
093: mTransport = transport;
094: mTargetNS = targetNS;
095: mXMLSchemaNS = xmlSchemaNS;
096: mSOAPEncodingNS = soapEncodingNS;
097:
098: mService = new Service(new QName(mTargetNS, mServiceName));
099:
100: mTypeMappingRegistry = mService.getTypeMappingRegistry();
101: mHandlerRegistry = mService.getHandlerRegistry();
102:
103: getHandlerChain().add(
104: new HandlerInfo(LZSOAPHandler.class, null, null));
105:
106: // Register default type mapping and default soap encoding type mapping.
107: mDefaultTypeMapping = LZDefaultTypeMapping.getSingleton();
108: mDefaultSOAPEncodingTypeMapping = new LZDefaultSOAPEncodingTypeMapping();
109: mTypeMappingRegistry.registerDefault(mDefaultTypeMapping);
110: mTypeMappingRegistry.register(Constants.URI_SOAP11_ENC,
111: mDefaultSOAPEncodingTypeMapping);
112: }
113:
114: public synchronized byte[] createClientSOAPService(
115: String swfversion, int swfnum) throws ServiceException {
116: byte[] swfobj = (byte[]) mClientSOAPServiceMap.get(swfversion);
117: if (swfobj == null) {
118: try {
119: swfobj = ClientSOAPService.createObject(this , swfnum);
120: mClientSOAPServiceMap.put(swfversion, swfobj);
121: } catch (IOException e) {
122: throw new ServiceException(
123: /* (non-Javadoc)
124: * @i18n.test
125: * @org-mes="could not create client SOAP service object"
126: */
127: org.openlaszlo.i18n.LaszloMessages.getMessage(
128: LZSOAPService.class.getName(), "051018-130"));
129: }
130: }
131: return swfobj;
132: }
133:
134: /**
135: * Get client SWF representation of service.
136: */
137: public byte[] getClientSOAPService(int swfnum)
138: throws ServiceException {
139: String swfversion = LPS.getSWFVersion(swfnum);
140: byte[] swfobj = (byte[]) mClientSOAPServiceMap.get(swfversion);
141: if (swfobj == null)
142: swfobj = createClientSOAPService(swfversion, swfnum);
143: return swfobj;
144: }
145:
146: /**
147: * Invoke operation with parameters. Parameters are represented in XML like:
148: *
149: * <params>
150: * <param>param1</param>
151: * <param>param2</param>
152: * <param>param3</param>
153: * <params>
154: *
155: * In document style, the string in the <param> element should be an
156: * XML-escaped string. For example, suppose you were trying to send two
157: * documents that looked like:
158: *
159: * doc1: <a>1</a>
160: * doc2: <b>2</b>
161: *
162: * The XML parameter string should look as follows:
163: *
164: * <params>
165: * <param>%3Ca%3E1%3C/a%3E</param>
166: * <param>%3Cb%3E2%3C/b%3E</param>
167: * </params>
168: *
169: * @param operation operation to invoke
170: * @param xml XML from client that includes header and body. The format looks like:
171: *
172: * <e><h>XML_HEADER</h><b>XML_BODY</b></e>
173: *
174: * where XML_BODY is
175: *
176: * <params>
177: * <param>PARAM1</param>
178: * <param>PARAM2</param>
179: * <param>...</param>
180: * </params>
181: *
182: * @return object array where the first parameter is a string indicating the
183: * style used to invoke the function (rpc|document) and the second is the
184: * return value. For document styles, an array of SOAPBody message items are
185: * returned.
186: */
187: public Object[] invoke(String operation, String xml)
188: throws AxisFault, ServiceException, RemoteException {
189:
190: mLogger.debug("invoke()");
191:
192: try {
193: Element envelope = LZSOAPUtils.xmlStringToElement(xml);
194:
195: Element body = LZSOAPUtils.getFirstElementByTagName(
196: envelope, "b");
197: Element paramsEl = LZSOAPUtils.getFirstElementByTagName(
198: body, "params");
199:
200: Object[] callArr = createCall(operation, paramsEl);
201: Call call = (Call) callArr[0];
202: Object[] params = (Object[]) callArr[1];
203:
204: Element header = LZSOAPUtils.getFirstElementByTagName(
205: envelope, "h");
206: NodeList headerNodes = header.getChildNodes();
207: for (int i = 0; i < headerNodes.getLength(); i++) {
208: Node headerNode = (Node) headerNodes.item(i);
209: if (headerNode.getNodeType() != Node.ELEMENT_NODE)
210: continue;
211: call.addHeader(new SOAPHeaderElement(
212: (Element) headerNode));
213: }
214:
215: // Pass back style and return value from call
216: Object returnValue = call.invoke(params);
217:
218: // Return header, if any
219: SOAPHeader responseHeader = call.getResponseMessage()
220: .getSOAPEnvelope().getHeader();
221:
222: LZSOAPOperation op = (LZSOAPOperation) mOperations
223: .get(operation);
224: return new Object[] { op.getStyle(), returnValue,
225: responseHeader };
226:
227: } catch (AxisFault e) {
228: mLogger.error("AxisFault");
229: throw e;
230: } catch (IOException e) {
231: mLogger.error("IOException", e);
232: throw new ServiceException(e.getMessage());
233: } catch (org.xml.sax.SAXException e) {
234: mLogger.error("SAXException:", e);
235: throw new ServiceException(e.getMessage());
236: } catch (SOAPException e) {
237: mLogger.error("SOAPException", e);
238: throw new ServiceException(e.getMessage());
239: }
240:
241: }
242:
243: Node getParamValue(Element param) {
244: NodeList list = param.getChildNodes();
245: int len = list.getLength();
246: if (len == 0) {
247: return null;
248: }
249:
250: // if a subelement exists, the param must be an array or object.
251: for (int i = 0; i < list.getLength(); i++) {
252: Node node = list.item(i);
253: if (node.getNodeType() == Node.ELEMENT_NODE) {
254: return param;
255: }
256: }
257:
258: return list.item(0);
259: }
260:
261: /**
262: * @return true if paramNode is text node, else false.
263: */
264: public boolean isTextNode(Node paramNode) {
265: return (paramNode == null || paramNode.getNodeType() == Node.TEXT_NODE);
266: }
267:
268: /**
269: * @return true if paramNode is an element node, else false.
270: */
271: public boolean isElementNode(Node paramNode) {
272: return (paramNode.getNodeType() == Node.ELEMENT_NODE);
273: }
274:
275: /**
276: * @return list of rpc parameters
277: */
278: public List setRPCParams(Call call, NodeList paramsList,
279: List parts, LZSOAPOperation op) throws ServiceException,
280: IOException, SAXException, DOMException {
281:
282: if (mLogger.isDebugEnabled()) {
283: mLogger.debug("setRPCParams");
284: }
285:
286: int pos = 0; // parameter position
287: List params = new ArrayList();
288: for (int i = 0; i < paramsList.getLength(); i++) {
289:
290: Node node = (Node) paramsList.item(i);
291: if (node.getNodeType() != Node.ELEMENT_NODE)
292: continue;
293:
294: Element p = (Element) node;
295: Node paramNode = getParamValue(p);
296:
297: LZSOAPPart part = (LZSOAPPart) parts.get(pos);
298: ComplexType type = part.getType();
299:
300: if (type.isArray()) {
301:
302: if (!isElementNode(paramNode)) {
303: throw new ServiceException(
304: /* (non-Javadoc)
305: * @i18n.test
306: * @org-mes="parameter " + p[0] + " should an array"
307: */
308: org.openlaszlo.i18n.LaszloMessages.getMessage(
309: LZSOAPService.class.getName(),
310: "051018-304", new Object[] { new Integer(
311: pos + 1) }));
312: }
313:
314: call.addParameter(part.getName(), type.getBase()
315: .getName(), ArrayList.class, part
316: .getParameterMode());
317:
318: ArrayWrapper aw = new ArrayWrapper((Element) paramNode,
319: type);
320: ArrayList list = new ArrayList();
321: list.add(aw);
322: params.add(list);
323: if (mLogger.isDebugEnabled()) {
324: mLogger.debug("array param: " + aw);
325: }
326:
327: } else {
328:
329: call.addParameter(part.getName(), type.getName(), part
330: .getParameterMode());
331:
332: if (isTextNode(paramNode)) { // primitive param
333: Text text = (Text) paramNode;
334: String strValue = (text != null ? text.getData()
335: : "");
336: params.add(((LZSOAPPart) parts.get(pos))
337: .valueOf(strValue));
338: if (mLogger.isDebugEnabled()) {
339: mLogger.debug(
340: /* (non-Javadoc)
341: * @i18n.test
342: * @org-mes="primitive param: " + p[0]
343: */
344: org.openlaszlo.i18n.LaszloMessages
345: .getMessage(LZSOAPService.class
346: .getName(), "051018-335",
347: new Object[] { strValue }));
348: }
349:
350: } else if (isElementNode(paramNode)) { // object param
351: ObjectWrapper ow = new ObjectWrapper(
352: (Element) paramNode);
353: params.add(ow);
354: if (mLogger.isDebugEnabled()) {
355: mLogger.debug("object param: " + ow);
356: }
357:
358: } else {
359: throw new ServiceException(
360: /* (non-Javadoc)
361: * @i18n.test
362: * @org-mes="bad parameter " + p[0] + ": " + p[1]
363: */
364: org.openlaszlo.i18n.LaszloMessages.getMessage(
365: LZSOAPService.class.getName(),
366: "051018-353", new Object[] {
367: new Integer(pos + 1), paramNode }));
368: }
369:
370: }
371:
372: ++pos;
373: }
374:
375: if (params.size() != parts.size()) {
376: throw new ServiceException(
377: /* (non-Javadoc)
378: * @i18n.test
379: * @org-mes="wrong number of params"
380: */
381: org.openlaszlo.i18n.LaszloMessages.getMessage(
382: LZSOAPService.class.getName(), "051018-368"));
383: }
384:
385: // Set return type
386: parts = op.getOutputMessage().getParts();
387: int size = parts.size();
388: if (size > 1) {
389: throw new ServiceException(
390: /* (non-Javadoc)
391: * @i18n.test
392: * @org-mes="multiple return values unsupported"
393: */
394: org.openlaszlo.i18n.LaszloMessages.getMessage(
395: LZSOAPService.class.getName(), "051018-382"));
396: }
397:
398: // If return type isn't void, set it.
399: if (size != 0) {
400: LZSOAPPart part = (LZSOAPPart) parts.get(0);
401: ComplexType type = part.getType();
402: if (type != null) {
403: call.setReturnType(type.getName());
404: } else {
405: mLogger.warn(
406: /* (non-Javadoc)
407: * @i18n.test
408: * @org-mes="type of return unspecified"
409: */
410: org.openlaszlo.i18n.LaszloMessages.getMessage(
411: LZSOAPService.class.getName(), "051018-399"));
412: }
413: }
414:
415: return params;
416: }
417:
418: /**
419: * @return list of document style parameters
420: */
421: public List setDocumentParams(Call call, NodeList paramsList,
422: List parts, LZSOAPOperation op) throws ServiceException,
423: IOException, SAXException, DOMException {
424:
425: if (mLogger.isDebugEnabled()) {
426: mLogger.debug(
427: /* (non-Javadoc)
428: * @i18n.test
429: * @org-mes="setDocumentParams"
430: */
431: org.openlaszlo.i18n.LaszloMessages.getMessage(
432: LZSOAPService.class.getName(), "051018-421"));
433: }
434:
435: call.setProperty(Call.OPERATION_STYLE_PROPERTY, "document");
436: call.setProperty(Call.SOAPACTION_USE_PROPERTY,
437: new Boolean(true));
438: call.setProperty(Call.SOAPACTION_URI_PROPERTY, op
439: .getSoapAction());
440:
441: List params = new ArrayList();
442: for (int i = 0; i < paramsList.getLength(); i++) {
443:
444: Node node = (Node) paramsList.item(i);
445: if (node.getNodeType() != Node.ELEMENT_NODE)
446: continue;
447:
448: Element p = (Element) node;
449:
450: // get the XML string and convert to an element
451: Text text = (Text) p.getFirstChild();
452: String data = URLDecoder.decode(text.getData());
453: Element docElement = LZSOAPUtils.xmlStringToElement(data);
454: params.add(new SOAPBodyElement(docElement));
455:
456: }
457:
458: return params;
459: }
460:
461: /**
462: * @param operation name of operation.
463: * @param paramsEl parameter element nodes.
464: * @return array with Call object as first value and a parameter array to invoke call with.
465: */
466: public Object[] createCall(String operation, Element paramsEl)
467: throws ServiceException {
468: LZSOAPOperation op = (LZSOAPOperation) mOperations
469: .get(operation);
470: if (op == null) {
471: throw new ServiceException(
472: /* (non-Javadoc)
473: * @i18n.test
474: * @org-mes="could not find operation named " + p[0]
475: */
476: org.openlaszlo.i18n.LaszloMessages.getMessage(
477: LZSOAPService.class.getName(), "051018-462",
478: new Object[] { operation }));
479: }
480:
481: try {
482: Call call = (org.apache.axis.client.Call) mService
483: .createCall(new QName(mTargetNS, mPort));
484: call.setOperationName(new QName(mTargetNS, op.getName()));
485: call.setTargetEndpointAddress(mEndpointAddress);
486:
487: NodeList paramsList = paramsEl.getChildNodes();
488: List parts = op.getInputMessage().getParts();
489:
490: List params = null;
491: if (op.getStyle().equals("document"))
492: params = setDocumentParams(call, paramsList, parts, op);
493: else
494: /* rpc */
495: params = setRPCParams(call, paramsList, parts, op);
496:
497: return new Object[] { call, params.toArray() };
498: } catch (IOException e) {
499: mLogger.error("IOException", e);
500: throw new ServiceException("IOException: " + e.getMessage());
501: } catch (SAXException e) {
502: mLogger.error("SAXException", e);
503: throw new ServiceException("SAXException: "
504: + e.getMessage());
505: } catch (DOMException e) {
506: mLogger.error("DOMException", e);
507: throw new ServiceException("DOMException: "
508: + e.getMessage());
509: }
510: }
511:
512: public TypeMappingRegistry getTypeMappingRegistry() {
513: return mTypeMappingRegistry;
514: }
515:
516: public HandlerRegistry getHandlerRegistry() {
517: return mHandlerRegistry;
518: }
519:
520: public List getHandlerChain() {
521: return mHandlerRegistry.getHandlerChain(new QName(mTargetNS,
522: mPort));
523: }
524:
525: public Service getService() {
526: return mService;
527: }
528:
529: /** Set map of LZSOAPOperations. */
530: public void setOperations(Map operations) {
531: mOperations = operations;
532: }
533:
534: /** @return map of LZSOAPOperations. */
535: public Map getOperations() {
536: return mOperations;
537: }
538:
539: public String getTargetNS() {
540: return mTargetNS;
541: }
542:
543: public String getSOAPEncodingNS() {
544: return mSOAPEncodingNS;
545: }
546:
547: public String getServiceName() {
548: return mServiceName;
549: }
550:
551: public String getPort() {
552: return mPort;
553: }
554:
555: public String getEndpointAddress() {
556: return mEndpointAddress;
557: }
558:
559: public String getTransport() {
560: return mTransport;
561: }
562:
563: /** WSDL URL for this SOAP service. */
564: public String getWSDL() {
565: return mWSDL;
566: }
567:
568: /** Set WSDL URL for this SOAP service. */
569: public void setWSDL(String wsdl) {
570: mWSDL = wsdl;
571: }
572:
573: /**
574: * This gets called by WSDLParser after WSDL schema is read.
575: */
576: public void setSchemaComplexTypes(Map schemaComplexTypes) {
577: mSchemaComplexTypes = schemaComplexTypes;
578:
579: // now register these complex types with type mapper.
580: if (mSchemaComplexTypes != null) {
581: Iterator iter = mSchemaComplexTypes.values().iterator();
582: while (iter.hasNext()) {
583: ComplexType value = (ComplexType) iter.next();
584: if (value.getType() == ComplexType.TYPE_STRUCT) {
585: QName structQName = value.getName();
586: // Just to be safe, registering in both default type mapping
587: // and SOAP type mapping.
588: mDefaultTypeMapping.register(ObjectWrapper.class,
589: structQName, mObjectSerializerFactory,
590: mSWFObjectDeserializerFactory);
591: mDefaultSOAPEncodingTypeMapping.register(
592: ObjectWrapper.class, structQName,
593: mObjectSerializerFactory,
594: mSWFObjectDeserializerFactory);
595: if (mLogger.isDebugEnabled()) {
596: mLogger.debug(
597: /* (non-Javadoc)
598: * @i18n.test
599: * @org-mes="registered type mapping for object: " + p[0]
600: */
601: org.openlaszlo.i18n.LaszloMessages.getMessage(
602: LZSOAPService.class.getName(),
603: "051018-581",
604: new Object[] { structQName }));
605: }
606: }
607: // NOTE: arrays/collections are handled by default type mapping
608: // in LZDefaultTypeMapping. -pk
609: }
610: }
611: }
612:
613: /**
614: * @return map of ComplexType.
615: */
616: public Map getSchemaComplexTypes() {
617: return mSchemaComplexTypes;
618: }
619:
620: public void toComplexTypesXML(StringBuffer sb) {
621: sb.append("<complex-types>");
622: if (mSchemaComplexTypes != null) {
623: Iterator iter = mSchemaComplexTypes.entrySet().iterator();
624: while (iter.hasNext()) {
625: Map.Entry entry = (Map.Entry) iter.next();
626: ComplexType ct = (ComplexType) entry.getValue();
627: ct.toXML(sb);
628: }
629: }
630: sb.append("</complex-types>");
631: }
632:
633: public void toOperationXML(StringBuffer sb) {
634: sb.append("<operations>");
635: Iterator iter = mOperations.keySet().iterator();
636: while (iter.hasNext()) {
637: String key = (String) iter.next();
638: LZSOAPOperation op = (LZSOAPOperation) mOperations.get(key);
639: op.toXML(sb);
640: }
641: sb.append("</operations>");
642: }
643:
644: public void toXML(StringBuffer sb) {
645: sb.append("<service").append(" name=\"").append(mServiceName)
646: .append("\"").append(" port=\"").append(mPort).append(
647: "\"").append(" endpoint=\"").append(
648: mEndpointAddress).append("\"").append(
649: " transport=\"").append(mTransport)
650: .append("\"").append(" target-namespace=\"").append(
651: mTargetNS).append("\">");
652: toOperationXML(sb);
653: toComplexTypesXML(sb);
654: sb.append("</service>");
655: }
656:
657: // public String toString() {
658: // return "-- SOAPService ------------------------------\n"
659: // + "service=" + mServiceName + "\n"
660: // + "port=" + mPort + "\n"
661: // + "endpoint=" + mEndpointAddress + "\n"
662: // + "transport=" + mTransport + "\n"
663: // + "target namespace=" + mTargetNS + "\n\n"
664: // + " ==== OPERATIONS ====\n\n"
665: // + toOperationString()
666: // + " ==== SCHEMA COMPLEX TYPES ====\n\n"
667: // + stringComplexTypes()
668: // + "---------------------------------------------";
669: // }
670:
671: // public String toOperationString() {
672: // StringBuffer buf = new StringBuffer();
673: // Iterator iter = mOperations.keySet().iterator();
674: // while (iter.hasNext()) {
675: // String key = (String)iter.next();
676: // LZSOAPOperation op = (LZSOAPOperation)mOperations.get(key);
677: // buf.append(op).append("\n");
678: // }
679: // return buf.toString();
680: // }
681:
682: }
|