001: /*
002: * Copyright (c) 1998-2007 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.soap.skeleton;
031:
032: import com.caucho.jaxb.JAXBUtil;
033: import com.caucho.jaxb.JAXBContextImpl;
034: import static com.caucho.soap.wsdl.WSDLConstants.*;
035: import com.caucho.soap.jaxws.HandlerChainInvoker;
036: import com.caucho.soap.jaxws.JAXWSUtil;
037: import com.caucho.soap.jaxws.PortInfoImpl;
038: import com.caucho.soap.wsdl.WSDLDefinitions;
039: import com.caucho.util.Attachment;
040: import com.caucho.util.AttachmentReader;
041: import com.caucho.util.L10N;
042: import com.caucho.xml.XmlPrinter;
043: import com.caucho.xml.stream.StaxUtil;
044:
045: import org.w3c.dom.Node;
046: import javax.activation.DataHandler;
047: import javax.jws.HandlerChain;
048: import javax.jws.WebService;
049: import javax.servlet.http.HttpServletRequest;
050: import javax.servlet.http.HttpServletResponse;
051: import static javax.xml.XMLConstants.*;
052: import javax.xml.bind.JAXBContext;
053: import javax.xml.bind.JAXBException;
054: import javax.xml.bind.Marshaller;
055: import javax.xml.bind.Unmarshaller;
056: import javax.xml.namespace.QName;
057: import static javax.xml.soap.SOAPConstants.*;
058: import javax.xml.stream.XMLInputFactory;
059: import javax.xml.stream.XMLOutputFactory;
060: import javax.xml.stream.XMLStreamException;
061: import javax.xml.stream.XMLStreamReader;
062: import javax.xml.stream.XMLStreamWriter;
063: import javax.xml.transform.Source;
064: import javax.xml.transform.dom.DOMSource;
065: import javax.xml.transform.dom.DOMResult;
066: import javax.xml.transform.stream.StreamResult;
067: import javax.xml.ws.BindingType;
068: import javax.xml.ws.WebServiceException;
069: import javax.xml.ws.handler.Handler;
070: import javax.xml.ws.handler.HandlerResolver;
071: import javax.xml.ws.handler.PortInfo;
072: import static javax.xml.ws.handler.MessageContext.*;
073: import javax.xml.ws.soap.SOAPBinding;
074:
075: import java.io.ByteArrayInputStream;
076: import java.io.ByteArrayOutputStream;
077: import java.io.CharArrayReader;
078: import java.io.CharArrayWriter;
079: import java.io.File;
080: import java.io.FileInputStream;
081: import java.io.FileWriter;
082: import java.io.InputStream;
083: import java.io.IOException;
084: import java.io.OutputStream;
085: import java.io.OutputStreamWriter;
086: import java.io.Writer;
087:
088: import java.lang.reflect.Method;
089: import java.net.MalformedURLException;
090: import java.util.ArrayList;
091: import java.util.Enumeration;
092: import java.util.HashMap;
093: import java.util.List;
094: import java.util.Map;
095: import java.util.logging.Level;
096: import java.util.logging.Logger;
097:
098: /**
099: * Invokes a SOAP request on a Java POJO
100: */
101: public class DirectSkeleton extends Skeleton {
102: private static final Logger log = Logger
103: .getLogger(DirectSkeleton.class.getName());
104: public static final L10N L = new L10N(DirectSkeleton.class);
105:
106: private static final String TARGET_NAMESPACE_PREFIX = "m";
107:
108: private boolean _separateSchema = false;
109: private JAXBContextImpl _context;
110: private Marshaller _marshaller;
111: private Node _wsdlNode;
112:
113: private HashMap<String, AbstractAction> _actionNames = new HashMap<String, AbstractAction>();
114:
115: private HashMap<Method, AbstractAction> _actionMethods = new HashMap<Method, AbstractAction>();
116:
117: private Class _api;
118:
119: private HandlerChainInvoker _handlerChain;
120:
121: private String _bindingId;
122: private String _namespace;
123: private String _portType;
124: private String _portName;
125: private String _serviceName;
126: private String _wsdlLocation = "REPLACE_WITH_ACTUAL_URL";
127: private PortInfo _portInfo;
128: private WSDLDefinitions _wsdl;
129:
130: // The URI in SOAPBinding is wrong, but matches that of JAVAEE
131: private String _soapNamespaceURI = "http://schemas.xmlsoap.org/wsdl/soap/";
132: private String _soapTransport = "http://schemas.xmlsoap.org/soap/http";
133: private String _soapStyle = "document";
134:
135: private CharArrayWriter _wsdlBuffer = new CharArrayWriter();
136: private boolean _wsdlGenerated = false;
137:
138: private CharArrayWriter _schemaBuffer = new CharArrayWriter();
139: private boolean _schemaGenerated = false;
140:
141: private static XMLInputFactory _inputFactory;
142: private static XMLOutputFactory _outputFactory;
143:
144: private static XMLInputFactory getXMLInputFactory()
145: throws XMLStreamException {
146: if (_inputFactory == null)
147: _inputFactory = XMLInputFactory.newInstance();
148:
149: return _inputFactory;
150: }
151:
152: private static XMLOutputFactory getXMLOutputFactory()
153: throws XMLStreamException {
154: if (_outputFactory == null) {
155: _outputFactory = XMLOutputFactory.newInstance();
156: _outputFactory.setProperty(
157: XMLOutputFactory.IS_REPAIRING_NAMESPACES,
158: Boolean.TRUE);
159: }
160:
161: return _outputFactory;
162: }
163:
164: public DirectSkeleton(Class impl, Class api,
165: JAXBContextImpl context, String wsdlLocation,
166: String targetNamespace, WSDLDefinitions wsdl)
167: throws WebServiceException {
168: WebService webService = (WebService) impl
169: .getAnnotation(WebService.class);
170:
171: _api = api;
172: _namespace = targetNamespace;
173: _portType = getPortType(impl, api);
174:
175: if (webService != null && !"".equals(webService.portName()))
176: _portName = webService.portName();
177: else if (webService != null && !"".equals(webService.name()))
178: _portName = webService.name() + "Port";
179: else
180: _portName = impl.getSimpleName() + "Port";
181:
182: if (webService != null && !"".equals(webService.serviceName()))
183: _serviceName = webService.serviceName();
184: else
185: _serviceName = impl.getSimpleName() + "Service";
186:
187: if (webService != null && !"".equals(webService.wsdlLocation()))
188: _wsdlLocation = webService.wsdlLocation();
189: else
190: _wsdlLocation = wsdlLocation;
191:
192: _wsdl = wsdl;
193:
194: _bindingId = SOAPBinding.SOAP11HTTP_BINDING;
195:
196: BindingType bindingType = (BindingType) _api
197: .getAnnotation(BindingType.class);
198:
199: if (bindingType != null)
200: _bindingId = bindingType.value();
201:
202: javax.jws.soap.SOAPBinding soapBinding = (javax.jws.soap.SOAPBinding) _api
203: .getAnnotation(javax.jws.soap.SOAPBinding.class);
204:
205: if (soapBinding != null
206: && soapBinding.style() == javax.jws.soap.SOAPBinding.Style.RPC)
207: _soapStyle = "rpc";
208:
209: _context = context;
210:
211: QName portName = new QName(_namespace, _portName);
212: QName serviceName = new QName(_namespace, _serviceName);
213:
214: _portInfo = new PortInfoImpl(_bindingId, portName, serviceName);
215:
216: HandlerChain handlerChain = (HandlerChain) _api
217: .getAnnotation(HandlerChain.class);
218:
219: if (handlerChain != null) {
220: HandlerResolver handlerResolver = JAXWSUtil
221: .createHandlerResolver(_api, handlerChain);
222:
223: List<Handler> chain = handlerResolver
224: .getHandlerChain(_portInfo);
225:
226: if (chain != null)
227: _handlerChain = new HandlerChainInvoker(chain);
228: }
229: }
230:
231: public String getWsdlLocation() {
232: return _wsdlLocation;
233: }
234:
235: public String getPortName() {
236: return _portName;
237: }
238:
239: public String getNamespace() {
240: return _namespace;
241: }
242:
243: static String getPortType(Class impl, Class api)
244: throws WebServiceException {
245: WebService webService = (WebService) impl
246: .getAnnotation(WebService.class);
247:
248: if (webService != null) {
249: if ("".equals(webService.name())
250: && "".equals(webService.endpointInterface()))
251: return impl.getSimpleName();
252:
253: if (!"".equals(webService.name())
254: && "".equals(webService.endpointInterface()))
255: return webService.name();
256:
257: if ("".equals(webService.name())
258: && !"".equals(webService.endpointInterface())) {
259: webService = (WebService) api
260: .getAnnotation(WebService.class);
261:
262: if (webService != null && !"".equals(webService.name()))
263: return webService.name();
264:
265: else
266: return api.getSimpleName();
267: }
268:
269: if (!"".equals(webService.name())
270: && !"".equals(webService.endpointInterface()))
271: throw new WebServiceException(
272: L
273: .l(
274: "Cannot specify both name and endpointInterface properties in a WebService annotation: {0}",
275: impl));
276: }
277:
278: return impl.getSimpleName();
279: }
280:
281: public void addAction(Method method, AbstractAction action) {
282: if (log.isLoggable(Level.FINER))
283: log.finer("Adding " + action + " to " + this );
284:
285: _actionNames.put(action.getInputName(), action);
286: _actionMethods.put(method, action);
287: }
288:
289: public Object invoke(Method method, String url, Object[] args)
290: throws IOException, XMLStreamException,
291: MalformedURLException, JAXBException, Throwable {
292: return invoke(method, url, args, null);
293: }
294:
295: /**
296: * Invokes the request on a remote object using an outbound XML stream.
297: */
298: public Object invoke(Method method, String url, Object[] args,
299: HandlerChainInvoker handlerChain) throws IOException,
300: XMLStreamException, MalformedURLException, JAXBException,
301: Throwable {
302: AbstractAction action = _actionMethods.get(method);
303:
304: if (action != null)
305: return action.invoke(url, args, handlerChain);
306: else if ("toString".equals(method.getName()))
307: return "SoapStub[" + (_api != null ? _api.getName() : "")
308: + "]";
309: else
310: throw new RuntimeException(L.l("not a web method: {0}",
311: method.getName()));
312: }
313:
314: /**
315: * Invokes the request on a local object using an inbound XML stream.
316: */
317: public void invoke(Object service, HttpServletRequest request,
318: HttpServletResponse response) throws IOException,
319: XMLStreamException, Throwable {
320: InputStream is = request.getInputStream();
321: OutputStream os = response.getOutputStream();
322:
323: XMLStreamReader in = null;
324: XMLStreamWriter out = null;
325: DOMResult domResult = null;
326:
327: String contentType = request.getHeader("Content-Type");
328:
329: List<Attachment> attachments = null;
330:
331: if (contentType != null
332: && contentType.startsWith("multipart/related"))
333: attachments = AttachmentReader.read(is, contentType);
334:
335: if (_handlerChain != null) {
336: is = _handlerChain.invokeServerInbound(request, os);
337:
338: if (is == null)
339: return;
340:
341: domResult = new DOMResult();
342:
343: in = getXMLInputFactory().createXMLStreamReader(is);
344: out = getXMLOutputFactory()
345: .createXMLStreamWriter(domResult);
346: } else if (attachments != null && attachments.size() > 0) {
347: Attachment body = attachments.get(0);
348: ByteArrayInputStream bais = new ByteArrayInputStream(body
349: .getContents());
350:
351: in = getXMLInputFactory().createXMLStreamReader(bais);
352: out = getXMLOutputFactory().createXMLStreamWriter(os);
353: } else {
354: in = getXMLInputFactory().createXMLStreamReader(is);
355: out = getXMLOutputFactory().createXMLStreamWriter(os);
356: }
357:
358: response.setStatus(invoke(service, in, out, attachments));
359:
360: if (_handlerChain != null) {
361: Source source = new DOMSource(domResult.getNode());
362: _handlerChain.invokeServerOutbound(source, os);
363: }
364: }
365:
366: private int invoke(Object service, XMLStreamReader in,
367: XMLStreamWriter out, List<Attachment> attachments)
368: throws IOException, XMLStreamException, Throwable {
369: in.nextTag();
370:
371: // XXX Namespace
372: in.require(XMLStreamReader.START_ELEMENT, null, "Envelope");
373:
374: in.nextTag();
375:
376: XMLStreamReader header = null;
377:
378: if ("Header".equals(in.getName().getLocalPart())) {
379: in.nextTag();
380:
381: XMLOutputFactory outputFactory = getXMLOutputFactory();
382: CharArrayWriter writer = new CharArrayWriter();
383: StreamResult result = new StreamResult(writer);
384: XMLStreamWriter xmlWriter = outputFactory
385: .createXMLStreamWriter(result);
386:
387: StaxUtil.copyReaderToWriter(in, xmlWriter);
388:
389: CharArrayReader reader = new CharArrayReader(writer
390: .toCharArray());
391:
392: XMLInputFactory inputFactory = getXMLInputFactory();
393: header = inputFactory.createXMLStreamReader(reader);
394:
395: in.nextTag();
396: }
397:
398: // XXX Namespace?
399: in.require(XMLStreamReader.START_ELEMENT, null, "Body");
400:
401: in.nextTag();
402:
403: String actionName = in.getName().getLocalPart();
404:
405: // services/1318: special corner case where no method name is given
406: // May happen with Document BARE methods w/no arguments
407: if ("Body".equals(actionName)
408: && in.getEventType() == in.END_ELEMENT)
409: actionName = "";
410:
411: out.writeStartDocument("UTF-8", "1.0");
412: out.writeStartElement(SOAP_ENVELOPE_PREFIX, "Envelope",
413: SOAP_ENVELOPE);
414: out.writeNamespace(SOAP_ENVELOPE_PREFIX, SOAP_ENVELOPE);
415: //out.writeNamespace("xsi", XMLNS_XSI);
416: out.writeNamespace("xsd", XMLNS_XSD);
417:
418: AbstractAction action = _actionNames.get(actionName);
419:
420: // XXX: exceptions<->faults
421: int responseCode = 500;
422:
423: if (action != null)
424: responseCode = action.invoke(service, header, in, out,
425: attachments);
426:
427: else {
428: // skip the unknown action
429: while (in.getEventType() != in.END_ELEMENT
430: || !"Body".equals(in.getName().getLocalPart()))
431: in.nextTag();
432:
433: writeClientFault(out);
434: }
435:
436: // XXX Namespace?
437: in.require(XMLStreamReader.END_ELEMENT, null, "Body");
438: in.nextTag();
439: in.require(XMLStreamReader.END_ELEMENT, null, "Envelope");
440:
441: out.writeEndElement(); // Envelope
442:
443: out.flush();
444:
445: return responseCode;
446: }
447:
448: public void setSeparateSchema(boolean separateSchema) {
449: if (_separateSchema != separateSchema) {
450: _separateSchema = separateSchema;
451: _wsdlGenerated = false;
452: }
453: }
454:
455: public void dumpWSDL(OutputStream os) throws IOException,
456: XMLStreamException, JAXBException {
457: OutputStreamWriter out = null;
458:
459: try {
460: out = new OutputStreamWriter(os);
461: dumpWSDL(out);
462: } finally {
463: if (out != null)
464: out.close();
465: }
466: }
467:
468: public void dumpWSDL(Writer w) throws IOException,
469: XMLStreamException, JAXBException {
470: generateWSDL();
471: _wsdlBuffer.writeTo(w);
472: }
473:
474: /**
475: * To be accurate, all of the actions must have been added before this
476: * method is run for the first time.
477: **/
478: public void generateWSDL() throws IOException, XMLStreamException,
479: JAXBException {
480: if (_wsdlGenerated)
481: return;
482:
483: // We write to DOM so that we can pretty print it. Since this only
484: // happens once, it's not too much of a burden.
485: DOMResult result = new DOMResult();
486: XMLOutputFactory factory = getXMLOutputFactory();
487: XMLStreamWriter out = factory.createXMLStreamWriter(result);
488:
489: out.writeStartDocument("UTF-8", "1.0");
490:
491: // <definitions>
492:
493: out.setDefaultNamespace(WSDL_NAMESPACE);
494: out.writeStartElement(WSDL_NAMESPACE, "definitions");
495: out.writeAttribute("targetNamespace", _namespace);
496: out.writeAttribute("name", _serviceName);
497: out.writeNamespace(TARGET_NAMESPACE_PREFIX, _namespace);
498: out.writeNamespace("soap", _soapNamespaceURI);
499:
500: // <types>
501:
502: out.writeStartElement(WSDL_NAMESPACE, "types");
503:
504: if (_separateSchema) {
505: out.writeStartElement(W3C_XML_SCHEMA_NS_URI, "schema");
506:
507: out.writeEmptyElement(W3C_XML_SCHEMA_NS_URI, "import");
508: out.writeAttribute("namespace", _namespace);
509: out.writeAttribute("schemaLocation", _serviceName
510: + "_schema1.xsd");
511:
512: out.writeEndElement(); // schema
513: } else
514: writeSchema(out);
515:
516: out.writeEndElement(); // types
517:
518: // <messages>
519:
520: for (AbstractAction action : _actionNames.values())
521: action.writeWSDLMessages(out, _soapNamespaceURI);
522:
523: // <portType>
524:
525: out.writeStartElement(WSDL_NAMESPACE, "portType");
526: out.writeAttribute("name", _portType);
527:
528: for (AbstractAction action : _actionNames.values())
529: action.writeWSDLOperation(out, _soapNamespaceURI);
530:
531: out.writeEndElement(); // portType
532:
533: // <binding>
534:
535: out.writeStartElement(WSDL_NAMESPACE, "binding");
536: out.writeAttribute("name", _portName + "Binding");
537: out.writeAttribute("type", TARGET_NAMESPACE_PREFIX + ':'
538: + _portType);
539:
540: out.writeEmptyElement(_soapNamespaceURI, "binding");
541: out.writeAttribute("transport", _soapTransport);
542: out.writeAttribute("style", _soapStyle);
543:
544: for (AbstractAction action : _actionNames.values())
545: action.writeWSDLBindingOperation(out, _soapNamespaceURI);
546:
547: out.writeEndElement(); // binding
548:
549: // <service>
550:
551: out.writeStartElement(WSDL_NAMESPACE, "service");
552: out.writeAttribute("name", _serviceName);
553:
554: out.writeStartElement(WSDL_NAMESPACE, "port");
555: out.writeAttribute("name", _portName);
556: out.writeAttribute("binding", TARGET_NAMESPACE_PREFIX + ':'
557: + _portName + "Binding");
558:
559: out.writeEmptyElement(_soapNamespaceURI, "address");
560: out.writeAttribute("location", _wsdlLocation);
561:
562: out.writeEndElement(); // port
563:
564: out.writeEndElement(); // service
565:
566: out.writeEndElement(); // definitions
567:
568: _wsdlBuffer = new CharArrayWriter();
569:
570: XmlPrinter printer = new XmlPrinter(_wsdlBuffer);
571: printer.setPrintDeclaration(true);
572: printer.setStandalone("true");
573: printer.printPrettyXml(result.getNode());
574:
575: _wsdlGenerated = true;
576: }
577:
578: public void dumpSchema(OutputStream os) throws IOException,
579: XMLStreamException, JAXBException {
580: OutputStreamWriter out = null;
581:
582: try {
583: out = new OutputStreamWriter(os);
584: dumpSchema(out);
585: } finally {
586: if (out != null)
587: out.close();
588: }
589: }
590:
591: public void dumpSchema(Writer w) throws IOException,
592: XMLStreamException, JAXBException {
593: generateSchema();
594: _schemaBuffer.writeTo(w);
595: }
596:
597: public void generateSchema() throws IOException,
598: XMLStreamException, JAXBException {
599: if (_schemaGenerated)
600: return;
601:
602: // We write to DOM so that we can pretty print it. Since this only
603: // happens once, it's not too much of a burden.
604: DOMResult result = new DOMResult();
605: XMLOutputFactory factory = getXMLOutputFactory();
606: XMLStreamWriter out = factory.createXMLStreamWriter(result);
607:
608: out.writeStartDocument("UTF-8", "1.0");
609:
610: writeSchema(out);
611:
612: _schemaBuffer = new CharArrayWriter();
613:
614: XmlPrinter printer = new XmlPrinter(_schemaBuffer);
615: printer.setPrintDeclaration(true);
616: printer.setStandalone("true");
617: printer.printPrettyXml(result.getNode());
618:
619: _schemaGenerated = true;
620: }
621:
622: public void writeSchema(XMLStreamWriter out)
623: throws XMLStreamException, JAXBException {
624: out.writeStartElement("xsd", "schema", W3C_XML_SCHEMA_NS_URI);
625: out.writeAttribute("version", "1.0");
626: out.writeAttribute("targetNamespace", _namespace);
627: out.writeNamespace(TARGET_NAMESPACE_PREFIX, _namespace);
628:
629: _context.generateSchemaWithoutHeader(out);
630:
631: for (AbstractAction action : _actionNames.values())
632: action.writeSchema(out, _namespace, _context);
633:
634: out.writeEndElement(); // schema
635:
636: out.flush();
637: }
638:
639: /**
640: * Dumps a WSDL into the specified directory using the service name
641: * annotation if present. (Mainly for TCK, wsgen)
642: */
643: public void dumpWSDL(String dir) throws IOException,
644: XMLStreamException, JAXBException {
645: FileWriter wsdlOut = null;
646: FileWriter xsdOut = null;
647:
648: try {
649: wsdlOut = new FileWriter(new File(dir, _serviceName
650: + ".wsdl"));
651: dumpWSDL(wsdlOut);
652:
653: if (_separateSchema) {
654: xsdOut = new FileWriter(new File(dir, _serviceName
655: + "_schema1.xsd"));
656: dumpSchema(xsdOut);
657: }
658: } finally {
659: if (wsdlOut != null)
660: wsdlOut.close();
661:
662: if (xsdOut != null)
663: xsdOut.close();
664: }
665: }
666:
667: public String toString() {
668: return "DirectSkeleton[" + _api + "]";
669: }
670:
671: private void writeClientFault(XMLStreamWriter out)
672: throws IOException, XMLStreamException, JAXBException {
673: out.writeStartElement(SOAP_ENVELOPE_PREFIX, "Body",
674: SOAP_ENVELOPE);
675: out.writeStartElement(Skeleton.SOAP_ENVELOPE_PREFIX, "Fault",
676: Skeleton.SOAP_ENVELOPE);
677:
678: out.writeStartElement("faultcode");
679: out.writeCharacters(Skeleton.SOAP_ENVELOPE_PREFIX + ":Client");
680: out.writeEndElement(); // faultcode
681:
682: out.writeEndElement(); // Fault
683: out.writeEndElement(); // Body
684: }
685: }
|