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.interceptor;
018
019 import java.util.ArrayList;
020 import java.util.Collections;
021 import java.util.List;
022 import java.util.Map;
023 import java.util.concurrent.RejectedExecutionException;
024
025 import org.apache.camel.AsyncCallback;
026 import org.apache.camel.AsyncProcessor;
027 import org.apache.camel.CamelContext;
028 import org.apache.camel.CamelContextAware;
029 import org.apache.camel.Channel;
030 import org.apache.camel.Exchange;
031 import org.apache.camel.Processor;
032 import org.apache.camel.Service;
033 import org.apache.camel.model.ModelChannel;
034 import org.apache.camel.model.ProcessorDefinition;
035 import org.apache.camel.processor.InterceptorToAsyncProcessorBridge;
036 import org.apache.camel.processor.RouteContextProcessor;
037 import org.apache.camel.spi.InterceptStrategy;
038 import org.apache.camel.spi.LifecycleStrategy;
039 import org.apache.camel.spi.RouteContext;
040 import org.apache.camel.support.ServiceSupport;
041 import org.apache.camel.util.AsyncProcessorHelper;
042 import org.apache.camel.util.ObjectHelper;
043 import org.apache.camel.util.OrderedComparator;
044 import org.apache.camel.util.ServiceHelper;
045 import org.slf4j.Logger;
046 import org.slf4j.LoggerFactory;
047
048 /**
049 * DefaultChannel is the default {@link Channel}.
050 * <p/>
051 * The current implementation is just a composite containing the interceptors and error handler
052 * that beforehand was added to the route graph directly.
053 * <br/>
054 * With this {@link Channel} we can in the future implement better strategies for routing the
055 * {@link Exchange} in the route graph, as we have a {@link Channel} between each and every node
056 * in the graph.
057 *
058 * @version
059 */
060 public class DefaultChannel extends ServiceSupport implements ModelChannel {
061
062 private static final transient Logger LOG = LoggerFactory.getLogger(DefaultChannel.class);
063
064 private final List<InterceptStrategy> interceptors = new ArrayList<InterceptStrategy>();
065 private Processor errorHandler;
066 // the next processor (non wrapped)
067 private Processor nextProcessor;
068 // the real output to invoke that has been wrapped
069 private Processor output;
070 private ProcessorDefinition<?> definition;
071 private ProcessorDefinition<?> childDefinition;
072 private CamelContext camelContext;
073 private RouteContext routeContext;
074 private RouteContextProcessor routeContextProcessor;
075
076 public List<Processor> next() {
077 List<Processor> answer = new ArrayList<Processor>(1);
078 answer.add(nextProcessor);
079 return answer;
080 }
081
082 public boolean hasNext() {
083 return nextProcessor != null;
084 }
085
086 public void setNextProcessor(Processor next) {
087 this.nextProcessor = next;
088 }
089
090 public Processor getOutput() {
091 // the errorHandler is already decorated with interceptors
092 // so it contain the entire chain of processors, so we can safely use it directly as output
093 // if no error handler provided we use the output
094 // TODO: Camel 3.0 we should determine the output dynamically at runtime instead of having the
095 // the error handlers, interceptors, etc. woven in at design time
096 return errorHandler != null ? errorHandler : output;
097 }
098
099 public void setOutput(Processor output) {
100 this.output = output;
101 }
102
103 public Processor getNextProcessor() {
104 return nextProcessor;
105 }
106
107 public boolean hasInterceptorStrategy(Class<?> type) {
108 for (InterceptStrategy strategy : interceptors) {
109 if (type.isInstance(strategy)) {
110 return true;
111 }
112 }
113 return false;
114 }
115
116 public void setErrorHandler(Processor errorHandler) {
117 this.errorHandler = errorHandler;
118 }
119
120 public Processor getErrorHandler() {
121 return errorHandler;
122 }
123
124 public void addInterceptStrategy(InterceptStrategy strategy) {
125 interceptors.add(strategy);
126 }
127
128 public void addInterceptStrategies(List<InterceptStrategy> strategies) {
129 interceptors.addAll(strategies);
130 }
131
132 public List<InterceptStrategy> getInterceptStrategies() {
133 return interceptors;
134 }
135
136 public ProcessorDefinition<?> getProcessorDefinition() {
137 return definition;
138 }
139
140 public void setChildDefinition(ProcessorDefinition<?> childDefinition) {
141 this.childDefinition = childDefinition;
142 }
143
144 public RouteContext getRouteContext() {
145 return routeContext;
146 }
147
148 @Override
149 protected void doStart() throws Exception {
150 // create route context processor to wrap output
151 routeContextProcessor = new RouteContextProcessor(routeContext, getOutput());
152 ServiceHelper.startServices(errorHandler, output, routeContextProcessor);
153 }
154
155 @Override
156 protected void doStop() throws Exception {
157 ServiceHelper.stopServices(output, errorHandler, routeContextProcessor);
158 }
159
160 public void initChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception {
161 this.routeContext = routeContext;
162 this.definition = outputDefinition;
163 this.camelContext = routeContext.getCamelContext();
164
165 Processor target = nextProcessor;
166 Processor next;
167
168 // init CamelContextAware as early as possible on target
169 if (target instanceof CamelContextAware) {
170 ((CamelContextAware) target).setCamelContext(camelContext);
171 }
172
173 // the definition to wrap should be the fine grained,
174 // so if a child is set then use it, if not then its the original output used
175 ProcessorDefinition<?> targetOutputDef = childDefinition != null ? childDefinition : outputDefinition;
176 LOG.debug("Initialize channel for target: '{}'", targetOutputDef);
177
178 // fix parent/child relationship. This will be the case of the routes has been
179 // defined using XML DSL or end user may have manually assembled a route from the model.
180 // Background note: parent/child relationship is assembled on-the-fly when using Java DSL (fluent builders)
181 // where as when using XML DSL (JAXB) then it fixed after, but if people are using custom interceptors
182 // then we need to fix the parent/child relationship beforehand, and thus we can do it here
183 // ideally we need the design time route -> runtime route to be a 2-phase pass (scheduled work for Camel 3.0)
184 if (childDefinition != null && outputDefinition != childDefinition) {
185 childDefinition.setParent(outputDefinition);
186 }
187
188 // first wrap the output with the managed strategy if any
189 InterceptStrategy managed = routeContext.getManagedInterceptStrategy();
190 if (managed != null) {
191 next = target == nextProcessor ? null : nextProcessor;
192 target = managed.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next);
193 }
194
195 // then wrap the output with the tracer
196 TraceInterceptor trace = (TraceInterceptor) getOrCreateTracer().wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, null);
197 // trace interceptor need to have a reference to route context so we at runtime can enable/disable tracing on-the-fly
198 trace.setRouteContext(routeContext);
199 target = trace;
200
201 // sort interceptors according to ordered
202 Collections.sort(interceptors, new OrderedComparator());
203 // then reverse list so the first will be wrapped last, as it would then be first being invoked
204 Collections.reverse(interceptors);
205 // wrap the output with the configured interceptors
206 for (InterceptStrategy strategy : interceptors) {
207 next = target == nextProcessor ? null : nextProcessor;
208 // skip tracer as we did the specially beforehand and it could potentially be added as an interceptor strategy
209 if (strategy instanceof Tracer) {
210 continue;
211 }
212 // skip stream caching as it must be wrapped as outer most, which we do later
213 if (strategy instanceof StreamCaching) {
214 continue;
215 }
216 // use the fine grained definition (eg the child if available). Its always possible to get back to the parent
217 Processor wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next);
218 if (!(wrapped instanceof AsyncProcessor)) {
219 LOG.warn("Interceptor: " + strategy + " at: " + outputDefinition + " does not return an AsyncProcessor instance."
220 + " This causes the asynchronous routing engine to not work as optimal as possible."
221 + " See more details at the InterceptStrategy javadoc."
222 + " Camel will use a bridge to adapt the interceptor to the asynchronous routing engine,"
223 + " but its not the most optimal solution. Please consider changing your interceptor to comply.");
224
225 // use a bridge and wrap again which allows us to adapt and leverage the asynchronous routing engine anyway
226 // however its not the most optimal solution, but we can still run.
227 InterceptorToAsyncProcessorBridge bridge = new InterceptorToAsyncProcessorBridge(target);
228 wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, bridge, next);
229 bridge.setTarget(wrapped);
230 wrapped = bridge;
231 }
232 target = wrapped;
233 }
234
235 // sets the delegate to our wrapped output
236 output = target;
237 }
238
239 @Override
240 public void postInitChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception {
241 for (InterceptStrategy strategy : interceptors) {
242 // apply stream caching at the end as it should be outer most
243 if (strategy instanceof StreamCaching) {
244 if (errorHandler != null) {
245 errorHandler = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, errorHandler, null);
246 } else {
247 output = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), outputDefinition, output, null);
248 }
249 break;
250 }
251 }
252 }
253
254 private InterceptStrategy getOrCreateTracer() {
255 InterceptStrategy tracer = Tracer.getTracer(camelContext);
256 if (tracer == null) {
257 if (camelContext.getRegistry() != null) {
258 // lookup in registry
259 Map<String, Tracer> map = camelContext.getRegistry().lookupByType(Tracer.class);
260 if (map.size() == 1) {
261 tracer = map.values().iterator().next();
262 }
263 }
264 if (tracer == null) {
265 // fallback to use the default tracer
266 tracer = camelContext.getDefaultTracer();
267
268 // configure and use any trace formatter if any exists
269 Map<String, TraceFormatter> formatters = camelContext.getRegistry().lookupByType(TraceFormatter.class);
270 if (formatters.size() == 1) {
271 TraceFormatter formatter = formatters.values().iterator().next();
272 if (tracer instanceof Tracer) {
273 ((Tracer) tracer).setFormatter(formatter);
274 }
275 }
276 }
277 }
278
279 // which we must manage as well
280 for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
281 if (tracer instanceof Service) {
282 strategy.onServiceAdd(camelContext, (Service) tracer, null);
283 }
284 }
285
286 return tracer;
287 }
288
289 public void process(Exchange exchange) throws Exception {
290 AsyncProcessorHelper.process(this, exchange);
291 }
292
293 public boolean process(final Exchange exchange, final AsyncCallback callback) {
294 Processor processor = getOutput();
295 if (processor == null || !continueProcessing(exchange)) {
296 // we should not continue routing so we are done
297 callback.done(true);
298 return true;
299 }
300
301 // process the exchange using the route context processor
302 ObjectHelper.notNull(routeContextProcessor, "RouteContextProcessor", this);
303 return routeContextProcessor.process(exchange, callback);
304 }
305
306 /**
307 * Strategy to determine if we should continue processing the {@link Exchange}.
308 */
309 protected boolean continueProcessing(Exchange exchange) {
310 Object stop = exchange.getProperty(Exchange.ROUTE_STOP);
311 if (stop != null) {
312 boolean doStop = exchange.getContext().getTypeConverter().convertTo(Boolean.class, stop);
313 if (doStop) {
314 LOG.debug("Exchange is marked to stop routing: {}", exchange);
315 return false;
316 }
317 }
318
319 // determine if we can still run, or the camel context is forcing a shutdown
320 boolean forceShutdown = camelContext.getShutdownStrategy().forceShutdown(this);
321 if (forceShutdown) {
322 LOG.debug("Run not allowed as ShutdownStrategy is forcing shutting down, will reject executing exchange: {}", exchange);
323 if (exchange.getException() == null) {
324 exchange.setException(new RejectedExecutionException());
325 }
326 return false;
327 }
328
329 // yes we can continue
330 return true;
331 }
332
333 @Override
334 public String toString() {
335 // just output the next processor as all the interceptors and error handler is just too verbose
336 return "Channel[" + nextProcessor + "]";
337 }
338
339 }