/*
* 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.servicemix.jbi.cluster.engine;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jbi.servicedesc.ServiceEndpoint;
import javax.jbi.messaging.MessageExchange;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import javax.xml.namespace.QName;
import org.apache.servicemix.nmr.api.Endpoint;
import org.apache.servicemix.nmr.api.Channel;
import org.apache.servicemix.nmr.api.Exchange;
import org.apache.servicemix.nmr.api.Role;
import org.apache.servicemix.nmr.api.Status;
import org.apache.servicemix.nmr.api.Pattern;
import org.apache.servicemix.nmr.api.Message;
import org.apache.servicemix.nmr.api.EndpointRegistry;
import org.apache.servicemix.nmr.api.service.ServiceHelper;
import org.apache.servicemix.nmr.api.internal.InternalEndpoint;
import org.apache.servicemix.nmr.api.internal.InternalExchange;
import org.apache.servicemix.nmr.api.event.EndpointListener;
import org.apache.servicemix.nmr.api.event.ExchangeListener;
import org.apache.servicemix.nmr.core.ServiceRegistryImpl;
import org.apache.servicemix.jbi.runtime.impl.MessageExchangeImpl;
import org.apache.servicemix.jbi.runtime.impl.ServiceEndpointImpl;
import org.apache.servicemix.jbi.runtime.impl.DeliveryChannelImpl;
import org.apache.servicemix.jbi.cluster.requestor.JmsRequestor;
import org.apache.servicemix.jbi.cluster.requestor.Transacted;
import org.apache.servicemix.jbi.cluster.requestor.JmsRequestorListener;
import org.apache.servicemix.jbi.cluster.requestor.JmsRequestorPool;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
/**
* Throttling
* ==========
* As the processing of consumed JMS messages is performed asynchronously, in a case
* where there are lots of requests pending in the queue, we need to limit the number
* of exchanges sent into the NMR at a given time so that it does not end in out of
* memory errors. In order to do so, the <code>maxPendingExchanges</code> property can
* be configured. The cluster endpoint keeps track of the number of exchange that it has
* send and which are not fully processed. If the maximum number is reached, it will stop
* the consumption of new requests until that number comes back below the threshold.
* The default value is 4096.
*
* Use of JMS selectors
* ====================
* In order for this endpoint to be used to cluster several services, and in order
* to use a minimum amount of JMS destinations, the cluster endpoint uses selector to
* listen to exchanges it is interested in.
* Another requirement is that the response for a given JMS message is consumed only
* by the cluster endpoint that originally sent the JMS request.
* Therefore, we use the <code>name</code> property of this endpoint (which has to be
* unique in the cluster) as a JMS property on the messages. The endpoint acting as a
* JBI provider will create a JMS message and set this property. It will only consume
* messages that have the needed value for the cluster name, hence only consuming his
* own responses. On the JBI consumer side of the endpoint, the endpoint will consume
* JMS messages of two kinds: new requests (containing an IN message and a JBI target
* that is available on the container), and also replies targeted to this cluster endpoint.
* Also, the <code>maxPendingExchanges</code> on the JBI consumer side has an effect
* here. When the number of requests that have been sent into the NMR but not fully
* processed yet reaches the above value, the JMS consumer will not consume messages
* corresponding to new requests, but will only service replies. This behavior will
* remain until some pending exchanges are processed.
*
* Transactions
* ============
* Four transactional modesl are define: None, ClientAck, Jms and Xa. The first one
* will use JMS session in auto acknowledge mode, which means there will be no redelivery
* at all. The <code>ClientAck</code> mode will use client acknowledgements on the JMS
* sessions and will send the ack when the exchange comes back. The <code>Jms</code> mode
* uses JMS local transactions, and the <code>Xa</code> uses XA transactions.
* The behavior of the cluster endpoint when receiving back the exchange is also
* controlled by the <code>rollbackOnErrors</code> flag. If this flag is set and the
* transaction model is not <code>None</code>, any exchange that comes back in an
* <code>Error</code> status will lead to the transaction being rolled back (or the ack
* not sent). This also means that no error status can be conveyed back to the original
* exchange.
* TODO: this should be configured on a per endpoint basis
*
* Using multiple clustered endpoints:
* If/when this cluster endpoint is used to cluster multiple endpoints
* (i.e. an interceptor is used to redirect exchanges to this endpoint),
* and if the containers do not have the exact same set of endpoints deployed,
* a jms consumer may consume JMS messages targeted to an endpoint which happen
* to not be available in this container. In such a case, the jms consumer will
* use a JMS selector to ensure it will only consume JMS messages it will be able
* to process. This is of course a bit more CPU intensive on the broker.
* When this cluster endpoint is used in an explicit wiring between two endpoints,
* or if the container are supposed to have the exact same set of endpoints available,
* the <code>assumeSameContainers</code> flag can be turned on, which will disable the
* use of JMS selectors.
*
* Transactions support
* Transactions are supported at the JMS consumer level. In JMS, such transactions
* will usually involve receiving a JMS message and sending a response back in the
* same transaction. Transactions are supported if the JBI statuses are conveyed back
* but in such cases, the transaction will never be rolled back. It thus means
* it will provide a guarantee if the container is shut down, but will not offer a
* redelivery mechanism in case something went wrong while processing the message.
* TODO: handle XA transactions
* TODO: transactions are not handled on the provider side
*
* Conveying JBI status back
* The boolean property <code>conveyJbiStatus</code> can be used to
* control whether the JBI statuses (DONE and ERROR) are always sent back
* across the JMS layer. It changes the number of JMS messages sent
* for a given JBI exchange and also change the transactions semantics.
* When JBI statuses are always sent, an InOnly request will be translated
* into two JMS messages (one for the In message and another one for the
* DONE or ERROR status or three for an InOnly MEP.
* This means that the transaction (if any) will not be rolled back if the
* exchange comes back with an ERROR status. On the opposite, if JBI statuses
* are not sent back, a single JMS message will be
* used: if the exchange comes back with an ERROR, the transaction on the JMS
* consumer side will be rolled back and the message redelivered; nothing will
* be reported back to the JMS producer side.
* Note that this behavior (not conveying JBI statuses and rolling back the
* transaction) is only available when using transactions as the message need
* to be redelivered by the JMS broker.
* Therefore the InOnly exchange DONE status will be sent immediately after the
* JMS message has been sent. For a RobustInOnly, the JMS consumer will sent back
* a fault or a DONE status, but an ERROR status will cause the transaction to be
* rolled back and the message redelivered.
* For an InOut MEP, either two or three messages will be used if JBI statuses
* are conveyed or not.
* TODO: what about InOptionalOut which allows a fault to be sent back from the
* JBI consumer to the provider after receiving the out message ?
* TODO: update doc with rollbackOnErrors
* this flag is only used on the JMS consumer side and sent in the JMS message
* for the other side to know how to handle the message
* TODO: the rollbackOnErrors flag should be configured on a per-endpoint basis
*
* TODO: add a cache level
* not caching the connection would only work when using non temporary
* queues for the reply destination
* TODO: handle JMS exceptions => refresh connection
* note that refreshing the connection when using temporary queues
* would lead to loosing messages in the temp queue
*
* TODO: simplify the selectors when a single endpoint is clustered
*
*/
public class ClusterEngine extends ServiceRegistryImpl<ClusterRegistration>
implements Endpoint, EndpointListener, ExchangeListener {
/**
* Default maximum number of pending exchanges
*/
public static final int DEFAULT_MAX_PENDING_EXCHANGES = 4096;
/**
* Name of the JMS property holding the type of message sent
*/
protected static final String JBI_MESSAGE = "JBIMessage";
/**
* The JMS message contains a JBI In message
*/
protected static final int JBI_MESSAGE_IN = 0;
/**
* The JMS message contains a JBI Out message
*/
protected static final int JBI_MESSAGE_OUT = 1;
/**
* The JMS message contains a JBI Fault message
*/
protected static final int JBI_MESSAGE_FAULT = 2;
/**
* The JMS message contains a JBI DONE status
*/
protected static final int JBI_MESSAGE_DONE = 3;
/**
* The JMS message contains a JBI ERROR status
*/
protected static final int JBI_MESSAGE_ERROR = 4;
/**
* JMS property holding the Message Exchange Pattern
*/
protected static final String JBI_MEP = "JBIMep";
/**
* JMS property holding the interface QName for the exchange
*/
protected static final String JBI_INTERFACE = "JBIInterface";
/**
* JMS property holding the operation QName for the exchange
*/
protected static final String JBI_OPERATION = "JBIOperation";
/**
* JMS property holding the service QName for the exchange
*/
protected static final String JBI_SERVICE = "JBIService";
/**
* JMS property holding the endpoint for the exchange
*/
protected static final String JBI_ENDPOINT = "JBIEndpoint";
/**
* JMS property holding the correlation id
*/
protected static final String PROPERTY_CORR_ID = "ClusterCorrId";
/**
* JMS property holding the correlation id to be used for the reply.
*/
protected static final String PROPERTY_SENDER_CORR_ID = "SenderClusterCorrId";
/**
* JMS property containing the rollbackOnErrors flag for this message.
* This property is set on the IN message so that the cluster endpoint
* consuming this message will handle it with the right behavior in case
* the cluster endpoint that sent the message has a different configuration.
*/
protected static final String PROPERTY_ROLLBACK_ON_ERRORS = "ClusterRollbackOnErrors";
/**
* JMS property holding the name of the cluster (used with selectors)
*/
protected static final String PROPERTY_CLUSTER_NAME = "ClusterName";
/**
* JMS property holding the name of the cluster to use for the response
*/
protected static final String PROPERTY_SENDER_CLUSTER_NAME = "SenderClusterName";
protected static final Log logger = LogFactory.getLog(ClusterEngine.class);
protected boolean rollbackOnErrors = true;
protected String name;
protected JmsRequestorPool pool;
protected Channel channel;
protected AtomicBoolean started = new AtomicBoolean();
protected final Map<String, Exchange> exchanges = new ConcurrentHashMap<String, Exchange>();
protected String selector;
protected AtomicInteger pendingExchanges = new AtomicInteger();
protected AtomicBoolean pauseConsumption = new AtomicBoolean(false);
protected int maxPendingExchanges = DEFAULT_MAX_PENDING_EXCHANGES;
public Channel getChannel() {
return channel;
}
public void setChannel(Channel channel) {
this.channel = channel;
try {
start();
} catch (Exception e) {
throw new RuntimeException("Unable to start cluster endpoint", e);
}
}
public JmsRequestorPool getPool() {
return pool;
}
public void setPool(JmsRequestorPool pool) {
this.pool = pool;
}
public boolean isRollbackOnErrors() {
return rollbackOnErrors;
}
public void setRollbackOnErrors(boolean rollbackOnErrors) {
this.rollbackOnErrors = rollbackOnErrors;
}
public String getName() {
return name;
}
/**
* A unique name for this cluster endpoint.
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
public int getMaxPendingExchanges() {
return maxPendingExchanges;
}
/**
* Specifies the maximum number of pending exchanges on the JMS consumer side.
* This allows to limit the number of JBI exchanges sent into the NMR at a given time.
* If set to a huge number, the NMR may be flooded by exchanges and run out of memory.
*
* @param maxPendingExchanges
*/
public void setMaxPendingExchanges(int maxPendingExchanges) {
this.maxPendingExchanges = maxPendingExchanges;
}
public void afterPropertiesSet() throws Exception {
if (pool == null) {
throw new IllegalArgumentException("'pool' must be set");
}
}
public void start() throws Exception {
if (started.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Starting cluster endpoint: " + name);
}
pool.setListener(new JmsRequestorListener() {
public void onMessage(JmsRequestor requestor) throws Exception {
process(requestor);
}
});
invalidateSelector();
pool.start();
}
}
public void destroy() throws Exception {
if (started.compareAndSet(true, false)) {
if (logger.isDebugEnabled()) {
logger.debug("Stopping cluster endpoint: " + name);
}
// TODO: We should first stop receiving new requests, then wait for all pending exchanges to be processed
// maxPendingExchanges = 0;
// if (pauseConsumption.compareAndSet(false, true)) {
// invalidateSelector();
// }
// while (pendingExchanges.get() > 0) {
// Thread.sleep(100);
// }
pool.stop();
}
}
public void pause() {
if (pauseConsumption.compareAndSet(false, true)) {
if (logger.isDebugEnabled()) {
logger.debug("Pausing cluster endpoint: " + name);
}
invalidateSelector();
}
}
public void resume() {
if (pauseConsumption.compareAndSet(true, false)) {
if (logger.isDebugEnabled()) {
logger.debug("Resuming cluster endpoint: " + name);
}
invalidateSelector();
}
}
//-------------------------------------------------------------------------
// Endpoint listener
//-------------------------------------------------------------------------
public void endpointRegistered(InternalEndpoint endpoint) {
invalidateSelector();
}
public void endpointUnregistered(InternalEndpoint endpoint) {
invalidateSelector();
}
//-------------------------------------------------------------------------
// Exchange listener
//-------------------------------------------------------------------------
public void exchangeSent(Exchange exchange) {
// Intercept exchanges
if (exchange instanceof InternalExchange
&& exchange.getStatus() == Status.Active && exchange.getRole() == Role.Consumer
&& exchange.getOut(false) == null && exchange.getFault(false) == null) {
// Filter JBI endpoints
InternalEndpoint source = ((InternalExchange) exchange).getSource();
for (ClusterRegistration reg : getServices()) {
if (reg.match(source)) {
exchange.setTarget(getChannel().getNMR().getEndpointRegistry().lookup(ServiceHelper.createMap(Endpoint.NAME, name)));
return;
}
}
}
}
public void exchangeDelivered(Exchange exchange) {
}
public void exchangeFailed(Exchange exchange) {
}
public void invalidateSelector() {
selector = null;
if (pool != null) {
String selector;
// If we're pausing comsumption of new messages, use a selector that will
// only select messages directed to this very container (i.e. not new
// exchanges).
if (pauseConsumption.get()) {
selector = PROPERTY_CLUSTER_NAME + " = '" + name + "'";
// Else we use the full selector that includes the JBI targets
} else {
selector = getSelector();
}
pool.setMessageSelector(selector);
}
}
protected String getSelector() {
if (selector == null) {
Set<String> interfaces = new HashSet<String>();
Set<String> services = new HashSet<String>();
Set<String> endpoints = new HashSet<String>();
for (ServiceEndpoint se : getAllEndpoints()) {
// This endpoint is not a JBI endpoint, so we don't need to filter it out
QName[] itfs = se.getInterfaces();
if (itfs != null) {
for (QName itf : itfs) {
interfaces.add(itf.toString());
}
}
services.add(se.getServiceName().toString());
endpoints.add("{" + se.getServiceName().toString() + "}" + se.getEndpointName());
}
StringBuilder selector = new StringBuilder();
if (!endpoints.isEmpty()) {
selector.append("(");
selector.append(JBI_MESSAGE).append(" = ").append(JBI_MESSAGE_IN).append(" AND (");
if (!interfaces.isEmpty()) {
selector.append(JBI_INTERFACE).append(" IN (");
boolean first = true;
for (String s : interfaces) {
if (!first) {
selector.append(", ");
} else {
first = false;
}
selector.append("'").append(s).append("'");
}
selector.append(")");
}
if (!interfaces.isEmpty()) {
selector.append(" OR ");
}
selector.append(JBI_SERVICE).append(" IN (");
boolean first = true;
for (String s : services) {
if (!first) {
selector.append(", ");
} else {
first = false;
}
selector.append("'").append(s).append("'");
}
selector.append(")");
selector.append(" OR ");
selector.append(JBI_ENDPOINT).append(" IN (");
first = true;
for (String s : endpoints) {
if (!first) {
selector.append(", ");
} else {
first = false;
}
selector.append("'").append(s).append("'");
}
selector.append(")");
selector.append(")");
selector.append(")");
selector.append(" OR ");
}
selector.append(PROPERTY_CLUSTER_NAME).append(" = '").append(name).append("'");
this.selector = selector.toString();
}
return this.selector;
}
//-------------------------------------------------------------------------
// NMR helpers
//-------------------------------------------------------------------------
protected ServiceEndpoint getEndpoint(QName serviceName, String endpointName) {
return new ServiceEndpointImpl(serviceName, endpointName);
}
protected List<ServiceEndpoint> getAllEndpoints() {
List<ServiceEndpoint> endpoints = new ArrayList<ServiceEndpoint>();
EndpointRegistry registry = getChannel().getNMR().getEndpointRegistry();
for (Endpoint ep : registry.getServices()) {
Map<String,?> props = registry.getProperties(ep);
// Check if this endpoint is addressable in the JBI space
if (props.get(Endpoint.SERVICE_NAME) != null && props.get(Endpoint.ENDPOINT_NAME) != null
&& !Boolean.valueOf((String) props.get(Endpoint.UNTARGETABLE))) {
endpoints.add(new ServiceEndpointImpl(props));
}
}
return endpoints;
}
protected void done(Exchange exchange) {
exchange.setStatus(Status.Done);
send(exchange);
}
protected void fail(Exchange exchange, Exception e) {
exchange.setStatus(Status.Error);
exchange.setError(e);
send(exchange);
}
protected void send(Exchange exchange) {
decrementPendingExchangeIfNeeded(exchange);
getChannel().send(exchange);
}
//-------------------------------------------------------------------------
// JMS / JBI processing
//-------------------------------------------------------------------------
public void process(Exchange exchange) {
try {
String corrId = (String) exchange.getProperty(PROPERTY_CORR_ID + "." + name);
if (corrId != null) {
JmsRequestor item = pool.resume(corrId);
synchronized (item) {
try {
processExchange(item, exchange);
} finally {
item.close();
}
}
} else {
JmsRequestor item = pool.newRequestor();
synchronized (item) {
try {
item.begin();
processExchange(item, exchange);
} finally {
item.close();
}
}
}
} catch (Exception e) {
// TODO what id the problem is a JMS exception or related
fail(exchange, e);
}
}
/**
* Process a JMS message
*
* @param requestor the item to use
* @throws JMSException if an error occur
*/
protected void process(JmsRequestor requestor) throws JMSException {
javax.jms.Message message = requestor.getMessage();
int type = message.getIntProperty(JBI_MESSAGE);
switch (type) {
case JBI_MESSAGE_DONE: {
String corrId = message.getStringProperty(PROPERTY_CORR_ID);
if (corrId == null) {
throw new IllegalStateException("Incoming JMS message has no correlationId");
}
Exchange exchange = exchanges.remove(corrId);
if (exchange == null) {
throw new IllegalStateException("Exchange not found for id " + corrId);
}
done(exchange);
break;
}
case JBI_MESSAGE_ERROR: {
String corrId = message.getStringProperty(PROPERTY_CORR_ID);
if (corrId == null) {
throw new IllegalStateException("Incoming JMS message has no correlationId");
}
Exchange exchange = exchanges.remove(corrId);
if (exchange == null) {
throw new IllegalStateException("Exchange not found for id " + corrId);
}
fail(exchange, (Exception)((ObjectMessage) message).getObject());
break;
}
case JBI_MESSAGE_IN: {
String mep = message.getStringProperty(JBI_MEP);
if (mep == null) {
throw new IllegalStateException("Exchange MEP not found for JMS message " + message.getJMSMessageID());
}
Exchange exchange = getChannel().createExchange(Pattern.fromWsdlUri(mep));
exchange.setProperty(PROPERTY_ROLLBACK_ON_ERRORS + "." + name, message.getBooleanProperty(PROPERTY_ROLLBACK_ON_ERRORS));
if (message.propertyExists(JBI_INTERFACE)) {
exchange.setProperty(MessageExchangeImpl.INTERFACE_NAME_PROP, QName.valueOf(message.getStringProperty(JBI_INTERFACE)));
}
if (message.propertyExists(JBI_OPERATION)) {
exchange.setOperation(QName.valueOf(message.getStringProperty(JBI_OPERATION)));
}
if (message.propertyExists(JBI_SERVICE)) {
exchange.setProperty(MessageExchangeImpl.SERVICE_NAME_PROP, QName.valueOf(message.getStringProperty(JBI_SERVICE)));
}
if (message.propertyExists(JBI_ENDPOINT)) {
QName q = QName.valueOf(message.getStringProperty(JBI_ENDPOINT));
String e = q.getLocalPart();
q = QName.valueOf(q.getNamespaceURI());
ServiceEndpoint se = getEndpoint(q, e);
// TODO: check that endpoint exists
exchange.setProperty(MessageExchangeImpl.SERVICE_ENDPOINT_PROP, se);
}
// Re-process JBI addressing
DeliveryChannelImpl.createTarget(getChannel().getNMR(), exchange);
// TODO: read exchange properties
Message msg = unmarshallMessage(message);
exchange.setIn(msg);
exchanges.put(exchange.getId(), exchange);
if (pendingExchanges.incrementAndGet() >= maxPendingExchanges) {
if (pauseConsumption.compareAndSet(false, true)) {
invalidateSelector();
}
}
exchange.setProperty(PROPERTY_CORR_ID + "." + name, exchange.getId());
requestor.suspend(exchange.getId());
if (requestor.getTransaction() != null) {
exchange.setProperty(MessageExchange.JTA_TRANSACTION_PROPERTY_NAME, requestor.getTransaction());
}
send(exchange);
break;
}
case JBI_MESSAGE_OUT: {
String corrId = message.getStringProperty(PROPERTY_CORR_ID);
if (corrId == null) {
throw new IllegalStateException("Incoming JMS message has no correlationId");
}
Exchange exchange = exchanges.get(corrId);
if (exchange == null) {
throw new IllegalStateException("Exchange not found for id " + corrId);
}
Message msg = unmarshallMessage(message);
exchange.setOut(msg);
exchanges.put(exchange.getId(), exchange);
exchange.setProperty(PROPERTY_CORR_ID + "." + name, exchange.getId());
requestor.suspend(exchange.getId());
if (requestor.getTransaction() != null) {
exchange.setProperty(MessageExchange.JTA_TRANSACTION_PROPERTY_NAME, requestor.getTransaction());
}
send(exchange);
break;
}
case JBI_MESSAGE_FAULT: {
String corrId = message.getStringProperty(PROPERTY_CORR_ID);
if (corrId == null) {
throw new IllegalStateException("Incoming JMS message has no correlationId");
}
Exchange exchange = exchanges.get(corrId);
if (exchange == null) {
throw new IllegalStateException("Exchange not found for id " + corrId);
}
Message msg = unmarshallMessage(message);
exchange.setFault(msg);
exchanges.put(exchange.getId(), exchange);
exchange.setProperty(PROPERTY_CORR_ID + "." + name, exchange.getId());
requestor.suspend(exchange.getId());
if (requestor.getTransaction() != null) {
exchange.setProperty(MessageExchange.JTA_TRANSACTION_PROPERTY_NAME, requestor.getTransaction());
}
send(exchange);
break;
}
default: {
throw new IllegalStateException("Received unknown message type: " + type);
}
}
}
/**
* Process a JBI exchange
*
* @param requestor the item to use
* @param exchange the exchange to process
* @throws Exception if an error occur
*/
protected void processExchange(JmsRequestor requestor, Exchange exchange) throws Exception {
synchronized (requestor) {
decrementPendingExchangeIfNeeded(exchange);
boolean rollbackOnErrors;
if (exchange.getRole() == Role.Consumer) {
rollbackOnErrors = Boolean.TRUE.equals(exchange.getProperty(PROPERTY_ROLLBACK_ON_ERRORS + "." + name));
} else {
rollbackOnErrors = this.rollbackOnErrors;
}
if (exchange.getStatus() == Status.Active) {
Message msg = exchange.getFault(false);
int type;
if (msg != null) {
type = JBI_MESSAGE_FAULT;
} else {
msg = exchange.getOut(false);
if (msg != null) {
type = JBI_MESSAGE_OUT;
} else {
msg = exchange.getIn(false);
if (msg != null) {
type = JBI_MESSAGE_IN;
} else {
throw new IllegalStateException("No normalized message on an active exchange: " + exchange);
}
}
}
javax.jms.Message message = requestor.getSession().createObjectMessage(msg);
message.setIntProperty(JBI_MESSAGE, type);
if (type == JBI_MESSAGE_IN) {
rollbackOnErrors = this.rollbackOnErrors;
exchange.setProperty(PROPERTY_ROLLBACK_ON_ERRORS + "." + name, rollbackOnErrors);
message.setStringProperty(JBI_MEP, exchange.getPattern().getWsdlUri());
if (exchange.getProperty(MessageExchangeImpl.INTERFACE_NAME_PROP) != null) {
message.setStringProperty(JBI_INTERFACE, exchange.getProperty(MessageExchangeImpl.INTERFACE_NAME_PROP).toString());
}
if (exchange.getOperation() != null) {
message.setStringProperty(JBI_OPERATION, exchange.getOperation().toString());
}
if (exchange.getProperty(MessageExchangeImpl.SERVICE_NAME_PROP) != null) {
message.setStringProperty(JBI_SERVICE, exchange.getProperty(MessageExchangeImpl.SERVICE_NAME_PROP).toString());
}
if (exchange.getProperty(MessageExchangeImpl.SERVICE_ENDPOINT_PROP) != null) {
ServiceEndpoint se = (ServiceEndpoint) exchange.getProperty(MessageExchangeImpl.SERVICE_ENDPOINT_PROP);
message.setStringProperty(JBI_ENDPOINT, "{" + se.getServiceName().toString() + "}" + se.getEndpointName());
}
// TODO: write exchange properties
}
message.setBooleanProperty(PROPERTY_ROLLBACK_ON_ERRORS, rollbackOnErrors);
boolean expectResponse;
if (!rollbackOnErrors) {
expectResponse = true;
} else {
switch (exchange.getPattern()) {
case InOnly:
expectResponse = false;
break;
case RobustInOnly:
expectResponse = exchange.getRole() == Role.Provider;
break;
case InOut:
expectResponse = exchange.getRole() == Role.Provider;
break;
default:
// TODO:
expectResponse = true;
break;
}
}
if (expectResponse) {
exchanges.put(exchange.getId(), exchange);
message.setStringProperty(PROPERTY_SENDER_CLUSTER_NAME, name);
message.setStringProperty(PROPERTY_SENDER_CORR_ID, exchange.getId());
if (requestor.getMessage() != null) {
message.setStringProperty(ClusterEngine.PROPERTY_CLUSTER_NAME, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CLUSTER_NAME));
message.setStringProperty(ClusterEngine.PROPERTY_CORR_ID, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CORR_ID));
}
requestor.send(message);
} else {
message.setStringProperty(PROPERTY_SENDER_CLUSTER_NAME, name);
message.setStringProperty(PROPERTY_SENDER_CORR_ID, null);
if (requestor.getMessage() != null) {
message.setStringProperty(ClusterEngine.PROPERTY_CLUSTER_NAME, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CLUSTER_NAME));
message.setStringProperty(ClusterEngine.PROPERTY_CORR_ID, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CORR_ID));
}
requestor.send(message);
// TODO: send done in the tx synchronization
done(exchange);
}
} else if (exchange.getStatus() == Status.Done) {
boolean doSend;
if (!rollbackOnErrors) {
doSend = true;
} else {
switch (exchange.getPattern()) {
case InOnly:
// never send done for InOnly
doSend = false;
break;
case RobustInOnly:
// only send done when there is no fault
// which means the exchange has a consumer role
doSend = exchange.getRole() == Role.Consumer;
break;
case InOptionalOut:
// TODO
doSend = true;
break;
case InOut:
// in an InOut mep, the DONE status always come from the JBI consumer
doSend = false;
break;
default:
throw new IllegalStateException("Unsupported MEP: " + exchange.getPattern());
}
}
if (doSend) {
javax.jms.Message message = requestor.getSession().createMessage();
message.setIntProperty(JBI_MESSAGE, JBI_MESSAGE_DONE);
message.setStringProperty(PROPERTY_SENDER_CLUSTER_NAME, name);
message.setStringProperty(PROPERTY_SENDER_CORR_ID, null);
if (requestor.getMessage() != null) {
message.setStringProperty(ClusterEngine.PROPERTY_CLUSTER_NAME, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CLUSTER_NAME));
message.setStringProperty(ClusterEngine.PROPERTY_CORR_ID, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CORR_ID));
}
requestor.send(message);
}
} else if (exchange.getStatus() == Status.Error) {
boolean doSend;
if (!rollbackOnErrors) {
doSend = true;
} else {
switch (exchange.getPattern()) {
case InOnly:
// never send errors for InOnly
doSend = false;
break;
case RobustInOnly:
// do not send exchange from the provider back to the consumer
doSend = pool.getTransacted() == Transacted.None || exchange.getRole() != Role.Consumer;
break;
case InOptionalOut:
// TODO
doSend = true;
break;
case InOut:
doSend = pool.getTransacted() == Transacted.None || exchange.getRole() != Role.Consumer;
break;
default:
throw new IllegalStateException("Unsupported MEP: " + exchange.getPattern());
}
}
if (doSend) {
javax.jms.Message message = requestor.getSession().createObjectMessage(exchange.getError());
message.setIntProperty(JBI_MESSAGE, JBI_MESSAGE_ERROR);
message.setStringProperty(PROPERTY_SENDER_CLUSTER_NAME, name);
message.setStringProperty(PROPERTY_SENDER_CORR_ID, null);
if (requestor.getMessage() != null) {
message.setStringProperty(ClusterEngine.PROPERTY_CLUSTER_NAME, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CLUSTER_NAME));
message.setStringProperty(ClusterEngine.PROPERTY_CORR_ID, requestor.getMessage().getStringProperty(ClusterEngine.PROPERTY_SENDER_CORR_ID));
}
requestor.send(message);
} else {
requestor.setRollbackOnly();
}
} else {
throw new IllegalStateException("Unknown exchange status: " + exchange);
}
}
}
protected void decrementPendingExchangeIfNeeded(Exchange exchange) {
if (exchange.getRole() == Role.Consumer && exchange.getStatus() != Status.Active) {
if (pendingExchanges.decrementAndGet() < maxPendingExchanges) {
if (pauseConsumption.compareAndSet(true, false)) {
invalidateSelector();
}
}
}
}
protected Message unmarshallMessage(javax.jms.Message message) throws JMSException {
Message msg = null;
ClassLoader oldCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
msg = (Message) ((ObjectMessage) message).getObject();
} finally {
Thread.currentThread().setContextClassLoader(oldCl);
}
return msg;
}
}