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