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