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