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