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}