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