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 }