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.List; 021import java.util.StringTokenizer; 022 023import org.apache.camel.CamelContext; 024import org.apache.camel.Endpoint; 025import org.apache.camel.ExtendedCamelContext; 026import org.apache.camel.FailedToCreateRouteException; 027import org.apache.camel.NoSuchEndpointException; 028import org.apache.camel.Processor; 029import org.apache.camel.Route; 030import org.apache.camel.RuntimeCamelException; 031import org.apache.camel.ShutdownRoute; 032import org.apache.camel.ShutdownRunningTask; 033import org.apache.camel.builder.AdviceWithRouteBuilder; 034import org.apache.camel.builder.AdviceWithTask; 035import org.apache.camel.builder.EndpointConsumerBuilder; 036import org.apache.camel.builder.RouteBuilder; 037import org.apache.camel.model.Model; 038import org.apache.camel.model.ProcessorDefinition; 039import org.apache.camel.model.PropertyDefinition; 040import org.apache.camel.model.RouteDefinition; 041import org.apache.camel.model.RoutesDefinition; 042import org.apache.camel.processor.ContractAdvice; 043import org.apache.camel.reifier.rest.RestBindingReifier; 044import org.apache.camel.spi.Contract; 045import org.apache.camel.spi.LifecycleStrategy; 046import org.apache.camel.spi.RouteContext; 047import org.apache.camel.spi.RoutePolicy; 048import org.apache.camel.spi.RoutePolicyFactory; 049import org.apache.camel.support.CamelContextHelper; 050import org.apache.camel.util.ObjectHelper; 051 052public class RouteReifier extends ProcessorReifier<RouteDefinition> { 053 054 public RouteReifier(RouteContext routeContext, ProcessorDefinition<?> definition) { 055 super(routeContext, (RouteDefinition) definition); 056 } 057 058 public RouteReifier(CamelContext camelContext, ProcessorDefinition<?> definition) { 059 super(camelContext, (RouteDefinition) definition); 060 } 061 062 /** 063 * Advices this route with the route builder. 064 * <p/> 065 * <b>Important:</b> It is recommended to only advice a given route once 066 * (you can of course advice multiple routes). If you do it multiple times, 067 * then it may not work as expected, especially when any kind of error 068 * handling is involved. The Camel team plan for Camel 3.0 to support this 069 * as internal refactorings in the routing engine is needed to support this 070 * properly. 071 * <p/> 072 * You can use a regular {@link RouteBuilder} but the specialized 073 * {@link AdviceWithRouteBuilder} has additional features when using the 074 * <a href="http://camel.apache.org/advicewith.html">advice with</a> 075 * feature. We therefore suggest you to use the 076 * {@link AdviceWithRouteBuilder}. 077 * <p/> 078 * The advice process will add the interceptors, on exceptions, on 079 * completions etc. configured from the route builder to this route. 080 * <p/> 081 * This is mostly used for testing purpose to add interceptors and the likes 082 * to an existing route. 083 * <p/> 084 * Will stop and remove the old route from camel context and add and start 085 * this new advised route. 086 * 087 * @param definition the model definition 088 * @param camelContext the camel context 089 * @param builder the route builder 090 * @return a new route which is this route merged with the route builder 091 * @throws Exception can be thrown from the route builder 092 * @see AdviceWithRouteBuilder 093 */ 094 public static RouteDefinition adviceWith(RouteDefinition definition, CamelContext camelContext, RouteBuilder builder) throws Exception { 095 ObjectHelper.notNull(definition, "RouteDefinition"); 096 ObjectHelper.notNull(camelContext, "CamelContext"); 097 ObjectHelper.notNull(builder, "RouteBuilder"); 098 099 if (definition.getInput() == null) { 100 throw new IllegalArgumentException("RouteDefinition has no input"); 101 } 102 return new RouteReifier(camelContext, definition).adviceWith(builder); 103 } 104 105 @Override 106 public Processor createProcessor() throws Exception { 107 throw new UnsupportedOperationException("Not implemented for RouteDefinition"); 108 } 109 110 public Route createRoute() { 111 try { 112 return doCreateRoute(camelContext, routeContext); 113 } catch (FailedToCreateRouteException e) { 114 throw e; 115 } catch (Exception e) { 116 // wrap in exception which provide more details about which route 117 // was failing 118 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), e); 119 } 120 } 121 122 public Endpoint resolveEndpoint(String uri) throws NoSuchEndpointException { 123 ObjectHelper.notNull(camelContext, "CamelContext"); 124 return CamelContextHelper.getMandatoryEndpoint(camelContext, uri); 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 164 // inject this route into the advice route builder so it can access this route 165 // and offer features to manipulate the route directly 166 boolean logRoutesAsXml = true; 167 if (builder instanceof AdviceWithRouteBuilder) { 168 AdviceWithRouteBuilder arb = (AdviceWithRouteBuilder)builder; 169 arb.setOriginalRoute(definition); 170 logRoutesAsXml = arb.isLogRouteAsXml(); 171 } 172 173 // configure and prepare the routes from the builder 174 RoutesDefinition routes = builder.configureRoutes(camelContext); 175 176 log.debug("AdviceWith routes: {}", routes); 177 178 // we can only advice with a route builder without any routes 179 if (!builder.getRouteCollection().getRoutes().isEmpty()) { 180 throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes." + " Remove all routes from the route builder."); 181 } 182 // we can not advice with error handlers (if you added a new error 183 // handler in the route builder) 184 // we must check the error handler on builder is not the same as on 185 // camel context, as that would be the default 186 // context scoped error handler, in case no error handlers was 187 // configured 188 if (builder.getRouteCollection().getErrorHandlerFactory() != null 189 && camelContext.adapt(ExtendedCamelContext.class).getErrorHandlerFactory() != builder.getRouteCollection().getErrorHandlerFactory()) { 190 throw new IllegalArgumentException("You can not advice with error handlers. Remove the error handlers from the route builder."); 191 } 192 193 String beforeAsXml = null; 194 if (logRoutesAsXml && log.isInfoEnabled()) { 195 try { 196 ExtendedCamelContext ecc = camelContext.adapt(ExtendedCamelContext.class); 197 beforeAsXml = ecc.getModelToXMLDumper().dumpModelAsXml(camelContext, definition); 198 } catch (Throwable e) { 199 // ignore, it may be due jaxb is not on classpath etc 200 } 201 } 202 203 // stop and remove this existing route 204 camelContext.getExtension(Model.class).removeRouteDefinition(definition); 205 206 // any advice with tasks we should execute first? 207 if (builder instanceof AdviceWithRouteBuilder) { 208 List<AdviceWithTask> tasks = ((AdviceWithRouteBuilder)builder).getAdviceWithTasks(); 209 for (AdviceWithTask task : tasks) { 210 task.task(); 211 } 212 } 213 214 // now merge which also ensures that interceptors and the likes get 215 // mixed in correctly as well 216 RouteDefinition merged = routes.route(definition); 217 218 // add the new merged route 219 camelContext.getExtension(Model.class).getRouteDefinitions().add(0, merged); 220 221 // log the merged route at info level to make it easier to end users to 222 // spot any mistakes they may have made 223 if (log.isInfoEnabled()) { 224 log.info("AdviceWith route after: {}", merged); 225 } 226 227 if (beforeAsXml != null && logRoutesAsXml && log.isInfoEnabled()) { 228 try { 229 ExtendedCamelContext ecc = camelContext.adapt(ExtendedCamelContext.class); 230 String afterAsXml = ecc.getModelToXMLDumper().dumpModelAsXml(camelContext, merged); 231 log.info("Adviced route before/after as XML:\n{}\n{}", beforeAsXml, afterAsXml); 232 } catch (Throwable e) { 233 // ignore, it may be due jaxb is not on classpath etc 234 } 235 } 236 237 // If the camel context is started then we start the route 238 if (camelContext.isStarted()) { 239 camelContext.getExtension(Model.class).addRouteDefinition(merged); 240 } 241 return merged; 242 } 243 244 // Implementation methods 245 // ------------------------------------------------------------------------- 246 protected Route doCreateRoute(CamelContext camelContext, RouteContext routeContext) throws Exception { 247 // configure error handler 248 routeContext.setErrorHandlerFactory(definition.getErrorHandlerFactory()); 249 250 // configure tracing 251 if (definition.getTrace() != null) { 252 Boolean isTrace = CamelContextHelper.parseBoolean(camelContext, definition.getTrace()); 253 if (isTrace != null) { 254 routeContext.setTracing(isTrace); 255 if (isTrace) { 256 log.debug("Tracing is enabled on route: {}", definition.getId()); 257 // tracing is added in the DefaultChannel so we can enable 258 // it on the fly 259 } 260 } 261 } 262 263 // configure message history 264 if (definition.getMessageHistory() != null) { 265 Boolean isMessageHistory = CamelContextHelper.parseBoolean(camelContext, definition.getMessageHistory()); 266 if (isMessageHistory != null) { 267 routeContext.setMessageHistory(isMessageHistory); 268 if (isMessageHistory) { 269 log.debug("Message history is enabled on route: {}", definition.getId()); 270 } 271 } 272 } 273 274 // configure Log EIP mask 275 if (definition.getLogMask() != null) { 276 Boolean isLogMask = CamelContextHelper.parseBoolean(camelContext, definition.getLogMask()); 277 if (isLogMask != null) { 278 routeContext.setLogMask(isLogMask); 279 if (isLogMask) { 280 log.debug("Security mask for Logging is enabled on route: {}", definition.getId()); 281 } 282 } 283 } 284 285 // configure stream caching 286 if (definition.getStreamCache() != null) { 287 Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, definition.getStreamCache()); 288 if (isStreamCache != null) { 289 routeContext.setStreamCaching(isStreamCache); 290 if (isStreamCache) { 291 log.debug("StreamCaching is enabled on route: {}", definition.getId()); 292 } 293 } 294 } 295 296 // configure delayer 297 if (definition.getDelayer() != null) { 298 Long delayer = CamelContextHelper.parseLong(camelContext, definition.getDelayer()); 299 if (delayer != null) { 300 routeContext.setDelayer(delayer); 301 if (delayer > 0) { 302 log.debug("Delayer is enabled with: {} ms. on route: {}", delayer, definition.getId()); 303 } else { 304 log.debug("Delayer is disabled on route: {}", definition.getId()); 305 } 306 } 307 } 308 309 // configure route policy 310 if (definition.getRoutePolicies() != null && !definition.getRoutePolicies().isEmpty()) { 311 for (RoutePolicy policy : definition.getRoutePolicies()) { 312 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 313 routeContext.getRoutePolicyList().add(policy); 314 } 315 } 316 if (definition.getRoutePolicyRef() != null) { 317 StringTokenizer policyTokens = new StringTokenizer(definition.getRoutePolicyRef(), ","); 318 while (policyTokens.hasMoreTokens()) { 319 String ref = policyTokens.nextToken().trim(); 320 RoutePolicy policy = CamelContextHelper.mandatoryLookup(camelContext, ref, RoutePolicy.class); 321 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 322 routeContext.getRoutePolicyList().add(policy); 323 } 324 } 325 if (camelContext.getRoutePolicyFactories() != null) { 326 for (RoutePolicyFactory factory : camelContext.getRoutePolicyFactories()) { 327 RoutePolicy policy = factory.createRoutePolicy(camelContext, definition.getId(), definition); 328 if (policy != null) { 329 log.debug("RoutePolicy is enabled: {} on route: {}", policy, definition.getId()); 330 routeContext.getRoutePolicyList().add(policy); 331 } 332 } 333 } 334 335 // configure auto startup 336 Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, definition.getAutoStartup()); 337 if (isAutoStartup != null) { 338 log.debug("Using AutoStartup {} on route: {}", isAutoStartup, definition.getId()); 339 routeContext.setAutoStartup(isAutoStartup); 340 } 341 342 // configure startup order 343 if (definition.getStartupOrder() != null) { 344 routeContext.setStartupOrder(definition.getStartupOrder()); 345 } 346 347 // configure shutdown 348 if (definition.getShutdownRoute() != null) { 349 log.debug("Using ShutdownRoute {} on route: {}", definition.getShutdownRoute(), definition.getId()); 350 routeContext.setShutdownRoute(parse(ShutdownRoute.class, definition.getShutdownRoute())); 351 } 352 if (definition.getShutdownRunningTask() != null) { 353 log.debug("Using ShutdownRunningTask {} on route: {}", definition.getShutdownRunningTask(), definition.getId()); 354 routeContext.setShutdownRunningTask(parse(ShutdownRunningTask.class, definition.getShutdownRunningTask())); 355 } 356 357 // should inherit the intercept strategies we have defined 358 routeContext.setInterceptStrategies(definition.getInterceptStrategies()); 359 360 // resolve endpoint 361 Endpoint endpoint = definition.getInput().getEndpoint(); 362 if (endpoint == null) { 363 EndpointConsumerBuilder def = definition.getInput().getEndpointConsumerBuilder(); 364 if (def != null) { 365 endpoint = def.resolve(camelContext); 366 } else { 367 endpoint = routeContext.resolveEndpoint(definition.getInput().getEndpointUri()); 368 } 369 } 370 routeContext.setEndpoint(endpoint); 371 372 // notify route context created 373 for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) { 374 strategy.onRouteContextCreate(routeContext); 375 } 376 377 // validate route has output processors 378 if (!hasOutputs(definition.getOutputs(), true)) { 379 String at = definition.getInput().toString(); 380 Exception cause = new IllegalArgumentException("Route " + definition.getId() + " has no output processors." 381 + " You need to add outputs to the route such as to(\"log:foo\")."); 382 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), at, cause); 383 } 384 385 List<ProcessorDefinition<?>> list = new ArrayList<>(definition.getOutputs()); 386 for (ProcessorDefinition<?> output : list) { 387 try { 388 ProcessorReifier.reifier(routeContext, output).addRoutes(); 389 } catch (Exception e) { 390 throw new FailedToCreateRouteException(definition.getId(), definition.toString(), output.toString(), e); 391 } 392 } 393 394 if (definition.getRestBindingDefinition() != null) { 395 try { 396 routeContext.addAdvice(new RestBindingReifier(routeContext, definition.getRestBindingDefinition()).createRestBindingAdvice()); 397 } catch (Exception e) { 398 throw RuntimeCamelException.wrapRuntimeCamelException(e); 399 } 400 } 401 402 // wrap in contract 403 if (definition.getInputType() != null || definition.getOutputType() != null) { 404 Contract contract = new Contract(); 405 if (definition.getInputType() != null) { 406 contract.setInputType(parseString(definition.getInputType().getUrn())); 407 contract.setValidateInput(parseBoolean(definition.getInputType().getValidate(), false)); 408 } 409 if (definition.getOutputType() != null) { 410 contract.setOutputType(parseString(definition.getOutputType().getUrn())); 411 contract.setValidateOutput(parseBoolean(definition.getOutputType().getValidate(), false)); 412 } 413 routeContext.addAdvice(new ContractAdvice(contract)); 414 // make sure to enable data type as its in use when using 415 // input/output types on routes 416 camelContext.setUseDataType(true); 417 } 418 419 // Set route properties 420 routeContext.addProperty(Route.ID_PROPERTY, definition.getId()); 421 routeContext.addProperty(Route.CUSTOM_ID_PROPERTY, Boolean.toString(definition.hasCustomIdAssigned())); 422 routeContext.addProperty(Route.PARENT_PROPERTY, Integer.toHexString(definition.hashCode())); 423 routeContext.addProperty(Route.DESCRIPTION_PROPERTY, definition.getDescriptionText()); 424 if (definition.getGroup() != null) { 425 routeContext.addProperty(Route.GROUP_PROPERTY, definition.getGroup()); 426 } 427 String rest = Boolean.toString(definition.isRest() != null && definition.isRest()); 428 routeContext.addProperty(Route.REST_PROPERTY, rest); 429 430 List<PropertyDefinition> properties = definition.getRouteProperties(); 431 if (properties != null) { 432 final String[] reservedProperties = new String[] {Route.ID_PROPERTY, Route.CUSTOM_ID_PROPERTY, Route.PARENT_PROPERTY, Route.DESCRIPTION_PROPERTY, Route.GROUP_PROPERTY, 433 Route.REST_PROPERTY}; 434 435 for (PropertyDefinition prop : properties) { 436 try { 437 final String key = CamelContextHelper.parseText(camelContext, prop.getKey()); 438 final String val = CamelContextHelper.parseText(camelContext, prop.getValue()); 439 440 for (String property : reservedProperties) { 441 if (property.equalsIgnoreCase(key)) { 442 throw new IllegalArgumentException("Cannot set route property " + property + " as it is a reserved property"); 443 } 444 } 445 446 routeContext.addProperty(key, val); 447 } catch (Exception e) { 448 throw RuntimeCamelException.wrapRuntimeCamelException(e); 449 } 450 } 451 } 452 453 return routeContext.commit(); 454 } 455 456}