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