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