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