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    }