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