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.model; 018 019import java.io.UnsupportedEncodingException; 020import java.net.URISyntaxException; 021import java.util.ArrayList; 022import java.util.HashSet; 023import java.util.Iterator; 024import java.util.LinkedHashSet; 025import java.util.List; 026import java.util.Set; 027 028import org.apache.camel.CamelContext; 029import org.apache.camel.builder.ErrorHandlerBuilder; 030import org.apache.camel.util.CamelContextHelper; 031import org.apache.camel.util.EndpointHelper; 032import org.apache.camel.util.IntrospectionSupport; 033import org.apache.camel.util.ObjectHelper; 034import org.apache.camel.util.URISupport; 035 036import static org.apache.camel.model.ProcessorDefinitionHelper.filterTypeInOutputs; 037 038/** 039 * Helper for {@link RouteDefinition} 040 * <p/> 041 * Utility methods to help preparing {@link RouteDefinition} before they are added to 042 * {@link org.apache.camel.CamelContext}. 043 */ 044@SuppressWarnings({"unchecked", "rawtypes", "deprecation"}) 045public final class RouteDefinitionHelper { 046 047 private RouteDefinitionHelper() { 048 } 049 050 /** 051 * Gather all the endpoint uri's the route is using from the EIPs that has a static endpoint defined. 052 * 053 * @param route the route 054 * @param includeInputs whether to include inputs 055 * @param includeOutputs whether to include outputs 056 * @return the endpoints uris 057 */ 058 public static Set<String> gatherAllStaticEndpointUris(CamelContext camelContext, RouteDefinition route, boolean includeInputs, boolean includeOutputs) { 059 return gatherAllEndpointUris(camelContext, route, includeInputs, includeOutputs, false); 060 } 061 062 /** 063 * Gather all the endpoint uri's the route is using from the EIPs that has a static or dynamic endpoint defined. 064 * 065 * @param route the route 066 * @param includeInputs whether to include inputs 067 * @param includeOutputs whether to include outputs 068 * @param includeDynamic whether to include dynamic outputs which has been in use during routing at runtime, gathered from the {@link org.apache.camel.spi.RuntimeEndpointRegistry}. 069 * @return the endpoints uris 070 */ 071 public static Set<String> gatherAllEndpointUris(CamelContext camelContext, RouteDefinition route, boolean includeInputs, boolean includeOutputs, boolean includeDynamic) { 072 Set<String> answer = new LinkedHashSet<String>(); 073 074 if (includeInputs) { 075 for (FromDefinition from : route.getInputs()) { 076 String uri = normalizeUri(from.getEndpointUri()); 077 if (uri != null) { 078 answer.add(uri); 079 } 080 } 081 } 082 083 if (includeOutputs) { 084 Iterator<EndpointRequiredDefinition> it = filterTypeInOutputs(route.getOutputs(), EndpointRequiredDefinition.class); 085 while (it.hasNext()) { 086 String uri = normalizeUri(it.next().getEndpointUri()); 087 if (uri != null) { 088 answer.add(uri); 089 } 090 } 091 if (includeDynamic && camelContext.getRuntimeEndpointRegistry() != null) { 092 List<String> endpoints = camelContext.getRuntimeEndpointRegistry().getEndpointsPerRoute(route.getId(), false); 093 for (String uri : endpoints) { 094 if (uri != null) { 095 answer.add(uri); 096 } 097 } 098 } 099 } 100 101 return answer; 102 } 103 104 private static String normalizeUri(String uri) { 105 try { 106 return URISupport.normalizeUri(uri); 107 } catch (UnsupportedEncodingException e) { 108 // ignore 109 } catch (URISyntaxException e) { 110 // ignore 111 } 112 return null; 113 } 114 115 /** 116 * Force assigning ids to the routes 117 * 118 * @param context the camel context 119 * @param routes the routes 120 * @throws Exception is thrown if error force assign ids to the routes 121 */ 122 public static void forceAssignIds(CamelContext context, List<RouteDefinition> routes) throws Exception { 123 // handle custom assigned id's first, and then afterwards assign auto generated ids 124 Set<String> customIds = new HashSet<String>(); 125 126 for (final RouteDefinition route : routes) { 127 // if there was a custom id assigned, then make sure to support property placeholders 128 if (route.hasCustomIdAssigned()) { 129 final String originalId = route.getId(); 130 final String id = context.resolvePropertyPlaceholders(originalId); 131 // only set id if its changed, such as we did property placeholder 132 if (!originalId.equals(id)) { 133 route.setId(id); 134 ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() { 135 @Override 136 public void run() { 137 route.setId(originalId); 138 } 139 }); 140 } 141 customIds.add(id); 142 } 143 } 144 145 // auto assign route ids 146 for (final RouteDefinition route : routes) { 147 if (route.getId() == null) { 148 // keep assigning id's until we find a free name 149 boolean done = false; 150 String id = null; 151 while (!done) { 152 id = context.getNodeIdFactory().createId(route); 153 done = !customIds.contains(id); 154 } 155 route.setId(id); 156 ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() { 157 @Override 158 public void run() { 159 route.setId(null); 160 route.setCustomId(false); 161 } 162 }); 163 route.setCustomId(false); 164 customIds.add(route.getId()); 165 } 166 } 167 } 168 169 /** 170 * Validates that the target route has no duplicate id's from any of the existing routes. 171 * 172 * @param target the target route 173 * @param routes the existing routes 174 * @return <tt>null</tt> if no duplicate id's detected, otherwise the first found duplicate id is returned. 175 */ 176 public static String validateUniqueIds(RouteDefinition target, List<RouteDefinition> routes) { 177 Set<String> routesIds = new LinkedHashSet<String>(); 178 // gather all ids for the existing route, but only include custom ids, and no abstract ids 179 // as abstract nodes is cross-cutting functionality such as interceptors etc 180 for (RouteDefinition route : routes) { 181 // skip target route as we gather ids in a separate set 182 if (route == target) { 183 continue; 184 } 185 ProcessorDefinitionHelper.gatherAllNodeIds(route, routesIds, true, false); 186 } 187 188 // gather all ids for the target route, but only include custom ids, and no abstract ids 189 // as abstract nodes is cross-cutting functionality such as interceptors etc 190 Set<String> targetIds = new LinkedHashSet<String>(); 191 ProcessorDefinitionHelper.gatherAllNodeIds(target, targetIds, true, false); 192 193 // now check for clash with the target route 194 for (String id : targetIds) { 195 if (routesIds.contains(id)) { 196 return id; 197 } 198 } 199 200 return null; 201 } 202 203 public static void initParent(ProcessorDefinition parent) { 204 List<ProcessorDefinition<?>> children = parent.getOutputs(); 205 for (ProcessorDefinition child : children) { 206 child.setParent(parent); 207 if (child.getOutputs() != null && !child.getOutputs().isEmpty()) { 208 // recursive the children 209 initParent(child); 210 } 211 } 212 } 213 214 private static void initParentAndErrorHandlerBuilder(ProcessorDefinition parent) { 215 List<ProcessorDefinition<?>> children = parent.getOutputs(); 216 for (ProcessorDefinition child : children) { 217 child.setParent(parent); 218 if (child.getOutputs() != null && !child.getOutputs().isEmpty()) { 219 // recursive the children 220 initParentAndErrorHandlerBuilder(child); 221 } 222 } 223 } 224 225 public static void prepareRouteForInit(RouteDefinition route, List<ProcessorDefinition<?>> abstracts, 226 List<ProcessorDefinition<?>> lower) { 227 // filter the route into abstracts and lower 228 for (ProcessorDefinition output : route.getOutputs()) { 229 if (output.isAbstract()) { 230 abstracts.add(output); 231 } else { 232 lower.add(output); 233 } 234 } 235 } 236 237 /** 238 * Prepares the route. 239 * <p/> 240 * This method does <b>not</b> mark the route as prepared afterwards. 241 * 242 * @param context the camel context 243 * @param route the route 244 */ 245 public static void prepareRoute(ModelCamelContext context, RouteDefinition route) { 246 prepareRoute(context, route, null, null, null, null, null); 247 } 248 249 /** 250 * Prepares the route which supports context scoped features such as onException, interceptors and onCompletions 251 * <p/> 252 * This method does <b>not</b> mark the route as prepared afterwards. 253 * 254 * @param context the camel context 255 * @param route the route 256 * @param onExceptions optional list of onExceptions 257 * @param intercepts optional list of interceptors 258 * @param interceptFromDefinitions optional list of interceptFroms 259 * @param interceptSendToEndpointDefinitions optional list of interceptSendToEndpoints 260 * @param onCompletions optional list onCompletions 261 */ 262 public static void prepareRoute(ModelCamelContext context, RouteDefinition route, 263 List<OnExceptionDefinition> onExceptions, 264 List<InterceptDefinition> intercepts, 265 List<InterceptFromDefinition> interceptFromDefinitions, 266 List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions, 267 List<OnCompletionDefinition> onCompletions) { 268 269 Runnable propertyPlaceholdersChangeReverter = ProcessorDefinitionHelper.createPropertyPlaceholdersChangeReverter(); 270 try { 271 prepareRouteImp(context, route, onExceptions, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions, onCompletions); 272 } finally { 273 // Lets restore 274 propertyPlaceholdersChangeReverter.run(); 275 } 276 } 277 278 /** 279 * Prepares the route which supports context scoped features such as onException, interceptors and onCompletions 280 * <p/> 281 * This method does <b>not</b> mark the route as prepared afterwards. 282 * 283 * @param context the camel context 284 * @param route the route 285 * @param onExceptions optional list of onExceptions 286 * @param intercepts optional list of interceptors 287 * @param interceptFromDefinitions optional list of interceptFroms 288 * @param interceptSendToEndpointDefinitions optional list of interceptSendToEndpoints 289 * @param onCompletions optional list onCompletions 290 */ 291 private static void prepareRouteImp(ModelCamelContext context, RouteDefinition route, 292 List<OnExceptionDefinition> onExceptions, 293 List<InterceptDefinition> intercepts, 294 List<InterceptFromDefinition> interceptFromDefinitions, 295 List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions, 296 List<OnCompletionDefinition> onCompletions) { 297 298 // init the route inputs 299 initRouteInputs(context, route.getInputs()); 300 301 // abstracts is the cross cutting concerns 302 List<ProcessorDefinition<?>> abstracts = new ArrayList<ProcessorDefinition<?>>(); 303 304 // upper is the cross cutting concerns such as interceptors, error handlers etc 305 List<ProcessorDefinition<?>> upper = new ArrayList<ProcessorDefinition<?>>(); 306 307 // lower is the regular route 308 List<ProcessorDefinition<?>> lower = new ArrayList<ProcessorDefinition<?>>(); 309 310 RouteDefinitionHelper.prepareRouteForInit(route, abstracts, lower); 311 312 // parent and error handler builder should be initialized first 313 initParentAndErrorHandlerBuilder(context, route, abstracts, onExceptions); 314 // validate top-level violations 315 validateTopLevel(route.getOutputs()); 316 // then interceptors 317 initInterceptors(context, route, abstracts, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions); 318 // then on completion 319 initOnCompletions(abstracts, upper, onCompletions); 320 // then transactions 321 initTransacted(abstracts, lower); 322 // then on exception 323 initOnExceptions(abstracts, upper, onExceptions); 324 325 // rebuild route as upper + lower 326 route.clearOutput(); 327 route.getOutputs().addAll(lower); 328 route.getOutputs().addAll(0, upper); 329 } 330 331 /** 332 * Sanity check the route, that it has input(s) and outputs. 333 * 334 * @param route the route 335 * @throws IllegalArgumentException is thrown if the route is invalid 336 */ 337 public static void sanityCheckRoute(RouteDefinition route) { 338 ObjectHelper.notNull(route, "route"); 339 340 if (route.getInputs() == null || route.getInputs().isEmpty()) { 341 String msg = "Route has no inputs: " + route; 342 if (route.getId() != null) { 343 msg = "Route " + route.getId() + " has no inputs: " + route; 344 } 345 throw new IllegalArgumentException(msg); 346 } 347 348 if (route.getOutputs() == null || route.getOutputs().isEmpty()) { 349 String msg = "Route has no outputs: " + route; 350 if (route.getId() != null) { 351 msg = "Route " + route.getId() + " has no outputs: " + route; 352 } 353 throw new IllegalArgumentException(msg); 354 } 355 } 356 357 /** 358 * Validates that top-level only definitions is not added in the wrong places, such as nested 359 * inside a splitter etc. 360 */ 361 private static void validateTopLevel(List<ProcessorDefinition<?>> children) { 362 for (ProcessorDefinition child : children) { 363 // validate that top-level is only added on the route (eg top level) 364 RouteDefinition route = ProcessorDefinitionHelper.getRoute(child); 365 boolean parentIsRoute = route != null && child.getParent() == route; 366 if (child.isTopLevelOnly() && !parentIsRoute) { 367 throw new IllegalArgumentException("The output must be added as top-level on the route. Try moving " + child + " to the top of route."); 368 } 369 if (child.getOutputs() != null && !child.getOutputs().isEmpty()) { 370 validateTopLevel(child.getOutputs()); 371 } 372 } 373 } 374 375 private static void initRouteInputs(CamelContext camelContext, List<FromDefinition> inputs) { 376 // resolve property placeholders on route inputs which hasn't been done yet 377 for (FromDefinition input : inputs) { 378 try { 379 ProcessorDefinitionHelper.resolvePropertyPlaceholders(camelContext, input); 380 } catch (Exception e) { 381 throw ObjectHelper.wrapRuntimeCamelException(e); 382 } 383 } 384 } 385 386 private static void initParentAndErrorHandlerBuilder(ModelCamelContext context, RouteDefinition route, 387 List<ProcessorDefinition<?>> abstracts, List<OnExceptionDefinition> onExceptions) { 388 389 if (context != null) { 390 // let the route inherit the error handler builder from camel context if none already set 391 392 // must clone to avoid side effects while building routes using multiple RouteBuilders 393 ErrorHandlerBuilder builder = context.getErrorHandlerBuilder(); 394 if (builder != null) { 395 builder = builder.cloneBuilder(); 396 route.setErrorHandlerBuilderIfNull(builder); 397 } 398 } 399 400 // init parent and error handler builder on the route 401 initParentAndErrorHandlerBuilder(route); 402 403 // set the parent and error handler builder on the global on exceptions 404 if (onExceptions != null) { 405 for (OnExceptionDefinition global : onExceptions) { 406 initParentAndErrorHandlerBuilder(global); 407 } 408 } 409 } 410 411 412 private static void initOnExceptions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper, 413 List<OnExceptionDefinition> onExceptions) { 414 // add global on exceptions if any 415 if (onExceptions != null && !onExceptions.isEmpty()) { 416 for (OnExceptionDefinition output : onExceptions) { 417 // these are context scoped on exceptions so set this flag 418 output.setRouteScoped(false); 419 abstracts.add(output); 420 } 421 } 422 423 // now add onExceptions to the route 424 for (ProcessorDefinition output : abstracts) { 425 if (output instanceof OnExceptionDefinition) { 426 // on exceptions must be added at top, so the route flow is correct as 427 // on exceptions should be the first outputs 428 429 // find the index to add the on exception, it should be in the top 430 // but it should add itself after any existing onException 431 int index = 0; 432 for (int i = 0; i < upper.size(); i++) { 433 ProcessorDefinition up = upper.get(i); 434 if (!(up instanceof OnExceptionDefinition)) { 435 index = i; 436 break; 437 } else { 438 index++; 439 } 440 } 441 upper.add(index, output); 442 } 443 } 444 } 445 446 private static void initInterceptors(CamelContext context, RouteDefinition route, 447 List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper, 448 List<InterceptDefinition> intercepts, 449 List<InterceptFromDefinition> interceptFromDefinitions, 450 List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) { 451 452 // move the abstracts interceptors into the dedicated list 453 for (ProcessorDefinition processor : abstracts) { 454 if (processor instanceof InterceptSendToEndpointDefinition) { 455 if (interceptSendToEndpointDefinitions == null) { 456 interceptSendToEndpointDefinitions = new ArrayList<InterceptSendToEndpointDefinition>(); 457 } 458 interceptSendToEndpointDefinitions.add((InterceptSendToEndpointDefinition) processor); 459 } else if (processor instanceof InterceptFromDefinition) { 460 if (interceptFromDefinitions == null) { 461 interceptFromDefinitions = new ArrayList<InterceptFromDefinition>(); 462 } 463 interceptFromDefinitions.add((InterceptFromDefinition) processor); 464 } else if (processor instanceof InterceptDefinition) { 465 if (intercepts == null) { 466 intercepts = new ArrayList<InterceptDefinition>(); 467 } 468 intercepts.add((InterceptDefinition) processor); 469 } 470 } 471 472 doInitInterceptors(context, route, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions); 473 } 474 475 private static void doInitInterceptors(CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> upper, 476 List<InterceptDefinition> intercepts, 477 List<InterceptFromDefinition> interceptFromDefinitions, 478 List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) { 479 480 // configure intercept 481 if (intercepts != null && !intercepts.isEmpty()) { 482 for (InterceptDefinition intercept : intercepts) { 483 intercept.afterPropertiesSet(); 484 // init the parent 485 initParent(intercept); 486 // add as first output so intercept is handled before the actual route and that gives 487 // us the needed head start to init and be able to intercept all the remaining processing steps 488 upper.add(0, intercept); 489 } 490 } 491 492 // configure intercept from 493 if (interceptFromDefinitions != null && !interceptFromDefinitions.isEmpty()) { 494 for (InterceptFromDefinition intercept : interceptFromDefinitions) { 495 496 // should we only apply interceptor for a given endpoint uri 497 boolean match = true; 498 if (intercept.getUri() != null) { 499 500 // the uri can have property placeholders so resolve them first 501 String pattern; 502 try { 503 pattern = context.resolvePropertyPlaceholders(intercept.getUri()); 504 } catch (Exception e) { 505 throw ObjectHelper.wrapRuntimeCamelException(e); 506 } 507 boolean isRefPattern = pattern.startsWith("ref*") || pattern.startsWith("ref:"); 508 509 match = false; 510 for (FromDefinition input : route.getInputs()) { 511 // a bit more logic to lookup the endpoint as it can be uri/ref based 512 String uri = input.getUri(); 513 // if the pattern is not a ref itself, then resolve the ref uris, so we can match the actual uri's with each other 514 if (!isRefPattern) { 515 if (uri != null && uri.startsWith("ref:")) { 516 // its a ref: so lookup the endpoint to get its url 517 String ref = uri.substring(4); 518 uri = CamelContextHelper.getMandatoryEndpoint(context, ref).getEndpointUri(); 519 } else if (input.getRef() != null) { 520 // lookup the endpoint to get its url 521 uri = CamelContextHelper.getMandatoryEndpoint(context, input.getRef()).getEndpointUri(); 522 } 523 } 524 if (EndpointHelper.matchEndpoint(context, uri, pattern)) { 525 match = true; 526 break; 527 } 528 } 529 } 530 531 if (match) { 532 intercept.afterPropertiesSet(); 533 // init the parent 534 initParent(intercept); 535 // add as first output so intercept is handled before the actual route and that gives 536 // us the needed head start to init and be able to intercept all the remaining processing steps 537 upper.add(0, intercept); 538 } 539 } 540 } 541 542 // configure intercept send to endpoint 543 if (interceptSendToEndpointDefinitions != null && !interceptSendToEndpointDefinitions.isEmpty()) { 544 for (InterceptSendToEndpointDefinition intercept : interceptSendToEndpointDefinitions) { 545 intercept.afterPropertiesSet(); 546 // init the parent 547 initParent(intercept); 548 // add as first output so intercept is handled before the actual route and that gives 549 // us the needed head start to init and be able to intercept all the remaining processing steps 550 upper.add(0, intercept); 551 } 552 } 553 } 554 555 private static void initOnCompletions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper, 556 List<OnCompletionDefinition> onCompletions) { 557 List<OnCompletionDefinition> completions = new ArrayList<OnCompletionDefinition>(); 558 559 // find the route scoped onCompletions 560 for (ProcessorDefinition out : abstracts) { 561 if (out instanceof OnCompletionDefinition) { 562 completions.add((OnCompletionDefinition) out); 563 } 564 } 565 566 // only add global onCompletion if there are no route already 567 if (completions.isEmpty() && onCompletions != null) { 568 completions = onCompletions; 569 // init the parent 570 for (OnCompletionDefinition global : completions) { 571 initParent(global); 572 } 573 } 574 575 // are there any completions to init at all? 576 if (completions.isEmpty()) { 577 return; 578 } 579 580 upper.addAll(completions); 581 } 582 583 private static void initTransacted(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> lower) { 584 TransactedDefinition transacted = null; 585 586 // add to correct type 587 for (ProcessorDefinition<?> type : abstracts) { 588 if (type instanceof TransactedDefinition) { 589 if (transacted == null) { 590 transacted = (TransactedDefinition) type; 591 } else { 592 throw new IllegalArgumentException("The route can only have one transacted defined"); 593 } 594 } 595 } 596 597 if (transacted != null) { 598 // the outputs should be moved to the transacted policy 599 transacted.getOutputs().addAll(lower); 600 // and add it as the single output 601 lower.clear(); 602 lower.add(transacted); 603 } 604 } 605 606 /** 607 * Force assigning ids to the give node and all its children (recursively). 608 * <p/> 609 * This is needed when doing tracing or the likes, where each node should have its id assigned 610 * so the tracing can pin point exactly. 611 * 612 * @param context the camel context 613 * @param processor the node 614 */ 615 public static void forceAssignIds(CamelContext context, final ProcessorDefinition processor) { 616 // force id on the child 617 processor.idOrCreate(context.getNodeIdFactory()); 618 619 // if there was a custom id assigned, then make sure to support property placeholders 620 if (processor.hasCustomIdAssigned()) { 621 try { 622 final String originalId = processor.getId(); 623 String id = context.resolvePropertyPlaceholders(originalId); 624 // only set id if its changed, such as we did property placeholder 625 if (!originalId.equals(id)) { 626 processor.setId(id); 627 ProcessorDefinitionHelper.addPropertyPlaceholdersChangeRevertAction(new Runnable() { 628 @Override 629 public void run() { 630 processor.setId(originalId); 631 } 632 }); 633 } 634 } catch (Exception e) { 635 throw ObjectHelper.wrapRuntimeCamelException(e); 636 } 637 } 638 639 List<ProcessorDefinition<?>> children = processor.getOutputs(); 640 if (children != null && !children.isEmpty()) { 641 for (ProcessorDefinition child : children) { 642 forceAssignIds(context, child); 643 } 644 } 645 } 646 647}