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.AsyncCallback;
024 import org.apache.camel.AsyncProcessor;
025 import org.apache.camel.Exchange;
026 import org.apache.camel.Message;
027 import org.apache.camel.Processor;
028 import org.apache.camel.impl.converter.AsyncProcessorTypeConverter;
029 import org.apache.camel.util.AsyncProcessorHelper;
030 import org.apache.camel.util.ExchangeHelper;
031 import org.apache.commons.logging.Log;
032 import org.apache.commons.logging.LogFactory;
033
034 /**
035 * Creates a Pipeline pattern where the output of the previous step is sent as
036 * input to the next step, reusing the same message exchanges
037 *
038 * @version $Revision: 746709 $
039 */
040 public class Pipeline extends MulticastProcessor implements AsyncProcessor {
041 private static final transient Log LOG = LogFactory.getLog(Pipeline.class);
042
043 public Pipeline(Collection<Processor> processors) {
044 super(processors);
045 }
046
047 public static Processor newInstance(List<Processor> processors) {
048 if (processors.isEmpty()) {
049 return null;
050 } else if (processors.size() == 1) {
051 return processors.get(0);
052 }
053 return new Pipeline(processors);
054 }
055
056 public void process(Exchange exchange) throws Exception {
057 AsyncProcessorHelper.process(this, exchange);
058 }
059
060 public boolean process(Exchange original, AsyncCallback callback) {
061 Iterator<Processor> processors = getProcessors().iterator();
062 Exchange nextExchange = original;
063 boolean first = true;
064 while (true) {
065 boolean exceptionHandled = hasExceptionBeenHandled(nextExchange);
066 if (nextExchange.isFailed() || exceptionHandled) {
067 // The Exchange.EXCEPTION_HANDLED property is only set if satisfactory handling was done
068 // by the error handler. It's still an exception, the exchange still failed.
069 if (LOG.isDebugEnabled()) {
070 LOG.debug("Message exchange has failed so breaking out of pipeline: " + nextExchange
071 + " exception: " + nextExchange.getException() + " fault: "
072 + nextExchange.getFault(false)
073 + (exceptionHandled ? " handled by the error handler" : ""));
074 }
075 break;
076 }
077 if (!processors.hasNext()) {
078 break;
079 }
080
081 AsyncProcessor processor = AsyncProcessorTypeConverter.convert(processors.next());
082
083 if (first) {
084 first = false;
085 } else {
086 nextExchange = createNextExchange(processor, nextExchange);
087 }
088
089 boolean sync = process(original, nextExchange, callback, processors, processor);
090 // Continue processing the pipeline synchronously ...
091 if (!sync) {
092 // The pipeline will be completed async...
093 return false;
094 }
095 }
096
097 // If we get here then the pipeline was processed entirely
098 // synchronously.
099 if (LOG.isTraceEnabled()) {
100 // logging nextExchange as it contains the exchange that might have altered the payload and since
101 // we are logging the completion if will be confusing if we log the original instead
102 // we could also consider logging the original and the nextExchange then we have *before* and *after* snapshots
103 LOG.trace("Processing compelete for exchangeId: " + original.getExchangeId() + " >>> " + nextExchange);
104 }
105 ExchangeHelper.copyResults(original, nextExchange);
106 callback.done(true);
107 return true;
108 }
109
110 private boolean process(final Exchange original, final Exchange exchange, final AsyncCallback callback, final Iterator<Processor> processors, AsyncProcessor processor) {
111 if (LOG.isTraceEnabled()) {
112 // this does the actual processing so log at trace level
113 LOG.trace("Processing exchangeId: " + exchange.getExchangeId() + " >>> " + exchange);
114 }
115 return processor.process(exchange, new AsyncCallback() {
116 public void done(boolean sync) {
117 // We only have to handle async completion of the pipeline..
118 if (sync) {
119 return;
120 }
121
122 // Continue processing the pipeline...
123 Exchange nextExchange = exchange;
124 while (processors.hasNext()) {
125 AsyncProcessor processor = AsyncProcessorTypeConverter.convert(processors.next());
126
127 boolean exceptionHandled = hasExceptionBeenHandled(nextExchange);
128 if (nextExchange.isFailed() || exceptionHandled) {
129 // The Exchange.EXCEPTION_HANDLED property is only set if satisfactory handling was done
130 // by the error handler. It's still an exception, the exchange still failed.
131 if (LOG.isDebugEnabled()) {
132 LOG.debug("Message exchange has failed so breaking out of pipeline: " + nextExchange
133 + " exception: " + nextExchange.getException() + " fault: "
134 + nextExchange.getFault(false)
135 + (exceptionHandled ? " handled by the error handler" : ""));
136 }
137 break;
138 }
139
140 nextExchange = createNextExchange(processor, nextExchange);
141 sync = process(original, nextExchange, callback, processors, processor);
142 if (!sync) {
143 return;
144 }
145 }
146
147 ExchangeHelper.copyResults(original, nextExchange);
148 callback.done(false);
149 }
150 });
151 }
152
153
154 private static boolean hasExceptionBeenHandled(Exchange nextExchange) {
155 return Boolean.TRUE.equals(nextExchange.getProperty(Exchange.EXCEPTION_HANDLED));
156 }
157
158 /**
159 * Strategy method to create the next exchange from the previous exchange.
160 * <p/>
161 * Remember to copy the original exchange id otherwise correlation of ids in the log is a problem
162 *
163 * @param producer the producer used to send to the endpoint
164 * @param previousExchange the previous exchange
165 * @return a new exchange
166 */
167 protected Exchange createNextExchange(Processor producer, Exchange previousExchange) {
168 Exchange answer = previousExchange.newInstance();
169 // we must use the same id as this is a snapshot strategy where Camel copies a snapshot
170 // before processing the next step in the pipeline, so we have a snapshot of the exchange
171 // just before. This snapshot is used if Camel should do redeliveries (re try) using
172 // DeadLetterChannel. That is why it's important the id is the same, as it is the *same*
173 // exchange being routed.
174 answer.setExchangeId(previousExchange.getExchangeId());
175
176 answer.getProperties().putAll(previousExchange.getProperties());
177
178 // now lets set the input of the next exchange to the output of the
179 // previous message if it is not null
180 Message previousOut = previousExchange.getOut(false);
181 Message in = answer.getIn();
182 if (previousOut != null) {
183 in.copyFrom(previousOut);
184 } else {
185 in.copyFrom(previousExchange.getIn());
186 }
187 return answer;
188 }
189
190 @Override
191 public String toString() {
192 return "Pipeline" + getProcessors();
193 }
194 }