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.builder; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Optional; 024import java.util.concurrent.atomic.AtomicBoolean; 025 026import org.apache.camel.CamelContext; 027import org.apache.camel.Endpoint; 028import org.apache.camel.ExtendedCamelContext; 029import org.apache.camel.Ordered; 030import org.apache.camel.Route; 031import org.apache.camel.RoutesBuilder; 032import org.apache.camel.ValueHolder; 033import org.apache.camel.impl.transformer.TransformerKey; 034import org.apache.camel.impl.validator.ValidatorKey; 035import org.apache.camel.model.FromDefinition; 036import org.apache.camel.model.InterceptDefinition; 037import org.apache.camel.model.InterceptFromDefinition; 038import org.apache.camel.model.InterceptSendToEndpointDefinition; 039import org.apache.camel.model.Model; 040import org.apache.camel.model.OnCompletionDefinition; 041import org.apache.camel.model.OnExceptionDefinition; 042import org.apache.camel.model.RouteDefinition; 043import org.apache.camel.model.RoutesDefinition; 044import org.apache.camel.model.rest.RestConfigurationDefinition; 045import org.apache.camel.model.rest.RestDefinition; 046import org.apache.camel.model.rest.RestsDefinition; 047import org.apache.camel.model.transformer.TransformerDefinition; 048import org.apache.camel.model.validator.ValidatorDefinition; 049import org.apache.camel.reifier.transformer.TransformerReifier; 050import org.apache.camel.reifier.validator.ValidatorReifier; 051import org.apache.camel.spi.DataType; 052import org.apache.camel.spi.PropertiesComponent; 053import org.apache.camel.spi.RestConfiguration; 054import org.apache.camel.spi.Transformer; 055import org.apache.camel.spi.Validator; 056import org.apache.camel.util.ObjectHelper; 057import org.apache.camel.util.StringHelper; 058import org.apache.camel.util.function.ThrowingConsumer; 059import org.slf4j.Logger; 060import org.slf4j.LoggerFactory; 061 062/** 063 * A <a href="http://camel.apache.org/dsl.html">Java DSL</a> which is used to 064 * build {@link Route} instances in a {@link CamelContext} for smart routing. 065 */ 066public abstract class RouteBuilder extends BuilderSupport implements RoutesBuilder, Ordered { 067 protected Logger log = LoggerFactory.getLogger(getClass()); 068 private AtomicBoolean initialized = new AtomicBoolean(false); 069 private RestsDefinition restCollection = new RestsDefinition(); 070 private Map<String, RestConfigurationDefinition> restConfigurations; 071 private List<TransformerBuilder> transformerBuilders = new ArrayList<>(); 072 private List<ValidatorBuilder> validatorBuilders = new ArrayList<>(); 073 private RoutesDefinition routeCollection = new RoutesDefinition(); 074 private final List<RouteBuilderLifecycleStrategy> lifecycleInterceptors = new ArrayList<>(); 075 076 public RouteBuilder() { 077 this(null); 078 } 079 080 public RouteBuilder(CamelContext context) { 081 super(context); 082 } 083 084 /** 085 * Override this method to define ordering of {@link RouteBuilder} classes that are added to 086 * Camel from various runtimes such as camel-main, camel-spring-boot. This allows end users 087 * to control the ordering if some routes must be added and started before others. 088 * <p/> 089 * Use low numbers for higher priority. Normally the sorting will start from 0 and move upwards. 090 * So if you want to be last then use {@link Integer#MAX_VALUE} or eg {@link #LOWEST}. 091 */ 092 @Override 093 public int getOrder() { 094 return LOWEST; 095 } 096 097 /** 098 * Add routes to a context using a lambda expression. It can be used as 099 * following: 100 * 101 * <pre> 102 * RouteBuilder.addRoutes(context, rb -> 103 * rb.from("direct:inbound").bean(ProduceTemplateBean.class))); 104 * </pre> 105 * 106 * @param context the camel context to add routes 107 * @param rbc a lambda expression receiving the {@code RouteBuilder} to use 108 * to create routes 109 * @throws Exception if an error occurs 110 */ 111 public static void addRoutes(CamelContext context, ThrowingConsumer<RouteBuilder, Exception> rbc) throws Exception { 112 context.addRoutes(new RouteBuilder(context) { 113 @Override 114 public void configure() throws Exception { 115 rbc.accept(this); 116 } 117 }); 118 } 119 120 @Override 121 public String toString() { 122 return getRouteCollection().toString(); 123 } 124 125 /** 126 * <b>Called on initialization to build the routes using the fluent builder 127 * syntax.</b> 128 * <p/> 129 * This is a central method for RouteBuilder implementations to implement 130 * the routes using the Java fluent builder syntax. 131 * 132 * @throws Exception can be thrown during configuration 133 */ 134 public abstract void configure() throws Exception; 135 136 /** 137 * Binds the bean to the repository (if possible). 138 * 139 * @param id the id of the bean 140 * @param bean the bean 141 */ 142 public void bindToRegistry(String id, Object bean) { 143 getContext().getRegistry().bind(id, bean); 144 } 145 146 /** 147 * Binds the bean to the repository (if possible). 148 * 149 * @param id the id of the bean 150 * @param type the type of the bean to associate the binding 151 * @param bean the bean 152 */ 153 public void bindToRegistry(String id, Class<?> type, Object bean) { 154 getContext().getRegistry().bind(id, type, bean); 155 } 156 157 /** 158 * Configures the REST services 159 * 160 * @return the builder 161 */ 162 public RestConfigurationDefinition restConfiguration() { 163 return restConfiguration(""); 164 } 165 166 /** 167 * Configures the REST service for the given component 168 * 169 * @return the builder 170 */ 171 public RestConfigurationDefinition restConfiguration(String component) { 172 if (restConfigurations == null) { 173 restConfigurations = new HashMap<>(); 174 } 175 RestConfigurationDefinition restConfiguration = restConfigurations.get(component); 176 if (restConfiguration == null) { 177 restConfiguration = new RestConfigurationDefinition(); 178 if (!component.isEmpty()) { 179 restConfiguration.component(component); 180 } 181 restConfigurations.put(component, restConfiguration); 182 } 183 return restConfiguration; 184 } 185 186 /** 187 * Creates a new REST service 188 * 189 * @return the builder 190 */ 191 public RestDefinition rest() { 192 getRestCollection().setCamelContext(getContext()); 193 RestDefinition answer = getRestCollection().rest(); 194 configureRest(answer); 195 return answer; 196 } 197 198 /** 199 * Creates a new REST service 200 * 201 * @param path the base path 202 * @return the builder 203 */ 204 public RestDefinition rest(String path) { 205 getRestCollection().setCamelContext(getContext()); 206 RestDefinition answer = getRestCollection().rest(path); 207 configureRest(answer); 208 return answer; 209 } 210 211 /** 212 * Create a new {@code TransformerBuilder}. 213 * 214 * @return the builder 215 */ 216 public TransformerBuilder transformer() { 217 TransformerBuilder tdb = new TransformerBuilder(); 218 transformerBuilders.add(tdb); 219 return tdb; 220 } 221 222 /** 223 * Create a new {@code ValidatorBuilder}. 224 * 225 * @return the builder 226 */ 227 public ValidatorBuilder validator() { 228 ValidatorBuilder vb = new ValidatorBuilder(); 229 validatorBuilders.add(vb); 230 return vb; 231 } 232 233 /** 234 * Creates a new route from the given URI input 235 * 236 * @param uri the from uri 237 * @return the builder 238 */ 239 public RouteDefinition from(String uri) { 240 getRouteCollection().setCamelContext(getContext()); 241 RouteDefinition answer = getRouteCollection().from(uri); 242 configureRoute(answer); 243 return answer; 244 } 245 246 /** 247 * Creates a new route from the given URI input 248 * 249 * @param uri the String formatted from uri 250 * @param args arguments for the string formatting of the uri 251 * @return the builder 252 */ 253 public RouteDefinition fromF(String uri, Object... args) { 254 getRouteCollection().setCamelContext(getContext()); 255 RouteDefinition answer = getRouteCollection().from(String.format(uri, args)); 256 configureRoute(answer); 257 return answer; 258 } 259 260 /** 261 * Creates a new route from the given endpoint 262 * 263 * @param endpoint the from endpoint 264 * @return the builder 265 */ 266 public RouteDefinition from(Endpoint endpoint) { 267 getRouteCollection().setCamelContext(getContext()); 268 RouteDefinition answer = getRouteCollection().from(endpoint); 269 configureRoute(answer); 270 return answer; 271 } 272 273 public RouteDefinition from(EndpointConsumerBuilder endpointDefinition) { 274 getRouteCollection().setCamelContext(getContext()); 275 RouteDefinition answer = getRouteCollection().from(endpointDefinition); 276 configureRoute(answer); 277 return answer; 278 } 279 280 /** 281 * Installs the given 282 * <a href="http://camel.apache.org/error-handler.html">error handler</a> 283 * builder 284 * 285 * @param errorHandlerBuilder the error handler to be used by default for 286 * all child routes 287 */ 288 public void errorHandler(ErrorHandlerBuilder errorHandlerBuilder) { 289 if (!getRouteCollection().getRoutes().isEmpty()) { 290 throw new IllegalArgumentException("errorHandler must be defined before any routes in the RouteBuilder"); 291 } 292 getRouteCollection().setCamelContext(getContext()); 293 setErrorHandlerBuilder(errorHandlerBuilder); 294 } 295 296 /** 297 * Injects a property placeholder value with the given key converted to the 298 * given type. 299 * 300 * @param key the property key 301 * @param type the type to convert the value as 302 * @return the value, or <tt>null</tt> if value is empty 303 * @throws Exception is thrown if property with key not found or error 304 * converting to the given type. 305 */ 306 public <T> T propertyInject(String key, Class<T> type) throws Exception { 307 StringHelper.notEmpty(key, "key"); 308 ObjectHelper.notNull(type, "Class type"); 309 310 // the properties component is mandatory 311 PropertiesComponent pc = getContext().getPropertiesComponent(); 312 // resolve property 313 Optional<String> value = pc.resolveProperty(key); 314 315 if (value.isPresent()) { 316 return getContext().getTypeConverter().mandatoryConvertTo(type, value.get()); 317 } else { 318 return null; 319 } 320 } 321 322 /** 323 * Adds a route for an interceptor that intercepts every processing step. 324 * 325 * @return the builder 326 */ 327 public InterceptDefinition intercept() { 328 if (!getRouteCollection().getRoutes().isEmpty()) { 329 throw new IllegalArgumentException("intercept must be defined before any routes in the RouteBuilder"); 330 } 331 getRouteCollection().setCamelContext(getContext()); 332 return getRouteCollection().intercept(); 333 } 334 335 /** 336 * Adds a route for an interceptor that intercepts incoming messages on any 337 * inputs in this route 338 * 339 * @return the builder 340 */ 341 public InterceptFromDefinition interceptFrom() { 342 if (!getRouteCollection().getRoutes().isEmpty()) { 343 throw new IllegalArgumentException("interceptFrom must be defined before any routes in the RouteBuilder"); 344 } 345 getRouteCollection().setCamelContext(getContext()); 346 return getRouteCollection().interceptFrom(); 347 } 348 349 /** 350 * Adds a route for an interceptor that intercepts incoming messages on the 351 * given endpoint. 352 * 353 * @param uri endpoint uri 354 * @return the builder 355 */ 356 public InterceptFromDefinition interceptFrom(String uri) { 357 if (!getRouteCollection().getRoutes().isEmpty()) { 358 throw new IllegalArgumentException("interceptFrom must be defined before any routes in the RouteBuilder"); 359 } 360 getRouteCollection().setCamelContext(getContext()); 361 return getRouteCollection().interceptFrom(uri); 362 } 363 364 /** 365 * Applies a route for an interceptor if an exchange is send to the given 366 * endpoint 367 * 368 * @param uri endpoint uri 369 * @return the builder 370 */ 371 public InterceptSendToEndpointDefinition interceptSendToEndpoint(String uri) { 372 if (!getRouteCollection().getRoutes().isEmpty()) { 373 throw new IllegalArgumentException("interceptSendToEndpoint must be defined before any routes in the RouteBuilder"); 374 } 375 getRouteCollection().setCamelContext(getContext()); 376 return getRouteCollection().interceptSendToEndpoint(uri); 377 } 378 379 /** 380 * <a href="http://camel.apache.org/exception-clause.html">Exception 381 * clause</a> for catching certain exceptions and handling them. 382 * 383 * @param exception exception to catch 384 * @return the builder 385 */ 386 public OnExceptionDefinition onException(Class<? extends Throwable> exception) { 387 // is only allowed at the top currently 388 if (!getRouteCollection().getRoutes().isEmpty()) { 389 throw new IllegalArgumentException("onException must be defined before any routes in the RouteBuilder"); 390 } 391 getRouteCollection().setCamelContext(getContext()); 392 return getRouteCollection().onException(exception); 393 } 394 395 /** 396 * <a href="http://camel.apache.org/exception-clause.html">Exception 397 * clause</a> for catching certain exceptions and handling them. 398 * 399 * @param exceptions list of exceptions to catch 400 * @return the builder 401 */ 402 public OnExceptionDefinition onException(Class<? extends Throwable>... exceptions) { 403 OnExceptionDefinition last = null; 404 for (Class<? extends Throwable> ex : exceptions) { 405 last = last == null ? onException(ex) : last.onException(ex); 406 } 407 return last != null ? last : onException(Exception.class); 408 } 409 410 /** 411 * <a href="http://camel.apache.org/oncompletion.html">On completion</a> 412 * callback for doing custom routing when the 413 * {@link org.apache.camel.Exchange} is complete. 414 * 415 * @return the builder 416 */ 417 public OnCompletionDefinition onCompletion() { 418 // is only allowed at the top currently 419 if (!getRouteCollection().getRoutes().isEmpty()) { 420 throw new IllegalArgumentException("onCompletion must be defined before any routes in the RouteBuilder"); 421 } 422 getRouteCollection().setCamelContext(getContext()); 423 return getRouteCollection().onCompletion(); 424 } 425 426 @Override 427 public void addRoutesToCamelContext(CamelContext context) throws Exception { 428 // must configure routes before rests 429 configureRoutes(context); 430 configureRests(context); 431 432 // but populate rests before routes, as we want to turn rests into 433 // routes 434 populateRests(); 435 populateTransformers(); 436 populateValidators(); 437 populateRoutes(); 438 } 439 440 /** 441 * Configures the routes 442 * 443 * @param context the Camel context 444 * @return the routes configured 445 * @throws Exception can be thrown during configuration 446 */ 447 public RoutesDefinition configureRoutes(CamelContext context) throws Exception { 448 setContext(context); 449 checkInitialized(); 450 routeCollection.setCamelContext(context); 451 return routeCollection; 452 } 453 454 /** 455 * Configures the rests 456 * 457 * @param context the Camel context 458 * @return the rests configured 459 * @throws Exception can be thrown during configuration 460 */ 461 public RestsDefinition configureRests(CamelContext context) throws Exception { 462 setContext(context); 463 restCollection.setCamelContext(context); 464 return restCollection; 465 } 466 467 @Override 468 public void setErrorHandlerBuilder(ErrorHandlerBuilder errorHandlerBuilder) { 469 super.setErrorHandlerBuilder(errorHandlerBuilder); 470 getRouteCollection().setErrorHandlerFactory(getErrorHandlerBuilder()); 471 } 472 473 /** 474 * Adds the given {@link RouteBuilderLifecycleStrategy} to be used. 475 */ 476 public void addLifecycleInterceptor(RouteBuilderLifecycleStrategy interceptor) { 477 lifecycleInterceptors.add(interceptor); 478 } 479 480 /** 481 * Adds the given {@link RouteBuilderLifecycleStrategy}. 482 */ 483 public void removeLifecycleInterceptor(RouteBuilderLifecycleStrategy interceptor) { 484 lifecycleInterceptors.remove(interceptor); 485 } 486 487 // Implementation methods 488 // ----------------------------------------------------------------------- 489 protected void checkInitialized() throws Exception { 490 if (initialized.compareAndSet(false, true)) { 491 // Set the CamelContext ErrorHandler here 492 CamelContext camelContext = getContext(); 493 if (camelContext.adapt(ExtendedCamelContext.class).getErrorHandlerFactory() instanceof ErrorHandlerBuilder) { 494 setErrorHandlerBuilder((ErrorHandlerBuilder)camelContext.adapt(ExtendedCamelContext.class).getErrorHandlerFactory()); 495 } 496 497 for (RouteBuilderLifecycleStrategy interceptor : lifecycleInterceptors) { 498 interceptor.beforeConfigure(this); 499 } 500 501 configure(); 502 // mark all route definitions as custom prepared because 503 // a route builder prepares the route definitions correctly already 504 for (RouteDefinition route : getRouteCollection().getRoutes()) { 505 route.markPrepared(); 506 } 507 508 for (RouteBuilderLifecycleStrategy interceptor : lifecycleInterceptors) { 509 interceptor.afterConfigure(this); 510 } 511 } 512 } 513 514 protected void populateRoutes() throws Exception { 515 CamelContext camelContext = getContext(); 516 if (camelContext == null) { 517 throw new IllegalArgumentException("CamelContext has not been injected!"); 518 } 519 getRouteCollection().setCamelContext(camelContext); 520 camelContext.getExtension(Model.class).addRouteDefinitions(getRouteCollection().getRoutes()); 521 } 522 523 protected void populateRests() throws Exception { 524 CamelContext camelContext = getContext(); 525 if (camelContext == null) { 526 throw new IllegalArgumentException("CamelContext has not been injected!"); 527 } 528 getRestCollection().setCamelContext(camelContext); 529 530 // setup rest configuration before adding the rests 531 if (getRestConfigurations() != null) { 532 for (Map.Entry<String, RestConfigurationDefinition> entry : getRestConfigurations().entrySet()) { 533 RestConfiguration config = entry.getValue().asRestConfiguration(getContext()); 534 if ("".equals(entry.getKey())) { 535 camelContext.setRestConfiguration(config); 536 } else { 537 camelContext.addRestConfiguration(config); 538 } 539 } 540 } 541 // cannot add rests as routes yet as we need to initialize this 542 // specially 543 camelContext.getExtension(Model.class).addRestDefinitions(getRestCollection().getRests(), false); 544 545 // convert rests api-doc into routes so they are routes for runtime 546 for (RestConfiguration config : camelContext.getRestConfigurations()) { 547 if (config.getApiContextPath() != null) { 548 // avoid adding rest-api multiple times, in case multiple 549 // RouteBuilder classes is added 550 // to the CamelContext, as we only want to setup rest-api once 551 // so we check all existing routes if they have rest-api route 552 // already added 553 boolean hasRestApi = false; 554 for (RouteDefinition route : camelContext.getExtension(Model.class).getRouteDefinitions()) { 555 FromDefinition from = route.getInput(); 556 if (from.getEndpointUri() != null && from.getEndpointUri().startsWith("rest-api:")) { 557 hasRestApi = true; 558 } 559 } 560 if (!hasRestApi) { 561 RouteDefinition route = RestDefinition.asRouteApiDefinition(camelContext, config); 562 log.debug("Adding routeId: {} as rest-api route", route.getId()); 563 getRouteCollection().route(route); 564 } 565 } 566 } 567 // add rest as routes and have them prepared as well via 568 // routeCollection.route method 569 getRestCollection().getRests().forEach(rest -> rest.asRouteDefinition(getContext()).forEach(route -> getRouteCollection().route(route))); 570 } 571 572 protected void populateTransformers() { 573 CamelContext camelContext = getContext(); 574 if (camelContext == null) { 575 throw new IllegalArgumentException("CamelContext has not been injected!"); 576 } 577 for (TransformerBuilder tdb : transformerBuilders) { 578 tdb.configure(camelContext); 579 } 580 581 // create and register transformers on transformer registry 582 for (TransformerDefinition def : camelContext.getExtension(Model.class).getTransformers()) { 583 Transformer transformer = TransformerReifier.reifier(camelContext, def).createTransformer(); 584 camelContext.getTransformerRegistry().put(createTransformerKey(def), transformer); 585 } 586 } 587 588 private static ValueHolder<String> createTransformerKey(TransformerDefinition def) { 589 return ObjectHelper.isNotEmpty(def.getScheme()) ? new TransformerKey(def.getScheme()) : new TransformerKey(new DataType(def.getFromType()), new DataType(def.getToType())); 590 } 591 592 protected void populateValidators() { 593 CamelContext camelContext = getContext(); 594 if (camelContext == null) { 595 throw new IllegalArgumentException("CamelContext has not been injected!"); 596 } 597 for (ValidatorBuilder vb : validatorBuilders) { 598 vb.configure(camelContext); 599 } 600 601 // create and register validators on validator registry 602 for (ValidatorDefinition def : camelContext.getExtension(Model.class).getValidators()) { 603 Validator validator = ValidatorReifier.reifier(camelContext, def).createValidator(); 604 camelContext.getValidatorRegistry().put(createValidatorKey(def), validator); 605 } 606 } 607 608 private static ValidatorKey createValidatorKey(ValidatorDefinition def) { 609 return new ValidatorKey(new DataType(def.getType())); 610 } 611 612 public RestsDefinition getRestCollection() { 613 return restCollection; 614 } 615 616 public Map<String, RestConfigurationDefinition> getRestConfigurations() { 617 return restConfigurations; 618 } 619 620 public void setRestCollection(RestsDefinition restCollection) { 621 this.restCollection = restCollection; 622 } 623 624 public void setRouteCollection(RoutesDefinition routeCollection) { 625 this.routeCollection = routeCollection; 626 } 627 628 public RoutesDefinition getRouteCollection() { 629 return this.routeCollection; 630 } 631 632 protected void configureRest(RestDefinition rest) { 633 // noop 634 } 635 636 protected void configureRoute(RouteDefinition route) { 637 // noop 638 } 639 640}