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