001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.processor;
018
019 import org.apache.camel.Endpoint;
020 import org.apache.camel.Exchange;
021 import org.apache.camel.ExchangePattern;
022 import org.apache.camel.Message;
023 import org.apache.camel.Processor;
024 import org.apache.camel.Producer;
025 import org.apache.camel.ProducerCallback;
026 import org.apache.camel.impl.DefaultExchange;
027 import org.apache.camel.impl.ProducerCache;
028 import org.apache.camel.impl.ServiceSupport;
029 import org.apache.camel.model.RoutingSlipDefinition;
030 import org.apache.camel.util.ExchangeHelper;
031 import org.apache.commons.logging.Log;
032 import org.apache.commons.logging.LogFactory;
033
034 import static org.apache.camel.util.ObjectHelper.notNull;
035
036 /**
037 * Implements a <a href="http://camel.apache.org/routing-slip.html">Routing Slip</a>
038 * pattern where the list of actual endpoints to send a message exchange to are
039 * dependent on the value of a message header.
040 */
041 public class RoutingSlip extends ServiceSupport implements Processor, Traceable {
042 private static final transient Log LOG = LogFactory.getLog(RoutingSlip.class);
043 private ProducerCache producerCache;
044 private final String header;
045 private final String uriDelimiter;
046
047 public RoutingSlip(String header) {
048 this(header, RoutingSlipDefinition.DEFAULT_DELIMITER);
049 }
050
051 public RoutingSlip(String header, String uriDelimiter) {
052 notNull(header, "header");
053 notNull(uriDelimiter, "uriDelimiter");
054
055 this.header = header;
056 this.uriDelimiter = uriDelimiter;
057 }
058
059 @Override
060 public String toString() {
061 return "RoutingSlip[header=" + header + " uriDelimiter=" + uriDelimiter + "]";
062 }
063
064 public String getTraceLabel() {
065 return "routingSlip[" + header + "]";
066 }
067
068 public void process(Exchange exchange) throws Exception {
069 Message message = exchange.getIn();
070 String[] recipients = recipients(message);
071 Exchange current = exchange;
072
073 for (String nextRecipient : recipients) {
074 Endpoint endpoint = resolveEndpoint(exchange, nextRecipient);
075
076 Exchange copy = new DefaultExchange(current);
077 updateRoutingSlip(current);
078 copyOutToIn(copy, current);
079
080 try {
081 getProducerCache(exchange).doInProducer(endpoint, copy, null, new ProducerCallback<Object>() {
082 public Object doInProducer(Producer producer, Exchange exchange, ExchangePattern exchangePattern) throws Exception {
083 // set property which endpoint we send to
084 exchange.setProperty(Exchange.TO_ENDPOINT, producer.getEndpoint().getEndpointUri());
085 producer.process(exchange);
086 return exchange;
087 }
088 });
089 } catch (Exception e) {
090 // catch exception so we can decide if we want to continue or not
091 copy.setException(e);
092 } finally {
093 current = copy;
094 }
095
096 // Decide whether to continue with the recipients or not; similar logic to the Pipeline
097 boolean exceptionHandled = hasExceptionBeenHandledByErrorHandler(current);
098 if (current.isFailed() || current.isRollbackOnly() || exceptionHandled) {
099 // The Exchange.ERRORHANDLED_HANDLED property is only set if satisfactory handling was done
100 // by the error handler. It's still an exception, the exchange still failed.
101 if (LOG.isDebugEnabled()) {
102 StringBuilder sb = new StringBuilder();
103 sb.append("Message exchange has failed so breaking out of the routing slip: ").append(current);
104 if (current.isRollbackOnly()) {
105 sb.append(" Marked as rollback only.");
106 }
107 if (current.getException() != null) {
108 sb.append(" Exception: ").append(current.getException());
109 }
110 if (current.hasOut() && current.getOut().isFault()) {
111 sb.append(" Fault: ").append(current.getOut());
112 }
113 if (exceptionHandled) {
114 sb.append(" Handled by the error handler.");
115 }
116 LOG.debug(sb.toString());
117 }
118 break;
119 }
120 }
121 ExchangeHelper.copyResults(exchange, current);
122 }
123
124 private static boolean hasExceptionBeenHandledByErrorHandler(Exchange nextExchange) {
125 return Boolean.TRUE.equals(nextExchange.getProperty(Exchange.ERRORHANDLER_HANDLED));
126 }
127
128 protected ProducerCache getProducerCache(Exchange exchange) throws Exception {
129 // setup producer cache as we need to use the pluggable service pool defined on camel context
130 if (producerCache == null) {
131 this.producerCache = new ProducerCache(exchange.getContext());
132 this.producerCache.start();
133 }
134 return this.producerCache;
135 }
136
137 protected Endpoint resolveEndpoint(Exchange exchange, Object recipient) {
138 return ExchangeHelper.resolveEndpoint(exchange, recipient);
139 }
140
141 protected void doStart() throws Exception {
142 if (producerCache != null) {
143 producerCache.start();
144 }
145 }
146
147 protected void doStop() throws Exception {
148 if (producerCache != null) {
149 producerCache.stop();
150 }
151 }
152
153 private void updateRoutingSlip(Exchange current) {
154 Message message = getResultMessage(current);
155 String oldSlip = message.getHeader(header, String.class);
156 if (oldSlip != null) {
157 int delimiterIndex = oldSlip.indexOf(uriDelimiter);
158 String newSlip = delimiterIndex > 0 ? oldSlip.substring(delimiterIndex + 1) : "";
159 message.setHeader(header, newSlip);
160 }
161 }
162
163 /**
164 * Returns the outbound message if available. Otherwise return the inbound
165 * message.
166 */
167 private Message getResultMessage(Exchange exchange) {
168 if (exchange.hasOut()) {
169 return exchange.getOut();
170 } else {
171 // if this endpoint had no out (like a mock endpoint) just take the in
172 return exchange.getIn();
173 }
174 }
175
176 /**
177 * Return the list of recipients defined in the routing slip in the
178 * specified message.
179 */
180 private String[] recipients(Message message) {
181 Object headerValue = message.getHeader(header);
182 if (headerValue != null && !headerValue.equals("")) {
183 return headerValue.toString().split(uriDelimiter);
184 }
185 return new String[] {};
186 }
187
188 /**
189 * Copy the outbound data in 'source' to the inbound data in 'result'.
190 */
191 private void copyOutToIn(Exchange result, Exchange source) {
192 result.setException(source.getException());
193
194 if (source.hasOut() && source.getOut().isFault()) {
195 result.getOut().copyFrom(source.getOut());
196 }
197
198 result.setIn(getResultMessage(source));
199
200 result.getProperties().clear();
201 result.getProperties().putAll(source.getProperties());
202 }
203 }