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