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