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.List;
021    
022    import org.apache.camel.CamelContext;
023    import org.apache.camel.builder.ErrorHandlerBuilder;
024    import org.apache.camel.util.CamelContextHelper;
025    import org.apache.camel.util.EndpointHelper;
026    import org.apache.camel.util.ObjectHelper;
027    
028    /**
029     * Helper for {@link RouteDefinition}
030     * <p/>
031     * Utility methods to help preparing {@link RouteDefinition} before they are added to
032     * {@link org.apache.camel.CamelContext}.
033     */
034    @SuppressWarnings({"unchecked", "rawtypes", "deprecation"})
035    public final class RouteDefinitionHelper {
036    
037        private RouteDefinitionHelper() {
038        }
039    
040        public static void initParent(ProcessorDefinition parent) {
041            List<ProcessorDefinition> children = parent.getOutputs();
042            for (ProcessorDefinition child : children) {
043                child.setParent(parent);
044                if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
045                    // recursive the children
046                    initParent(child);
047                }
048            }
049        }
050    
051        private static void initParentAndErrorHandlerBuilder(ProcessorDefinition parent) {
052            List<ProcessorDefinition> children = parent.getOutputs();
053            for (ProcessorDefinition child : children) {
054                child.setParent(parent);
055                if (child.getOutputs() != null && !child.getOutputs().isEmpty()) {
056                    // recursive the children
057                    initParentAndErrorHandlerBuilder(child);
058                }
059            }
060        }
061    
062        public static void prepareRouteForInit(RouteDefinition route, List<ProcessorDefinition<?>> abstracts,
063                                               List<ProcessorDefinition<?>> lower) {
064            // filter the route into abstracts and lower
065            for (ProcessorDefinition output : route.getOutputs()) {
066                if (output.isAbstract()) {
067                    abstracts.add(output);
068                } else {
069                    lower.add(output);
070                }
071            }
072        }
073    
074        /**
075         * Prepares the route.
076         * <p/>
077         * This method does <b>not</b> mark the route as prepared afterwards.
078         *
079         * @param context the camel context
080         * @param route   the route
081         */
082        public static void prepareRoute(ModelCamelContext context, RouteDefinition route) {
083            prepareRoute(context, route, null, null, null, null, null);
084        }
085    
086        /**
087         * Prepares the route which supports context scoped features such as onException, interceptors and onCompletions
088         * <p/>
089         * This method does <b>not</b> mark the route as prepared afterwards.
090         *
091         * @param context                            the camel context
092         * @param route                              the route
093         * @param onExceptions                       optional list of onExceptions
094         * @param intercepts                         optional list of interceptors
095         * @param interceptFromDefinitions           optional list of interceptFroms
096         * @param interceptSendToEndpointDefinitions optional list of interceptSendToEndpoints
097         * @param onCompletions                      optional list onCompletions
098         */
099        public static void prepareRoute(ModelCamelContext context, RouteDefinition route,
100                                        List<OnExceptionDefinition> onExceptions,
101                                        List<InterceptDefinition> intercepts,
102                                        List<InterceptFromDefinition> interceptFromDefinitions,
103                                        List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions,
104                                        List<OnCompletionDefinition> onCompletions) {
105    
106            // abstracts is the cross cutting concerns
107            List<ProcessorDefinition<?>> abstracts = new ArrayList<ProcessorDefinition<?>>();
108    
109            // upper is the cross cutting concerns such as interceptors, error handlers etc
110            List<ProcessorDefinition<?>> upper = new ArrayList<ProcessorDefinition<?>>();
111    
112            // lower is the regular route
113            List<ProcessorDefinition<?>> lower = new ArrayList<ProcessorDefinition<?>>();
114    
115            RouteDefinitionHelper.prepareRouteForInit(route, abstracts, lower);
116    
117            // parent and error handler builder should be initialized first
118            initParentAndErrorHandlerBuilder(context, route, abstracts, onExceptions);
119            // then interceptors
120            initInterceptors(context, route, abstracts, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions);
121            // then on completion
122            initOnCompletions(abstracts, upper, onCompletions);
123            // then transactions
124            initTransacted(abstracts, lower);
125            // then on exception
126            initOnExceptions(abstracts, upper, onExceptions);
127    
128            // rebuild route as upper + lower
129            route.clearOutput();
130            route.getOutputs().addAll(lower);
131            route.getOutputs().addAll(0, upper);
132        }
133    
134        /**
135         * Sanity check the route, that it has input(s) and outputs.
136         *
137         * @param route the route
138         * @throws IllegalArgumentException is thrown if the route is invalid
139         */
140        public static void sanityCheckRoute(RouteDefinition route) {
141            ObjectHelper.notNull(route, "route");
142    
143            if (route.getInputs() == null || route.getInputs().isEmpty()) {
144                String msg = "Route has no inputs: " + route;
145                if (route.getId() != null) {
146                    msg = "Route " + route.getId() + " has no inputs: " + route;
147                }
148                throw new IllegalArgumentException(msg);
149            }
150    
151            if (route.getOutputs() == null || route.getOutputs().isEmpty()) {
152                String msg = "Route has no outputs: " + route;
153                if (route.getId() != null) {
154                    msg = "Route " + route.getId() + " has no outputs: " + route;
155                }
156                throw new IllegalArgumentException(msg);
157            }
158        }
159    
160        private static void initParentAndErrorHandlerBuilder(ModelCamelContext context, RouteDefinition route,
161                                                             List<ProcessorDefinition<?>> abstracts, List<OnExceptionDefinition> onExceptions) {
162    
163            if (context != null) {
164                // let the route inherit the error handler builder from camel context if none already set
165    
166                // must clone to avoid side effects while building routes using multiple RouteBuilders
167                ErrorHandlerBuilder builder = context.getErrorHandlerBuilder();
168                if (builder != null) {
169                    builder = builder.cloneBuilder();
170                    route.setErrorHandlerBuilderIfNull(builder);
171                }
172            }
173    
174            // init parent and error handler builder on the route
175            initParentAndErrorHandlerBuilder(route);
176    
177            // set the parent and error handler builder on the global on exceptions
178            if (onExceptions != null) {
179                for (OnExceptionDefinition global : onExceptions) {
180                    //global.setErrorHandlerBuilder(context.getErrorHandlerBuilder());
181                    initParentAndErrorHandlerBuilder(global);
182                }
183            }
184        }
185    
186        private static void initOnExceptions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper,
187                                             List<OnExceptionDefinition> onExceptions) {
188            // add global on exceptions if any
189            if (onExceptions != null && !onExceptions.isEmpty()) {
190                for (OnExceptionDefinition output : onExceptions) {
191                    // these are context scoped on exceptions so set this flag
192                    output.setRouteScoped(false);
193                    abstracts.add(output);
194                }
195            }
196    
197            // now add onExceptions to the route
198            for (ProcessorDefinition output : abstracts) {
199                if (output instanceof OnExceptionDefinition) {
200                    // on exceptions must be added at top, so the route flow is correct as
201                    // on exceptions should be the first outputs
202    
203                    // find the index to add the on exception, it should be in the top
204                    // but it should add itself after any existing onException
205                    int index = 0;
206                    for (int i = 0; i < upper.size(); i++) {
207                        ProcessorDefinition up = upper.get(i);
208                        if (!(up instanceof OnExceptionDefinition)) {
209                            index = i;
210                            break;
211                        } else {
212                            index++;
213                        }
214                    }
215                    upper.add(index, output);
216                }
217            }
218        }
219    
220        private static void initInterceptors(CamelContext context, RouteDefinition route,
221                                             List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper,
222                                             List<InterceptDefinition> intercepts,
223                                             List<InterceptFromDefinition> interceptFromDefinitions,
224                                             List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) {
225    
226            // move the abstracts interceptors into the dedicated list
227            for (ProcessorDefinition processor : abstracts) {
228                if (processor instanceof InterceptSendToEndpointDefinition) {
229                    if (interceptSendToEndpointDefinitions == null) {
230                        interceptSendToEndpointDefinitions = new ArrayList<InterceptSendToEndpointDefinition>();
231                    }
232                    interceptSendToEndpointDefinitions.add((InterceptSendToEndpointDefinition) processor);
233                } else if (processor instanceof InterceptFromDefinition) {
234                    if (interceptFromDefinitions == null) {
235                        interceptFromDefinitions = new ArrayList<InterceptFromDefinition>();
236                    }
237                    interceptFromDefinitions.add((InterceptFromDefinition) processor);
238                } else if (processor instanceof InterceptDefinition) {
239                    if (intercepts == null) {
240                        intercepts = new ArrayList<InterceptDefinition>();
241                    }
242                    intercepts.add((InterceptDefinition) processor);
243                }
244            }
245    
246            doInitInterceptors(context, route, upper, intercepts, interceptFromDefinitions, interceptSendToEndpointDefinitions);
247        }
248    
249        private static void doInitInterceptors(CamelContext context, RouteDefinition route, List<ProcessorDefinition<?>> upper,
250                                               List<InterceptDefinition> intercepts,
251                                               List<InterceptFromDefinition> interceptFromDefinitions,
252                                               List<InterceptSendToEndpointDefinition> interceptSendToEndpointDefinitions) {
253    
254            // configure intercept
255            if (intercepts != null && !intercepts.isEmpty()) {
256                for (InterceptDefinition intercept : intercepts) {
257                    intercept.afterPropertiesSet();
258                    // init the parent
259                    initParent(intercept);
260                    // add as first output so intercept is handled before the actual route and that gives
261                    // us the needed head start to init and be able to intercept all the remaining processing steps
262                    upper.add(0, intercept);
263                }
264            }
265    
266            // configure intercept from
267            if (interceptFromDefinitions != null && !interceptFromDefinitions.isEmpty()) {
268                for (InterceptFromDefinition intercept : interceptFromDefinitions) {
269    
270                    // should we only apply interceptor for a given endpoint uri
271                    boolean match = true;
272                    if (intercept.getUri() != null) {
273                        match = false;
274                        for (FromDefinition input : route.getInputs()) {
275                            // a bit more logic to lookup the endpoint as it can be uri/ref based
276                            String uri = input.getUri();
277                            if (uri != null && uri.startsWith("ref:")) {
278                                // its a ref: so lookup the endpoint to get its url
279                                uri = CamelContextHelper.getMandatoryEndpoint(context, uri).getEndpointUri();
280                            } else if (input.getRef() != null) {
281                                // lookup the endpoint to get its url
282                                uri = CamelContextHelper.getMandatoryEndpoint(context, "ref:" + input.getRef()).getEndpointUri();
283                            }
284                            if (EndpointHelper.matchEndpoint(context, uri, intercept.getUri())) {
285                                match = true;
286                                break;
287                            }
288                        }
289                    }
290    
291                    if (match) {
292                        intercept.afterPropertiesSet();
293                        // init the parent
294                        initParent(intercept);
295                        // add as first output so intercept is handled before the actual route and that gives
296                        // us the needed head start to init and be able to intercept all the remaining processing steps
297                        upper.add(0, intercept);
298                    }
299                }
300            }
301    
302            // configure intercept send to endpoint
303            if (interceptSendToEndpointDefinitions != null && !interceptSendToEndpointDefinitions.isEmpty()) {
304                for (InterceptSendToEndpointDefinition intercept : interceptSendToEndpointDefinitions) {
305                    intercept.afterPropertiesSet();
306                    // init the parent
307                    initParent(intercept);
308                    // add as first output so intercept is handled before the actual route and that gives
309                    // us the needed head start to init and be able to intercept all the remaining processing steps
310                    upper.add(0, intercept);
311                }
312            }
313        }
314    
315        private static void initOnCompletions(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> upper,
316                                              List<OnCompletionDefinition> onCompletions) {
317            List<OnCompletionDefinition> completions = new ArrayList<OnCompletionDefinition>();
318    
319            // find the route scoped onCompletions
320            for (ProcessorDefinition out : abstracts) {
321                if (out instanceof OnCompletionDefinition) {
322                    completions.add((OnCompletionDefinition) out);
323                }
324            }
325    
326            // only add global onCompletion if there are no route already
327            if (completions.isEmpty() && onCompletions != null) {
328                completions = onCompletions;
329                // init the parent
330                for (OnCompletionDefinition global : completions) {
331                    initParent(global);
332                }
333            }
334    
335            // are there any completions to init at all?
336            if (completions.isEmpty()) {
337                return;
338            }
339    
340            upper.addAll(completions);
341        }
342    
343        private static void initTransacted(List<ProcessorDefinition<?>> abstracts, List<ProcessorDefinition<?>> lower) {
344            TransactedDefinition transacted = null;
345    
346            // add to correct type
347            for (ProcessorDefinition<?> type : abstracts) {
348                if (type instanceof TransactedDefinition) {
349                    if (transacted == null) {
350                        transacted = (TransactedDefinition) type;
351                    } else {
352                        throw new IllegalArgumentException("The route can only have one transacted defined");
353                    }
354                }
355            }
356    
357            if (transacted != null) {
358                // the outputs should be moved to the transacted policy
359                transacted.getOutputs().addAll(lower);
360                // and add it as the single output
361                lower.clear();
362                lower.add(transacted);
363            }
364        }
365    
366        /**
367         * Force assigning ids to the give node and all its children (recursively).
368         * <p/>
369         * This is needed when doing tracing or the likes, where each node should have its id assigned
370         * so the tracing can pin point exactly.
371         *
372         * @param context the camel context
373         * @param processor the node
374         */
375        public static void forceAssignIds(CamelContext context, ProcessorDefinition processor) {
376            // force id on the child
377            processor.idOrCreate(context.getNodeIdFactory());
378    
379            List<ProcessorDefinition> children = processor.getOutputs();
380            if (children != null && !children.isEmpty()) {
381                for (ProcessorDefinition child : children) {
382                    forceAssignIds(context, child);
383                }
384            }
385        }
386    
387    }