/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.soap.skeleton;
import com.caucho.jaxb.JAXBUtil;
import com.caucho.jaxb.JAXBContextImpl;
import static com.caucho.soap.wsdl.WSDLConstants.*;
import com.caucho.soap.jaxws.HandlerChainInvoker;
import com.caucho.soap.jaxws.JAXWSUtil;
import com.caucho.soap.jaxws.PortInfoImpl;
import com.caucho.soap.wsdl.WSDLDefinitions;
import com.caucho.util.Attachment;
import com.caucho.util.AttachmentReader;
import com.caucho.util.L10N;
import com.caucho.xml.XmlPrinter;
import com.caucho.xml.stream.StaxUtil;
import org.w3c.dom.Node;
import javax.activation.DataHandler;
import javax.jws.HandlerChain;
import javax.jws.WebService;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static javax.xml.XMLConstants.*;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import static javax.xml.soap.SOAPConstants.*;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.ws.BindingType;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.Handler;
import javax.xml.ws.handler.HandlerResolver;
import javax.xml.ws.handler.PortInfo;
import static javax.xml.ws.handler.MessageContext.*;
import javax.xml.ws.soap.SOAPBinding;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Invokes a SOAP request on a Java POJO
*/
public class DirectSkeleton extends Skeleton {
private static final Logger log =
Logger.getLogger(DirectSkeleton.class.getName());
public static final L10N L = new L10N(DirectSkeleton.class);
private static final String TARGET_NAMESPACE_PREFIX = "m";
private boolean _separateSchema = false;
private JAXBContextImpl _context;
private Marshaller _marshaller;
private Node _wsdlNode;
private HashMap<String,AbstractAction> _actionNames
= new HashMap<String,AbstractAction>();
private HashMap<Method,AbstractAction> _actionMethods
= new HashMap<Method,AbstractAction>();
private Class _api;
private HandlerChainInvoker _handlerChain;
private String _bindingId;
private String _namespace;
private String _portType;
private String _portName;
private String _serviceName;
private String _wsdlLocation = "REPLACE_WITH_ACTUAL_URL";
private PortInfo _portInfo;
private WSDLDefinitions _wsdl;
// The URI in SOAPBinding is wrong, but matches that of JAVAEE
private String _soapNamespaceURI = "http://schemas.xmlsoap.org/wsdl/soap/";
private String _soapTransport = "http://schemas.xmlsoap.org/soap/http";
private String _soapStyle = "document";
private CharArrayWriter _wsdlBuffer = new CharArrayWriter();
private boolean _wsdlGenerated = false;
private CharArrayWriter _schemaBuffer = new CharArrayWriter();
private boolean _schemaGenerated = false;
private static XMLInputFactory _inputFactory;
private static XMLOutputFactory _outputFactory;
private static XMLInputFactory getXMLInputFactory()
throws XMLStreamException
{
if (_inputFactory == null)
_inputFactory = XMLInputFactory.newInstance();
return _inputFactory;
}
private static XMLOutputFactory getXMLOutputFactory()
throws XMLStreamException
{
if (_outputFactory == null) {
_outputFactory = XMLOutputFactory.newInstance();
_outputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES,
Boolean.TRUE);
}
return _outputFactory;
}
public DirectSkeleton(Class impl, Class api,
JAXBContextImpl context,
String wsdlLocation,
String targetNamespace,
WSDLDefinitions wsdl)
throws WebServiceException
{
WebService webService = (WebService) impl.getAnnotation(WebService.class);
_api = api;
_namespace = targetNamespace;
_portType = getPortType(impl, api);
if (webService != null && ! "".equals(webService.portName()))
_portName = webService.portName();
else if (webService != null && ! "".equals(webService.name()))
_portName = webService.name() + "Port";
else
_portName = impl.getSimpleName() + "Port";
if (webService != null && ! "".equals(webService.serviceName()))
_serviceName = webService.serviceName();
else
_serviceName = impl.getSimpleName() + "Service";
if (webService != null && ! "".equals(webService.wsdlLocation()))
_wsdlLocation = webService.wsdlLocation();
else
_wsdlLocation = wsdlLocation;
_wsdl = wsdl;
_bindingId = SOAPBinding.SOAP11HTTP_BINDING;
BindingType bindingType =
(BindingType) _api.getAnnotation(BindingType.class);
if (bindingType != null)
_bindingId = bindingType.value();
javax.jws.soap.SOAPBinding soapBinding =
(javax.jws.soap.SOAPBinding)
_api.getAnnotation(javax.jws.soap.SOAPBinding.class);
if (soapBinding != null &&
soapBinding.style() == javax.jws.soap.SOAPBinding.Style.RPC)
_soapStyle = "rpc";
_context = context;
QName portName = new QName(_namespace, _portName);
QName serviceName = new QName(_namespace, _serviceName);
_portInfo = new PortInfoImpl(_bindingId, portName, serviceName);
HandlerChain handlerChain =
(HandlerChain) _api.getAnnotation(HandlerChain.class);
if (handlerChain != null) {
HandlerResolver handlerResolver =
JAXWSUtil.createHandlerResolver(_api, handlerChain);
List<Handler> chain = handlerResolver.getHandlerChain(_portInfo);
if (chain != null)
_handlerChain = new HandlerChainInvoker(chain);
}
}
public String getWsdlLocation()
{
return _wsdlLocation;
}
public String getPortName()
{
return _portName;
}
public String getNamespace()
{
return _namespace;
}
static String getPortType(Class impl, Class api)
throws WebServiceException
{
WebService webService = (WebService) impl.getAnnotation(WebService.class);
if (webService != null) {
if ("".equals(webService.name()) &&
"".equals(webService.endpointInterface()))
return impl.getSimpleName();
if (! "".equals(webService.name()) &&
"".equals(webService.endpointInterface()))
return webService.name();
if ("".equals(webService.name()) &&
! "".equals(webService.endpointInterface())) {
webService = (WebService) api.getAnnotation(WebService.class);
if (webService != null && ! "".equals(webService.name()))
return webService.name();
else
return api.getSimpleName();
}
if (! "".equals(webService.name()) &&
! "".equals(webService.endpointInterface()))
throw new WebServiceException(L.l("Cannot specify both name and endpointInterface properties in a WebService annotation: {0}", impl));
}
return impl.getSimpleName();
}
public void addAction(Method method, AbstractAction action)
{
if (log.isLoggable(Level.FINER))
log.finer("Adding " + action + " to " + this);
_actionNames.put(action.getInputName(), action);
_actionMethods.put(method, action);
}
public Object invoke(Method method, String url, Object[] args)
throws IOException, XMLStreamException, MalformedURLException,
JAXBException, Throwable
{
return invoke(method, url, args, null);
}
/**
* Invokes the request on a remote object using an outbound XML stream.
*/
public Object invoke(Method method, String url, Object[] args,
HandlerChainInvoker handlerChain)
throws IOException, XMLStreamException, MalformedURLException,
JAXBException, Throwable
{
AbstractAction action = _actionMethods.get(method);
if (action != null)
return action.invoke(url, args, handlerChain);
else if ("toString".equals(method.getName()))
return "SoapStub[" + (_api != null ? _api.getName() : "") + "]";
else
throw new RuntimeException(L.l("not a web method: {0}",
method.getName()));
}
/**
* Invokes the request on a local object using an inbound XML stream.
*/
public void invoke(Object service,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, XMLStreamException, Throwable
{
InputStream is = request.getInputStream();
OutputStream os = response.getOutputStream();
XMLStreamReader in = null;
XMLStreamWriter out = null;
DOMResult domResult = null;
String contentType = request.getHeader("Content-Type");
List<Attachment> attachments = null;
if (contentType != null && contentType.startsWith("multipart/related"))
attachments = AttachmentReader.read(is, contentType);
if (_handlerChain != null) {
is = _handlerChain.invokeServerInbound(request, os);
if (is == null)
return;
domResult = new DOMResult();
in = getXMLInputFactory().createXMLStreamReader(is);
out = getXMLOutputFactory().createXMLStreamWriter(domResult);
}
else if (attachments != null && attachments.size() > 0) {
Attachment body = attachments.get(0);
ByteArrayInputStream bais = new ByteArrayInputStream(body.getContents());
in = getXMLInputFactory().createXMLStreamReader(bais);
out = getXMLOutputFactory().createXMLStreamWriter(os);
}
else {
in = getXMLInputFactory().createXMLStreamReader(is);
out = getXMLOutputFactory().createXMLStreamWriter(os);
}
response.setStatus(invoke(service, in, out, attachments));
if (_handlerChain != null) {
Source source = new DOMSource(domResult.getNode());
_handlerChain.invokeServerOutbound(source, os);
}
}
private int invoke(Object service, XMLStreamReader in, XMLStreamWriter out,
List<Attachment> attachments)
throws IOException, XMLStreamException, Throwable
{
in.nextTag();
// XXX Namespace
in.require(XMLStreamReader.START_ELEMENT, null, "Envelope");
in.nextTag();
XMLStreamReader header = null;
if ("Header".equals(in.getName().getLocalPart())) {
in.nextTag();
XMLOutputFactory outputFactory = getXMLOutputFactory();
CharArrayWriter writer = new CharArrayWriter();
StreamResult result = new StreamResult(writer);
XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(result);
StaxUtil.copyReaderToWriter(in, xmlWriter);
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
XMLInputFactory inputFactory = getXMLInputFactory();
header = inputFactory.createXMLStreamReader(reader);
in.nextTag();
}
// XXX Namespace?
in.require(XMLStreamReader.START_ELEMENT, null, "Body");
in.nextTag();
String actionName = in.getName().getLocalPart();
// services/1318: special corner case where no method name is given
// May happen with Document BARE methods w/no arguments
if ("Body".equals(actionName) && in.getEventType() == in.END_ELEMENT)
actionName = "";
out.writeStartDocument("UTF-8", "1.0");
out.writeStartElement(SOAP_ENVELOPE_PREFIX, "Envelope", SOAP_ENVELOPE);
out.writeNamespace(SOAP_ENVELOPE_PREFIX, SOAP_ENVELOPE);
//out.writeNamespace("xsi", XMLNS_XSI);
out.writeNamespace("xsd", XMLNS_XSD);
AbstractAction action = _actionNames.get(actionName);
// XXX: exceptions<->faults
int responseCode = 500;
if (action != null)
responseCode = action.invoke(service, header, in, out, attachments);
else {
// skip the unknown action
while (in.getEventType() != in.END_ELEMENT ||
! "Body".equals(in.getName().getLocalPart()))
in.nextTag();
writeClientFault(out);
}
// XXX Namespace?
in.require(XMLStreamReader.END_ELEMENT, null, "Body");
in.nextTag();
in.require(XMLStreamReader.END_ELEMENT, null, "Envelope");
out.writeEndElement(); // Envelope
out.flush();
return responseCode;
}
public void setSeparateSchema(boolean separateSchema)
{
if (_separateSchema != separateSchema) {
_separateSchema = separateSchema;
_wsdlGenerated = false;
}
}
public void dumpWSDL(OutputStream os)
throws IOException, XMLStreamException, JAXBException
{
OutputStreamWriter out = null;
try {
out = new OutputStreamWriter(os);
dumpWSDL(out);
}
finally {
if (out != null)
out.close();
}
}
public void dumpWSDL(Writer w)
throws IOException, XMLStreamException, JAXBException
{
generateWSDL();
_wsdlBuffer.writeTo(w);
}
/**
* To be accurate, all of the actions must have been added before this
* method is run for the first time.
**/
public void generateWSDL()
throws IOException, XMLStreamException, JAXBException
{
if (_wsdlGenerated)
return;
// We write to DOM so that we can pretty print it. Since this only
// happens once, it's not too much of a burden.
DOMResult result = new DOMResult();
XMLOutputFactory factory = getXMLOutputFactory();
XMLStreamWriter out = factory.createXMLStreamWriter(result);
out.writeStartDocument("UTF-8", "1.0");
// <definitions>
out.setDefaultNamespace(WSDL_NAMESPACE);
out.writeStartElement(WSDL_NAMESPACE, "definitions");
out.writeAttribute("targetNamespace", _namespace);
out.writeAttribute("name", _serviceName);
out.writeNamespace(TARGET_NAMESPACE_PREFIX, _namespace);
out.writeNamespace("soap", _soapNamespaceURI);
// <types>
out.writeStartElement(WSDL_NAMESPACE, "types");
if (_separateSchema) {
out.writeStartElement(W3C_XML_SCHEMA_NS_URI, "schema");
out.writeEmptyElement(W3C_XML_SCHEMA_NS_URI, "import");
out.writeAttribute("namespace", _namespace);
out.writeAttribute("schemaLocation", _serviceName + "_schema1.xsd");
out.writeEndElement(); // schema
}
else
writeSchema(out);
out.writeEndElement(); // types
// <messages>
for (AbstractAction action : _actionNames.values())
action.writeWSDLMessages(out, _soapNamespaceURI);
// <portType>
out.writeStartElement(WSDL_NAMESPACE, "portType");
out.writeAttribute("name", _portType);
for (AbstractAction action : _actionNames.values())
action.writeWSDLOperation(out, _soapNamespaceURI);
out.writeEndElement(); // portType
// <binding>
out.writeStartElement(WSDL_NAMESPACE, "binding");
out.writeAttribute("name", _portName + "Binding");
out.writeAttribute("type", TARGET_NAMESPACE_PREFIX + ':' + _portType);
out.writeEmptyElement(_soapNamespaceURI, "binding");
out.writeAttribute("transport", _soapTransport);
out.writeAttribute("style", _soapStyle);
for (AbstractAction action : _actionNames.values())
action.writeWSDLBindingOperation(out, _soapNamespaceURI);
out.writeEndElement(); // binding
// <service>
out.writeStartElement(WSDL_NAMESPACE, "service");
out.writeAttribute("name", _serviceName);
out.writeStartElement(WSDL_NAMESPACE, "port");
out.writeAttribute("name", _portName);
out.writeAttribute("binding",
TARGET_NAMESPACE_PREFIX + ':' + _portName + "Binding");
out.writeEmptyElement(_soapNamespaceURI, "address");
out.writeAttribute("location", _wsdlLocation);
out.writeEndElement(); // port
out.writeEndElement(); // service
out.writeEndElement(); // definitions
_wsdlBuffer = new CharArrayWriter();
XmlPrinter printer = new XmlPrinter(_wsdlBuffer);
printer.setPrintDeclaration(true);
printer.setStandalone("true");
printer.printPrettyXml(result.getNode());
_wsdlGenerated = true;
}
public void dumpSchema(OutputStream os)
throws IOException, XMLStreamException, JAXBException
{
OutputStreamWriter out = null;
try {
out = new OutputStreamWriter(os);
dumpSchema(out);
}
finally {
if (out != null)
out.close();
}
}
public void dumpSchema(Writer w)
throws IOException, XMLStreamException, JAXBException
{
generateSchema();
_schemaBuffer.writeTo(w);
}
public void generateSchema()
throws IOException, XMLStreamException, JAXBException
{
if (_schemaGenerated)
return;
// We write to DOM so that we can pretty print it. Since this only
// happens once, it's not too much of a burden.
DOMResult result = new DOMResult();
XMLOutputFactory factory = getXMLOutputFactory();
XMLStreamWriter out = factory.createXMLStreamWriter(result);
out.writeStartDocument("UTF-8", "1.0");
writeSchema(out);
_schemaBuffer = new CharArrayWriter();
XmlPrinter printer = new XmlPrinter(_schemaBuffer);
printer.setPrintDeclaration(true);
printer.setStandalone("true");
printer.printPrettyXml(result.getNode());
_schemaGenerated = true;
}
public void writeSchema(XMLStreamWriter out)
throws XMLStreamException, JAXBException
{
out.writeStartElement("xsd", "schema", W3C_XML_SCHEMA_NS_URI);
out.writeAttribute("version", "1.0");
out.writeAttribute("targetNamespace", _namespace);
out.writeNamespace(TARGET_NAMESPACE_PREFIX, _namespace);
_context.generateSchemaWithoutHeader(out);
for (AbstractAction action : _actionNames.values())
action.writeSchema(out, _namespace, _context);
out.writeEndElement(); // schema
out.flush();
}
/**
* Dumps a WSDL into the specified directory using the service name
* annotation if present. (Mainly for TCK, wsgen)
*/
public void dumpWSDL(String dir)
throws IOException, XMLStreamException, JAXBException
{
FileWriter wsdlOut = null;
FileWriter xsdOut = null;
try {
wsdlOut = new FileWriter(new File(dir, _serviceName + ".wsdl"));
dumpWSDL(wsdlOut);
if (_separateSchema) {
xsdOut = new FileWriter(new File(dir, _serviceName + "_schema1.xsd"));
dumpSchema(xsdOut);
}
}
finally {
if (wsdlOut != null)
wsdlOut.close();
if (xsdOut != null)
xsdOut.close();
}
}
public String toString()
{
return "DirectSkeleton[" + _api + "]";
}
private void writeClientFault(XMLStreamWriter out)
throws IOException, XMLStreamException, JAXBException
{
out.writeStartElement(SOAP_ENVELOPE_PREFIX, "Body", SOAP_ENVELOPE);
out.writeStartElement(Skeleton.SOAP_ENVELOPE_PREFIX,
"Fault",
Skeleton.SOAP_ENVELOPE);
out.writeStartElement("faultcode");
out.writeCharacters(Skeleton.SOAP_ENVELOPE_PREFIX + ":Client");
out.writeEndElement(); // faultcode
out.writeEndElement(); // Fault
out.writeEndElement(); // Body
}
}