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                }
146                customIds.add(id);
147            } else {
148                RestDefinition rest = route.getRestDefinition();
149                if (rest != null && route.isRest()) {
150                    VerbDefinition verb = findVerbDefinition(rest, route.getInput().getEndpointUri());
151                    if (verb != null) {
152                        String id = verb.getId();
153                        if (verb.hasCustomIdAssigned() && ObjectHelper.isNotEmpty(id) && !customIds.contains(id)) {
154                            route.setId(id);
155                            customIds.add(id);
156                        }
157                    }
158                }
159            }
160        }
161
162        // auto assign route ids
163        for (final RouteDefinition route : routes) {
164            if (route.getId() == null) {
165                // keep assigning id's until we find a free name
166
167                boolean done = false;
168                String id = null;
169                int attempts = 0;
170                while (!done && attempts < 1000) {
171                    attempts++;
172                    id = route.idOrCreate(context.adapt(ExtendedCamelContext.class).getNodeIdFactory());
173                    if (customIds.contains(id)) {
174                        // reset id and try again
175                        route.setId(null);
176                    } else {
177                        done = true;
178                    }
179                }
180                if (!done) {
181                    throw new IllegalArgumentException("Cannot auto assign id to route: " + route);
182                }
183                route.setId(id);
184                route.setCustomId(false);
185                customIds.add(route.getId());
186            }
187            RestDefinition rest = route.getRestDefinition();
188            if (rest != null && route.isRest()) {
189                VerbDefinition verb = findVerbDefinition(rest, route.getInput().getEndpointUri());
190                if (verb != null) {
191                    String id = verb.idOrCreate(context.adapt(ExtendedCamelContext.class).getNodeIdFactory());
192                    if (!verb.getUsedForGeneratingNodeId()) {
193                        id = route.getId();
194                    }
195                    verb.setRouteId(id);
196                }
197
198                // if its the rest/rest-api endpoints then they should include
199                // the route id as well
200                if (ObjectHelper.isNotEmpty(route.getInput())) {
201                    FromDefinition fromDefinition = route.getInput();
202                    String endpointUri = fromDefinition.getEndpointUri();
203                    if (ObjectHelper.isNotEmpty(endpointUri) && (endpointUri.startsWith("rest:") || endpointUri.startsWith("rest-api:"))) {
204                        Map<String, Object> options = new HashMap<>(1);
205                        options.put("routeId", route.getId());
206                        endpointUri = URISupport.appendParametersToURI(endpointUri, options);
207
208                        // replace uri with new routeId
209                        fromDefinition.setUri(endpointUri);
210                        route.setInput(fromDefinition);
211                    }
212                }
213            }
214        }
215    }
216
217    /**
218     * Find verb associated with the route by mapping uri
219     */
220    private static VerbDefinition findVerbDefinition(RestDefinition rest, String endpointUri) {
221        VerbDefinition ret = null;
222        String preVerbUri = "";
223        for (VerbDefinition verb : rest.getVerbs()) {
224            String verbUri = rest.buildFromUri(verb);
225            if (endpointUri.startsWith(verbUri) && preVerbUri.length() < verbUri.length()) {
226                // if there are multiple verb uri match, select the most
227                // specific one
228                // for example if the endpoint Uri is
229                // rest:get:/user:/{id}/user?produces=text%2Fplain
230                // then the verbUri rest:get:/user:/{id}/user should overweigh
231                // the est:get:/user:/{id}
232                preVerbUri = verbUri;
233                ret = verb;
234            }
235        }
236        return ret;
237    }
238
239    /**
240     * Validates that the target route has no duplicate id's from any of the
241     * existing routes.
242     *
243     * @param target the target route
244     * @param routes the existing routes
245     * @return <tt>null</tt> if no duplicate id's detected, otherwise the first
246     *         found duplicate id is returned.
247     */
248    public static String validateUniqueIds(RouteDefinition target, List<RouteDefinition> routes) {
249        Set<String> routesIds = new LinkedHashSet<>();
250        // gather all ids for the existing route, but only include custom ids,
251        // and no abstract ids
252        // as abstract nodes is cross-cutting functionality such as interceptors
253        // etc
254        for (RouteDefinition route : routes) {
255            // skip target route as we gather ids in a separate set
256            if (route == target) {
257                continue;
258            }
259            ProcessorDefinitionHelper.gatherAllNodeIds(route, routesIds, true, false);
260        }
261
262        // gather all ids for the target route, but only include custom ids, and
263        // no abstract ids
264        // as abstract nodes is cross-cutting functionality such as interceptors
265        // etc
266        Set<String> targetIds = new LinkedHashSet<>();
267        ProcessorDefinitionHelper.gatherAllNodeIds(target, targetIds, true, false);
268
269        // now check for clash with the target route
270        for (String id : targetIds) {
271            if (routesIds.contains(id)) {
272                return id;
273            }
274        }
275
276        return null;
277    }
278
279    public static void initParent(ProcessorDefinition parent) {
280        List<ProcessorDefinition<?>> children = parent.getOutputs();
281        for (ProcessorDefinition child : children) {
282            child.setParent(parent);
283            if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
284                // recursive the children
285                initParent(child);
286            }
287        }
288    }
289
290    public static void prepareRouteForInit(RouteDefinition route, List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> lower) {
291        // filter the route into abstracts and lower
292        for (ProcessorDefinition output : route.getOutputs()) {
293            if (output.isAbstract()) {
294                abstracts.add(output);
295            } else {
296                lower.add(output);
297            }
298        }
299    }
300
301    /**
302     * Prepares the route.
303     * <p/>
304     * This method does <b>not</b> mark the route as prepared afterwards.
305     *
306     * @param context the camel context
307     * @param route the route
308     */
309    public static void prepareRoute(CamelContext context, RouteDefinition route) {
310        prepareRoute(context, route, null, null, null, null, null);
311    }
312
313    /**
314     * Prepares the route which supports context scoped features such as
315     * onException, interceptors and onCompletions
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     * @param onExceptions optional list of onExceptions
322     * @param intercepts optional list of interceptors
323     * @param interceptFromDefinitions optional list of interceptFroms
324     * @param interceptSendToEndpointDefinitions optional list of
325     *            interceptSendToEndpoints
326     * @param onCompletions optional list onCompletions
327     */
328    public static void prepareRoute(CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions, List<InterceptDefinition> intercepts,
329                                    List<InterceptFromDefinition> interceptFromDefinitions, List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions,
330                                    List<OnCompletionDefinition> onCompletions) {
331
332        prepareRouteImp(context, route, onExceptions, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions, onCompletions);
333    }
334
335    /**
336     * Prepares the route which supports context scoped features such as
337     * 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
347     *            interceptSendToEndpoints
348     * @param onCompletions optional list onCompletions
349     */
350    private static void prepareRouteImp(CamelContext context, RouteDefinition route, List<OnExceptionDefinition> onExceptions, List<InterceptDefinition> intercepts,
351                                        List<InterceptFromDefinition> interceptFromDefinitions, List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions,
352                                        List<OnCompletionDefinition> onCompletions) {
353
354        // init the route inputs
355        initRouteInput(context, route.getInput());
356
357        // abstracts is the cross cutting concerns
358        List<ProcessorDefinition<?>> abstracts = new ArrayList<>();
359
360        // upper is the cross cutting concerns such as interceptors, error
361        // 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.getInput() == null) {
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
418     * places, such as nested 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 = 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 initRouteInput(CamelContext camelContext, FromDefinition input) {
435        // noop
436    }
437
438    private static void initParentAndErrorHandlerBuilder(CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> abstracts,
439                                                         List<OnExceptionDefinition> onExceptions) {
440
441        if (context != null) {
442            // let the route inherit the error handler builder from camel
443            // context if none already set
444
445            // must clone to avoid side effects while building routes using
446            // multiple RouteBuilders
447            ErrorHandlerFactory builder = context.adapt(ExtendedCamelContext.class).getErrorHandlerFactory();
448            if (builder != null) {
449                if (builder instanceof ErrorHandlerBuilder) {
450                    builder = ((ErrorHandlerBuilder)builder).cloneBuilder();
451                    route.setErrorHandlerFactoryIfNull(builder);
452                } else {
453                    throw new UnsupportedOperationException("The ErrorHandlerFactory must implement ErrorHandlerBuilder");
454                }
455            }
456        }
457
458        // init parent and error handler builder on the route
459        initParent(route);
460
461        // set the parent and error handler builder on the global on exceptions
462        if (onExceptions != null) {
463            for (OnExceptionDefinition global : onExceptions) {
464                initParent(global);
465            }
466        }
467    }
468
469    private static void initOnExceptions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper, List<OnExceptionDefinition> onExceptions) {
470        // add global on exceptions if any
471        if (onExceptions != null && !onExceptions.isEmpty()) {
472            for (OnExceptionDefinition output : onExceptions) {
473                // these are context scoped on exceptions so set this flag
474                output.setRouteScoped(false);
475                abstracts.add(output);
476            }
477        }
478
479        // now add onExceptions to the route
480        for (ProcessorDefinition output : abstracts) {
481            if (output instanceof OnExceptionDefinition) {
482                // on exceptions must be added at top, so the route flow is
483                // correct as
484                // on exceptions should be the first outputs
485
486                // find the index to add the on exception, it should be in the
487                // top
488                // but it should add itself after any existing onException
489                int index = 0;
490                for (int i = 0; i < upper.size(); i++) {
491                    ProcessorDefinition up = upper.get(i);
492                    if (!(up instanceof OnExceptionDefinition)) {
493                        index = i;
494                        break;
495                    } else {
496                        index++;
497                    }
498                }
499                upper.add(index, output);
500            }
501        }
502    }
503
504    private static void initInterceptors(CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper,
505                                         List<InterceptDefinition> intercepts, List<InterceptFromDefinition> interceptFromDefinitions,
506                                         List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) {
507
508        // move the abstracts interceptors into the dedicated list
509        for (ProcessorDefinition processor : abstracts) {
510            if (processor instanceof InterceptSendToEndpointDefinition) {
511                if (interceptSendToEndpointDefinitions == null) {
512                    interceptSendToEndpointDefinitions = new ArrayList<>();
513                }
514                interceptSendToEndpointDefinitions.add((InterceptSendToEndpointDefinition)processor);
515            } else if (processor instanceof InterceptFromDefinition) {
516                if (interceptFromDefinitions == null) {
517                    interceptFromDefinitions = new ArrayList<>();
518                }
519                interceptFromDefinitions.add((InterceptFromDefinition)processor);
520            } else if (processor instanceof InterceptDefinition) {
521                if (intercepts == null) {
522                    intercepts = new ArrayList<>();
523                }
524                intercepts.add((InterceptDefinition)processor);
525            }
526        }
527
528        doInitInterceptors(context, route, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions);
529    }
530
531    private static void doInitInterceptors(CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> upper, List<InterceptDefinition> intercepts,
532                                           List<InterceptFromDefinition> interceptFromDefinitions, List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) {
533
534        // configure intercept
535        if (intercepts != null && !intercepts.isEmpty()) {
536            for (InterceptDefinition intercept : intercepts) {
537                intercept.afterPropertiesSet();
538                // init the parent
539                initParent(intercept);
540                // add as first output so intercept is handled before the actual
541                // route and that gives
542                // us the needed head start to init and be able to intercept all
543                // the remaining processing steps
544                upper.add(0, intercept);
545            }
546        }
547
548        // configure intercept from
549        if (interceptFromDefinitions != null && !interceptFromDefinitions.isEmpty()) {
550            for (InterceptFromDefinition intercept : interceptFromDefinitions) {
551
552                // should we only apply interceptor for a given endpoint uri
553                boolean match = true;
554                if (intercept.getUri() != null) {
555
556                    // the uri can have property placeholders so resolve them
557                    // first
558                    String pattern;
559                    try {
560                        pattern = context.resolvePropertyPlaceholders(intercept.getUri());
561                    } catch (Exception e) {
562                        throw RuntimeCamelException.wrapRuntimeCamelException(e);
563                    }
564                    boolean isRefPattern = pattern.startsWith("ref*") || pattern.startsWith("ref:");
565
566                    match = false;
567
568                    // a bit more logic to lookup the endpoint as it can be
569                    // uri/ref based
570                    String uri = route.getInput().getEndpointUri();
571                    // if the pattern is not a ref itself, then resolve the ref
572                    // 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                        }
579                    }
580                    if (EndpointHelper.matchEndpoint(context, uri, pattern)) {
581                        match = true;
582                    }
583                }
584
585                if (match) {
586                    intercept.afterPropertiesSet();
587                    // init the parent
588                    initParent(intercept);
589                    // add as first output so intercept is handled before the
590                    // actual route and that gives
591                    // us the needed head start to init and be able to intercept
592                    // all the remaining processing steps
593                    upper.add(0, intercept);
594                }
595            }
596        }
597
598        // configure intercept send to endpoint
599        if (interceptSendToEndpointDefinitions != null && !interceptSendToEndpointDefinitions.isEmpty()) {
600            for (InterceptSendToEndpointDefinition intercept : interceptSendToEndpointDefinitions) {
601                intercept.afterPropertiesSet();
602                // init the parent
603                initParent(intercept);
604                // add as first output so intercept is handled before the actual
605                // route and that gives
606                // us the needed head start to init and be able to intercept all
607                // the remaining processing steps
608                upper.add(0, intercept);
609            }
610        }
611    }
612
613    private static void initOnCompletions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper, List<OnCompletionDefinition> onCompletions) {
614        List<OnCompletionDefinition> completions = new ArrayList<>();
615
616        // find the route scoped onCompletions
617        for (ProcessorDefinition out : abstracts) {
618            if (out instanceof OnCompletionDefinition) {
619                completions.add((OnCompletionDefinition)out);
620            }
621        }
622
623        // only add global onCompletion if there are no route already
624        if (completions.isEmpty() && onCompletions != null) {
625            completions = onCompletions;
626            // init the parent
627            for (OnCompletionDefinition global : completions) {
628                initParent(global);
629            }
630        }
631
632        // are there any completions to init at all?
633        if (completions.isEmpty()) {
634            return;
635        }
636
637        upper.addAll(completions);
638    }
639
640    private static void initSagas(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> lower) {
641        SagaDefinition saga = null;
642
643        // add to correct type
644        for (ProcessorDefinition<?> type : abstracts) {
645            if (type instanceof SagaDefinition) {
646                if (saga == null) {
647                    saga = (SagaDefinition)type;
648                } else {
649                    throw new IllegalArgumentException("The route can only have one saga defined");
650                }
651            }
652        }
653
654        if (saga != null) {
655            // the outputs should be moved to the transacted policy
656            saga.getOutputs().addAll(lower);
657            // and add it as the single output
658            lower.clear();
659            lower.add(saga);
660        }
661    }
662
663    private static void initTransacted(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> lower) {
664        TransactedDefinition transacted = null;
665
666        // add to correct type
667        for (ProcessorDefinition<?> type : abstracts) {
668            if (type instanceof TransactedDefinition) {
669                if (transacted == null) {
670                    transacted = (TransactedDefinition)type;
671                } else {
672                    throw new IllegalArgumentException("The route can only have one transacted defined");
673                }
674            }
675        }
676
677        if (transacted != null) {
678            // the outputs should be moved to the transacted policy
679            transacted.getOutputs().addAll(lower);
680            // and add it as the single output
681            lower.clear();
682            lower.add(transacted);
683        }
684    }
685
686    /**
687     * Force assigning ids to the give node and all its children (recursively).
688     * <p/>
689     * This is needed when doing tracing or the likes, where each node should
690     * have its id assigned so the tracing can pin point exactly.
691     *
692     * @param context the camel context
693     * @param processor the node
694     */
695    public static void forceAssignIds(CamelContext context, final ProcessorDefinition processor) {
696        // force id on the child
697        processor.idOrCreate(context.adapt(ExtendedCamelContext.class).getNodeIdFactory());
698
699        // if there was a custom id assigned, then make sure to support property
700        // placeholders
701        if (processor.hasCustomIdAssigned()) {
702            try {
703                final String originalId = processor.getId();
704                String id = context.resolvePropertyPlaceholders(originalId);
705                // only set id if its changed, such as we did property
706                // placeholder
707                if (!originalId.equals(id)) {
708                    processor.setId(id);
709                }
710            } catch (Exception e) {
711                throw RuntimeCamelException.wrapRuntimeCamelException(e);
712            }
713        }
714
715        List<ProcessorDefinition<?>> children = processor.getOutputs();
716        if (children != null && !children.isEmpty()) {
717            for (ProcessorDefinition child : children) {
718                forceAssignIds(context, child);
719            }
720        }
721    }
722
723    public static String getRouteMessage(String route) {
724        // ensure to sanitize uri's in the route so we do not show sensitive
725        // information such as passwords
726        route = URISupport.sanitizeUri(route);
727        // cut the route after 60 chars so it won't be too big in the message
728        // users just need to be able to identify the route so they know where
729        // to look
730        if (route.length() > 60) {
731            return route.substring(0, 60) + "...";
732        } else {
733            return route;
734        }
735    }
736}