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 instanceof TransactedDefinition || output instanceof PolicyDefinition) { 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 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() + " not found in registry or as a thread pool profile."); 550 } 551 return answer; 552 } else if (useDefault) { 553 return manager.newDefaultScheduledThreadPool(definition, name); 554 } 555 556 return null; 557 } 558 559 /** 560 * The RestoreAction is used to track all the undo/restore actions 561 * that need to be performed to undo any resolution to property placeholders 562 * that have been applied to the camel route defs. This class is private 563 * so it does not get used directly. It's mainly used by the {@see createPropertyPlaceholdersChangeReverter()} 564 * method. 565 */ 566 private static final class RestoreAction implements Runnable { 567 568 private final RestoreAction prevChange; 569 private final ArrayList<Runnable> actions = new ArrayList<Runnable>(); 570 571 private RestoreAction(RestoreAction prevChange) { 572 this.prevChange = prevChange; 573 } 574 575 @Override 576 public void run() { 577 for (Runnable action : actions) { 578 action.run(); 579 } 580 actions.clear(); 581 if (prevChange == null) { 582 CURRENT_RESTORE_ACTION.remove(); 583 } else { 584 CURRENT_RESTORE_ACTION.set(prevChange); 585 } 586 } 587 } 588 589 /** 590 * Creates a Runnable which when run will revert property placeholder 591 * updates to the camel route definitions that were done after this method 592 * is called. The Runnable MUST be executed and MUST be executed in the 593 * same thread this method is called from. Therefore it's recommend you 594 * use it in try/finally block like in the following example: 595 * <p/> 596 * <pre> 597 * Runnable undo = ProcessorDefinitionHelper.createPropertyPlaceholdersChangeReverter(); 598 * try { 599 * // All property resolutions in this block will be reverted. 600 * } finally { 601 * undo.run(); 602 * } 603 * </pre> 604 * 605 * @return a Runnable that when run, will revert any property place holder 606 * changes that occurred on the current thread . 607 */ 608 public static Runnable createPropertyPlaceholdersChangeReverter() { 609 RestoreAction prevChanges = CURRENT_RESTORE_ACTION.get(); 610 RestoreAction rc = new RestoreAction(prevChanges); 611 CURRENT_RESTORE_ACTION.set(rc); 612 return rc; 613 } 614 615 private static void addRestoreAction(final Object target, final Map<String, Object> properties) { 616 addRestoreAction(null, target, properties); 617 } 618 619 private static void addRestoreAction(final CamelContext context, final Object target, final Map<String, Object> properties) { 620 if (properties.isEmpty()) { 621 return; 622 } 623 624 RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); 625 if (restoreAction == null) { 626 return; 627 } 628 629 restoreAction.actions.add(new Runnable() { 630 @Override 631 public void run() { 632 try { 633 IntrospectionSupport.setProperties(context, null, target, properties); 634 } catch (Exception e) { 635 LOG.warn("Could not restore definition properties", e); 636 } 637 } 638 }); 639 } 640 641 public static void addPropertyPlaceholdersChangeRevertAction(Runnable action) { 642 RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); 643 if (restoreAction == null) { 644 return; 645 } 646 647 restoreAction.actions.add(action); 648 } 649 650 /** 651 * Inspects the given definition and resolves any property placeholders from its properties. 652 * <p/> 653 * This implementation will check all the getter/setter pairs on this instance and for all the values 654 * (which is a String type) will be property placeholder resolved. 655 * 656 * @param routeContext the route context 657 * @param definition the definition 658 * @throws Exception is thrown if property placeholders was used and there was an error resolving them 659 * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) 660 * @see org.apache.camel.component.properties.PropertiesComponent 661 * @deprecated use {@link #resolvePropertyPlaceholders(org.apache.camel.CamelContext, Object)} 662 */ 663 @Deprecated 664 public static void resolvePropertyPlaceholders(RouteContext routeContext, Object definition) throws Exception { 665 resolvePropertyPlaceholders(routeContext.getCamelContext(), definition); 666 } 667 668 /** 669 * Inspects the given definition and resolves any property placeholders from its properties. 670 * <p/> 671 * This implementation will check all the getter/setter pairs on this instance and for all the values 672 * (which is a String type) will be property placeholder resolved. The definition should implement {@link OtherAttributesAware} 673 * 674 * @param camelContext the Camel context 675 * @param definition the definition which should implement {@link OtherAttributesAware} 676 * @throws Exception is thrown if property placeholders was used and there was an error resolving them 677 * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) 678 * @see org.apache.camel.component.properties.PropertiesComponent 679 */ 680 public static void resolvePropertyPlaceholders(CamelContext camelContext, Object definition) throws Exception { 681 LOG.trace("Resolving property placeholders for: {}", definition); 682 683 // find all getter/setter which we can use for property placeholders 684 Map<String, Object> properties = new HashMap<String, Object>(); 685 IntrospectionSupport.getProperties(definition, properties, null); 686 687 OtherAttributesAware other = null; 688 if (definition instanceof OtherAttributesAware) { 689 other = (OtherAttributesAware) definition; 690 } 691 // include additional properties which have the Camel placeholder QName 692 // and when the definition parameter is this (otherAttributes belong to this) 693 if (other != null && other.getOtherAttributes() != null) { 694 for (QName key : other.getOtherAttributes().keySet()) { 695 if (Constants.PLACEHOLDER_QNAME.equals(key.getNamespaceURI())) { 696 String local = key.getLocalPart(); 697 Object value = other.getOtherAttributes().get(key); 698 if (value != null && value instanceof String) { 699 // enforce a properties component to be created if none existed 700 CamelContextHelper.lookupPropertiesComponent(camelContext, true); 701 702 // value must be enclosed with placeholder tokens 703 String s = (String) value; 704 String prefixToken = camelContext.getPropertyPrefixToken(); 705 String suffixToken = camelContext.getPropertySuffixToken(); 706 if (prefixToken == null) { 707 throw new IllegalArgumentException("Property with name [" + local + "] uses property placeholders; however, no properties component is configured."); 708 } 709 710 if (!s.startsWith(prefixToken)) { 711 s = prefixToken + s; 712 } 713 if (!s.endsWith(suffixToken)) { 714 s = s + suffixToken; 715 } 716 value = s; 717 } 718 properties.put(local, value); 719 } 720 } 721 } 722 723 Map<String, Object> changedProperties = new HashMap<String, Object>(); 724 if (!properties.isEmpty()) { 725 LOG.trace("There are {} properties on: {}", properties.size(), definition); 726 // lookup and resolve properties for String based properties 727 for (Map.Entry<String, Object> entry : properties.entrySet()) { 728 // the name is always a String 729 String name = entry.getKey(); 730 Object value = entry.getValue(); 731 if (value instanceof String) { 732 // value must be a String, as a String is the key for a property placeholder 733 String text = (String) value; 734 text = camelContext.resolvePropertyPlaceholders(text); 735 if (text != value) { 736 // invoke setter as the text has changed 737 boolean changed = IntrospectionSupport.setProperty(camelContext.getTypeConverter(), definition, name, text); 738 if (!changed) { 739 throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + definition); 740 } 741 changedProperties.put(name, value); 742 if (LOG.isDebugEnabled()) { 743 LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text}); 744 } 745 } 746 } 747 } 748 } 749 addRestoreAction(camelContext, definition, changedProperties); 750 } 751 752 /** 753 * Inspects the given definition and resolves known fields 754 * <p/> 755 * This implementation will check all the getter/setter pairs on this instance and for all the values 756 * (which is a String type) will check if it refers to a known field (such as on Exchange). 757 * 758 * @param definition the definition 759 */ 760 public static void resolveKnownConstantFields(Object definition) throws Exception { 761 LOG.trace("Resolving known fields for: {}", definition); 762 763 // find all String getter/setter 764 Map<String, Object> properties = new HashMap<String, Object>(); 765 IntrospectionSupport.getProperties(definition, properties, null); 766 767 Map<String, Object> changedProperties = new HashMap<String, Object>(); 768 if (!properties.isEmpty()) { 769 LOG.trace("There are {} properties on: {}", properties.size(), definition); 770 771 // lookup and resolve known constant fields for String based properties 772 for (Map.Entry<String, Object> entry : properties.entrySet()) { 773 String name = entry.getKey(); 774 Object value = entry.getValue(); 775 if (value instanceof String) { 776 // we can only resolve String typed values 777 String text = (String) value; 778 779 // is the value a known field (currently we only support constants from Exchange.class) 780 if (text.startsWith("Exchange.")) { 781 String field = ObjectHelper.after(text, "Exchange."); 782 String constant = ObjectHelper.lookupConstantFieldValue(Exchange.class, field); 783 if (constant != null) { 784 // invoke setter as the text has changed 785 IntrospectionSupport.setProperty(definition, name, constant); 786 changedProperties.put(name, value); 787 if (LOG.isDebugEnabled()) { 788 LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, constant}); 789 } 790 } else { 791 throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class"); 792 } 793 } 794 } 795 } 796 } 797 addRestoreAction(definition, changedProperties); 798 } 799 800}