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}