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: 56608 $ 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 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 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_PROPERTY)); 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 }