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