/* * 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 Emil Ong */ package com.caucho.soap.jaxws; import java.io.*; import java.net.HttpURLConnection; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.activation.DataHandler; import javax.servlet.http.HttpServletRequest; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import javax.xml.soap.MessageFactory; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPFault; import javax.xml.soap.SOAPMessage; import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; import javax.xml.ws.*; import javax.xml.ws.handler.*; import static javax.xml.ws.handler.MessageContext.*; import javax.xml.ws.handler.soap.*; import javax.xml.ws.soap.SOAPFaultException; import com.caucho.util.L10N; import com.caucho.xml.stream.StaxUtil; import com.caucho.xml.stream.XMLStreamReaderImpl; import com.caucho.xml.stream.XMLStreamWriterImpl; /** * Responsible for invoking a handler chain. * * Expected execution order: * - prepare() * - invoke(In|Out)bound() * - [close()] * - finish() **/ public class HandlerChainInvoker { private final static Logger log = Logger.getLogger(HandlerChainInvoker.class.getName()); private final static L10N L = new L10N(HandlerChainInvoker.class); private final List<Handler> _chain; private final BindingProvider _bindingProvider; // maintains state over a request-response pattern private final boolean[] _invoked; private Source _source; private LogicalMessageContextImpl _logicalContext; private SOAPMessageContextImpl _soapContext; private RuntimeException _runtimeException = null; private ProtocolException _protocolException = null; private boolean _request; private boolean _outbound; private int _i; private static Transformer _transformer; public HandlerChainInvoker(List<Handler> chain) { _chain = JAXWSUtil.sortHandlerChain(chain); _invoked = new boolean[_chain.size()]; _bindingProvider = null; } public HandlerChainInvoker(List<Handler> chain, BindingProvider bindingProvider) { _chain = JAXWSUtil.sortHandlerChain(chain); _invoked = new boolean[_chain.size()]; _bindingProvider = bindingProvider; } public InputStream invokeServerInbound(HttpServletRequest request, OutputStream os) throws IOException { Map<String,DataHandler> attachments = new HashMap<String,DataHandler>(); Map<String,Object> httpProperties = new HashMap<String,Object>(); httpProperties.put(HTTP_REQUEST_METHOD, request.getMethod()); Map<String,List<String>> headers = new HashMap<String,List<String>>(); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = (String) headerNames.nextElement(); List<String> values = new ArrayList<String>(); Enumeration headerValues = request.getHeaders(name); while (headerValues.hasMoreElements()) { String value = (String) headerValues.nextElement(); values.add(value); } headers.put(name, values); } httpProperties.put(HTTP_REQUEST_HEADERS, headers); prepare(httpProperties, /*request=*/true); if (! invokeInbound(request.getInputStream(), attachments)) { if (getProtocolException() != null) { reverseDirection(); invokeInboundFaultHandlers(); } else if (getRuntimeException() == null) uninvokeInbound(); closeServer(); finish(os); return null; } return finish(); } public void invokeServerOutbound(Source source, OutputStream os) { Map<String,DataHandler> attachments = new HashMap<String,DataHandler>(); Map<String,Object> httpProperties = new HashMap<String,Object>(); httpProperties.put(HTTP_RESPONSE_CODE, Integer.valueOf(200)); httpProperties.put(HTTP_RESPONSE_HEADERS, new HashMap<String,List<String>>()); prepare(httpProperties, /*request=*/false); if (! invokeOutbound(source, attachments)) { if (getProtocolException() != null) { reverseDirection(); invokeOutboundFaultHandlers(); } /* else if (getRuntimeException() != null) { closeServer(); finish(response.getOutputStream()); throw getRuntimeException(); }*/ } // XXX closeServer(); finish(os); } public boolean invokeClientOutbound(Source source, OutputStream out) throws ProtocolException { // XXX fill this in... Map<String,DataHandler> attachments = new HashMap<String,DataHandler>(); Map<String,Object> httpProperties = new HashMap<String,Object>(); httpProperties.put(HTTP_REQUEST_METHOD, "POST"); httpProperties.put(HTTP_REQUEST_HEADERS, new HashMap<String,List<String>>()); prepare(httpProperties, /*request=*/true); if (! invokeOutbound(source, attachments)) { // XXX handle Oneway if (getProtocolException() != null) { reverseDirection(); invokeOutboundFaultHandlers(); closeClient(); if (getRuntimeException() != null) throw getRuntimeException(); if (getProtocolException() != null) throw getProtocolException(); return false; } else if (getRuntimeException() != null) { closeClient(); throw getRuntimeException(); } else { uninvokeOutbound(); closeClient(); if (getRuntimeException() != null) throw getRuntimeException(); if (getProtocolException() != null) throw getProtocolException(); return false; } } finish(out); return true; } public InputStream invokeClientInbound(HttpURLConnection httpConnection) throws IOException { // XXX fill this in... Map<String,DataHandler> attachments = new HashMap<String,DataHandler>(); Map<String,Object> httpProperties = new HashMap<String,Object>(); httpProperties.put(HTTP_RESPONSE_CODE, Integer.valueOf(httpConnection.getResponseCode())); httpProperties.put(HTTP_RESPONSE_HEADERS, httpConnection.getHeaderFields()); prepare(httpProperties, /*request=*/false); if (! invokeInbound(httpConnection.getInputStream(), attachments)) { if (getProtocolException() != null) { reverseDirection(); invokeInboundFaultHandlers(); if (getRuntimeException() != null) throw getRuntimeException(); } else if (getRuntimeException() != null) { closeClient(); throw getRuntimeException(); } } // XXX closeClient(); return finish(); } public void prepare(Map<String,Object> httpProperties, boolean request) { _logicalContext = new LogicalMessageContextImpl(); _soapContext = new SOAPMessageContextImpl(); _runtimeException = null; _protocolException = null; _request = request; _logicalContext.putAll(httpProperties); _soapContext.putAll(httpProperties); importAppProperties(request); } public void closeServer() { for (int i = 0; i < _chain.size(); i++) close(i); } public void closeClient() { for (int i = _chain.size() - 1; i >= 0; i--) close(i); } public void finish(OutputStream out) { exportAppProperties(_request); /* if (_runtimeException != null) return;*/ try { getTransformer().transform(_source, new StreamResult(out)); } catch (TransformerException e) { throw new WebServiceException(e); } } public InputStream finish() { exportAppProperties(_request); if (_runtimeException != null) return null; try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); getTransformer().transform(_source, new StreamResult(baos)); return new ByteArrayInputStream(baos.toByteArray()); } catch (TransformerException e) { throw new WebServiceException(e); } } /** * Invoke the handler chain for an outbound message. **/ public boolean invokeOutbound(Source source, Map<String,DataHandler> attachments) throws WebServiceException { _source = source; // Set the mandatory properties _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); _logicalContext.put(OUTBOUND_MESSAGE_ATTACHMENTS, attachments); _soapContext.put(OUTBOUND_MESSAGE_ATTACHMENTS, attachments); for (_i = 0; _i < _chain.size(); _i++) { boolean success = handleMessage(_i); if (_protocolException != null) return false; if (_runtimeException != null) return false; if (! success) return false; } return true; } /** * When a message direction is reversed within the chain, this method * runs the message backwards through the previous handlers. This * method should only be invoked when a handler returns false, but * does not throw any kind of exception. **/ public boolean uninvokeOutbound() throws WebServiceException { // Set the mandatory properties _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); // NOTE: the order is reversed for inbound messages for (_i--; _i >= 0; _i--) { boolean success = handleMessage(_i); if (_protocolException != null) return false; if (_runtimeException != null) return false; if (! success) return false; } return true; } public boolean invokeOutboundFaultHandlers() { for (_i--; _i >= 0; _i--) { if (! handleFault(_i)) return false; if (_runtimeException != null) return false; } return true; } /** * Invoke the handler chain for an inbound message. **/ public boolean invokeInbound(InputStream in, Map<String,DataHandler> attachments) throws WebServiceException { _outbound = false; _source = null; try { DOMResult dom = new DOMResult(); getTransformer().transform(new StreamSource(in), dom); // XXX The TCK seems to assume a source that will stand up to repeated // reads... meaning that StreamSource and SAXSource are out, so DOM // must be what they want. _source = new DOMSource(dom.getNode()); } catch (Exception e) { throw new WebServiceException(e); } // Set the mandatory properties _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); _logicalContext.put(INBOUND_MESSAGE_ATTACHMENTS, attachments); _soapContext.put(INBOUND_MESSAGE_ATTACHMENTS, attachments); // NOTE: the order is reversed for inbound messages for (_i = _chain.size() - 1; _i >= 0; _i--) { boolean success = handleMessage(_i); if (_protocolException != null) return false; if (_runtimeException != null) return false; if (! success) return false; } return true; } /** * When a message direction is reversed within the chain, this method * runs the message backwards through the previous handlers. This * method should only be invoked when a handler returns false, but * does not throw any kind of exception. **/ public boolean uninvokeInbound() throws WebServiceException { // Set the mandatory properties _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); for (_i++; _i < _chain.size(); _i++) { boolean success = handleMessage(_i); if (_protocolException != null) return false; if (_runtimeException != null) return false; if (! success) return false; } return true; } public boolean invokeInboundFaultHandlers() { for (_i++; _i < _chain.size(); _i++) { if (! handleFault(_i)) return false; if (_runtimeException != null) return false; } return true; } public void reverseDirection() { Boolean direction = (Boolean) _logicalContext.get(MESSAGE_OUTBOUND_PROPERTY); if (direction != null) { if (Boolean.TRUE.equals(direction)) { _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.FALSE); } else { _logicalContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); _soapContext.put(MESSAGE_OUTBOUND_PROPERTY, Boolean.TRUE); } } } public ProtocolException getProtocolException() { return _protocolException; } public RuntimeException getRuntimeException() { return _runtimeException; } public Source getSource() { return _source; } private boolean handleMessage(int i) { Handler handler = _chain.get(i); boolean success = false; _invoked[i] = true; try { if (handler instanceof LogicalHandler) { _logicalContext.getMessage().setPayload(_source); success = handler.handleMessage(_logicalContext); _source = _logicalContext.getMessage().getPayload(); } else if (handler instanceof SOAPHandler) { try { _soapContext.setMessage(_source); success = handler.handleMessage(_soapContext); _source = _soapContext.getMessage().getSOAPPart().getContent(); } catch (SOAPException e) { throw new WebServiceException(e); } } else { throw new WebServiceException(L.l("Unsupported Handler type: {0}", handler.getClass().getName())); } } catch (ProtocolException e) { _protocolException = e; serializeProtocolException(); } catch (RuntimeException e) { _runtimeException = e; serializeRuntimeException(); } return success; } private boolean handleFault(int i) { Handler handler = _chain.get(i); boolean success = false; _invoked[i] = true; try { if (handler instanceof LogicalHandler) { _logicalContext.getMessage().setPayload(_source); success = handler.handleFault(_logicalContext); _source = _logicalContext.getMessage().getPayload(); } else if (handler instanceof SOAPHandler) { try { _soapContext.setMessage(_source); success = handler.handleFault(_soapContext); _source = _soapContext.getMessage().getSOAPPart().getContent(); } catch (SOAPException e) { throw new WebServiceException(e); } } else { throw new WebServiceException(L.l("Unsupported Handler type: {0}", handler.getClass().getName())); } _protocolException = null; } catch (ProtocolException e) { _protocolException = e; serializeProtocolException(); } catch (RuntimeException e) { _runtimeException = e; serializeRuntimeException(); } return success; } private void close(int i) { Handler handler = _chain.get(i); if (! _invoked[i]) return; _invoked[i] = false; if (handler instanceof LogicalHandler) { _logicalContext.getMessage().setPayload(_source); handler.close(_logicalContext); _source = _logicalContext.getMessage().getPayload(); } else if (handler instanceof SOAPHandler) { try { _soapContext.setMessage(_source); handler.close(_soapContext); _source = _soapContext.getMessage().getSOAPPart().getContent(); } catch (SOAPException e) { throw new WebServiceException(e); } } } private void importAppProperties(boolean request) { if (_bindingProvider == null) return; if (request) { _logicalContext.putAll(_bindingProvider.getRequestContext(), Scope.APPLICATION); _soapContext.putAll(_bindingProvider.getRequestContext(), Scope.APPLICATION); } else { _logicalContext.putAll(_bindingProvider.getResponseContext(), Scope.APPLICATION); _soapContext.putAll(_bindingProvider.getResponseContext(), Scope.APPLICATION); } } private void exportAppProperties(boolean request) { if (_bindingProvider == null) return; if (request) { Map<String,Object> map = null; map = _logicalContext.getScopedSubMap(Scope.APPLICATION); _bindingProvider.getRequestContext().putAll(map); map = _soapContext.getScopedSubMap(Scope.APPLICATION); _bindingProvider.getRequestContext().putAll(map); } else { Map<String,Object> map = null; map = _logicalContext.getScopedSubMap(Scope.APPLICATION); _bindingProvider.getResponseContext().putAll(map); map = _soapContext.getScopedSubMap(Scope.APPLICATION); _bindingProvider.getResponseContext().putAll(map); } } private void serializeProtocolException() throws WebServiceException { if (_protocolException instanceof SOAPFaultException) { SOAPFaultException sfe = (SOAPFaultException) _protocolException; SOAPFault fault = sfe.getFault(); try { MessageFactory factory = _soapContext.getMessageFactory(); SOAPMessage message = factory.createMessage(); message.getSOAPBody().addChildElement(fault); _soapContext.setMessage(message); _source = _soapContext.getMessage().getSOAPPart().getContent(); } catch (SOAPException se) { throw new WebServiceException(se); } } else { throw new WebServiceException(L.l("Unsupported ProtocolException: {0}", _protocolException)); } } private void serializeRuntimeException() throws WebServiceException { try { MessageFactory factory = _soapContext.getMessageFactory(); SOAPMessage message = factory.createMessage(); QName faultcode = new QName(message.getSOAPBody().getNamespaceURI(), "Server", message.getSOAPBody().getPrefix()); message.getSOAPBody().addFault(faultcode, _runtimeException.getMessage()); _soapContext.setMessage(message); _source = _soapContext.getMessage().getSOAPPart().getContent(); } catch (SOAPException se) { throw new WebServiceException(se); } } private static Transformer getTransformer() throws TransformerException { if (_transformer == null) { TransformerFactory factory = TransformerFactory.newInstance(); _transformer = factory.newTransformer(); } return _transformer; } public String toString() { StringBuilder sb = new StringBuilder("HandlerChainInvoker[\n"); for (Handler handler : _chain) { sb.append(handler.toString()); sb.append('\n'); } sb.append(']'); return sb.toString(); } public void printSource() { try { StreamResult result = new StreamResult(System.out); getTransformer().transform(_source, result); } catch (Exception e) { } } public String getDirection() { return _outbound ? "Outbound" : "Inbound"; } }