001: /*
002: * CoadunationLib: The coaduntion implementation library.
003: * Copyright (C) 2006 Rift IT Contracting
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
018: *
019: * WebServiceWrapper.java
020: *
021: * The web service wrapper responsible for wrapping the SOAP web service
022: * information.
023: */
024:
025: // package paths
026: package com.rift.coad.lib.webservice;
027:
028: // java imports
029: import java.io.ByteArrayOutputStream;
030: import java.io.ByteArrayInputStream;
031: import java.io.File;
032: import java.io.FileInputStream;
033: import java.io.InputStream;
034: import java.lang.ClassLoader;
035: import java.net.InetAddress;
036: import java.util.Iterator;
037: import java.util.Map;
038: import java.util.HashMap;
039: import java.util.Vector;
040: import java.util.Set;
041: import java.util.regex.Matcher;
042: import java.util.regex.Pattern;
043: import javax.xml.namespace.QName;
044: import java.lang.reflect.Method;
045: import javax.naming.InitialContext;
046: import javax.transaction.Status;
047: import javax.transaction.UserTransaction;
048:
049: // w3c imports
050: import org.w3c.dom.Document;
051:
052: // logging import
053: import org.apache.log4j.Logger;
054:
055: // ibm imports
056: import com.ibm.wsdl.OperationImpl;
057:
058: // jaxb imports
059: import javax.xml.soap.MimeHeaders;
060:
061: // axis imports
062: import org.apache.axis.AxisFault;
063: import org.apache.axis.AxisEngine;
064: import org.apache.axis.Constants;
065: import org.apache.axis.Message;
066: import org.apache.axis.MessageContext;
067: import org.apache.axis.constants.Scope;
068: import org.apache.axis.description.ServiceDesc;
069: import org.apache.axis.handlers.soap.SOAPService;
070: import org.apache.axis.message.SOAPEnvelope;
071: import org.apache.axis.message.SOAPFault;
072: import org.apache.axis.providers.java.RPCProvider;
073: import org.apache.axis.transport.http.NonBlockingBufferedInputStream;
074: import org.apache.axis.server.AxisServer;
075: import org.apache.axis.utils.ClassUtils;
076: import org.apache.axis.utils.Messages;
077: import org.apache.axis.utils.XMLUtils;
078: import org.apache.axis.wsdl.gen.Parser;
079: import org.apache.axis.wsdl.symbolTable.SymbolTable;
080: import org.apache.axis.wsdl.symbolTable.BaseType;
081: import org.apache.axis.wsdl.symbolTable.BindingEntry;
082: import org.apache.axis.wsdl.symbolTable.Type;
083: import org.apache.axis.wsdl.symbolTable.DefinedType;
084: import javax.xml.rpc.encoding.TypeMapping;
085: import javax.xml.rpc.encoding.TypeMappingRegistry;
086:
087: // coadunation imports
088: import com.rift.coad.lib.configuration.Configuration;
089: import com.rift.coad.lib.configuration.ConfigurationFactory;
090: import com.rift.coad.lib.deployment.WebServiceInfo;
091: import com.rift.coad.lib.deployment.DeploymentLoader;
092: import com.rift.coad.lib.httpd.HttpDaemon;
093: import com.rift.coad.lib.httpd.MimeTypes;
094: import com.rift.coad.lib.thirdparty.axis.AxisManager;
095: import com.rift.coad.lib.thirdparty.axis.AxisException;
096:
097: /**
098: * The web service wrapper responsible for wrapping the SOAP web service
099: * information.
100: *
101: * @author Brett Chaldecott
102: */
103: public class WebServiceWrapper {
104:
105: // the class log variable
106: protected Logger log = Logger.getLogger(WebServiceWrapper.class
107: .getName());
108:
109: // class constants
110: private final static String PARSER_PATTERN = "address[\\s]+location=\"[a-z0-9://_-]*\"";
111: private final static String SERVICE_END_POINT_FORMAT = "address location=\"http://%s:%d%s\"";
112: private final static String URL_FORMAT = "http://%s:%d%s";
113: private final static String HOST = "host";
114: private final static String PORT = "port";
115: private final static String TRANSPORT_NAME = "SimpleHTTP";
116:
117: // the classes member variables
118: private String serviceEndPoint = null;
119: private String url = null;
120: private String path = null;
121: private String role = null;
122: private SOAPService service = null;
123: private ClassLoader classLoader = null;
124: private boolean transaction = false;
125: private InitialContext context = null;
126: private UserTransaction ut = null;
127: private Vector extraClasses = null;
128:
129: /**
130: * Creates a new instance of WebServiceWrapper.
131: *
132: * @param webServiceInfo The reference to the web service information.
133: * @param deploymentLoader The object responsible for loading a deployment
134: * file.
135: * @exception WebServiceException
136: */
137: public WebServiceWrapper(WebServiceInfo webServiceInfo,
138: DeploymentLoader deploymentLoader)
139: throws WebServiceException {
140:
141: try {
142: Configuration config = ConfigurationFactory.getInstance()
143: .getConfig(WebServiceWrapper.class);
144: context = new InitialContext();
145:
146: String host = config.getString(HOST, InetAddress
147: .getLocalHost().getCanonicalHostName());
148: int port = (int) config.getLong(PORT,
149: HttpDaemon.DEFAULT_PORT);
150:
151: path = webServiceInfo.getPath();
152: role = webServiceInfo.getRole();
153: serviceEndPoint = String.format(SERVICE_END_POINT_FORMAT,
154: host, port, path);
155: url = String.format(URL_FORMAT, host, port, path);
156:
157: // retrieve a reverence to the axis engine
158: AxisEngine engine = AxisManager.getInstance().getServer();
159:
160: // test the class
161: deploymentLoader.getClass(webServiceInfo.getClassName());
162:
163: // try different handlers
164: service = new SOAPService(new WebServiceInvoker(
165: deploymentLoader.getClassLoader()));
166:
167: service.setName(webServiceInfo.getClassName());
168: service.setOption(RPCProvider.OPTION_CLASSNAME,
169: webServiceInfo.getClassName());
170: service.setOption(RPCProvider.OPTION_ALLOWEDMETHODS, "*");
171: service.setOption(RPCProvider.OPTION_SCOPE, Scope.DEFAULT
172: .getName());
173:
174: // validate
175: String wsdlPath = deploymentLoader.getTmpDir()
176: .getAbsolutePath()
177: + File.separator + webServiceInfo.getWSDLPath();
178: validate(wsdlPath, deploymentLoader.getClassLoader()
179: .loadClass(webServiceInfo.getClassName()));
180: extraClasses = webServiceInfo.getClasses();
181: generateTypeMapping(wsdlPath, deploymentLoader
182: .getClassLoader().loadClass(
183: webServiceInfo.getClassName()));
184: // wsdl parameters
185: ServiceDesc serviceDesc = service.getServiceDescription();
186: serviceDesc.setWSDLFile(wsdlPath);
187:
188: service.setEngine(engine);
189: service.init();
190:
191: // set the class loader
192: classLoader = deploymentLoader.getClassLoader();
193:
194: // set the class loader
195: ClassUtils.setClassLoader(webServiceInfo.getClassName(),
196: deploymentLoader.getClassLoader());
197:
198: transaction = webServiceInfo.getTransaction();
199: if (transaction) {
200: ut = (UserTransaction) context
201: .lookup("java:comp/UserTransaction");
202: }
203:
204: } catch (Exception ex) {
205: throw new WebServiceException(
206: "Failed to initialize the web service : "
207: + ex.getMessage(), ex);
208: }
209: }
210:
211: /**
212: * This method returns the path to the web service.
213: *
214: * @return The string containing the path to the web service.
215: */
216: public String getPath() {
217: return path;
218: }
219:
220: /**
221: * This method returns the role of the web service.
222: *
223: * @return The string containing the role information.
224: */
225: public String getRole() {
226: return role;
227: }
228:
229: /**
230: * This method return the reference to the soap service.
231: *
232: * @return The reference to the soap service.
233: */
234: public SOAPService getService() {
235: return service;
236: }
237:
238: /**
239: * This method returns the class loader that can be used to load the service.
240: *
241: * @return The reference to the class loader.
242: */
243: public ClassLoader getClassLoader() {
244: return classLoader;
245: }
246:
247: /**
248: * This method returns a string containing the genreated WSDL
249: *
250: * @return String The string containing the wsdl.
251: * @exception WebServiceException
252: */
253: public String generateWSDL() throws WebServiceException {
254: // setup the axis engine
255: MessageContext msgContext = null;
256: try {
257: msgContext = setupContext();
258: AxisServer engine = AxisManager.getInstance().getServer();
259: engine.generateWSDL(msgContext);
260: Document doc = (Document) msgContext.getProperty("WSDL");
261: XMLUtils.normalize(doc.getDocumentElement());
262: String wsdl = XMLUtils.PrettyDocumentToString(doc);
263: Pattern pattern = Pattern.compile(PARSER_PATTERN,
264: Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
265: return pattern.matcher(wsdl).replaceFirst(serviceEndPoint);
266: } catch (WebServiceException ex) {
267: throw ex;
268: } catch (AxisException ex) {
269: throw new WebServiceException(
270: "Failed to retrieve a valid axis reference : "
271: + ex.getMessage(), ex);
272: } catch (Exception ex) {
273: processException(msgContext, ex);
274: }
275: // this statement will never get reached as the process exception will
276: // always throw, but to prevent the compiler from complaining it is
277: // put here.
278: return null;
279: }
280:
281: /**
282: * This message takes a string message block and returns a string message
283: * block.
284: *
285: * @return The string result.
286: * @param message The message to process.
287: * @exception WebServiceException
288: */
289: public String processRequest(String message)
290: throws WebServiceException {
291: return processRequest(new ByteArrayInputStream(message
292: .getBytes()), new MimeHeaders());
293: }
294:
295: /**
296: * This method process the input stream passed to it and returns a string
297: * message.
298: *
299: * @return An XML string result of the request.
300: * @param in The input stream containing the message to process.
301: * @param headers The headers to process.
302: * @exception WebServiceException
303: */
304: public String processRequest(InputStream in, MimeHeaders headers)
305: throws WebServiceException {
306: MessageContext msgContext = null;
307: boolean ownTransaction = false;
308: try {
309: if (transaction
310: && (ut.getStatus() == Status.STATUS_NO_TRANSACTION)) {
311: ut.begin();
312: ownTransaction = true;
313: }
314: msgContext = setupContext();
315: AxisServer engine = AxisManager.getInstance().getServer();
316: NonBlockingBufferedInputStream inputStream = new NonBlockingBufferedInputStream();
317: inputStream.setInputStream(in);
318:
319: // setup a message request
320: Message requestMsg = new Message(inputStream, false,
321: headers);
322: msgContext.setRequestMessage(requestMsg);
323:
324: // invoke the request
325: engine.invoke(msgContext);
326:
327: // retrieve the result
328: Message responseMsg = msgContext.getResponseMessage();
329: ByteArrayOutputStream output = new ByteArrayOutputStream();
330: responseMsg.writeTo(output);
331: output.flush();
332: if (ownTransaction) {
333: ut.commit();
334: ownTransaction = false;
335: }
336: return output.toString();
337: } catch (WebServiceException ex) {
338: throw ex;
339: } catch (Exception ex) {
340: processException(msgContext, ex);
341: } finally {
342: if (ownTransaction) {
343: try {
344: if (ut.getStatus() == Status.STATUS_ACTIVE) {
345: ut.rollback();
346: }
347: } catch (Exception ex2) {
348: log.error("Failed to rollback the changes : "
349: + ex2.getMessage(), ex2);
350: }
351: }
352: }
353:
354: // this statement will never get reached as the process exception will
355: // always throw, but to prevent the compiler from complaining it is
356: // put here.
357: return null;
358: }
359:
360: /**
361: * This method validates the WSDL file
362: *
363: * @param wsdlPath The path to the wsdl file to perform the validation for.
364: * @param ref The reference to the class to validate.
365: * @exception WebServiceException
366: */
367: private void validate(String wsdlPath, Class ref)
368: throws WebServiceException {
369: try {
370: // TODO: Improve WSDL validation to check more than just the operation names
371: // This will be done at a later date when it becomes a problem
372:
373: Parser parser = new Parser();
374: parser.run(wsdlPath);
375: Set operations = getOpertations(parser);
376: Method[] methods = ref.getDeclaredMethods();
377: for (Iterator iter = operations.iterator(); iter.hasNext();) {
378: OperationImpl operation = (OperationImpl) iter.next();
379: boolean found = false;
380: for (int index = 0; index < methods.length; index++) {
381: if (methods[index].getName().equals(
382: operation.getName())) {
383: found = true;
384: }
385: }
386: if (!found) {
387: throw new WebServiceException("The operation ["
388: + operation.getName() + "] not found on ["
389: + ref.getName() + "]");
390: }
391: }
392: } catch (WebServiceException ex) {
393: throw ex;
394: } catch (Exception ex) {
395: log.error("Validation on [" + wsdlPath + "] failed : "
396: + ex.getMessage(), ex);
397: throw new WebServiceException("Validation on [" + wsdlPath
398: + "] failed : " + ex.getMessage(), ex);
399: }
400: }
401:
402: /**
403: * This method returns the list of operations on a wsdl interface.
404: *
405: * @return The set containing the list of operations.
406: * @param parser The reference to the parser
407: */
408: private Set getOpertations(Parser parser)
409: throws WebServiceException {
410: SymbolTable symbolTable = parser.getSymbolTable();
411: Map index = symbolTable.getHashMap();
412: for (Iterator iter = index.keySet().iterator(); iter.hasNext();) {
413: QName key = (QName) iter.next();
414: Object value = index.get(key);
415: if (value instanceof Vector) {
416: Vector list = (Vector) value;
417: for (int count = 0; count < list.size(); count++) {
418: Object listValue = list.get(count);
419: if (listValue instanceof BindingEntry) {
420: BindingEntry entry = (BindingEntry) listValue;
421: return entry.getOperations();
422: }
423: }
424: }
425: }
426: throw new WebServiceException(
427: "There are no operations defined for this wsdl file");
428: }
429:
430: /**
431: * This method validates the WSDL file
432: *
433: * @param wsdlPath The path to the wsdl file to perform the validation for.
434: * @param ref The reference to the class to validate.
435: * @exception WebServiceException
436: */
437: private void generateTypeMapping(String wsdlPath, Class ref)
438: throws WebServiceException {
439: try {
440: Parser parser = new Parser();
441: parser.run(wsdlPath);
442: SymbolTable symbolTable = parser.getSymbolTable();
443: Map index = parser.getSymbolTable().getTypeIndex();
444: //this.service.getTypeMappingRegistry().register("http://schemas.xmlsoap.org/soap/encoding/")
445: Map classTypes = getTypes(ref);
446: TypeMappingRegistry registry = this .service
447: .getTypeMappingRegistry();
448: TypeMapping typeMapping = registry.createTypeMapping();
449: for (Iterator iter = index.keySet().iterator(); iter
450: .hasNext();) {
451: QName key = (QName) iter.next();
452: Type value = (Type) index.get(key);
453: if (value instanceof DefinedType) {
454: DefinedType defType = (DefinedType) value;
455:
456: if (defType.getRefType() == null) {
457: Class refType = (Class) classTypes.get(defType
458: .getQName().getLocalPart());
459: if (refType == null
460: && defType.getQName().getLocalPart()
461: .equalsIgnoreCase("MapItem")) {
462: refType = java.util.Map.class;
463: } else if (refType == null) {
464: continue;
465: }
466: log.debug("Key [" + key.toString()
467: + "] value ["
468: + defType.getQName().getLocalPart()
469: + "] [" + defType.getBaseType() + "] ["
470: + defType.toString() + "] ["
471: + refType.getName() + "]");
472: typeMapping
473: .register(
474: refType,
475: defType.getQName(),
476: new org.apache.axis.encoding.ser.BeanSerializerFactory(
477: refType, defType
478: .getQName()),
479: new org.apache.axis.encoding.ser.BeanDeserializerFactory(
480: refType, defType
481: .getQName()));
482: }
483:
484: }
485: }
486: this .service.getTypeMappingRegistry().register(
487: "http://schemas.xmlsoap.org/soap/encoding/",
488: typeMapping);
489: } catch (Exception ex) {
490: log.error("Failed to retrieve the type mapping ["
491: + wsdlPath + "] because : " + ex.getMessage(), ex);
492: throw new WebServiceException(
493: "Failed to retrieve the type mapping [" + wsdlPath
494: + "] failed : " + ex.getMessage(), ex);
495: }
496: }
497:
498: /**
499: * This method setups the message context
500: *
501: * @returns A message context.
502: * @exception WebServiceException
503: */
504: private MessageContext setupContext() throws WebServiceException {
505: try {
506: // setup the axis engine
507: AxisServer engine = AxisManager.getInstance().getServer();
508: MessageContext msgContext = new MessageContext(engine);
509: msgContext.setTransportName(TRANSPORT_NAME);
510: msgContext.setProperty(MessageContext.TRANS_URL, url);
511: msgContext.setProperty(Constants.MC_REALPATH, path);
512: msgContext.setProperty(Constants.MC_RELATIVE_PATH, path);
513: msgContext.setService(service);
514: return msgContext;
515: } catch (Exception ex) {
516: log.error("Failed to setup the message context : "
517: + ex.getMessage(), ex);
518: throw new WebServiceException(
519: "Failed to setup the message context : "
520: + ex.getMessage(), ex);
521: }
522: }
523:
524: /**
525: * This method handles the exceptions generated by axis generically.
526: *
527: * @param msgContext The message context.
528: * @param ex The exception to throw
529: */
530: private void processException(MessageContext msgContext,
531: Exception ex) throws WebServiceException {
532: try {
533: // check that the message context has been set
534: if (msgContext == null) {
535: throw new WebServiceException(
536: "Failed to process request : "
537: + ex.getMessage(), ex);
538: }
539:
540: log.debug("Fault messsage is : " + ex.getMessage(), ex);
541:
542: // handle the axis fault
543: AxisFault af;
544: if (ex instanceof AxisFault) {
545: af = (AxisFault) ex;
546: log.debug(Messages.getMessage("serverFault00"), af);
547: } else {
548: af = AxisFault.makeFault(ex);
549: }
550:
551: // There may be headers we want to preserve in the
552: // response message - so if it's there, just add the
553: // FaultElement to it. Otherwise, make a new one.
554: Message responseMsg = msgContext.getResponseMessage();
555: if (responseMsg == null) {
556: responseMsg = new Message(af);
557: responseMsg.setMessageContext(msgContext);
558: } else {
559: SOAPEnvelope env = responseMsg.getSOAPEnvelope();
560: env.clearBody();
561: env.addBodyElement(new SOAPFault((AxisFault) ex));
562: }
563: ByteArrayOutputStream output = new ByteArrayOutputStream();
564: responseMsg.writeTo(output);
565: output.flush();
566:
567: // throw an exception with the xml content wrapped properly
568: throw new WebServiceException(output.toString(),
569: MimeTypes.XML);
570: } catch (WebServiceException ex2) {
571: throw ex2;
572: } catch (Exception ex2) {
573: log.error("Failed to process the exception : "
574: + ex2.getMessage(), ex2);
575: throw new WebServiceException(
576: "Failed to process the exception : "
577: + ex2.getMessage(), ex2);
578: }
579: }
580:
581: /**
582: * This method returns the list of types for the class.
583: */
584: public Map getTypes(Class ref) throws WebServiceException {
585: Map entries = new HashMap();
586: Method[] methods = ref.getMethods();
587: for (int index = 0; index < methods.length; index++) {
588: Class returnType = methods[index].getReturnType();
589: Class[] parameterTypes = methods[index].getParameterTypes();
590: Class[] exceptionTypes = methods[index].getExceptionTypes();
591: if (!returnType.isPrimitive() && !returnType.isArray()) {
592: entries.put(returnType.getSimpleName(), returnType);
593: } else if (returnType.isArray()
594: && !returnType.getComponentType().isPrimitive()) {
595: entries
596: .put(returnType.getComponentType()
597: .getSimpleName(), returnType
598: .getComponentType());
599: }
600: for (int paramIndex = 0; paramIndex < parameterTypes.length; paramIndex++) {
601: Class param = parameterTypes[paramIndex];
602: if (!param.isPrimitive() && !param.isArray()) {
603: entries.put(param.getSimpleName(), param);
604: } else if (param.isArray()
605: && !param.getComponentType().isPrimitive()) {
606: entries.put(param.getComponentType()
607: .getSimpleName(), param.getComponentType());
608: }
609: }
610: for (int exceptionIndex = 0; exceptionIndex < exceptionTypes.length; exceptionIndex++) {
611: Class exception = exceptionTypes[exceptionIndex];
612: if (exception != java.rmi.RemoteException.class) {
613: entries.put(exception.getSimpleName(), exception);
614: }
615: }
616:
617: }
618: for (int index = 0; index < extraClasses.size(); index++) {
619: String className = (String) extraClasses.get(index);
620: try {
621: Class extraClass = Class.forName(className, false, ref
622: .getClassLoader());
623: entries.put(extraClass.getSimpleName(), extraClass);
624: } catch (Throwable ex) {
625: log.error("Failed to retrieve the class [" + className
626: + "] because : " + ex.getMessage(), ex);
627: }
628: }
629: return entries;
630: }
631: }
|