/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.model; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; import javax.xml.namespace.QName; import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.spi.ExecutorServiceManager; import org.apache.camel.spi.RouteContext; import org.apache.camel.util.CamelContextHelper; import org.apache.camel.util.IntrospectionSupport; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Helper class for ProcessorDefinition and the other model classes. */ public final class ProcessorDefinitionHelper { private static final Logger LOG = LoggerFactory.getLogger(ProcessorDefinitionHelper.class); private static final ThreadLocal<RestoreAction> CURRENT_RESTORE_ACTION = new ThreadLocal<RestoreAction>(); private ProcessorDefinitionHelper() { } /** * Looks for the given type in the list of outputs and recurring all the children as well. * * @param outputs list of outputs, can be null or empty. * @param type the type to look for * @return the found definitions, or <tt>null</tt> if not found */ public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) { return filterTypeInOutputs(outputs, type, -1); } /** * Looks for the given type in the list of outputs and recurring all the children as well. * * @param outputs list of outputs, can be null or empty. * @param type the type to look for * @param maxDeep maximum levels deep to traverse * @return the found definitions, or <tt>null</tt> if not found */ public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type, int maxDeep) { List<T> found = new ArrayList<T>(); doFindType(outputs, type, found, maxDeep); return found.iterator(); } /** * Looks for the given type in the list of outputs and recurring all the children as well. * Will stop at first found and return it. * * @param outputs list of outputs, can be null or empty. * @param type the type to look for * @return the first found type, or <tt>null</tt> if not found */ public static <T> T findFirstTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) { List<T> found = new ArrayList<T>(); doFindType(outputs, type, found, -1); if (found.isEmpty()) { return null; } return found.iterator().next(); } /** * Is the given child the first in the outputs from the parent? * * @param parentType the type the parent must be * @param node the node * @return <tt>true</tt> if first child, <tt>false</tt> otherwise */ public static boolean isFirstChildOfType(Class<?> parentType, ProcessorDefinition<?> node) { if (node == null || node.getParent() == null) { return false; } if (node.getParent().getOutputs().isEmpty()) { return false; } if (!(node.getParent().getClass().equals(parentType))) { return false; } return node.getParent().getOutputs().get(0).equals(node); } /** * Is the given node parent(s) of the given type * * @param parentType the parent type * @param node the current node * @param recursive whether or not to check grand parent(s) as well * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt> otherwise */ public static boolean isParentOfType(Class<?> parentType, ProcessorDefinition<?> node, boolean recursive) { if (node == null || node.getParent() == null) { return false; } if (parentType.isAssignableFrom(node.getParent().getClass())) { return true; } else if (recursive) { // recursive up the tree of parents return isParentOfType(parentType, node.getParent(), true); } else { // no match return false; } } /** * Gets the route definition the given node belongs to. * * @param node the node * @return the route, or <tt>null</tt> if not possible to find */ public static RouteDefinition getRoute(ProcessorDefinition<?> node) { if (node == null) { return null; } ProcessorDefinition<?> def = node; // drill to the top while (def != null && def.getParent() != null) { def = def.getParent(); } if (def instanceof RouteDefinition) { return (RouteDefinition) def; } else { // not found return null; } } /** * Gets the route id the given node belongs to. * * @param node the node * @return the route id, or <tt>null</tt> if not possible to find */ public static String getRouteId(ProcessorDefinition<?> node) { RouteDefinition route = getRoute(node); return route != null ? route.getId() : null; } /** * Traverses the node, including its children (recursive), and gathers all the node ids. * * @param node the target node * @param set set to store ids, if <tt>null</tt> a new set will be created * @param onlyCustomId whether to only store custom assigned ids (ie. {@link org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()} * @param includeAbstract whether to include abstract nodes (ie. {@link org.apache.camel.model.ProcessorDefinition#isAbstract()} * @return the set with the found ids. */ public static Set<String> gatherAllNodeIds(ProcessorDefinition<?> node, Set<String> set, boolean onlyCustomId, boolean includeAbstract) { if (node == null) { return set; } // skip abstract if (node.isAbstract() && !includeAbstract) { return set; } if (set == null) { set = new LinkedHashSet<String>(); } // add ourselves if (node.getId() != null) { if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) { set.add(node.getId()); } } // traverse outputs and recursive children as well List<ProcessorDefinition<?>> children = node.getOutputs(); if (children != null && !children.isEmpty()) { for (ProcessorDefinition<?> child : children) { // traverse children also gatherAllNodeIds(child, set, onlyCustomId, includeAbstract); } } return set; } private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int maxDeep) { // do we have any top level abstracts, then we should max deep one more level down // as that is really what we want to traverse as well if (maxDeep > 0) { for (ProcessorDefinition<?> out : outputs) { if (out.isAbstract() && out.isTopLevelOnly()) { maxDeep = maxDeep + 1; break; } } } // start from level 1 doFindType(outputs, type, found, 1, maxDeep); } @SuppressWarnings({"unchecked", "rawtypes"}) private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int current, int maxDeep) { if (outputs == null || outputs.isEmpty()) { return; } // break out if (maxDeep > 0 && current > maxDeep) { return; } for (ProcessorDefinition out : outputs) { // send is much common if (out instanceof SendDefinition) { SendDefinition send = (SendDefinition) out; List<ProcessorDefinition<?>> children = send.getOutputs(); doFindType(children, type, found, ++current, maxDeep); } // special for choice if (out instanceof ChoiceDefinition) { ChoiceDefinition choice = (ChoiceDefinition) out; // ensure to add ourself if we match also if (type.isInstance(choice)) { found.add((T) choice); } // only look at when/otherwise if current < maxDeep (or max deep is disabled) if (maxDeep < 0 || current < maxDeep) { for (WhenDefinition when : choice.getWhenClauses()) { if (type.isInstance(when)) { found.add((T) when); } List<ProcessorDefinition<?>> children = when.getOutputs(); doFindType(children, type, found, ++current, maxDeep); } // otherwise is optional if (choice.getOtherwise() != null) { List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs(); doFindType(children, type, found, ++current, maxDeep); } } // do not check children as we already did that continue; } // special for try ... catch ... finally if (out instanceof TryDefinition) { TryDefinition doTry = (TryDefinition) out; // ensure to add ourself if we match also if (type.isInstance(doTry)) { found.add((T) doTry); } // only look at children if current < maxDeep (or max deep is disabled) if (maxDeep < 0 || current < maxDeep) { List<ProcessorDefinition<?>> doTryOut = doTry.getOutputsWithoutCatches(); doFindType(doTryOut, type, found, ++current, maxDeep); List<CatchDefinition> doTryCatch = doTry.getCatchClauses(); for (CatchDefinition doCatch : doTryCatch) { doFindType(doCatch.getOutputs(), type, found, ++current, maxDeep); } if (doTry.getFinallyClause() != null) { doFindType(doTry.getFinallyClause().getOutputs(), type, found, ++current, maxDeep); } } // do not check children as we already did that continue; } // special for some types which has special outputs if (out instanceof OutputDefinition) { OutputDefinition outDef = (OutputDefinition) out; // ensure to add ourself if we match also if (type.isInstance(outDef)) { found.add((T) outDef); } List<ProcessorDefinition<?>> outDefOut = outDef.getOutputs(); doFindType(outDefOut, type, found, ++current, maxDeep); // do not check children as we already did that continue; } if (type.isInstance(out)) { found.add((T) out); } // try children as well List<ProcessorDefinition<?>> children = out.getOutputs(); doFindType(children, type, found, ++current, maxDeep); } } /** * Is there any outputs in the given list. * <p/> * Is used for check if the route output has any real outputs (non abstracts) * * @param outputs the outputs * @param excludeAbstract whether or not to exclude abstract outputs (e.g. skip onException etc.) * @return <tt>true</tt> if has outputs, otherwise <tt>false</tt> is returned */ @SuppressWarnings({"unchecked", "rawtypes"}) public static boolean hasOutputs(List<ProcessorDefinition<?>> outputs, boolean excludeAbstract) { if (outputs == null || outputs.isEmpty()) { return false; } if (!excludeAbstract) { return !outputs.isEmpty(); } for (ProcessorDefinition output : outputs) { if (output instanceof TransactedDefinition || output instanceof PolicyDefinition) { // special for those as they wrap entire output, so we should just check its output return hasOutputs(output.getOutputs(), excludeAbstract); } if (!output.isAbstract()) { return true; } } return false; } /** * Determines whether a new thread pool will be created or not. * <p/> * This is used to know if a new thread pool will be created, and therefore is not shared by others, and therefore * exclusive to the definition. * * @param routeContext the route context * @param definition the node definition which may leverage executor service. * @param useDefault whether to fallback and use a default thread pool, if no explicit configured * @return <tt>true</tt> if a new thread pool will be created, <tt>false</tt> if not * @see #getConfiguredExecutorService(org.apache.camel.spi.RouteContext, String, ExecutorServiceAwareDefinition, boolean) */ public static boolean willCreateNewThreadPool(RouteContext routeContext, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) { ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); if (definition.getExecutorService() != null) { // no there is a custom thread pool configured return false; } else if (definition.getExecutorServiceRef() != null) { ExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(definition.getExecutorServiceRef(), ExecutorService.class); // if no existing thread pool, then we will have to create a new thread pool return answer == null; } else if (useDefault) { return true; } return false; } /** * Will lookup in {@link org.apache.camel.spi.Registry} for a {@link ExecutorService} registered with the given * <tt>executorServiceRef</tt> name. * <p/> * This method will lookup for configured thread pool in the following order * <ul> * <li>from the {@link org.apache.camel.spi.Registry} if found</li> * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li> * <li>if none found, then <tt>null</tt> is returned.</li> * </ul> * * @param routeContext the route context * @param name name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService} * is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}. * @param source the source to use the thread pool * @param executorServiceRef reference name of the thread pool * @return the executor service, or <tt>null</tt> if none was found. */ public static ExecutorService lookupExecutorServiceRef(RouteContext routeContext, String name, Object source, String executorServiceRef) { ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); ObjectHelper.notNull(executorServiceRef, "executorServiceRef"); // lookup in registry first and use existing thread pool if exists ExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(executorServiceRef, ExecutorService.class); if (answer == null) { // then create a thread pool assuming the ref is a thread pool profile id answer = manager.newThreadPool(source, name, executorServiceRef); } return answer; } /** * Will lookup and get the configured {@link java.util.concurrent.ExecutorService} from the given definition. * <p/> * This method will lookup for configured thread pool in the following order * <ul> * <li>from the definition if any explicit configured executor service.</li> * <li>from the {@link org.apache.camel.spi.Registry} if found</li> * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li> * <li>if none found, then <tt>null</tt> is returned.</li> * </ul> * The various {@link ExecutorServiceAwareDefinition} should use this helper method to ensure they support * configured executor services in the same coherent way. * * @param routeContext the route context * @param name name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService} * is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}. * @param definition the node definition which may leverage executor service. * @param useDefault whether to fallback and use a default thread pool, if no explicit configured * @return the configured executor service, or <tt>null</tt> if none was configured. * @throws IllegalArgumentException is thrown if lookup of executor service in {@link org.apache.camel.spi.Registry} was not found */ public static ExecutorService getConfiguredExecutorService(RouteContext routeContext, String name, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) throws IllegalArgumentException { ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); // prefer to use explicit configured executor on the definition if (definition.getExecutorService() != null) { return definition.getExecutorService(); } else if (definition.getExecutorServiceRef() != null) { // lookup in registry first and use existing thread pool if exists ExecutorService answer = lookupExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef()); if (answer == null) { throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " not found in registry (as an ExecutorService instance) or as a thread pool profile."); } return answer; } else if (useDefault) { return manager.newDefaultThreadPool(definition, name); } return null; } /** * Will lookup in {@link org.apache.camel.spi.Registry} for a {@link ScheduledExecutorService} registered with the given * <tt>executorServiceRef</tt> name. * <p/> * This method will lookup for configured thread pool in the following order * <ul> * <li>from the {@link org.apache.camel.spi.Registry} if found</li> * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li> * <li>if none found, then <tt>null</tt> is returned.</li> * </ul> * * @param routeContext the route context * @param name name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService} * is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}. * @param source the source to use the thread pool * @param executorServiceRef reference name of the thread pool * @return the executor service, or <tt>null</tt> if none was found. */ public static ScheduledExecutorService lookupScheduledExecutorServiceRef(RouteContext routeContext, String name, Object source, String executorServiceRef) { ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); ObjectHelper.notNull(executorServiceRef, "executorServiceRef"); // lookup in registry first and use existing thread pool if exists ScheduledExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(executorServiceRef, ScheduledExecutorService.class); if (answer == null) { // then create a thread pool assuming the ref is a thread pool profile id answer = manager.newScheduledThreadPool(source, name, executorServiceRef); } return answer; } /** * Will lookup and get the configured {@link java.util.concurrent.ScheduledExecutorService} from the given definition. * <p/> * This method will lookup for configured thread pool in the following order * <ul> * <li>from the definition if any explicit configured executor service.</li> * <li>from the {@link org.apache.camel.spi.Registry} if found</li> * <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li> * <li>if none found, then <tt>null</tt> is returned.</li> * </ul> * The various {@link ExecutorServiceAwareDefinition} should use this helper method to ensure they support * configured executor services in the same coherent way. * * @param routeContext the rout context * @param name name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService} * is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}. * @param definition the node definition which may leverage executor service. * @param useDefault whether to fallback and use a default thread pool, if no explicit configured * @return the configured executor service, or <tt>null</tt> if none was configured. * @throws IllegalArgumentException is thrown if the found instance is not a ScheduledExecutorService type, * or lookup of executor service in {@link org.apache.camel.spi.Registry} was not found */ public static ScheduledExecutorService getConfiguredScheduledExecutorService(RouteContext routeContext, String name, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) throws IllegalArgumentException { ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager(); ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext()); // prefer to use explicit configured executor on the definition if (definition.getExecutorService() != null) { ExecutorService executorService = definition.getExecutorService(); if (executorService instanceof ScheduledExecutorService) { return (ScheduledExecutorService) executorService; } throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " is not an ScheduledExecutorService instance"); } else if (definition.getExecutorServiceRef() != null) { ScheduledExecutorService answer = lookupScheduledExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef()); if (answer == null) { throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " not found in registry (as an ScheduledExecutorService instance) or as a thread pool profile."); } return answer; } else if (useDefault) { return manager.newDefaultScheduledThreadPool(definition, name); } return null; } /** * The RestoreAction is used to track all the undo/restore actions * that need to be performed to undo any resolution to property placeholders * that have been applied to the camel route defs. This class is private * so it does not get used directly. It's mainly used by the {@see createPropertyPlaceholdersChangeReverter()} * method. */ private static final class RestoreAction implements Runnable { private final RestoreAction prevChange; private final ArrayList<Runnable> actions = new ArrayList<Runnable>(); private RestoreAction(RestoreAction prevChange) { this.prevChange = prevChange; } @Override public void run() { for (Runnable action : actions) { action.run(); } actions.clear(); if (prevChange == null) { CURRENT_RESTORE_ACTION.remove(); } else { CURRENT_RESTORE_ACTION.set(prevChange); } } } /** * Creates a Runnable which when run will revert property placeholder * updates to the camel route definitions that were done after this method * is called. The Runnable MUST be executed and MUST be executed in the * same thread this method is called from. Therefore it's recommend you * use it in try/finally block like in the following example: * <p/> * <pre> * Runnable undo = ProcessorDefinitionHelper.createPropertyPlaceholdersChangeReverter(); * try { * // All property resolutions in this block will be reverted. * } finally { * undo.run(); * } * </pre> * * @return a Runnable that when run, will revert any property place holder * changes that occurred on the current thread . */ public static Runnable createPropertyPlaceholdersChangeReverter() { RestoreAction prevChanges = CURRENT_RESTORE_ACTION.get(); RestoreAction rc = new RestoreAction(prevChanges); CURRENT_RESTORE_ACTION.set(rc); return rc; } private static void addRestoreAction(final Object target, final Map<String, Object> properties) { addRestoreAction(null, target, properties); } private static void addRestoreAction(final CamelContext context, final Object target, final Map<String, Object> properties) { if (properties.isEmpty()) { return; } RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); if (restoreAction == null) { return; } restoreAction.actions.add(new Runnable() { @Override public void run() { try { IntrospectionSupport.setProperties(context, null, target, properties); } catch (Exception e) { LOG.warn("Could not restore definition properties", e); } } }); } public static void addPropertyPlaceholdersChangeRevertAction(Runnable action) { RestoreAction restoreAction = CURRENT_RESTORE_ACTION.get(); if (restoreAction == null) { return; } restoreAction.actions.add(action); } /** * Inspects the given definition and resolves any property placeholders from its properties. * <p/> * This implementation will check all the getter/setter pairs on this instance and for all the values * (which is a String type) will be property placeholder resolved. * * @param routeContext the route context * @param definition the definition * @throws Exception is thrown if property placeholders was used and there was an error resolving them * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) * @see org.apache.camel.component.properties.PropertiesComponent * @deprecated use {@link #resolvePropertyPlaceholders(org.apache.camel.CamelContext, Object)} */ @Deprecated public static void resolvePropertyPlaceholders(RouteContext routeContext, Object definition) throws Exception { resolvePropertyPlaceholders(routeContext.getCamelContext(), definition); } /** * Inspects the given definition and resolves any property placeholders from its properties. * <p/> * This implementation will check all the getter/setter pairs on this instance and for all the values * (which is a String type) will be property placeholder resolved. The definition should implement {@link OtherAttributesAware} * * @param camelContext the Camel context * @param definition the definition which should implement {@link OtherAttributesAware} * @throws Exception is thrown if property placeholders was used and there was an error resolving them * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String) * @see org.apache.camel.component.properties.PropertiesComponent */ public static void resolvePropertyPlaceholders(CamelContext camelContext, Object definition) throws Exception { LOG.trace("Resolving property placeholders for: {}", definition); // find all getter/setter which we can use for property placeholders Map<String, Object> properties = new HashMap<String, Object>(); IntrospectionSupport.getProperties(definition, properties, null); OtherAttributesAware other = null; if (definition instanceof OtherAttributesAware) { other = (OtherAttributesAware) definition; } // include additional properties which have the Camel placeholder QName // and when the definition parameter is this (otherAttributes belong to this) if (other != null && other.getOtherAttributes() != null) { for (QName key : other.getOtherAttributes().keySet()) { if (Constants.PLACEHOLDER_QNAME.equals(key.getNamespaceURI())) { String local = key.getLocalPart(); Object value = other.getOtherAttributes().get(key); if (value != null && value instanceof String) { // enforce a properties component to be created if none existed CamelContextHelper.lookupPropertiesComponent(camelContext, true); // value must be enclosed with placeholder tokens String s = (String) value; String prefixToken = camelContext.getPropertyPrefixToken(); String suffixToken = camelContext.getPropertySuffixToken(); if (prefixToken == null) { throw new IllegalArgumentException("Property with name [" + local + "] uses property placeholders; however, no properties component is configured."); } if (!s.startsWith(prefixToken)) { s = prefixToken + s; } if (!s.endsWith(suffixToken)) { s = s + suffixToken; } value = s; } properties.put(local, value); } } } Map<String, Object> changedProperties = new HashMap<String, Object>(); if (!properties.isEmpty()) { LOG.trace("There are {} properties on: {}", properties.size(), definition); // lookup and resolve properties for String based properties for (Map.Entry<String, Object> entry : properties.entrySet()) { // the name is always a String String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof String) { // value must be a String, as a String is the key for a property placeholder String text = (String) value; text = camelContext.resolvePropertyPlaceholders(text); if (text != value) { // invoke setter as the text has changed boolean changed = IntrospectionSupport.setProperty(camelContext.getTypeConverter(), definition, name, text); if (!changed) { throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + definition); } changedProperties.put(name, value); if (LOG.isDebugEnabled()) { LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text}); } } } } } addRestoreAction(camelContext, definition, changedProperties); } /** * Inspects the given definition and resolves known fields * <p/> * This implementation will check all the getter/setter pairs on this instance and for all the values * (which is a String type) will check if it refers to a known field (such as on Exchange). * * @param definition the definition */ public static void resolveKnownConstantFields(Object definition) throws Exception { LOG.trace("Resolving known fields for: {}", definition); // find all String getter/setter Map<String, Object> properties = new HashMap<String, Object>(); IntrospectionSupport.getProperties(definition, properties, null); Map<String, Object> changedProperties = new HashMap<String, Object>(); if (!properties.isEmpty()) { LOG.trace("There are {} properties on: {}", properties.size(), definition); // lookup and resolve known constant fields for String based properties for (Map.Entry<String, Object> entry : properties.entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); if (value instanceof String) { // we can only resolve String typed values String text = (String) value; // is the value a known field (currently we only support constants from Exchange.class) if (text.startsWith("Exchange.")) { String field = ObjectHelper.after(text, "Exchange."); String constant = ObjectHelper.lookupConstantFieldValue(Exchange.class, field); if (constant != null) { // invoke setter as the text has changed IntrospectionSupport.setProperty(definition, name, constant); changedProperties.put(name, value); if (LOG.isDebugEnabled()) { LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, constant}); } } else { throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class"); } } } } } addRestoreAction(definition, changedProperties); } }