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