/*
* $Id: MuleUniversalConduit.java 22162 2011-06-09 18:23:00Z dfeist $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.module.cxf.transport;
import static org.apache.cxf.message.Message.DECOUPLED_CHANNEL_MESSAGE;
import org.mule.DefaultMuleEvent;
import org.mule.DefaultMuleMessage;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.MuleMessage;
import org.mule.api.MuleSession;
import org.mule.api.endpoint.OutboundEndpoint;
import org.mule.api.transformer.TransformerException;
import org.mule.api.transport.OutputHandler;
import org.mule.module.cxf.CxfConfiguration;
import org.mule.module.cxf.CxfConstants;
import org.mule.module.cxf.CxfOutboundMessageProcessor;
import org.mule.module.cxf.support.DelegatingOutputStream;
import org.mule.session.DefaultMuleSession;
import org.mule.transformer.types.DataTypeFactory;
import org.mule.transport.NullPayload;
import org.mule.transport.http.HttpConnector;
import org.mule.transport.http.HttpConstants;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.endpoint.ClientImpl;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.ExchangeImpl;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.AbstractConduit;
import org.apache.cxf.transport.MessageObserver;
import org.apache.cxf.ws.addressing.AttributedURIType;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.wsdl.EndpointReferenceUtils;
/**
* A Conduit is primarily responsible for sending messages from CXF to somewhere
* else. This conduit takes messages which are being written and sends them to the
* Mule bus.
*/
public class MuleUniversalConduit extends AbstractConduit
{
private static final Logger LOGGER = LogUtils.getL7dLogger(MuleUniversalConduit.class);
private EndpointInfo endpoint;
private CxfConfiguration configuration;
private MuleUniversalTransport transport;
private boolean closeInput = true;
private Map<String,OutboundEndpoint> endpoints = new HashMap<String, OutboundEndpoint>();
/**
* @param ei The Endpoint being invoked by this destination.
* @param t The EPR associated with this Conduit - i.e. the reply destination.
*/
public MuleUniversalConduit(MuleUniversalTransport transport,
CxfConfiguration configuration,
EndpointInfo ei,
EndpointReferenceType t)
{
super(getTargetReference(ei, t));
this.transport = transport;
this.endpoint = ei;
this.configuration = configuration;
}
@Override
public void close(Message msg) throws IOException
{
OutputStream os = msg.getContent(OutputStream.class);
if (os != null)
{
os.close();
}
if (closeInput)
{
InputStream in = msg.getContent(InputStream.class);
if (in != null)
{
in.close();
}
}
}
@Override
protected Logger getLogger()
{
return LOGGER;
}
/**
* Prepare the message for writing.
*/
public void prepare(final Message message) throws IOException
{
// save in a separate place in case we need to resend the request
final ByteArrayOutputStream cache = new ByteArrayOutputStream();
final DelegatingOutputStream delegating = new DelegatingOutputStream(cache);
message.setContent(OutputStream.class, delegating);
message.setContent(DelegatingOutputStream.class, delegating);
OutputHandler handler = new OutputHandler()
{
public void write(MuleEvent event, OutputStream out) throws IOException
{
out.write(cache.toByteArray());
delegating.setOutputStream(out);
// resume writing!
message.getInterceptorChain().doIntercept(message);
}
};
MuleEvent event = (MuleEvent) message.getExchange().get(CxfConstants.MULE_EVENT);
// are we sending an out of band response for a server side request?
boolean decoupled = event != null && message.getExchange().getInMessage() != null;
OutboundEndpoint ep = null;
if (event == null || decoupled)
{
// we've got an out of band WS-RM message or a message from a standalone client
MuleContext muleContext = configuration.getMuleContext();
MuleMessage muleMsg = new DefaultMuleMessage(handler, muleContext);
MuleSession session = new DefaultMuleSession(muleContext);
String url = setupURL(message);
try
{
ep = getEndpoint(muleContext, url);
event = new DefaultMuleEvent(muleMsg, ep.getExchangePattern(), session);
}
catch (Exception e)
{
throw new Fault(e);
}
event.setTimeout(MuleEvent.TIMEOUT_NOT_SET_VALUE);
}
else
{
event.getMessage().setPayload(handler);
}
if (!decoupled)
{
message.getExchange().put(CxfConstants.MULE_EVENT, event);
}
message.put(CxfConstants.MULE_EVENT, event);
final MuleEvent finalEvent = event;
final OutboundEndpoint finalEndpoint = ep;
AbstractPhaseInterceptor<Message> i = new AbstractPhaseInterceptor<Message>(Phase.PRE_STREAM)
{
public void handleMessage(Message m) throws Fault
{
try
{
dispatchMuleMessage(m, finalEvent, finalEndpoint);
}
catch (IOException e)
{
throw new Fault(e);
}
}
};
message.getInterceptorChain().add(i);
}
protected synchronized OutboundEndpoint getEndpoint(MuleContext muleContext, String uri) throws MuleException
{
if (endpoints.get(uri) != null)
{
return endpoints.get(uri);
}
OutboundEndpoint endpoint = muleContext.getEndpointFactory().getOutboundEndpoint(uri);
endpoints.put(uri, endpoint);
return endpoint;
}
public String setupURL(Message message) throws MalformedURLException
{
String value = (String) message.get(Message.ENDPOINT_ADDRESS);
String pathInfo = (String) message.get(Message.PATH_INFO);
String queryString = (String) message.get(Message.QUERY_STRING);
String username = (String) message.get(BindingProvider.USERNAME_PROPERTY);
String password = (String) message.get(BindingProvider.PASSWORD_PROPERTY);
String result = value != null ? value : getTargetOrEndpoint();
if (username != null) {
int slashIdx = result.indexOf("//");
if (slashIdx != -1) {
result = result.substring(0, slashIdx + 2) + username + ":" + password + "@" + result.substring(slashIdx+2);
}
}
// REVISIT: is this really correct?
if (null != pathInfo && !result.endsWith(pathInfo))
{
result = result + pathInfo;
}
if (queryString != null)
{
result = result + "?" + queryString;
}
return result;
}
protected void dispatchMuleMessage(Message m, MuleEvent reqEvent, OutboundEndpoint endpoint) throws IOException {
try
{
MuleMessage req = reqEvent.getMessage();
req.setOutboundProperty(HttpConnector.HTTP_DISABLE_STATUS_CODE_EXCEPTION_CHECK, Boolean.TRUE.toString());
MuleEvent resEvent = processNext(reqEvent, m.getExchange(), endpoint);
if (resEvent == null)
{
m.getExchange().put(ClientImpl.FINISHED, Boolean.TRUE);
return;
}
// If we have a result, send it back to CXF
MuleMessage result = resEvent.getMessage();
InputStream is = getResponseBody(m, result);
if (is != null)
{
Message inMessage = new MessageImpl();
String encoding = result.getEncoding();
inMessage.put(Message.ENCODING, encoding);
String contentType = result.getOutboundProperty(HttpConstants.HEADER_CONTENT_TYPE, "text/xml");
if (encoding != null && contentType.indexOf("charset") < 0)
{
contentType += "; charset=" + result.getEncoding();
}
inMessage.put(Message.CONTENT_TYPE, contentType);
inMessage.setContent(InputStream.class, is);
inMessage.setExchange(m.getExchange());
getMessageObserver().onMessage(inMessage);
}
}
catch (Exception e)
{
if (e instanceof IOException)
{
throw (IOException) e;
}
IOException ex = new IOException("Could not send message to Mule.");
ex.initCause(e);
throw ex;
}
}
protected InputStream getResponseBody(Message m, MuleMessage result) throws TransformerException, IOException
{
boolean response = result != null
&& !NullPayload.getInstance().equals(result.getPayload())
&& !isOneway(m.getExchange());
if (response)
{
// Sometimes there may not actually be a body, in which case
// we want to act appropriately. E.g. one way invocations over a proxy
InputStream is = result.getPayload(DataTypeFactory.create(InputStream.class));
PushbackInputStream pb = new PushbackInputStream(is);
result.setPayload(pb);
int b = pb.read();
if (b != -1)
{
pb.unread(b);
return pb;
}
}
return null;
}
protected boolean isOneway(Exchange exchange)
{
return exchange != null && exchange.isOneWay();
}
protected String getTargetOrEndpoint()
{
if (target != null)
{
return target.getAddress().getValue();
}
return endpoint.getAddress().toString();
}
public void onClose(final Message m) throws IOException
{
// template method
}
protected MuleEvent processNext(MuleEvent event,
Exchange exchange, OutboundEndpoint endpoint) throws MuleException
{
CxfOutboundMessageProcessor processor = (CxfOutboundMessageProcessor) exchange.get(CxfConstants.CXF_OUTBOUND_MESSAGE_PROCESSOR);
MuleEvent response;
if (processor == null)
{
response = endpoint.process(event);
}
else
{
response = processor.processNext(event);
Holder<MuleEvent> holder = (Holder<MuleEvent>) exchange.get("holder");
holder.value = response;
}
return response;
}
@Override
public void close()
{
}
/**
* Get the target endpoint reference.
*
* @param ei the corresponding EndpointInfo
* @param t the given target EPR if available
* @return the actual target
*/
protected static EndpointReferenceType getTargetReference(EndpointInfo ei, EndpointReferenceType t)
{
EndpointReferenceType ref = null;
if (null == t)
{
ref = new EndpointReferenceType();
AttributedURIType address = new AttributedURIType();
address.setValue(ei.getAddress());
ref.setAddress(address);
if (ei.getService() != null)
{
EndpointReferenceUtils.setServiceAndPortName(ref, ei.getService().getName(), ei.getName()
.getLocalPart());
}
}
else
{
ref = t;
}
return ref;
}
/**
* Used to set appropriate message properties, exchange etc. as required for an
* incoming decoupled response (as opposed what's normally set by the Destination
* for an incoming request).
*/
protected class InterposedMessageObserver implements MessageObserver
{
/**
* Called for an incoming message.
*
* @param inMessage
*/
public void onMessage(Message inMessage)
{
// disposable exchange, swapped with real Exchange on correlation
inMessage.setExchange(new ExchangeImpl());
inMessage.put(DECOUPLED_CHANNEL_MESSAGE, Boolean.TRUE);
inMessage.put(Message.RESPONSE_CODE, HttpURLConnection.HTTP_OK);
inMessage.remove(Message.ASYNC_POST_RESPONSE_DISPATCH);
incomingObserver.onMessage(inMessage);
}
}
public void setCloseInput(boolean closeInput)
{
this.closeInput = closeInput;
}
protected CxfConfiguration getConnector()
{
return configuration;
}
protected EndpointInfo getEndpoint()
{
return endpoint;
}
protected MuleUniversalTransport getTransport()
{
return transport;
}
}