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