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