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