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