001: /* ****************************************************************************
002: * SOAPDataSource.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.data.swf;
011:
012: import org.openlaszlo.data.*;
013: import org.openlaszlo.iv.flash.api.FlashFile;
014: import org.openlaszlo.iv.flash.api.Frame;
015: import org.openlaszlo.iv.flash.api.Script;
016: import org.openlaszlo.iv.flash.api.action.DoAction;
017: import org.openlaszlo.iv.flash.util.FlashOutput;
018: import org.openlaszlo.iv.flash.util.IVException;
019: import org.openlaszlo.iv.flash.api.action.Actions;
020: import org.openlaszlo.iv.flash.api.action.Program;
021: import org.openlaszlo.iv.flash.util.FlashBuffer;
022: import org.openlaszlo.server.LPS; // LoadCount belongs in utils
023: import org.openlaszlo.servlets.LoadCount;
024: import org.openlaszlo.media.MimeType;
025: import org.openlaszlo.remote.swf.soap.LZSOAPService;
026: import org.openlaszlo.remote.swf.soap.WSDLException;
027: import org.openlaszlo.remote.swf.soap.WSDLParser;
028: import org.openlaszlo.remote.swf.soap.encoding.SOAPDataEncoder;
029: import org.openlaszlo.server.LPS;
030: import org.openlaszlo.xml.internal.XMLUtils;
031: import java.io.ByteArrayInputStream;
032: import java.io.File;
033: import java.io.InputStream;
034: import java.io.InterruptedIOException;
035: import java.io.IOException;
036: import java.io.StringReader;
037: import java.net.URLDecoder;
038: import java.net.URLEncoder;
039: import java.rmi.RemoteException;
040: import java.util.Date;
041: import java.util.HashMap;
042: import java.util.Iterator;
043: import java.util.Properties;
044: import java.util.Vector;
045: import javax.xml.parsers.DocumentBuilder;
046: import javax.xml.parsers.DocumentBuilderFactory;
047: import javax.xml.parsers.ParserConfigurationException;
048: import javax.xml.rpc.ServiceException;
049: import javax.servlet.http.HttpServletRequest;
050: import javax.servlet.http.HttpServletResponse;
051: import org.apache.axis.message.SOAPBodyElement;
052: import org.apache.axis.AxisFault;
053: import org.apache.log4j.Logger;
054: import org.xml.sax.InputSource;
055: import org.xml.sax.SAXException;
056: import org.w3c.dom.DOMException;
057: import org.w3c.dom.Element;
058: import org.w3c.dom.NodeList;
059: import org.w3c.dom.Text;
060: import org.apache.axis.AxisProperties;
061: import org.apache.axis.configuration.EngineConfigurationFactoryDefault;
062: import org.apache.axis.components.net.DefaultCommonsHTTPClientProperties;
063: import org.apache.axis.message.SOAPHeader;
064:
065: /**
066: * Data source for SOAP.
067: */
068: public class SOAPDataSource extends DataSource {
069: private static Logger mLogger = Logger
070: .getLogger(SOAPDataSource.class);
071:
072: /** Static document builder. */
073: static DocumentBuilder mBuilder = null;
074:
075: static String mLoadOption;
076:
077: static HashMap mServiceMap = new HashMap();
078:
079: //------------------------------------------------------------
080: // Begin soap info variables
081: //------------------------------------------------------------
082:
083: static long mLastCleared = -1;
084:
085: static LoadCount mInvokeLoad = new LoadCount(10);
086: static LoadCount mGetServiceLoad = new LoadCount(10);
087:
088: //------------------------------------------------------------
089: // End soap info variables
090: //------------------------------------------------------------
091:
092: {
093: DocumentBuilderFactory factory = DocumentBuilderFactory
094: .newInstance();
095: try {
096: mBuilder = factory.newDocumentBuilder();
097: } catch (ParserConfigurationException e) {
098: System.err.println(
099: /* (non-Javadoc)
100: * @i18n.test
101: * @org-mes="Can't create DocumentBuilder"
102: */
103: org.openlaszlo.i18n.LaszloMessages.getMessage(
104: SOAPDataSource.class.getName(), "051018-104"));
105: }
106:
107: mLoadOption = LPS.getProperty("rpc.soap.wsdlLoadOption",
108: "always");
109: if (mLogger.isDebugEnabled()) {
110: mLogger.debug(
111: /* (non-Javadoc)
112: * @i18n.test
113: * @org-mes="WSDL load option is set to \"" + p[0] + "\""
114: */
115: org.openlaszlo.i18n.LaszloMessages.getMessage(
116: SOAPDataSource.class.getName(), "051018-116",
117: new Object[] { mLoadOption }));
118: }
119: }
120:
121: public SOAPDataSource() {
122: clearLoadInfo();
123:
124: //------------------------------------------------------------
125: // AXIS CommonsHTTPClient properties
126: //------------------------------------------------------------
127: String clientConfigFile = LPS.getProperties().getProperty(
128: "axis.clientConfigFile",
129: LPS.getConfigDirectory() + File.separatorChar
130: + "client-config.wsdd");
131:
132: AxisProperties
133: .setProperty(
134: EngineConfigurationFactoryDefault.OPTION_CLIENT_CONFIG_FILE,
135: clientConfigFile);
136:
137: // Reuse HTTPDataSource HttpClient properties.
138: AxisProperties
139: .setProperty(
140: DefaultCommonsHTTPClientProperties.MAXIMUM_TOTAL_CONNECTIONS_PROPERTY_KEY,
141: "" + HTTPDataSource.getMaxTotalConnections());
142: AxisProperties
143: .setProperty(
144: DefaultCommonsHTTPClientProperties.MAXIMUM_CONNECTIONS_PER_HOST_PROPERTY_KEY,
145: "" + HTTPDataSource.getMaxConnectionsPerHost());
146: AxisProperties
147: .setProperty(
148: DefaultCommonsHTTPClientProperties.CONNECTION_POOL_TIMEOUT_KEY,
149: "" + HTTPDataSource.getConnectionPoolTimeout());
150: }
151:
152: /**
153: * @return unique name of this data source
154: */
155: public String name() {
156: return "soap";
157: }
158:
159: /**
160: * Sends system information to client.
161: *
162: * @throws DataSourceException if there was a problem retrieving or sending
163: * the data.
164: */
165: public Data getData(String app, HttpServletRequest req,
166: HttpServletResponse res, long lastModifiedTime)
167: throws InterruptedIOException, IOException,
168: DataSourceException {
169: if (mLogger.isDebugEnabled()) {
170: mLogger.debug("getData");
171: }
172:
173: String request = req.getParameter("request");
174: if (request == null || request.equals(""))
175: request = "invoke";
176:
177: int swfnum = LPS.getSWFVersionNum(req);
178:
179: long t0, t1;
180: t0 = System.currentTimeMillis();
181: if (request.equals("invoke")) {
182: mInvokeLoad.increment();
183: try {
184: return invoke(app, req, res, swfnum);
185: } finally {
186: t1 = System.currentTimeMillis();
187: mInvokeLoad.decrement((int) (t1 - t0));
188: }
189: } else if (request.equals("load")) {
190: mGetServiceLoad.increment();
191: try {
192: LZSOAPService service = getService(app, req, res, true);
193: return new SOAPData(service
194: .getClientSOAPService(swfnum));
195: } catch (ServiceException e) {
196: return exceptionToSOAPData(swfnum,
197: new DataSourceException(e.getMessage()));
198: } finally {
199: t1 = System.currentTimeMillis();
200: mGetServiceLoad.decrement((int) (t1 - t0));
201: }
202: }
203:
204: return exceptionToSOAPData(swfnum, new DataSourceException(
205: /* (non-Javadoc)
206: * @i18n.test
207: * @org-mes="unknown SOAP datasource request"
208: */
209: org.openlaszlo.i18n.LaszloMessages.getMessage(
210: SOAPDataSource.class.getName(), "051018-201")));
211: }
212:
213: public Data invoke(String app, HttpServletRequest req,
214: HttpServletResponse res, int swfnum) {
215:
216: if (!req.getMethod().equals("POST")) {
217: mLogger.error(
218: /* (non-Javadoc)
219: * @i18n.test
220: * @org-mes="request must be POST"
221: */
222: org.openlaszlo.i18n.LaszloMessages.getMessage(
223: SOAPDataSource.class.getName(), "051018-215"));
224: return exceptionToSOAPData(swfnum, new DataSourceException(
225: /* (non-Javadoc)
226: * @i18n.test
227: * @org-mes="request must be POST"
228: */
229: org.openlaszlo.i18n.LaszloMessages.getMessage(
230: SOAPDataSource.class.getName(), "051018-215")));
231: }
232:
233: String xml = req.getParameter("lzpostbody");
234: if (xml == null || xml.equals("")) {
235: mLogger.error(
236: /* (non-Javadoc)
237: * @i18n.test
238: * @org-mes="no post body"
239: */
240: org.openlaszlo.i18n.LaszloMessages.getMessage(
241: SOAPDataSource.class.getName(), "051018-236"));
242: return exceptionToSOAPData(swfnum, new DataSourceException(
243: /* (non-Javadoc)
244: * @i18n.test
245: * @org-mes="no post body"
246: */
247: org.openlaszlo.i18n.LaszloMessages.getMessage(
248: SOAPDataSource.class.getName(), "051018-236")));
249: }
250:
251: if (mLogger.isDebugEnabled()) {
252: mLogger.debug("lzpostbody: " + xml);
253: }
254:
255: try {
256:
257: LZSOAPService service = getService(app, req, res, false);
258: if (service == null) {
259: return exceptionToSOAPData(swfnum,
260: new DataSourceException(
261: /* (non-Javadoc)
262: * @i18n.test
263: * @org-mes="could not find service"
264: */
265: org.openlaszlo.i18n.LaszloMessages.getMessage(
266: SOAPDataSource.class.getName(),
267: "051018-264")));
268: }
269:
270: try {
271: String operation = req.getParameter("operation");
272: Object[] value = service.invoke(operation, xml);
273:
274: if (value[0].equals("rpc")) {
275: return programToSOAPData(swfnum,
276: (Program) value[1], (SOAPHeader) value[2]);
277: } else {
278: return documentsToSOAPData(swfnum,
279: (Vector) value[1], (SOAPHeader) value[2]);
280: }
281: } catch (AxisFault fault) {
282: // Axis throws exceptions for faults
283: return new SOAPData(new SOAPDataEncoder(swfnum)
284: .buildFromFault(fault));
285: }
286:
287: } catch (RemoteException e) {
288: mLogger.error("RemoteException: " + e.getMessage(), e);
289: return exceptionToSOAPData(swfnum, e);
290: } catch (DataSourceException e) {
291: mLogger.error("DataSourceException: " + e.getMessage(), e);
292: return exceptionToSOAPData(swfnum, e);
293: } catch (IOException e) {
294: mLogger.error("IOException: " + e.getMessage(), e);
295: return exceptionToSOAPData(swfnum, e);
296: } catch (ServiceException e) {
297: mLogger.error("ServiceException: " + e.getMessage(), e);
298: return exceptionToSOAPData(swfnum, e);
299: } catch (Exception e) {
300: mLogger.error("Exception: " + e.getMessage(), e);
301: return exceptionToSOAPData(swfnum, e);
302: }
303:
304: }
305:
306: SOAPData exceptionToSOAPData(int swfnum, Exception e) {
307: mLogger.error("Exception", e);
308: return new SOAPData(new SOAPDataEncoder(swfnum)
309: .buildFromException(e));
310: }
311:
312: SOAPData documentsToSOAPData(int swfnum, Vector docs,
313: SOAPHeader header) throws DataSourceException {
314:
315: if (mLogger.isDebugEnabled()) {
316: mLogger.debug(
317: /* (non-Javadoc)
318: * @i18n.test
319: * @org-mes="documentsToSOAPData"
320: */
321: org.openlaszlo.i18n.LaszloMessages.getMessage(
322: SOAPDataSource.class.getName(), "051018-323"));
323: }
324:
325: if (docs.size() == 0) {
326: mLogger.error(
327: /* (non-Javadoc)
328: * @i18n.test
329: * @org-mes="no document return"
330: */
331: org.openlaszlo.i18n.LaszloMessages.getMessage(
332: SOAPDataSource.class.getName(), "051018-334"));
333: throw new DataSourceException(
334: /* (non-Javadoc)
335: * @i18n.test
336: * @org-mes="no document return"
337: */
338: org.openlaszlo.i18n.LaszloMessages.getMessage(
339: SOAPDataSource.class.getName(), "051018-334"));
340: }
341:
342: try {
343: return new SOAPData(new SOAPDataEncoder(swfnum, docs,
344: header));
345: } catch (Exception e) {
346: mLogger.error(
347: /* (non-Javadoc)
348: * @i18n.test
349: * @org-mes="problems getting SOAP document element"
350: */
351: org.openlaszlo.i18n.LaszloMessages.getMessage(
352: SOAPDataSource.class.getName(), "051018-355"), e);
353: throw new DataSourceException(
354: /* (non-Javadoc)
355: * @i18n.test
356: * @org-mes="problems getting SOAP document element"
357: */
358: org.openlaszlo.i18n.LaszloMessages.getMessage(
359: SOAPDataSource.class.getName(), "051018-355"));
360: }
361: }
362:
363: /**
364: * Creates key for services map.
365: *
366: * @param wsdl WSDL URL.
367: * @param service SOAP service name.
368: * @param port SOAP port name.
369: * @return key to use for services map.
370: */
371: public String key(String wsdl, String service, String port) {
372: return wsdl + " " + service + " " + port;
373: }
374:
375: /**
376: * Get SOAP service.
377: *
378: * @param req
379: * @param res
380: * @exception DataSourceException
381: */
382: synchronized public LZSOAPService getService(String app,
383: HttpServletRequest req, HttpServletResponse res,
384: boolean checkLoadOption) throws DataSourceException {
385:
386: String wsdl = req.getParameter("wsdl");
387: String service = req.getParameter("service");
388: String port = req.getParameter("port");
389:
390: if (wsdl == null || wsdl.equals("")) {
391: throw new DataSourceException(
392: /* (non-Javadoc)
393: * @i18n.test
394: * @org-mes="no wsdl specified"
395: */
396: org.openlaszlo.i18n.LaszloMessages.getMessage(
397: SOAPDataSource.class.getName(), "051018-408"));
398: }
399:
400: LZSOAPService soapService = null;
401: if (checkLoadOption) {
402: if ("never" == mLoadOption.intern()) {
403: soapService = (LZSOAPService) mServiceMap.get(key(wsdl,
404: service, port));
405: }
406: } else {
407: soapService = (LZSOAPService) mServiceMap.get(key(wsdl,
408: service, port));
409: }
410: if (soapService != null)
411: return soapService;
412:
413: return fetchService(app, req, res);
414: }
415:
416: /**
417: * Fetch SOAP service over HTTP.
418: */
419: synchronized public LZSOAPService fetchService(String app,
420: HttpServletRequest req, HttpServletResponse res)
421: throws DataSourceException {
422:
423: String wsdl = req.getParameter("wsdl");
424: String service = req.getParameter("service");
425: String port = req.getParameter("port");
426:
427: // TODO: [2007-06-24 pkang] cache WSDLs.
428: try {
429: Data data;
430: String wsdlurl = getURL(req, wsdl);
431: if (wsdlurl.startsWith("file:")) {
432: data = FileDataSource.getFileData(app, req, res,
433: wsdlurl, -1);
434: } else if (wsdlurl.startsWith("http://")
435: || wsdlurl.startsWith("https://")) {
436: data = HTTPDataSource
437: .getHTTPData(req, res, wsdlurl, -1);
438: } else {
439: throw new DataSourceException(
440: /* (non-Javadoc)
441: * @i18n.test
442: * @org-mes="unsupported WSDL protocol: " + p[0]
443: */
444: org.openlaszlo.i18n.LaszloMessages.getMessage(
445: SOAPDataSource.class.getName(), "051018-453",
446: new Object[] { wsdlurl }));
447: }
448: String _wsdl = data.getAsString();
449: LZSOAPService soapService = WSDLParser.parse(wsdl,
450: new InputSource(new StringReader(_wsdl)), service,
451: port);
452:
453: if (soapService == null) {
454: throw new DataSourceException(
455: /* (non-Javadoc)
456: * @i18n.test
457: * @org-mes="could not find SOAP service (" + p[0] + ", " + p[1] + ", " + p[2] + ")"
458: */
459: org.openlaszlo.i18n.LaszloMessages.getMessage(
460: SOAPDataSource.class.getName(), "051018-468",
461: new Object[] { wsdl, service, port }));
462: }
463:
464: mServiceMap.put(key(wsdl, soapService.getServiceName(),
465: soapService.getPort()), soapService);
466:
467: // secondary mapping to the same service
468: if (service == null || port == null) {
469: mServiceMap.put(key(wsdl, service, port), soapService);
470: }
471:
472: return soapService;
473:
474: } catch (IOException e) {
475: mLogger.error("Exception", e);
476: throw new DataSourceException(e.getMessage());
477: } catch (WSDLException e) {
478: mLogger.error("Exception", e);
479: throw new DataSourceException(e.getMessage());
480: } catch (ServiceException e) {
481: mLogger.error("Exception", e);
482: throw new DataSourceException(e.getMessage());
483: } catch (Exception e) {
484: mLogger.error("Exception", e);
485: throw new DataSourceException(e.getMessage());
486: }
487: }
488:
489: SOAPData programToSOAPData(int swfnum, Program program,
490: SOAPHeader header) throws DataSourceException {
491:
492: if (mLogger.isDebugEnabled()) {
493: mLogger.debug(
494: /* (non-Javadoc)
495: * @i18n.test
496: * @org-mes="programToSOAPData"
497: */
498: org.openlaszlo.i18n.LaszloMessages.getMessage(
499: SOAPDataSource.class.getName(), "051018-508"));
500: }
501:
502: try {
503: return new SOAPData(new SOAPDataEncoder(swfnum, program,
504: header));
505: } catch (Exception e) {
506: mLogger.error(
507: /* (non-Javadoc)
508: * @i18n.test
509: * @org-mes="problems creating RPC program"
510: */
511: org.openlaszlo.i18n.LaszloMessages.getMessage(
512: SOAPDataSource.class.getName(), "051018-521"), e);
513: throw new DataSourceException(
514: /* (non-Javadoc)
515: * @i18n.test
516: * @org-mes="problems creating RPC program"
517: */
518: org.openlaszlo.i18n.LaszloMessages.getMessage(
519: SOAPDataSource.class.getName(), "051018-521"));
520: }
521: }
522:
523: /**
524: * @param serviceKey the key name to the LZSOAPService
525: */
526: synchronized public static void serviceXML(StringBuffer sb,
527: String serviceKey) {
528: LZSOAPService service = (LZSOAPService) mServiceMap
529: .get(serviceKey);
530: if (service != null) {
531: service.toXML(sb);
532: } else {
533: sb.append("<error>").append(serviceKey).append(
534: " service not found.").append("</error>");
535: }
536: }
537:
538: synchronized public static void listServicesXML(StringBuffer sb) {
539: sb.append("<services count=\"").append(mServiceMap.size())
540: .append("\">");
541: Iterator iter = mServiceMap.keySet().iterator();
542: while (iter.hasNext()) {
543: String k = (String) iter.next();
544: sb.append("<service").append(" name=\"").append(
545: XMLUtils.escapeXml(k)).append("\"").append(
546: " urlname=\"").append(URLEncoder.encode(k)).append(
547: "\"").append("/>");
548: }
549: sb.append("</services>");
550: }
551:
552: synchronized static public void toXML(StringBuffer sb) {
553: Date lc = new Date(mLastCleared);
554: sb.append("<soapinfo ").append(" last-cleared=\"").append(lc)
555: .append("\"").append(">");
556: {
557: sb.append(mInvokeLoad.toXML("invoke_load"));
558: sb.append(mGetServiceLoad.toXML("get_service_load"));
559: listServicesXML(sb);
560: }
561: sb.append("</soapinfo>");
562: }
563:
564: public static void clearLoadInfo() {
565: mInvokeLoad.reset();
566: mGetServiceLoad.reset();
567: mLastCleared = System.currentTimeMillis();
568: }
569:
570: /**
571: * A data object to hold session object.
572: */
573: public class SOAPData extends Data {
574: byte[] mObject = null;
575: SOAPDataEncoder mSOAPDataEncoder = null;
576:
577: public SOAPData(byte[] object) {
578: mObject = object;
579: }
580:
581: public SOAPData(SOAPDataEncoder de) {
582: mSOAPDataEncoder = de;
583: }
584:
585: public String getMimeType() {
586: return MimeType.SWF;
587: }
588:
589: public InputStream getInputStream() throws IOException {
590: if (mObject != null) {
591: return new ByteArrayInputStream(mObject);
592: } else if (mSOAPDataEncoder != null) {
593: return mSOAPDataEncoder.getInputStream();
594: }
595:
596: throw new IOException("no input stream");
597: }
598:
599: public long size() {
600: if (mObject != null) {
601: return mObject.length;
602: } else if (mSOAPDataEncoder != null) {
603: try {
604: return mSOAPDataEncoder.getSize();
605: } catch (IOException e) {
606: mLogger.warn(
607: /* (non-Javadoc)
608: * @i18n.test
609: * @org-mes="ioexception getting size soap data encoder"
610: */
611: org.openlaszlo.i18n.LaszloMessages.getMessage(
612: SOAPDataSource.class.getName(),
613: "051018-622"));
614: }
615: }
616:
617: return 0;
618: }
619: }
620: }
|