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 }