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