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.util.ArrayList; 020import java.util.HashMap; 021import java.util.Iterator; 022import java.util.LinkedHashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.concurrent.ExecutorService; 028import java.util.concurrent.ScheduledExecutorService; 029import java.util.function.Consumer; 030import java.util.function.Supplier; 031import javax.xml.namespace.QName; 032 033import org.apache.camel.CamelContext; 034import org.apache.camel.Exchange; 035import org.apache.camel.ExchangeConstantProvider; 036import org.apache.camel.NamedNode; 037import org.apache.camel.RuntimeCamelException; 038import org.apache.camel.spi.ExecutorServiceManager; 039import org.apache.camel.spi.PropertiesComponent; 040import org.apache.camel.spi.PropertyPlaceholderConfigurer; 041import org.apache.camel.spi.RouteContext; 042import org.apache.camel.support.PropertyBindingSupport; 043import org.apache.camel.util.ObjectHelper; 044import org.apache.camel.util.StringHelper; 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048/** 049 * Helper class for ProcessorDefinition and the other model classes. 050 */ 051public final class ProcessorDefinitionHelper { 052 053 private static final Logger LOG = LoggerFactory.getLogger(ProcessorDefinitionHelper.class); 054 private static final ThreadLocal<RestoreAction> CURRENT_RESTORE_ACTION = new ThreadLocal<>(); 055 056 private ProcessorDefinitionHelper() { 057 } 058 059 /** 060 * Looks for the given type in the list of outputs and recurring all the 061 * children as well. 062 * 063 * @param outputs list of outputs, can be null or empty. 064 * @param type the type to look for 065 * @return the found definitions, or <tt>null</tt> if not found 066 */ 067 public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) { 068 return filterTypeInOutputs(outputs, type, -1); 069 } 070 071 /** 072 * Looks for the given type in the list of outputs and recurring all the 073 * children as well. 074 * 075 * @param outputs list of outputs, can be null or empty. 076 * @param type the type to look for 077 * @param maxDeep maximum levels deep to traverse 078 * @return the found definitions, or <tt>null</tt> if not found 079 */ 080 public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type, int maxDeep) { 081 List<T> found = new ArrayList<>(); 082 doFindType(outputs, type, found, maxDeep); 083 return found.iterator(); 084 } 085 086 /** 087 * Looks for the given type in the list of outputs and recurring all the 088 * children as well. Will stop at first found and return it. 089 * 090 * @param outputs list of outputs, can be null or empty. 091 * @param type the type to look for 092 * @return the first found type, or <tt>null</tt> if not found 093 */ 094 public static <T> T findFirstTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) { 095 List<T> found = new ArrayList<>(); 096 doFindType(outputs, type, found, -1); 097 if (found.isEmpty()) { 098 return null; 099 } 100 return found.iterator().next(); 101 } 102 103 /** 104 * Is the given child the first in the outputs from the parent? 105 * 106 * @param parentType the type the parent must be 107 * @param node the node 108 * @return <tt>true</tt> if first child, <tt>false</tt> otherwise 109 */ 110 public static boolean isFirstChildOfType(Class<?> parentType, ProcessorDefinition<?> node) { 111 if (node == null || node.getParent() == null) { 112 return false; 113 } 114 115 if (node.getParent().getOutputs().isEmpty()) { 116 return false; 117 } 118 119 if (!(node.getParent().getClass().equals(parentType))) { 120 return false; 121 } 122 123 return node.getParent().getOutputs().get(0).equals(node); 124 } 125 126 /** 127 * Is the given node parent(s) of the given type 128 * 129 * @param parentType the parent type 130 * @param node the current node 131 * @param recursive whether or not to check grand parent(s) as well 132 * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt> 133 * otherwise 134 */ 135 public static boolean isParentOfType(Class<? extends ProcessorDefinition> parentType, ProcessorDefinition<?> node, boolean recursive) { 136 return findFirstParentOfType(parentType, node, recursive) != null; 137 } 138 139 /** 140 * Is the given node parent(s) of the given type 141 * 142 * @param parentType the parent type 143 * @param node the current node 144 * @param recursive whether or not to check grand parent(s) as well 145 * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt> 146 * otherwise 147 */ 148 public static <T extends ProcessorDefinition> T findFirstParentOfType(Class<T> parentType, ProcessorDefinition<?> node, boolean recursive) { 149 if (node == null || node.getParent() == null) { 150 return null; 151 } 152 153 if (parentType.isAssignableFrom(node.getParent().getClass())) { 154 return parentType.cast(node.getParent()); 155 } else if (recursive) { 156 // recursive up the tree of parents 157 return findFirstParentOfType(parentType, node.getParent(), true); 158 } else { 159 // no match 160 return null; 161 } 162 } 163 164 /** 165 * Gets the route definition the given node belongs to. 166 * 167 * @param node the node 168 * @return the route, or <tt>null</tt> if not possible to find 169 */ 170 public static RouteDefinition getRoute(NamedNode node) { 171 if (node == null) { 172 return null; 173 } 174 175 ProcessorDefinition<?> def = (ProcessorDefinition)node; 176 // drill to the top 177 while (def != null && def.getParent() != null) { 178 def = def.getParent(); 179 } 180 181 if (def instanceof RouteDefinition) { 182 return (RouteDefinition)def; 183 } else { 184 // not found 185 return null; 186 } 187 } 188 189 /** 190 * Gets the route id the given node belongs to. 191 * 192 * @param node the node 193 * @return the route id, or <tt>null</tt> if not possible to find 194 */ 195 public static String getRouteId(NamedNode node) { 196 RouteDefinition route = getRoute(node); 197 return route != null ? route.getId() : null; 198 } 199 200 /** 201 * Traverses the node, including its children (recursive), and gathers all 202 * the node ids. 203 * 204 * @param node the target node 205 * @param set set to store ids, if <tt>null</tt> a new set will be created 206 * @param onlyCustomId whether to only store custom assigned ids (ie. 207 * {@link org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()} 208 * @param includeAbstract whether to include abstract nodes (ie. 209 * {@link org.apache.camel.model.ProcessorDefinition#isAbstract()} 210 * @return the set with the found ids. 211 */ 212 public static Set<String> gatherAllNodeIds(ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) { 213 if (node == null) { 214 return set; 215 } 216 217 // skip abstract 218 if (node.isAbstract() && !includeAbstract) { 219 return set; 220 } 221 222 if (set == null) { 223 set = new LinkedHashSet<>(); 224 } 225 226 // add ourselves 227 if (node.getId() != null) { 228 if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) { 229 set.add(node.getId()); 230 } 231 } 232 233 // traverse outputs and recursive children as well 234 List<ProcessorDefinition<?>> children = node.getOutputs(); 235 if (children != null && !children.isEmpty()) { 236 for (ProcessorDefinition<?> child : children) { 237 // traverse children also 238 gatherAllNodeIds(child, set, onlyCustomId, includeAbstract); 239 } 240 } 241 242 return set; 243 } 244 245 private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int maxDeep) { 246 247 // do we have any top level abstracts, then we should max deep one more 248 // level down 249 // as that is really what we want to traverse as well 250 if (maxDeep > 0) { 251 for (ProcessorDefinition<?> out : outputs) { 252 if (out.isAbstract() && out.isTopLevelOnly()) { 253 maxDeep = maxDeep + 1; 254 break; 255 } 256 } 257 } 258 259 // start from level 1 260 doFindType(outputs, type, found, 1, maxDeep); 261 } 262 263 @SuppressWarnings({"unchecked", "rawtypes"}) 264 private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int current, int maxDeep) { 265 if (outputs == null || outputs.isEmpty()) { 266 return; 267 } 268 269 // break out 270 if (maxDeep > 0 && current > maxDeep) { 271 return; 272 } 273 274 for (ProcessorDefinition out : outputs) { 275 276 // send is much common 277 if (out instanceof SendDefinition) { 278 SendDefinition send = (SendDefinition)out; 279 List<ProcessorDefinition<?>> children = send.getOutputs(); 280 doFindType(children, type, found, ++current, maxDeep); 281 } 282 283 // special for choice 284 if (out instanceof ChoiceDefinition) { 285 ChoiceDefinition choice = (ChoiceDefinition)out; 286 287 // ensure to add ourself if we match also 288 if (type.isInstance(choice)) { 289 found.add((T)choice); 290 } 291 292 // only look at when/otherwise if current < maxDeep (or max deep 293 // is disabled) 294 if (maxDeep < 0 || current < maxDeep) { 295 for (WhenDefinition when : choice.getWhenClauses()) { 296 if (type.isInstance(when)) { 297 found.add((T)when); 298 } 299 List<ProcessorDefinition<?>> children = when.getOutputs(); 300 doFindType(children, type, found, ++current, maxDeep); 301 } 302 303 // otherwise is optional 304 if (choice.getOtherwise() != null) { 305 List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs(); 306 doFindType(children, type, found, ++current, maxDeep); 307 } 308 } 309 310 // do not check children as we already did that 311 continue; 312 } 313 314 // special for try ... catch ... finally 315 if (out instanceof TryDefinition) { 316 TryDefinition doTry = (TryDefinition)out; 317 318 // ensure to add ourself if we match also 319 if (type.isInstance(doTry)) { 320 found.add((T)doTry); 321 } 322 323 // only look at children if current < maxDeep (or max deep is 324 // disabled) 325 if (maxDeep < 0 || current < maxDeep) { 326 List<ProcessorDefinition<?>> doTryOut = doTry.getOutputsWithoutCatches(); 327 doFindType(doTryOut, type, found, ++current, maxDeep); 328 329 List<CatchDefinition> doTryCatch = doTry.getCatchClauses(); 330 for (CatchDefinition doCatch : doTryCatch) { 331 doFindType(doCatch.getOutputs(), type, found, ++current, maxDeep); 332 } 333 334 if (doTry.getFinallyClause() != null) { 335 doFindType(doTry.getFinallyClause().getOutputs(), type, found, ++current, maxDeep); 336 } 337 } 338 339 // do not check children as we already did that 340 continue; 341 } 342 343 // special for some types which has special outputs 344 if (out instanceof OutputDefinition) { 345 OutputDefinition outDef = (OutputDefinition)out; 346 347 // ensure to add ourself if we match also 348 if (type.isInstance(outDef)) { 349 found.add((T)outDef); 350 } 351 352 List<ProcessorDefinition<?>> outDefOut = outDef.getOutputs(); 353 doFindType(outDefOut, type, found, ++current, maxDeep); 354 355 // do not check children as we already did that 356 continue; 357 } 358 359 if (type.isInstance(out)) { 360 found.add((T)out); 361 } 362 363 // try children as well 364 List<ProcessorDefinition<?>> children = out.getOutputs(); 365 doFindType(children, type, found, ++current, maxDeep); 366 } 367 } 368 369 /** 370 * Is there any outputs in the given list. 371 * <p/> 372 * Is used for check if the route output has any real outputs (non 373 * abstracts) 374 * 375 * @param outputs the outputs 376 * @param excludeAbstract whether or not to exclude abstract outputs (e.g. 377 * skip onException etc.) 378 * @return <tt>true</tt> if has outputs, otherwise <tt>false</tt> is 379 * returned 380 */ 381 @SuppressWarnings({"unchecked", "rawtypes"}) 382 public static boolean hasOutputs(List<ProcessorDefinition<?>> outputs, boolean excludeAbstract) { 383 if (outputs == null || outputs.isEmpty()) { 384 return false; 385 } 386 if (!excludeAbstract) { 387 return !outputs.isEmpty(); 388 } 389 for (ProcessorDefinition output : outputs) { 390 if (output.isWrappingEntireOutput()) { 391 // special for those as they wrap entire output, so we should 392 // just check its output 393 return hasOutputs(output.getOutputs(), excludeAbstract); 394 } 395 if (!output.isAbstract()) { 396 return true; 397 } 398 } 399 return false; 400 } 401 402 /** 403 * Determines whether a new thread pool will be created or not. 404 * <p/> 405 * This is used to know if a new thread pool will be created, and therefore 406 * is not shared by others, and therefore exclusive to the definition. 407 * 408 * @param routeContext the route context 409 * @param definition the node definition which may leverage executor 410 * service. 411 * @param useDefault whether to fallback and use a default thread pool, if 412 * no explicit configured 413 * @return <tt>true</tt> if a new thread pool will be created, 414 * <tt>false</tt> if not 415 * @see #getConfiguredExecutorService(org.apache.camel.spi.RouteContext, 416 * String, ExecutorServiceAwareDefinition, boolean) 417 */ 418 public static boolean willCreateNewThreadPool(RouteContext routeContext, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) { 419 ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); 420 ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); 421 422 if (definition.getExecutorService() != null) { 423 // no there is a custom thread pool configured 424 return false; 425 } else if (definition.getExecutorServiceRef() != null) { 426 ExecutorService answer = routeContext.lookup(definition.getExecutorServiceRef(), ExecutorService.class); 427 // if no existing thread pool, then we will have to create a new 428 // thread pool 429 return answer == null; 430 } else if (useDefault) { 431 return true; 432 } 433 434 return false; 435 } 436 437 /** 438 * Will lookup in {@link org.apache.camel.spi.Registry} for a 439 * {@link ExecutorService} registered with the given 440 * <tt>executorServiceRef</tt> name. 441 * <p/> 442 * This method will lookup for configured thread pool in the following order 443 * <ul> 444 * <li>from the {@link org.apache.camel.spi.Registry} if found</li> 445 * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile 446 * ThreadPoolProfile(s)}.</li> 447 * <li>if none found, then <tt>null</tt> is returned.</li> 448 * </ul> 449 * 450 * @param routeContext the route context 451 * @param name name which is appended to the thread name, when the 452 * {@link java.util.concurrent.ExecutorService} is created based 453 * on a {@link org.apache.camel.spi.ThreadPoolProfile}. 454 * @param source the source to use the thread pool 455 * @param executorServiceRef reference name of the thread pool 456 * @return the executor service, or <tt>null</tt> if none was found. 457 */ 458 public static ExecutorService lookupExecutorServiceRef(RouteContext routeContext, String name, Object source, String executorServiceRef) { 459 460 ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); 461 ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); 462 ObjectHelper.notNull(executorServiceRef, "executorServiceRef"); 463 464 // lookup in registry first and use existing thread pool if exists 465 ExecutorService answer = routeContext.lookup(executorServiceRef, ExecutorService.class); 466 if (answer == null) { 467 // then create a thread pool assuming the ref is a thread pool 468 // profile id 469 answer = manager.newThreadPool(source, name, executorServiceRef); 470 } 471 return answer; 472 } 473 474 /** 475 * Will lookup and get the configured 476 * {@link java.util.concurrent.ExecutorService} from the given definition. 477 * <p/> 478 * This method will lookup for configured thread pool in the following order 479 * <ul> 480 * <li>from the definition if any explicit configured executor service.</li> 481 * <li>from the {@link org.apache.camel.spi.Registry} if found</li> 482 * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile 483 * ThreadPoolProfile(s)}.</li> 484 * <li>if none found, then <tt>null</tt> is returned.</li> 485 * </ul> 486 * The various {@link ExecutorServiceAwareDefinition} should use this helper 487 * method to ensure they support configured executor services in the same 488 * coherent way. 489 * 490 * @param routeContext the route context 491 * @param name name which is appended to the thread name, when the 492 * {@link java.util.concurrent.ExecutorService} is created based 493 * on a {@link org.apache.camel.spi.ThreadPoolProfile}. 494 * @param definition the node definition which may leverage executor 495 * service. 496 * @param useDefault whether to fallback and use a default thread pool, if 497 * no explicit configured 498 * @return the configured executor service, or <tt>null</tt> if none was 499 * configured. 500 * @throws IllegalArgumentException is thrown if lookup of executor service 501 * in {@link org.apache.camel.spi.Registry} was not found 502 */ 503 public static ExecutorService getConfiguredExecutorService(RouteContext routeContext, String name, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) 504 throws IllegalArgumentException { 505 ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); 506 ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); 507 508 // prefer to use explicit configured executor on the definition 509 if (definition.getExecutorService() != null) { 510 return definition.getExecutorService(); 511 } else if (definition.getExecutorServiceRef() != null) { 512 // lookup in registry first and use existing thread pool if exists 513 ExecutorService answer = lookupExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef()); 514 if (answer == null) { 515 throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() 516 + " not found in registry (as an ExecutorService instance) or as a thread pool profile."); 517 } 518 return answer; 519 } else if (useDefault) { 520 return manager.newDefaultThreadPool(definition, name); 521 } 522 523 return null; 524 } 525 526 /** 527 * Will lookup in {@link org.apache.camel.spi.Registry} for a 528 * {@link ScheduledExecutorService} registered with the given 529 * <tt>executorServiceRef</tt> name. 530 * <p/> 531 * This method will lookup for configured thread pool in the following order 532 * <ul> 533 * <li>from the {@link org.apache.camel.spi.Registry} if found</li> 534 * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile 535 * ThreadPoolProfile(s)}.</li> 536 * <li>if none found, then <tt>null</tt> is returned.</li> 537 * </ul> 538 * 539 * @param routeContext the route context 540 * @param name name which is appended to the thread name, when the 541 * {@link java.util.concurrent.ExecutorService} is created based 542 * on a {@link org.apache.camel.spi.ThreadPoolProfile}. 543 * @param source the source to use the thread pool 544 * @param executorServiceRef reference name of the thread pool 545 * @return the executor service, or <tt>null</tt> if none was found. 546 */ 547 public static ScheduledExecutorService lookupScheduledExecutorServiceRef(RouteContext routeContext, String name, Object source, String executorServiceRef) { 548 549 ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); 550 ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); 551 ObjectHelper.notNull(executorServiceRef, "executorServiceRef"); 552 553 // lookup in registry first and use existing thread pool if exists 554 ScheduledExecutorService answer = routeContext.lookup(executorServiceRef, ScheduledExecutorService.class); 555 if (answer == null) { 556 // then create a thread pool assuming the ref is a thread pool 557 // profile id 558 answer = manager.newScheduledThreadPool(source, name, executorServiceRef); 559 } 560 return answer; 561 } 562 563 /** 564 * Will lookup and get the configured 565 * {@link java.util.concurrent.ScheduledExecutorService} from the given 566 * definition. 567 * <p/> 568 * This method will lookup for configured thread pool in the following order 569 * <ul> 570 * <li>from the definition if any explicit configured executor service.</li> 571 * <li>from the {@link org.apache.camel.spi.Registry} if found</li> 572 * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile 573 * ThreadPoolProfile(s)}.</li> 574 * <li>if none found, then <tt>null</tt> is returned.</li> 575 * </ul> 576 * The various {@link ExecutorServiceAwareDefinition} should use this helper 577 * method to ensure they support configured executor services in the same 578 * coherent way. 579 * 580 * @param routeContext the rout context 581 * @param name name which is appended to the thread name, when the 582 * {@link java.util.concurrent.ExecutorService} is created based 583 * on a {@link org.apache.camel.spi.ThreadPoolProfile}. 584 * @param definition the node definition which may leverage executor 585 * service. 586 * @param useDefault whether to fallback and use a default thread pool, if 587 * no explicit configured 588 * @return the configured executor service, or <tt>null</tt> if none was 589 * configured. 590 * @throws IllegalArgumentException is thrown if the found instance is not a 591 * ScheduledExecutorService type, or lookup of executor service 592 * in {@link org.apache.camel.spi.Registry} was not found 593 */ 594 public static ScheduledExecutorService getConfiguredScheduledExecutorService(RouteContext routeContext, String name, ExecutorServiceAwareDefinition<?> definition, 595 boolean useDefault) 596 throws IllegalArgumentException { 597 ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); 598 ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); 599 600 // prefer to use explicit configured executor on the definition 601 if (definition.getExecutorService() != null) { 602 ExecutorService executorService = definition.getExecutorService(); 603 if (executorService instanceof ScheduledExecutorService) { 604 return (ScheduledExecutorService)executorService; 605 } 606 throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " is not an ScheduledExecutorService instance"); 607 } else if (definition.getExecutorServiceRef() != null) { 608 ScheduledExecutorService answer = lookupScheduledExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef()); 609 if (answer == null) { 610 throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() 611 + " not found in registry (as an ScheduledExecutorService instance) or as a thread pool profile."); 612 } 613 return answer; 614 } else if (useDefault) { 615 return manager.newDefaultScheduledThreadPool(definition, name); 616 } 617 618 return null; 619 } 620 621 /** 622 * The RestoreAction is used to track all the undo/restore actions that need 623 * to be performed to undo any resolution to property placeholders that have 624 * been applied to the camel route defs. This class is private so it does 625 * not get used directly. It's mainly used by the 626 * {@see createPropertyPlaceholdersChangeReverter()} method. 627 */ 628 private static final class RestoreAction implements Runnable { 629 630 private final RestoreAction prevChange; 631 private final ArrayList<Runnable> actions = new ArrayList<>(); 632 633 private RestoreAction(RestoreAction prevChange) { 634 this.prevChange = prevChange; 635 } 636 637 @Override 638 public void run() { 639 for (Runnable action : actions) { 640 action.run(); 641 } 642 actions.clear(); 643 if (prevChange == null) { 644 CURRENT_RESTORE_ACTION.remove(); 645 } else { 646 CURRENT_RESTORE_ACTION.set(prevChange); 647 } 648 } 649 } 650 651 /** 652 * Creates a Runnable which when run will revert property placeholder 653 * updates to the camel route definitions that were done after this method 654 * is called. The Runnable MUST be executed and MUST be executed in the same 655 * thread this method is called from. Therefore it's recommend you use it in 656 * try/finally block like in the following example: 657 * <p/> 658 * 659 * <pre> 660 * Runnable undo = ProcessorDefinitionHelper.createPropertyPlaceholdersChangeReverter(); 661 * try { 662 * // All property resolutions in this block will be reverted. 663 * } finally { 664 * undo.run(); 665 * } 666 * </pre> 667 * 668 * @return a Runnable that when run, will revert any property place holder 669 * changes that occurred on the current thread . 670 */ 671 public static Runnable createPropertyPlaceholdersChangeReverter() { 672 RestoreAction prevChanges = CURRENT_RESTORE_ACTION.get(); 673 RestoreAction rc = new RestoreAction(prevChanges); 674 CURRENT_RESTORE_ACTION.set(rc); 675 return rc; 676 } 677 678 private static void addRestoreAction(Map<String, Consumer<String>> writeProperties, final Map<String, String> properties) { 679 if (properties == null || properties.isEmpty()) { 680 return; 681 } 682 683 RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); 684 if (restoreAction == null) { 685 return; 686 } 687 688 restoreAction.actions.add(new Runnable() { 689 @Override 690 public void run() { 691 try { 692 properties.forEach((k, v) -> { 693 writeProperties.get(k).accept(v); 694 }); 695 } catch (Exception e) { 696 LOG.warn("Cannot restore definition properties. This exception is ignored.", e); 697 } 698 } 699 }); 700 } 701 702 public static void addPropertyPlaceholdersChangeRevertAction(Runnable action) { 703 RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); 704 if (restoreAction == null) { 705 return; 706 } 707 708 restoreAction.actions.add(action); 709 } 710 711 /** 712 * Inspects the given definition and resolves any property placeholders from 713 * its properties. 714 * <p/> 715 * This implementation will check all the getter/setter pairs on this 716 * instance and for all the values (which is a String type) will be property 717 * placeholder resolved. Additional properties are also resolved if the 718 * definition implements {@link OtherAttributesAware}. Also known constant 719 * fields on {@link Exchange} is replaced with their actual constant value, 720 * eg <tt>Exchange.FILE_NAME</tt> is replaced with <tt>CamelFileName</tt>. 721 * 722 * @param camelContext the Camel context 723 * @param definition the definition which should implement 724 * {@link OtherAttributesAware} 725 * @throws Exception is thrown if property placeholders was used and there 726 * was an error resolving them 727 * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) 728 * @see org.apache.camel.component.properties.PropertiesComponent 729 */ 730 @SuppressWarnings("unchecked") 731 public static void resolvePropertyPlaceholders(CamelContext camelContext, Object definition) throws Exception { 732 LOG.trace("Resolving property placeholders for: {}", definition); 733 734 // only do this for models that supports property placeholders 735 if (!(definition instanceof PropertyPlaceholderConfigurer)) { 736 return; 737 } 738 739 PropertyPlaceholderConfigurer ppa = (PropertyPlaceholderConfigurer)definition; 740 741 // find all getter/setter which we can use for property placeholders 742 Map<String, String> changedProperties = new HashMap<>(); 743 Map<String, Supplier<String>> readProperties = ppa.getReadPropertyPlaceholderOptions(camelContext); 744 Map<String, Consumer<String>> writeProperties = ppa.getWritePropertyPlaceholderOptions(camelContext); 745 746 // definitions may have additional placeholder properties (can typically 747 // be used by the XML DSL to 748 // allow to configure using placeholders for properties that are not 749 // xs:string types) 750 if (definition instanceof OtherAttributesAware) { 751 OtherAttributesAware ooa = (OtherAttributesAware)definition; 752 753 if (ooa.getOtherAttributes() != null && !ooa.getOtherAttributes().isEmpty()) { 754 Map<String, Supplier<String>> extraRead = new HashMap<>(); 755 if (readProperties != null && !readProperties.isEmpty()) { 756 extraRead.putAll(readProperties); 757 } 758 Map<String, Consumer<String>> extraWrite = new HashMap<>(); 759 if (writeProperties != null && !writeProperties.isEmpty()) { 760 extraWrite.putAll(writeProperties); 761 } 762 763 Map<QName, Object> other = ooa.getOtherAttributes(); 764 other.forEach((k, v) -> { 765 if (Constants.PLACEHOLDER_QNAME.equals(k.getNamespaceURI())) { 766 if (v instanceof String) { 767 // value must be enclosed with placeholder tokens 768 String s = (String)v; 769 String prefixToken = PropertiesComponent.PREFIX_TOKEN; 770 String suffixToken = PropertiesComponent.SUFFIX_TOKEN; 771 772 if (!s.startsWith(prefixToken)) { 773 s = prefixToken + s; 774 } 775 if (!s.endsWith(suffixToken)) { 776 s = s + suffixToken; 777 } 778 final String value = s; 779 extraRead.put(k.getLocalPart(), () -> value); 780 extraWrite.put(k.getLocalPart(), text -> { 781 try { 782 PropertyBindingSupport.build().withCamelContext(camelContext).withTarget(definition).withMandatory(true).withProperty(k.getLocalPart(), text) 783 .bind(); 784 } catch (Exception e) { 785 throw RuntimeCamelException.wrapRuntimeException(e); 786 } 787 }); 788 } 789 } 790 }); 791 readProperties = extraRead; 792 writeProperties = extraWrite; 793 } 794 } 795 796 if (readProperties != null && !readProperties.isEmpty()) { 797 if (LOG.isTraceEnabled()) { 798 LOG.trace("There are {} properties on: {}", readProperties.size(), definition); 799 } 800 801 // lookup and resolve properties for String based properties 802 for (Map.Entry<String, Supplier<String>> entry : readProperties.entrySet()) { 803 String name = entry.getKey(); 804 String value = entry.getValue().get(); 805 String text = camelContext.resolvePropertyPlaceholders(value); 806 807 // is the value a known field (currently we only support 808 // constants from Exchange.class) 809 if (text != null && text.startsWith("Exchange.")) { 810 String field = StringHelper.after(text, "Exchange."); 811 String constant = ExchangeConstantProvider.lookup(field); 812 if (constant != null) { 813 text = constant; 814 } else { 815 throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class"); 816 } 817 } 818 819 if (!Objects.equals(text, value)) { 820 writeProperties.get(name).accept(text); 821 changedProperties.put(name, value); 822 if (LOG.isDebugEnabled()) { 823 LOG.debug("Changed property [{}] from: {} to: {}", name, value, text); 824 } 825 } 826 } 827 } 828 addRestoreAction(writeProperties, changedProperties); 829 } 830 831}