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.reifier; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.StringTokenizer; 024 025import org.apache.camel.CamelContext; 026import org.apache.camel.Endpoint; 027import org.apache.camel.ExtendedCamelContext; 028import org.apache.camel.FailedToCreateRouteException; 029import org.apache.camel.Processor; 030import org.apache.camel.Route; 031import org.apache.camel.RuntimeCamelException; 032import org.apache.camel.ShutdownRoute; 033import org.apache.camel.ShutdownRunningTask; 034import org.apache.camel.builder.AdviceWithRouteBuilder; 035import org.apache.camel.builder.AdviceWithTask; 036import org.apache.camel.builder.EndpointConsumerBuilder; 037import org.apache.camel.builder.RouteBuilder; 038import org.apache.camel.impl.engine.DefaultRoute; 039import org.apache.camel.model.Model; 040import org.apache.camel.model.ProcessorDefinition; 041import org.apache.camel.model.PropertyDefinition; 042import org.apache.camel.model.RouteDefinition; 043import org.apache.camel.model.RouteDefinitionHelper; 044import org.apache.camel.model.RoutesDefinition; 045import org.apache.camel.processor.CamelInternalProcessor; 046import org.apache.camel.processor.ContractAdvice; 047import org.apache.camel.processor.Pipeline; 048import org.apache.camel.reifier.rest.RestBindingReifier; 049import org.apache.camel.spi.Contract; 050import org.apache.camel.spi.LifecycleStrategy; 051import org.apache.camel.spi.ManagementInterceptStrategy; 052import org.apache.camel.spi.RoutePolicy; 053import org.apache.camel.spi.RoutePolicyFactory; 054import org.apache.camel.util.ObjectHelper; 055 056public class RouteReifier extends ProcessorReifier<RouteDefinition> { 057 058 private static final String[] RESERVED_PROPERTIES = new String[] { 059 Route.ID_PROPERTY, Route.CUSTOM_ID_PROPERTY, Route.PARENT_PROPERTY, 060 Route.DESCRIPTION_PROPERTY, Route.GROUP_PROPERTY, 061 Route.REST_PROPERTY}; 062 063 public RouteReifier(CamelContext camelContext, ProcessorDefinition<?> definition) { 064 super(camelContext, (RouteDefinition) definition); 065 } 066 067 /** 068 * Advices this route with the route builder. 069 * <p/> 070 * <b>Important:</b> It is recommended to only advice a given route once 071 * (you can of course advice multiple routes). If you do it multiple times, 072 * then it may not work as expected, especially when any kind of error 073 * handling is involved. The Camel team plan for Camel 3.0 to support this 074 * as internal refactorings in the routing engine is needed to support this 075 * properly. 076 * <p/> 077 * You can use a regular {@link RouteBuilder} but the specialized 078 * {@link AdviceWithRouteBuilder} has additional features when using the 079 * <a href="http://camel.apache.org/advicewith.html">advice with</a> 080 * feature. We therefore suggest you to use the 081 * {@link AdviceWithRouteBuilder}. 082 * <p/> 083 * The advice process will add the interceptors, on exceptions, on 084 * completions etc. configured from the route builder to this route. 085 * <p/> 086 * This is mostly used for testing purpose to add interceptors and the likes 087 * to an existing route. 088 * <p/> 089 * Will stop and remove the old route from camel context and add and start 090 * this new advised route. 091 * 092 * @param definition the model definition 093 * @param camelContext the camel context 094 * @param builder the route builder 095 * @return a new route which is this route merged with the route builder 096 * @throws Exception can be thrown from the route builder 097 * @see AdviceWithRouteBuilder 098 */ 099 public static RouteDefinition adviceWith(RouteDefinition definition, CamelContext camelContext, RouteBuilder builder) throws Exception { 100 ObjectHelper.notNull(definition, "RouteDefinition"); 101 ObjectHelper.notNull(camelContext, "CamelContext"); 102 ObjectHelper.notNull(builder, "RouteBuilder"); 103 104 if (definition.getInput() == null) { 105 throw new IllegalArgumentException("RouteDefinition has no input"); 106 } 107 return new RouteReifier(camelContext, definition).adviceWith(builder); 108 } 109 110 @Override 111 public Processor createProcessor() throws Exception { 112 throw new UnsupportedOperationException("Not implemented for RouteDefinition"); 113 } 114 115 public Route createRoute() { 116 try { 117 return doCreateRoute(); 118 } catch (FailedToCreateRouteException e) { 119 throw e; 120 } catch (Exception e) { 121 // wrap in exception which provide more details about which route 122 // was failing 123 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), e); 124 } 125 } 126 127 /** 128 * Advices this route with the route builder. 129 * <p/> 130 * <b>Important:</b> It is recommended to only advice a given route once 131 * (you can of course advice multiple routes). If you do it multiple times, 132 * then it may not work as expected, especially when any kind of error 133 * handling is involved. The Camel team plan for Camel 3.0 to support this 134 * as internal refactorings in the routing engine is needed to support this 135 * properly. 136 * <p/> 137 * You can use a regular {@link RouteBuilder} but the specialized 138 * {@link org.apache.camel.builder.AdviceWithRouteBuilder} has additional 139 * features when using the 140 * <a href="http://camel.apache.org/advicewith.html">advice with</a> 141 * feature. We therefore suggest you to use the 142 * {@link org.apache.camel.builder.AdviceWithRouteBuilder}. 143 * <p/> 144 * The advice process will add the interceptors, on exceptions, on 145 * completions etc. configured from the route builder to this route. 146 * <p/> 147 * This is mostly used for testing purpose to add interceptors and the likes 148 * to an existing route. 149 * <p/> 150 * Will stop and remove the old route from camel context and add and start 151 * this new advised route. 152 * 153 * @param builder the route builder 154 * @return a new route which is this route merged with the route builder 155 * @throws Exception can be thrown from the route builder 156 * @see AdviceWithRouteBuilder 157 */ 158 @SuppressWarnings("deprecation") 159 public RouteDefinition adviceWith(RouteBuilder builder) throws Exception { 160 ObjectHelper.notNull(builder, "RouteBuilder"); 161 162 log.debug("AdviceWith route before: {}", this); 163 ExtendedCamelContext ecc = camelContext.adapt(ExtendedCamelContext.class); 164 Model model = camelContext.getExtension(Model.class); 165 166 // inject this route into the advice route builder so it can access this route 167 // and offer features to manipulate the route directly 168 if (builder instanceof AdviceWithRouteBuilder) { 169 AdviceWithRouteBuilder arb = (AdviceWithRouteBuilder)builder; 170 arb.setOriginalRoute(definition); 171 } 172 173 // configure and prepare the routes from the builder 174 RoutesDefinition routes = builder.configureRoutes(camelContext); 175 176 // was logging enabled or disabled 177 boolean logRoutesAsXml = true; 178 if (builder instanceof AdviceWithRouteBuilder) { 179 AdviceWithRouteBuilder arb = (AdviceWithRouteBuilder)builder; 180 logRoutesAsXml = arb.isLogRouteAsXml(); 181 } 182 183 log.debug("AdviceWith routes: {}", routes); 184 185 // we can only advice with a route builder without any routes 186 if (!builder.getRouteCollection().getRoutes().isEmpty()) { 187 throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes. Remove all routes from the route builder."); 188 } 189 // we can not advice with error handlers (if you added a new error 190 // handler in the route builder) 191 // we must check the error handler on builder is not the same as on 192 // camel context, as that would be the default 193 // context scoped error handler, in case no error handlers was 194 // configured 195 if (builder.getRouteCollection().getErrorHandlerFactory() != null 196 && ecc.getErrorHandlerFactory() != builder.getRouteCollection().getErrorHandlerFactory()) { 197 throw new IllegalArgumentException("You can not advice with error handlers. Remove the error handlers from the route builder."); 198 } 199 200 String beforeAsXml = null; 201 if (logRoutesAsXml && log.isInfoEnabled()) { 202 try { 203 beforeAsXml = ecc.getModelToXMLDumper().dumpModelAsXml(camelContext, definition); 204 } catch (Throwable e) { 205 // ignore, it may be due jaxb is not on classpath etc 206 } 207 } 208 209 // stop and remove this existing route 210 model.removeRouteDefinition(definition); 211 212 // any advice with tasks we should execute first? 213 if (builder instanceof AdviceWithRouteBuilder) { 214 List<AdviceWithTask> tasks = ((AdviceWithRouteBuilder) builder).getAdviceWithTasks(); 215 for (AdviceWithTask task : tasks) { 216 task.task(); 217 } 218 } 219 220 // now merge which also ensures that interceptors and the likes get 221 // mixed in correctly as well 222 RouteDefinition merged = routes.route(definition); 223 224 // add the new merged route 225 model.getRouteDefinitions().add(0, merged); 226 227 // log the merged route at info level to make it easier to end users to 228 // spot any mistakes they may have made 229 if (log.isInfoEnabled()) { 230 log.info("AdviceWith route after: {}", merged); 231 } 232 233 if (beforeAsXml != null && logRoutesAsXml && log.isInfoEnabled()) { 234 try { 235 String afterAsXml = ecc.getModelToXMLDumper().dumpModelAsXml(camelContext, merged); 236 log.info("Adviced route before/after as XML:\n{}\n{}", beforeAsXml, afterAsXml); 237 } catch (Throwable e) { 238 // ignore, it may be due jaxb is not on classpath etc 239 } 240 } 241 242 // If the camel context is started then we start the route 243 if (camelContext.isStarted()) { 244 model.addRouteDefinition(merged); 245 } 246 return merged; 247 } 248 249 // Implementation methods 250 // ------------------------------------------------------------------------- 251 protected Route doCreateRoute() throws Exception { 252 // resolve endpoint 253 Endpoint endpoint = definition.getInput().getEndpoint(); 254 if (endpoint == null) { 255 EndpointConsumerBuilder def = definition.getInput().getEndpointConsumerBuilder(); 256 if (def != null) { 257 endpoint = def.resolve(camelContext); 258 } else { 259 endpoint = resolveEndpoint(definition.getInput().getEndpointUri()); 260 } 261 } 262 263 // create route 264 String id = definition.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()); 265 String desc = RouteDefinitionHelper.getRouteMessage(definition.toString()); 266 DefaultRoute route = new DefaultRoute(camelContext, definition, id, desc, endpoint); 267 268 // configure error handler 269 route.setErrorHandlerFactory(definition.getErrorHandlerFactory()); 270 271 // configure tracing 272 if (definition.getTrace() != null) { 273 Boolean isTrace = parseBoolean(definition.getTrace()); 274 if (isTrace != null) { 275 route.setTracing(isTrace); 276 if (isTrace) { 277 log.debug("Tracing is enabled on route: {}", definition.getId()); 278 // tracing is added in the DefaultChannel so we can enable 279 // it on the fly 280 } 281 } 282 } 283 284 // configure message history 285 if (definition.getMessageHistory() != null) { 286 Boolean isMessageHistory = parseBoolean(definition.getMessageHistory()); 287 if (isMessageHistory != null) { 288 route.setMessageHistory(isMessageHistory); 289 if (isMessageHistory) { 290 log.debug("Message history is enabled on route: {}", definition.getId()); 291 } 292 } 293 } 294 295 // configure Log EIP mask 296 if (definition.getLogMask() != null) { 297 Boolean isLogMask = parseBoolean(definition.getLogMask()); 298 if (isLogMask != null) { 299 route.setLogMask(isLogMask); 300 if (isLogMask) { 301 log.debug("Security mask for Logging is enabled on route: {}", definition.getId()); 302 } 303 } 304 } 305 306 // configure stream caching 307 if (definition.getStreamCache() != null) { 308 Boolean isStreamCache = parseBoolean(definition.getStreamCache()); 309 if (isStreamCache != null) { 310 route.setStreamCaching(isStreamCache); 311 if (isStreamCache) { 312 log.debug("StreamCaching is enabled on route: {}", definition.getId()); 313 } 314 } 315 } 316 317 // configure delayer 318 if (definition.getDelayer() != null) { 319 Long delayer = parseDuration(definition.getDelayer()); 320 if (delayer != null) { 321 route.setDelayer(delayer); 322 if (delayer > 0) { 323 log.debug("Delayer is enabled with: {} ms. on route: {}", delayer, definition.getId()); 324 } else { 325 log.debug("Delayer is disabled on route: {}", definition.getId()); 326 } 327 } 328 } 329 330 // configure route policy 331 if (definition.getRoutePolicies() != null && !definition.getRoutePolicies().isEmpty()) { 332 for (RoutePolicy policy : definition.getRoutePolicies()) { 333 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 334 route.getRoutePolicyList().add(policy); 335 } 336 } 337 if (definition.getRoutePolicyRef() != null) { 338 StringTokenizer policyTokens = new StringTokenizer(definition.getRoutePolicyRef(), ","); 339 while (policyTokens.hasMoreTokens()) { 340 String ref = policyTokens.nextToken().trim(); 341 RoutePolicy policy = mandatoryLookup(ref, RoutePolicy.class); 342 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 343 route.getRoutePolicyList().add(policy); 344 } 345 } 346 if (camelContext.getRoutePolicyFactories() != null) { 347 for (RoutePolicyFactory factory : camelContext.getRoutePolicyFactories()) { 348 RoutePolicy policy = factory.createRoutePolicy(camelContext, definition.getId(), definition); 349 if (policy != null) { 350 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 351 route.getRoutePolicyList().add(policy); 352 } 353 } 354 } 355 356 // configure auto startup 357 Boolean isAutoStartup = parseBoolean(definition.getAutoStartup()); 358 359 // configure startup order 360 Integer startupOrder = definition.getStartupOrder(); 361 362 // configure shutdown 363 if (definition.getShutdownRoute() != null) { 364 log.debug("Using ShutdownRoute {} on route: {}", definition.getShutdownRoute(), definition.getId()); 365 route.setShutdownRoute(parse(ShutdownRoute.class, definition.getShutdownRoute())); 366 } 367 if (definition.getShutdownRunningTask() != null) { 368 log.debug("Using ShutdownRunningTask {} on route: {}", definition.getShutdownRunningTask(), definition.getId()); 369 route.setShutdownRunningTask(parse(ShutdownRunningTask.class, definition.getShutdownRunningTask())); 370 } 371 372 // should inherit the intercept strategies we have defined 373 route.getInterceptStrategies().addAll(definition.getInterceptStrategies()); 374 375 // notify route context created 376 for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) { 377 strategy.onRouteContextCreate(route); 378 } 379 380 // validate route has output processors 381 if (!hasOutputs(definition.getOutputs(), true)) { 382 String at = definition.getInput().toString(); 383 Exception cause = new IllegalArgumentException("Route " + definition.getId() + " has no output processors." 384 + " You need to add outputs to the route such as to(\"log:foo\")."); 385 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), at, cause); 386 } 387 388 List<ProcessorDefinition<?>> list = new ArrayList<>(definition.getOutputs()); 389 for (ProcessorDefinition<?> output : list) { 390 try { 391 ProcessorReifier.reifier(route, output).addRoutes(); 392 } catch (Exception e) { 393 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), output.toString(), e); 394 } 395 } 396 397 // now lets turn all of the event driven consumer processors into a single route 398 List<Processor> eventDrivenProcessors = route.getEventDrivenProcessors(); 399 if (eventDrivenProcessors.isEmpty()) { 400 return null; 401 } 402 403 // Set route properties 404 Map<String, Object> routeProperties = computeRouteProperties(); 405 406 // always use an pipeline even if there are only 1 processor as the pipeline 407 // handles preparing the response from the exchange in regard to IN vs OUT messages etc 408 Processor target = new Pipeline(camelContext, eventDrivenProcessors); 409 410 // and wrap it in a unit of work so the UoW is on the top, so the entire route will be in the same UoW 411 CamelInternalProcessor internal = new CamelInternalProcessor(camelContext, target); 412 internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(route, camelContext)); 413 414 // and then optionally add route policy processor if a custom policy is set 415 List<RoutePolicy> routePolicyList = route.getRoutePolicyList(); 416 if (routePolicyList != null && !routePolicyList.isEmpty()) { 417 for (RoutePolicy policy : routePolicyList) { 418 // add policy as service if we have not already done that (eg possible if two routes have the same service) 419 // this ensures Camel can control the lifecycle of the policy 420 if (!camelContext.hasService(policy)) { 421 try { 422 camelContext.addService(policy); 423 } catch (Exception e) { 424 throw RuntimeCamelException.wrapRuntimeCamelException(e); 425 } 426 } 427 } 428 429 internal.addAdvice(new CamelInternalProcessor.RoutePolicyAdvice(routePolicyList)); 430 } 431 432 // wrap in route inflight processor to track number of inflight exchanges for the route 433 internal.addAdvice(new CamelInternalProcessor.RouteInflightRepositoryAdvice(camelContext.getInflightRepository(), route.getRouteId())); 434 435 // wrap in JMX instrumentation processor that is used for performance stats 436 ManagementInterceptStrategy managementInterceptStrategy = route.getManagementInterceptStrategy(); 437 if (managementInterceptStrategy != null) { 438 internal.addAdvice(CamelInternalProcessor.wrap(managementInterceptStrategy.createProcessor("route"))); 439 } 440 441 // wrap in route lifecycle 442 internal.addAdvice(new CamelInternalProcessor.RouteLifecycleAdvice()); 443 444 // add advices 445 if (definition.getRestBindingDefinition() != null) { 446 try { 447 internal.addAdvice(new RestBindingReifier(route, definition.getRestBindingDefinition()).createRestBindingAdvice()); 448 } catch (Exception e) { 449 throw RuntimeCamelException.wrapRuntimeCamelException(e); 450 } 451 } 452 453 // wrap in contract 454 if (definition.getInputType() != null || definition.getOutputType() != null) { 455 Contract contract = new Contract(); 456 if (definition.getInputType() != null) { 457 contract.setInputType(parseString(definition.getInputType().getUrn())); 458 contract.setValidateInput(parseBoolean(definition.getInputType().getValidate(), false)); 459 } 460 if (definition.getOutputType() != null) { 461 contract.setOutputType(parseString(definition.getOutputType().getUrn())); 462 contract.setValidateOutput(parseBoolean(definition.getOutputType().getValidate(), false)); 463 } 464 internal.addAdvice(new ContractAdvice(contract)); 465 // make sure to enable data type as its in use when using 466 // input/output types on routes 467 camelContext.setUseDataType(true); 468 } 469 470 // and create the route that wraps all of this 471 route.setProcessor(internal); 472 route.getProperties().putAll(routeProperties); 473 route.setStartupOrder(startupOrder); 474 if (isAutoStartup != null) { 475 log.debug("Using AutoStartup {} on route: {}", isAutoStartup, definition.getId()); 476 route.setAutoStartup(isAutoStartup); 477 } 478 479 // after the route is created then set the route on the policy processor so we get hold of it 480 CamelInternalProcessor.RoutePolicyAdvice task = internal.getAdvice(CamelInternalProcessor.RoutePolicyAdvice.class); 481 if (task != null) { 482 task.setRoute(route); 483 } 484 CamelInternalProcessor.RouteLifecycleAdvice task2 = internal.getAdvice(CamelInternalProcessor.RouteLifecycleAdvice.class); 485 if (task2 != null) { 486 task2.setRoute(route); 487 } 488 489 // invoke init on route policy 490 if (routePolicyList != null && !routePolicyList.isEmpty()) { 491 for (RoutePolicy policy : routePolicyList) { 492 policy.onInit(route); 493 } 494 } 495 496 return route; 497 } 498 499 protected Map<String, Object> computeRouteProperties() { 500 Map<String, Object> routeProperties = new HashMap<>(); 501 routeProperties.put(Route.ID_PROPERTY, definition.getId()); 502 routeProperties.put(Route.CUSTOM_ID_PROPERTY, Boolean.toString(definition.hasCustomIdAssigned())); 503 routeProperties.put(Route.PARENT_PROPERTY, Integer.toHexString(definition.hashCode())); 504 routeProperties.put(Route.DESCRIPTION_PROPERTY, definition.getDescriptionText()); 505 if (definition.getGroup() != null) { 506 routeProperties.put(Route.GROUP_PROPERTY, definition.getGroup()); 507 } 508 String rest = Boolean.toString(definition.isRest() != null && definition.isRest()); 509 routeProperties.put(Route.REST_PROPERTY, rest); 510 511 List<PropertyDefinition> properties = definition.getRouteProperties(); 512 if (properties != null) { 513 514 for (PropertyDefinition prop : properties) { 515 try { 516 final String key = parseString(prop.getKey()); 517 final String val = parseString(prop.getValue()); 518 for (String property : RESERVED_PROPERTIES) { 519 if (property.equalsIgnoreCase(key)) { 520 throw new IllegalArgumentException("Cannot set route property " + property + " as it is a reserved property"); 521 } 522 } 523 routeProperties.put(key, val); 524 } catch (Exception e) { 525 throw RuntimeCamelException.wrapRuntimeCamelException(e); 526 } 527 } 528 } 529 return routeProperties; 530 } 531 532}