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