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