/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.cxf;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import javax.xml.ws.WebFault;
import org.w3c.dom.Element;
import org.apache.camel.AsyncCallback;
import org.apache.camel.ExchangePattern;
import org.apache.camel.ExchangeTimedOutException;
import org.apache.camel.Processor;
import org.apache.camel.component.cxf.common.message.CxfConstants;
import org.apache.camel.impl.DefaultConsumer;
import org.apache.camel.util.ObjectHelper;
import org.apache.cxf.continuations.Continuation;
import org.apache.cxf.continuations.ContinuationProvider;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.frontend.ServerFactoryBean;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Exchange;
import org.apache.cxf.message.FaultMode;
import org.apache.cxf.message.Message;
import org.apache.cxf.service.invoker.Invoker;
import org.apache.cxf.service.model.BindingOperationInfo;
import org.apache.cxf.ws.addressing.ContextUtils;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A Consumer of exchanges for a service in CXF. CxfConsumer acts a CXF
* service to receive requests, convert them, and forward them to Camel
* route for processing. It is also responsible for converting and sending
* back responses to CXF client.
*
* @version
*/
public class CxfConsumer extends DefaultConsumer {
private static final Logger LOG = LoggerFactory.getLogger(CxfConsumer.class);
private Server server;
private CxfEndpoint cxfEndpoint;
public CxfConsumer(final CxfEndpoint endpoint, Processor processor) throws Exception {
super(endpoint, processor);
cxfEndpoint = endpoint;
server = createServer();
}
protected Server createServer() throws Exception {
ServerFactoryBean svrBean = cxfEndpoint.createServerFactoryBean();
svrBean.setInvoker(new CxfConsumerInvoker(cxfEndpoint));
Server server = svrBean.create();
// Apply the server configurer if it is possible
if (cxfEndpoint.getCxfEndpointConfigurer() != null) {
cxfEndpoint.getCxfEndpointConfigurer().configureServer(server);
}
if (ObjectHelper.isNotEmpty(cxfEndpoint.getPublishedEndpointUrl())) {
server.getEndpoint().getEndpointInfo().setProperty("publishedEndpointUrl", cxfEndpoint.getPublishedEndpointUrl());
}
return server;
}
public Server getServer() {
return server;
}
@Override
protected void doStart() throws Exception {
super.doStart();
if (server == null) {
server = createServer();
}
server.start();
}
@Override
protected void doStop() throws Exception {
if (server != null) {
server.stop();
server.destroy();
server = null;
}
super.doStop();
}
private EndpointReferenceType getReplyTo(Object o) {
try {
return (EndpointReferenceType)o.getClass().getMethod("getReplyTo").invoke(o);
} catch (Throwable t) {
throw new Fault(t);
}
}
protected boolean isAsyncInvocationSupported(Exchange cxfExchange) {
Message cxfMessage = cxfExchange.getInMessage();
Object addressingProperties = cxfMessage.get(CxfConstants.WSA_HEADERS_INBOUND);
if (addressingProperties != null
&& !ContextUtils.isGenericAddress(getReplyTo(addressingProperties))) {
//it's decoupled endpoint, so already switch thread and
//use executors, which means underlying transport won't
//be block, so we shouldn't rely on continuation in
//this case, as the SuspendedInvocationException can't be
//caught by underlying transport. So we should use the SyncInvocation this time
return false;
}
// we assume it should support AsyncInvocation out of box
return true;
}
private class CxfConsumerInvoker implements Invoker {
private final CxfEndpoint endpoint;
CxfConsumerInvoker(CxfEndpoint endpoint) {
this.endpoint = endpoint;
}
// we receive a CXF request when this method is called
public Object invoke(Exchange cxfExchange, Object o) {
LOG.trace("Received CXF Request: {}", cxfExchange);
Continuation continuation;
if (!endpoint.isSynchronous() && isAsyncInvocationSupported(cxfExchange)
&& (continuation = getContinuation(cxfExchange)) != null) {
LOG.trace("Calling the Camel async processors.");
return asyncInvoke(cxfExchange, continuation);
} else {
LOG.trace("Calling the Camel sync processors.");
return syncInvoke(cxfExchange);
}
}
// NOTE this code cannot work with CXF 2.2.x and JMSContinuation
// as it doesn't break out the interceptor chain when we call it
private Object asyncInvoke(Exchange cxfExchange, final Continuation continuation) {
synchronized (continuation) {
if (continuation.isNew()) {
final org.apache.camel.Exchange camelExchange = prepareCamelExchange(cxfExchange);
// Now we don't set up the timeout value
LOG.trace("Suspending continuation of exchangeId: {}", camelExchange.getExchangeId());
// The continuation could be called before the suspend is called
continuation.suspend(cxfEndpoint.getContinuationTimeout());
continuation.setObject(camelExchange);
// use the asynchronous API to process the exchange
getAsyncProcessor().process(camelExchange, new AsyncCallback() {
public void done(boolean doneSync) {
// make sure the continuation resume will not be called before the suspend method in other thread
synchronized (continuation) {
LOG.trace("Resuming continuation of exchangeId: {}", camelExchange.getExchangeId());
// resume processing after both, sync and async callbacks
continuation.resume();
}
}
});
} else if (continuation.isResumed()) {
org.apache.camel.Exchange camelExchange = (org.apache.camel.Exchange)continuation.getObject();
try {
setResponseBack(cxfExchange, camelExchange);
} finally {
CxfConsumer.this.doneUoW(camelExchange);
}
} else if (!continuation.isResumed() && !continuation.isPending()) {
org.apache.camel.Exchange camelExchange = (org.apache.camel.Exchange)continuation.getObject();
try {
if (!continuation.isPending()) {
camelExchange.setException(new ExchangeTimedOutException(camelExchange, cxfEndpoint.getContinuationTimeout()));
}
setResponseBack(cxfExchange, camelExchange);
} finally {
CxfConsumer.this.doneUoW(camelExchange);
}
}
}
return null;
}
private Continuation getContinuation(Exchange cxfExchange) {
ContinuationProvider provider =
(ContinuationProvider)cxfExchange.getInMessage().get(ContinuationProvider.class.getName());
Continuation continuation = provider == null ? null : provider.getContinuation();
// Make sure we don't return the JMSContinuation, as it doesn't support the Continuation we wants
// Don't want to introduce the dependency of cxf-rt-transprot-jms here
if (continuation != null && continuation.getClass().getName().equals("org.apache.cxf.transport.jms.continuations.JMSContinuation")) {
return null;
} else {
return continuation;
}
}
private Object syncInvoke(Exchange cxfExchange) {
org.apache.camel.Exchange camelExchange = prepareCamelExchange(cxfExchange);
try {
try {
LOG.trace("Processing +++ START +++");
// send Camel exchange to the target processor
getProcessor().process(camelExchange);
} catch (Exception e) {
throw new Fault(e);
}
LOG.trace("Processing +++ END +++");
setResponseBack(cxfExchange, camelExchange);
} finally {
doneUoW(camelExchange);
}
// response should have been set in outMessage's content
return null;
}
private org.apache.camel.Exchange prepareCamelExchange(Exchange cxfExchange) {
// get CXF binding
CxfEndpoint endpoint = (CxfEndpoint)getEndpoint();
CxfBinding binding = endpoint.getCxfBinding();
// create a Camel exchange, the default MEP is InOut
org.apache.camel.Exchange camelExchange = endpoint.createExchange();
DataFormat dataFormat = endpoint.getDataFormat();
BindingOperationInfo boi = cxfExchange.getBindingOperationInfo();
// make sure the "boi" is remained as wrapped in PAYLOAD mode
if (boi != null && dataFormat == DataFormat.PAYLOAD && boi.isUnwrapped()) {
boi = boi.getWrappedOperation();
cxfExchange.put(BindingOperationInfo.class, boi);
}
if (boi != null) {
camelExchange.setProperty(BindingOperationInfo.class.getName(), boi);
LOG.trace("Set exchange property: BindingOperationInfo: {}", boi);
// set the message exchange patter with the boi
if (boi.getOperationInfo().isOneWay()) {
camelExchange.setPattern(ExchangePattern.InOnly);
}
} else {
if (cxfEndpoint.getExchangePattern().equals(ExchangePattern.InOnly)) {
camelExchange.setPattern(ExchangePattern.InOnly);
}
}
// set data format mode in Camel exchange
camelExchange.setProperty(CxfConstants.DATA_FORMAT_PROPERTY, dataFormat);
LOG.trace("Set Exchange property: {}={}", DataFormat.class.getName(), dataFormat);
camelExchange.setProperty(Message.MTOM_ENABLED, String.valueOf(endpoint.isMtomEnabled()));
if (endpoint.getMergeProtocolHeaders()) {
camelExchange.setProperty(CxfConstants.CAMEL_CXF_PROTOCOL_HEADERS_MERGED, Boolean.TRUE);
}
// bind the CXF request into a Camel exchange
binding.populateExchangeFromCxfRequest(cxfExchange, camelExchange);
// extract the javax.xml.ws header
Map<String, Object> context = new HashMap<String, Object>();
binding.extractJaxWsContext(cxfExchange, context);
// put the context into camelExchange
camelExchange.setProperty(CxfConstants.JAXWS_CONTEXT, context);
// we want to handle the UoW
try {
CxfConsumer.this.createUoW(camelExchange);
} catch (Exception e) {
log.error("Error processing request", e);
throw new Fault(e);
}
return camelExchange;
}
@SuppressWarnings("unchecked")
private void setResponseBack(Exchange cxfExchange, org.apache.camel.Exchange camelExchange) {
CxfEndpoint endpoint = (CxfEndpoint)getEndpoint();
CxfBinding binding = endpoint.getCxfBinding();
checkFailure(camelExchange, cxfExchange);
binding.populateCxfResponseFromExchange(camelExchange, cxfExchange);
// check failure again as fault could be discovered by converter
checkFailure(camelExchange, cxfExchange);
// copy the headers javax.xml.ws header back
binding.copyJaxWsContext(cxfExchange, (Map<String, Object>)camelExchange.getProperty(CxfConstants.JAXWS_CONTEXT));
}
private void checkFailure(org.apache.camel.Exchange camelExchange, Exchange cxfExchange) throws Fault {
final Throwable t;
if (camelExchange.isFailed()) {
org.apache.camel.Message camelMsg = camelExchange.hasOut() ? camelExchange.getOut() : camelExchange.getIn();
if (camelMsg.isFault()) {
t = camelMsg.getBody(Throwable.class);
} else {
t = camelExchange.getException();
}
cxfExchange.getInMessage().put(FaultMode.class, FaultMode.UNCHECKED_APPLICATION_FAULT);
if (t instanceof Fault) {
cxfExchange.getInMessage().put(FaultMode.class, FaultMode.CHECKED_APPLICATION_FAULT);
throw (Fault)t;
} else if (t != null) {
// This is not a CXF Fault. Build the CXF Fault manually.
Fault fault = new Fault(t);
if (fault.getMessage() == null) {
// The Fault has no Message. This is the case if it has
// no message, for example was a NullPointerException.
fault.setMessage(t.getClass().getSimpleName());
}
WebFault faultAnnotation = t.getClass().getAnnotation(WebFault.class);
Object faultInfo = null;
try {
Method method = t.getClass().getMethod("getFaultInfo");
faultInfo = method.invoke(t, new Object[0]);
} catch (Exception e) {
// do nothing here
}
if (faultAnnotation != null && faultInfo == null) {
// t has a JAX-WS WebFault annotation, which describes
// in detail the Web Service Fault that should be thrown. Add the
// detail.
Element detail = fault.getOrCreateDetail();
Element faultDetails = detail.getOwnerDocument()
.createElementNS(faultAnnotation.targetNamespace(), faultAnnotation.name());
detail.appendChild(faultDetails);
}
throw fault;
}
}
}
}
}