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