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