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 */ 017package org.apache.camel.processor.interceptor; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.camel.AsyncProcessor; 025import org.apache.camel.CamelContext; 026import org.apache.camel.CamelContextAware; 027import org.apache.camel.Channel; 028import org.apache.camel.Exchange; 029import org.apache.camel.Processor; 030import org.apache.camel.model.ModelChannel; 031import org.apache.camel.model.OnCompletionDefinition; 032import org.apache.camel.model.OnExceptionDefinition; 033import org.apache.camel.model.ProcessorDefinition; 034import org.apache.camel.model.ProcessorDefinitionHelper; 035import org.apache.camel.model.RouteDefinition; 036import org.apache.camel.model.RouteDefinitionHelper; 037import org.apache.camel.processor.CamelInternalProcessor; 038import org.apache.camel.processor.InterceptorToAsyncProcessorBridge; 039import org.apache.camel.processor.WrapProcessor; 040import org.apache.camel.spi.InterceptStrategy; 041import org.apache.camel.spi.RouteContext; 042import org.apache.camel.util.OrderedComparator; 043import org.apache.camel.util.ServiceHelper; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * DefaultChannel is the default {@link Channel}. 049 * <p/> 050 * The current implementation is just a composite containing the interceptors and error handler 051 * that beforehand was added to the route graph directly. 052 * <br/> 053 * With this {@link Channel} we can in the future implement better strategies for routing the 054 * {@link Exchange} in the route graph, as we have a {@link Channel} between each and every node 055 * in the graph. 056 * 057 * @version 058 */ 059public class DefaultChannel extends CamelInternalProcessor implements ModelChannel { 060 061 private static final Logger LOG = LoggerFactory.getLogger(DefaultChannel.class); 062 063 private final List<InterceptStrategy> interceptors = new ArrayList<InterceptStrategy>(); 064 private Processor errorHandler; 065 // the next processor (non wrapped) 066 private Processor nextProcessor; 067 // the real output to invoke that has been wrapped 068 private Processor output; 069 private ProcessorDefinition<?> definition; 070 private ProcessorDefinition<?> childDefinition; 071 private CamelContext camelContext; 072 private RouteContext routeContext; 073 074 public void setNextProcessor(Processor next) { 075 this.nextProcessor = next; 076 } 077 078 public Processor getOutput() { 079 // the errorHandler is already decorated with interceptors 080 // so it contain the entire chain of processors, so we can safely use it directly as output 081 // if no error handler provided we use the output 082 // TODO: Camel 3.0 we should determine the output dynamically at runtime instead of having the 083 // the error handlers, interceptors, etc. woven in at design time 084 return errorHandler != null ? errorHandler : output; 085 } 086 087 @Override 088 public boolean hasNext() { 089 return nextProcessor != null; 090 } 091 092 @Override 093 public List<Processor> next() { 094 if (!hasNext()) { 095 return null; 096 } 097 List<Processor> answer = new ArrayList<Processor>(1); 098 answer.add(nextProcessor); 099 return answer; 100 } 101 102 public void setOutput(Processor output) { 103 this.output = output; 104 } 105 106 public Processor getNextProcessor() { 107 return nextProcessor; 108 } 109 110 public boolean hasInterceptorStrategy(Class<?> type) { 111 for (InterceptStrategy strategy : interceptors) { 112 if (type.isInstance(strategy)) { 113 return true; 114 } 115 } 116 return false; 117 } 118 119 public void setErrorHandler(Processor errorHandler) { 120 this.errorHandler = errorHandler; 121 } 122 123 public Processor getErrorHandler() { 124 return errorHandler; 125 } 126 127 public void addInterceptStrategy(InterceptStrategy strategy) { 128 interceptors.add(strategy); 129 } 130 131 public void addInterceptStrategies(List<InterceptStrategy> strategies) { 132 interceptors.addAll(strategies); 133 } 134 135 public List<InterceptStrategy> getInterceptStrategies() { 136 return interceptors; 137 } 138 139 public ProcessorDefinition<?> getProcessorDefinition() { 140 return definition; 141 } 142 143 public void setChildDefinition(ProcessorDefinition<?> childDefinition) { 144 this.childDefinition = childDefinition; 145 } 146 147 public RouteContext getRouteContext() { 148 return routeContext; 149 } 150 151 @Override 152 protected void doStart() throws Exception { 153 // the output has now been created, so assign the output as the processor 154 setProcessor(getOutput()); 155 ServiceHelper.startServices(errorHandler, output); 156 } 157 158 @Override 159 protected void doStop() throws Exception { 160 if (!isContextScoped()) { 161 // only stop services if not context scoped (as context scoped is reused by others) 162 ServiceHelper.stopServices(output, errorHandler); 163 } 164 } 165 166 @Override 167 protected void doShutdown() throws Exception { 168 ServiceHelper.stopAndShutdownServices(output, errorHandler); 169 } 170 171 private boolean isContextScoped() { 172 if (definition instanceof OnExceptionDefinition) { 173 return !((OnExceptionDefinition) definition).isRouteScoped(); 174 } else if (definition instanceof OnCompletionDefinition) { 175 return !((OnCompletionDefinition) definition).isRouteScoped(); 176 } 177 178 return false; 179 } 180 181 @SuppressWarnings("deprecation") 182 public void initChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception { 183 this.routeContext = routeContext; 184 this.definition = outputDefinition; 185 this.camelContext = routeContext.getCamelContext(); 186 187 Processor target = nextProcessor; 188 Processor next; 189 190 // init CamelContextAware as early as possible on target 191 if (target instanceof CamelContextAware) { 192 ((CamelContextAware) target).setCamelContext(camelContext); 193 } 194 195 // the definition to wrap should be the fine grained, 196 // so if a child is set then use it, if not then its the original output used 197 ProcessorDefinition<?> targetOutputDef = childDefinition != null ? childDefinition : outputDefinition; 198 LOG.debug("Initialize channel for target: '{}'", targetOutputDef); 199 200 // fix parent/child relationship. This will be the case of the routes has been 201 // defined using XML DSL or end user may have manually assembled a route from the model. 202 // Background note: parent/child relationship is assembled on-the-fly when using Java DSL (fluent builders) 203 // where as when using XML DSL (JAXB) then it fixed after, but if people are using custom interceptors 204 // then we need to fix the parent/child relationship beforehand, and thus we can do it here 205 // ideally we need the design time route -> runtime route to be a 2-phase pass (scheduled work for Camel 3.0) 206 if (childDefinition != null && outputDefinition != childDefinition) { 207 childDefinition.setParent(outputDefinition); 208 } 209 210 // force the creation of an id 211 RouteDefinitionHelper.forceAssignIds(routeContext.getCamelContext(), definition); 212 213 // first wrap the output with the managed strategy if any 214 InterceptStrategy managed = routeContext.getManagedInterceptStrategy(); 215 if (managed != null) { 216 next = target == nextProcessor ? null : nextProcessor; 217 target = managed.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next); 218 } 219 220 // then wrap the output with the backlog and tracer (backlog first, as we do not want regular tracer to tracer the backlog) 221 InterceptStrategy tracer = getOrCreateBacklogTracer(); 222 camelContext.addService(tracer); 223 if (tracer instanceof BacklogTracer) { 224 BacklogTracer backlogTracer = (BacklogTracer) tracer; 225 226 RouteDefinition route = ProcessorDefinitionHelper.getRoute(definition); 227 boolean first = false; 228 if (route != null && !route.getOutputs().isEmpty()) { 229 first = route.getOutputs().get(0) == definition; 230 } 231 232 addAdvice(new BacklogTracerAdvice(backlogTracer.getQueue(), backlogTracer, targetOutputDef, route, first)); 233 234 // add debugger as well so we have both tracing and debugging out of the box 235 InterceptStrategy debugger = getOrCreateBacklogDebugger(); 236 camelContext.addService(debugger); 237 if (debugger instanceof BacklogDebugger) { 238 BacklogDebugger backlogDebugger = (BacklogDebugger) debugger; 239 addAdvice(new BacklogDebuggerAdvice(backlogDebugger, target, targetOutputDef)); 240 } 241 } 242 243 if (routeContext.isMessageHistory()) { 244 // add message history advice 245 addAdvice(new MessageHistoryAdvice(targetOutputDef)); 246 } 247 248 // the regular tracer is not a task on internalProcessor as this is not really needed 249 // end users have to explicit enable the tracer to use it, and then its okay if we wrap 250 // the processors (but by default tracer is disabled, and therefore we do not wrap processors) 251 tracer = getOrCreateTracer(); 252 camelContext.addService(tracer); 253 if (tracer != null) { 254 TraceInterceptor trace = (TraceInterceptor) tracer.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, null); 255 // trace interceptor need to have a reference to route context so we at runtime can enable/disable tracing on-the-fly 256 trace.setRouteContext(routeContext); 257 target = trace; 258 } 259 260 // sort interceptors according to ordered 261 Collections.sort(interceptors, new OrderedComparator()); 262 // then reverse list so the first will be wrapped last, as it would then be first being invoked 263 Collections.reverse(interceptors); 264 // wrap the output with the configured interceptors 265 for (InterceptStrategy strategy : interceptors) { 266 next = target == nextProcessor ? null : nextProcessor; 267 // skip tracer as we did the specially beforehand and it could potentially be added as an interceptor strategy 268 if (strategy instanceof Tracer) { 269 continue; 270 } 271 // skip stream caching as it must be wrapped as outer most, which we do later 272 if (strategy instanceof StreamCaching) { 273 continue; 274 } 275 // use the fine grained definition (eg the child if available). Its always possible to get back to the parent 276 Processor wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, target, next); 277 if (!(wrapped instanceof AsyncProcessor)) { 278 LOG.warn("Interceptor: " + strategy + " at: " + outputDefinition + " does not return an AsyncProcessor instance." 279 + " This causes the asynchronous routing engine to not work as optimal as possible." 280 + " See more details at the InterceptStrategy javadoc." 281 + " Camel will use a bridge to adapt the interceptor to the asynchronous routing engine," 282 + " but its not the most optimal solution. Please consider changing your interceptor to comply."); 283 284 // use a bridge and wrap again which allows us to adapt and leverage the asynchronous routing engine anyway 285 // however its not the most optimal solution, but we can still run. 286 InterceptorToAsyncProcessorBridge bridge = new InterceptorToAsyncProcessorBridge(target); 287 wrapped = strategy.wrapProcessorInInterceptors(routeContext.getCamelContext(), targetOutputDef, bridge, next); 288 // Avoid the stack overflow 289 if (!wrapped.equals(bridge)) { 290 bridge.setTarget(wrapped); 291 } else { 292 // Just skip the wrapped processor 293 bridge.setTarget(null); 294 } 295 wrapped = bridge; 296 } 297 if (!(wrapped instanceof WrapProcessor)) { 298 // wrap the target so it becomes a service and we can manage its lifecycle 299 wrapped = new WrapProcessor(wrapped, target); 300 } 301 target = wrapped; 302 } 303 304 if (routeContext.isStreamCaching()) { 305 addAdvice(new StreamCachingAdvice(camelContext.getStreamCachingStrategy())); 306 } 307 308 if (routeContext.getDelayer() != null && routeContext.getDelayer() > 0) { 309 addAdvice(new DelayerAdvice(routeContext.getDelayer())); 310 } 311 312 // sets the delegate to our wrapped output 313 output = target; 314 } 315 316 @Override 317 public void postInitChannel(ProcessorDefinition<?> outputDefinition, RouteContext routeContext) throws Exception { 318 // noop 319 } 320 321 private InterceptStrategy getOrCreateTracer() { 322 // only use tracer if explicit enabled 323 if (camelContext.isTracing() != null && !camelContext.isTracing()) { 324 return null; 325 } 326 327 InterceptStrategy tracer = Tracer.getTracer(camelContext); 328 if (tracer == null) { 329 if (camelContext.getRegistry() != null) { 330 // lookup in registry 331 Map<String, Tracer> map = camelContext.getRegistry().findByTypeWithName(Tracer.class); 332 if (map.size() == 1) { 333 tracer = map.values().iterator().next(); 334 } 335 } 336 if (tracer == null) { 337 // fallback to use the default tracer 338 tracer = camelContext.getDefaultTracer(); 339 340 // configure and use any trace formatter if any exists 341 Map<String, TraceFormatter> formatters = camelContext.getRegistry().findByTypeWithName(TraceFormatter.class); 342 if (formatters.size() == 1) { 343 TraceFormatter formatter = formatters.values().iterator().next(); 344 if (tracer instanceof Tracer) { 345 ((Tracer) tracer).setFormatter(formatter); 346 } 347 } 348 } 349 } 350 351 return tracer; 352 } 353 354 private InterceptStrategy getOrCreateBacklogTracer() { 355 InterceptStrategy tracer = BacklogTracer.getBacklogTracer(camelContext); 356 if (tracer == null) { 357 if (camelContext.getRegistry() != null) { 358 // lookup in registry 359 Map<String, BacklogTracer> map = camelContext.getRegistry().findByTypeWithName(BacklogTracer.class); 360 if (map.size() == 1) { 361 tracer = map.values().iterator().next(); 362 } 363 } 364 if (tracer == null) { 365 // fallback to use the default tracer 366 tracer = camelContext.getDefaultBacklogTracer(); 367 } 368 } 369 370 return tracer; 371 } 372 373 private InterceptStrategy getOrCreateBacklogDebugger() { 374 InterceptStrategy debugger = BacklogDebugger.getBacklogDebugger(camelContext); 375 if (debugger == null) { 376 if (camelContext.getRegistry() != null) { 377 // lookup in registry 378 Map<String, BacklogDebugger> map = camelContext.getRegistry().findByTypeWithName(BacklogDebugger.class); 379 if (map.size() == 1) { 380 debugger = map.values().iterator().next(); 381 } 382 } 383 if (debugger == null) { 384 // fallback to use the default debugger 385 debugger = camelContext.getDefaultBacklogDebugger(); 386 } 387 } 388 389 return debugger; 390 } 391 392 @Override 393 public String toString() { 394 // just output the next processor as all the interceptors and error handler is just too verbose 395 return "Channel[" + nextProcessor + "]"; 396 } 397 398}