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