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