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 java.util.Collection;
020 import java.util.Iterator;
021 import java.util.List;
022
023 import org.apache.camel.Exchange;
024 import org.apache.camel.Processor;
025 import org.apache.camel.impl.DefaultExchange;
026 import org.apache.camel.util.ExchangeHelper;
027 import org.apache.commons.logging.Log;
028 import org.apache.commons.logging.LogFactory;
029
030 /**
031 * Creates a Pipeline pattern where the output of the previous step is sent as
032 * input to the next step, reusing the same message exchanges
033 *
034 * @version $Revision: 836213 $
035 */
036 public class Pipeline extends MulticastProcessor implements Processor, Traceable {
037 private static final transient Log LOG = LogFactory.getLog(Pipeline.class);
038
039 public Pipeline(Collection<Processor> processors) {
040 super(processors);
041 }
042
043 public static Processor newInstance(List<Processor> processors) {
044 if (processors.isEmpty()) {
045 return null;
046 } else if (processors.size() == 1) {
047 return processors.get(0);
048 }
049 return new Pipeline(processors);
050 }
051
052 public void process(Exchange exchange) throws Exception {
053 Iterator<Processor> processors = getProcessors().iterator();
054 Exchange nextExchange = exchange;
055 boolean first = true;
056
057 while (continueRouting(processors, nextExchange)) {
058 if (first) {
059 first = false;
060 } else {
061 // prepare for next run
062 nextExchange = createNextExchange(nextExchange);
063 }
064
065 // get the next processor
066 Processor processor = processors.next();
067
068 // process the next exchange
069 try {
070 if (LOG.isTraceEnabled()) {
071 // this does the actual processing so log at trace level
072 LOG.trace("Processing exchangeId: " + nextExchange.getExchangeId() + " >>> " + nextExchange);
073 }
074 processor.process(nextExchange);
075 } catch (Exception e) {
076 nextExchange.setException(e);
077 }
078
079 // check for error if so we should break out
080 boolean exceptionHandled = hasExceptionBeenHandledByErrorHandler(nextExchange);
081 if (nextExchange.isFailed() || nextExchange.isRollbackOnly() || exceptionHandled) {
082 // The Exchange.ERRORHANDLED_HANDLED property is only set if satisfactory handling was done
083 // by the error handler. It's still an exception, the exchange still failed.
084 if (LOG.isDebugEnabled()) {
085 StringBuilder sb = new StringBuilder();
086 sb.append("Message exchange has failed so breaking out of pipeline: ").append(nextExchange);
087 if (nextExchange.isRollbackOnly()) {
088 sb.append(" Marked as rollback only.");
089 }
090 if (nextExchange.getException() != null) {
091 sb.append(" Exception: ").append(nextExchange.getException());
092 }
093 if (nextExchange.hasOut() && nextExchange.getOut().isFault()) {
094 sb.append(" Fault: ").append(nextExchange.getOut());
095 }
096 if (exceptionHandled) {
097 sb.append(" Handled by the error handler.");
098 }
099 LOG.debug(sb.toString());
100 }
101 break;
102 }
103 }
104
105 if (LOG.isTraceEnabled()) {
106 // logging nextExchange as it contains the exchange that might have altered the payload and since
107 // we are logging the completion if will be confusing if we log the original instead
108 // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
109 LOG.trace("Processing complete for exchangeId: " + exchange.getExchangeId() + " >>> " + nextExchange);
110 }
111
112 // copy results back to the original exchange
113 ExchangeHelper.copyResults(exchange, nextExchange);
114 }
115
116 private static boolean hasExceptionBeenHandledByErrorHandler(Exchange nextExchange) {
117 return Boolean.TRUE.equals(nextExchange.getProperty(Exchange.ERRORHANDLER_HANDLED));
118 }
119
120 /**
121 * Strategy method to create the next exchange from the previous exchange.
122 * <p/>
123 * Remember to copy the original exchange id otherwise correlation of ids in the log is a problem
124 *
125 * @param previousExchange the previous exchange
126 * @return a new exchange
127 */
128 protected Exchange createNextExchange(Exchange previousExchange) {
129 Exchange answer = new DefaultExchange(previousExchange);
130 // we must use the same id as this is a snapshot strategy where Camel copies a snapshot
131 // before processing the next step in the pipeline, so we have a snapshot of the exchange
132 // just before. This snapshot is used if Camel should do redeliveries (re try) using
133 // DeadLetterChannel. That is why it's important the id is the same, as it is the *same*
134 // exchange being routed.
135 answer.setExchangeId(previousExchange.getExchangeId());
136
137 answer.getProperties().putAll(previousExchange.getProperties());
138
139 // now lets set the input of the next exchange to the output of the
140 // previous message if it is not null
141 answer.setIn(previousExchange.hasOut()
142 ? previousExchange.getOut().copy() : previousExchange.getIn().copy());
143 return answer;
144 }
145
146 protected boolean continueRouting(Iterator<Processor> it, Exchange exchange) {
147 Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
148 if (stop != null) {
149 boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
150 if (doStop) {
151 if (LOG.isDebugEnabled()) {
152 LOG.debug("Exchange is marked to stop routing: " + exchange);
153 }
154 return false;
155 }
156 }
157
158 // continue if there are more processors to route
159 return it.hasNext();
160 }
161
162 @Override
163 public String toString() {
164 return "Pipeline" + getProcessors();
165 }
166
167 @Override
168 public String getTraceLabel() {
169 return "pipeline";
170 }
171 }