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