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
047    private ProcessorDefinitionHelper() {
048    }
049
050    /**
051     * Looks for the given type in the list of outputs and recurring all the children as well.
052     *
053     * @param outputs  list of outputs, can be null or empty.
054     * @param type     the type to look for
055     * @return         the found definitions, or <tt>null</tt> if not found
056     */
057    public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) {
058        return filterTypeInOutputs(outputs, type, -1);
059    }
060
061    /**
062     * Looks for the given type in the list of outputs and recurring all the children as well.
063     *
064     * @param outputs  list of outputs, can be null or empty.
065     * @param type     the type to look for
066     * @param maxDeep  maximum levels deep to traverse
067     * @return         the found definitions, or <tt>null</tt> if not found
068     */
069    public static <T> Iterator<T> filterTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type, int maxDeep) {
070        List<T> found = new ArrayList<T>();
071        doFindType(outputs, type, found, maxDeep);
072        return found.iterator();
073    }
074
075    /**
076     * Looks for the given type in the list of outputs and recurring all the children as well.
077     * Will stop at first found and return it.
078     *
079     * @param outputs  list of outputs, can be null or empty.
080     * @param type     the type to look for
081     * @return         the first found type, or <tt>null</tt> if not found
082     */
083    public static <T> T findFirstTypeInOutputs(List<ProcessorDefinition<?>> outputs, Class<T> type) {
084        List<T> found = new ArrayList<T>();
085        doFindType(outputs, type, found, -1);
086        if (found.isEmpty()) {
087            return null;
088        }
089        return found.iterator().next();
090    }
091
092    /**
093     * Is the given child the first in the outputs from the parent?
094     *
095     * @param parentType the type the parent must be
096     * @param node the node
097     * @return <tt>true</tt> if first child, <tt>false</tt> otherwise
098     */
099    public static boolean isFirstChildOfType(Class<?> parentType, ProcessorDefinition<?> node) {
100        if (node == null || node.getParent() == null) {
101            return false;
102        }
103
104        if (node.getParent().getOutputs().isEmpty()) {
105            return false;
106        }
107
108        if (!(node.getParent().getClass().equals(parentType))) {
109            return false;
110        }
111
112        return node.getParent().getOutputs().get(0).equals(node);
113    }
114
115    /**
116     * Is the given node parent(s) of the given type
117     * @param parentType   the parent type
118     * @param node         the current node
119     * @param recursive    whether or not to check grand parent(s) as well
120     * @return <tt>true</tt> if parent(s) is of given type, <tt>false</tt> otherwise
121     */
122    public static boolean isParentOfType(Class<?> parentType, ProcessorDefinition<?> node, boolean recursive) {
123        if (node == null || node.getParent() == null) {
124            return false;
125        }
126
127        if (parentType.isAssignableFrom(node.getParent().getClass())) {
128            return true;
129        } else if (recursive) {
130            // recursive up the tree of parents
131            return isParentOfType(parentType, node.getParent(), true);
132        } else {
133            // no match
134            return false;
135        }
136    }
137
138    /**
139     * Gets the route definition the given node belongs to.
140     *
141     * @param node the node
142     * @return the route, or <tt>null</tt> if not possible to find
143     */
144    public static RouteDefinition getRoute(ProcessorDefinition<?> node) {
145        if (node == null) {
146            return null;
147        }
148
149        ProcessorDefinition<?> def = node;
150        // drill to the top
151        while (def != null && def.getParent() != null) {
152            def = def.getParent();
153        }
154
155        if (def instanceof RouteDefinition) {
156            return (RouteDefinition) def;
157        } else {
158            // not found
159            return null;
160        }
161    }
162
163    /**
164     * Gets the route id the given node belongs to.
165     *
166     * @param node the node
167     * @return the route id, or <tt>null</tt> if not possible to find
168     */
169    public static String getRouteId(ProcessorDefinition<?> node) {
170        RouteDefinition route = getRoute(node);
171        return route != null ? route.getId() : null;
172    }
173
174    /**
175     * Traverses the node, including its children (recursive), and gathers all the node ids.
176     *
177     * @param node  the target node
178     * @param set   set to store ids, if <tt>null</tt> a new set will be created
179     * @param onlyCustomId  whether to only store custom assigned ids (ie. {@link org.apache.camel.model.OptionalIdentifiedDefinition#hasCustomIdAssigned()}
180     * @param includeAbstract whether to include abstract nodes (ie. {@link org.apache.camel.model.ProcessorDefinition#isAbstract()}
181     * @return the set with the found ids.
182     */
183    public static Set<String> gatherAllNodeIds(ProcessorDefinition<?> node, Set<String> set,
184                                               boolean onlyCustomId, boolean includeAbstract) {
185        if (node == null) {
186            return set;
187        }
188
189        // skip abstract
190        if (node.isAbstract() && !includeAbstract) {
191            return set;
192        }
193
194        if (set == null) {
195            set = new LinkedHashSet<String>();
196        }
197
198        // add ourselves
199        if (node.getId() != null) {
200            if (!onlyCustomId || node.hasCustomIdAssigned() && onlyCustomId) {
201                set.add(node.getId());
202            }
203        }
204
205        // traverse outputs and recursive children as well
206        List<ProcessorDefinition<?>> children = node.getOutputs();
207        if (children != null && !children.isEmpty()) {
208            for (ProcessorDefinition<?> child : children) {
209                // traverse children also
210                gatherAllNodeIds(child, set, onlyCustomId, includeAbstract);
211            }
212        }
213
214        return set;
215    }
216
217    private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int maxDeep) {
218
219        // do we have any top level abstracts, then we should max deep one more level down
220        // as that is really what we want to traverse as well
221        if (maxDeep > 0) {
222            for (ProcessorDefinition<?> out : outputs) {
223                if (out.isAbstract() && out.isTopLevelOnly()) {
224                    maxDeep = maxDeep + 1;
225                    break;
226                }
227            }
228        }
229
230        // start from level 1
231        doFindType(outputs, type, found, 1, maxDeep);
232    }
233
234    @SuppressWarnings({"unchecked", "rawtypes"})
235    private static <T> void doFindType(List<ProcessorDefinition<?>> outputs, Class<T> type, List<T> found, int current, int maxDeep) {
236        if (outputs == null || outputs.isEmpty()) {
237            return;
238        }
239
240        // break out
241        if (maxDeep > 0 && current > maxDeep) {
242            return;
243        }
244
245        for (ProcessorDefinition out : outputs) {
246
247            // send is much common
248            if (out instanceof SendDefinition) {
249                SendDefinition send = (SendDefinition) out;
250                List<ProcessorDefinition<?>> children = send.getOutputs();
251                doFindType(children, type, found, ++current, maxDeep);
252            }
253
254            // special for choice
255            if (out instanceof ChoiceDefinition) {
256                ChoiceDefinition choice = (ChoiceDefinition) out;
257
258                // ensure to add ourself if we match also
259                if (type.isInstance(choice)) {
260                    found.add((T)choice);
261                }
262
263                // only look at children if current < maxDeep (or max deep is disabled)
264                if (maxDeep < 0 || current < maxDeep) {
265                    for (WhenDefinition when : choice.getWhenClauses()) {
266                        if (type.isInstance(when)) {
267                            found.add((T) when);
268                        }
269                        List<ProcessorDefinition<?>> children = when.getOutputs();
270                        doFindType(children, type, found, ++current, maxDeep);
271                    }
272
273                    // otherwise is optional
274                    if (choice.getOtherwise() != null) {
275                        List<ProcessorDefinition<?>> children = choice.getOtherwise().getOutputs();
276                        doFindType(children, type, found, ++current, maxDeep);
277                    }
278                }
279
280                // do not check children as we already did that
281                continue;
282            }
283
284            // special for try ... catch ... finally
285            if (out instanceof TryDefinition) {
286                TryDefinition doTry = (TryDefinition) out;
287
288                // ensure to add ourself if we match also
289                if (type.isInstance(doTry)) {
290                    found.add((T)doTry);
291                }
292
293                // only look at children if current < maxDeep (or max deep is disabled)
294                if (maxDeep < 0 || current < maxDeep) {
295                    List<ProcessorDefinition<?>> doTryOut = doTry.getOutputsWithoutCatches();
296                    doFindType(doTryOut, type, found, ++current, maxDeep);
297
298                    List<CatchDefinition> doTryCatch = doTry.getCatchClauses();
299                    for (CatchDefinition doCatch : doTryCatch) {
300                        doFindType(doCatch.getOutputs(), type, found, ++current, maxDeep);
301                    }
302
303                    if (doTry.getFinallyClause() != null) {
304                        doFindType(doTry.getFinallyClause().getOutputs(), type, found, ++current, maxDeep);
305                    }
306                }
307
308                // do not check children as we already did that
309                continue;
310            }
311
312            // special for some types which has special outputs
313            if (out instanceof OutputDefinition) {
314                OutputDefinition outDef = (OutputDefinition) out;
315
316                // ensure to add ourself if we match also
317                if (type.isInstance(outDef)) {
318                    found.add((T)outDef);
319                }
320
321                List<ProcessorDefinition<?>> outDefOut = outDef.getOutputs();
322                doFindType(outDefOut, type, found, ++current, maxDeep);
323
324                // do not check children as we already did that
325                continue;
326            }
327
328            if (type.isInstance(out)) {
329                found.add((T)out);
330            }
331
332            // try children as well
333            List<ProcessorDefinition<?>> children = out.getOutputs();
334            doFindType(children, type, found, ++current, maxDeep);
335        }
336    }
337
338    /**
339     * Is there any outputs in the given list.
340     * <p/>
341     * Is used for check if the route output has any real outputs (non abstracts)
342     *
343     * @param outputs           the outputs
344     * @param excludeAbstract   whether or not to exclude abstract outputs (e.g. skip onException etc.)
345     * @return <tt>true</tt> if has outputs, otherwise <tt>false</tt> is returned
346     */
347    @SuppressWarnings({"unchecked", "rawtypes"})
348    public static boolean hasOutputs(List<ProcessorDefinition<?>> outputs, boolean excludeAbstract) {
349        if (outputs == null || outputs.isEmpty()) {
350            return false;
351        }
352        if (!excludeAbstract) {
353            return !outputs.isEmpty();
354        }
355        for (ProcessorDefinition output : outputs) {
356            if (output instanceof TransactedDefinition || output instanceof PolicyDefinition) {
357                // special for those as they wrap entire output, so we should just check its output
358                return hasOutputs(output.getOutputs(), excludeAbstract);
359            }
360            if (!output.isAbstract()) {
361                return true;
362            }
363        }
364        return false;
365    }
366
367    /**
368     * Determines whether a new thread pool will be created or not.
369     * <p/>
370     * This is used to know if a new thread pool will be created, and therefore is not shared by others, and therefore
371     * exclusive to the definition.
372     *
373     * @param routeContext   the route context
374     * @param definition     the node definition which may leverage executor service.
375     * @param useDefault     whether to fallback and use a default thread pool, if no explicit configured
376     * @return <tt>true</tt> if a new thread pool will be created, <tt>false</tt> if not
377     * @see #getConfiguredExecutorService(org.apache.camel.spi.RouteContext, String, ExecutorServiceAwareDefinition, boolean)
378     */
379    public static boolean willCreateNewThreadPool(RouteContext routeContext, ExecutorServiceAwareDefinition<?> definition, boolean useDefault) {
380        ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager();
381        ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext());
382        
383        if (definition.getExecutorService() != null) {
384            // no there is a custom thread pool configured
385            return false;
386        } else if (definition.getExecutorServiceRef() != null) {
387            ExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(definition.getExecutorServiceRef(), ExecutorService.class);
388            // if no existing thread pool, then we will have to create a new thread pool
389            return answer == null;
390        } else if (useDefault) {
391            return true;
392        }
393
394        return false;
395    }
396
397    /**
398     * Will lookup in {@link org.apache.camel.spi.Registry} for a {@link ExecutorService} registered with the given
399     * <tt>executorServiceRef</tt> name.
400     * <p/>
401     * This method will lookup for configured thread pool in the following order
402     * <ul>
403     *   <li>from the {@link org.apache.camel.spi.Registry} if found</li>
404     *   <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li>
405     *   <li>if none found, then <tt>null</tt> is returned.</li>
406     * </ul>
407     * @param routeContext   the route context
408     * @param name           name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService}
409     *                       is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}.
410     * @param source         the source to use the thread pool
411     * @param executorServiceRef reference name of the thread pool
412     * @return the executor service, or <tt>null</tt> if none was found.
413     */
414    public static ExecutorService lookupExecutorServiceRef(RouteContext routeContext, String name,
415                                                           Object source, String executorServiceRef) {
416
417        ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager();
418        ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext());
419        ObjectHelper.notNull(executorServiceRef, "executorServiceRef");
420
421        // lookup in registry first and use existing thread pool if exists
422        ExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(executorServiceRef, ExecutorService.class);
423        if (answer == null) {
424            // then create a thread pool assuming the ref is a thread pool profile id
425            answer = manager.newThreadPool(source, name, executorServiceRef);
426        }
427        return answer;
428    }
429
430    /**
431     * Will lookup and get the configured {@link java.util.concurrent.ExecutorService} from the given definition.
432     * <p/>
433     * This method will lookup for configured thread pool in the following order
434     * <ul>
435     *   <li>from the definition if any explicit configured executor service.</li>
436     *   <li>from the {@link org.apache.camel.spi.Registry} if found</li>
437     *   <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li>
438     *   <li>if none found, then <tt>null</tt> is returned.</li>
439     * </ul>
440     * The various {@link ExecutorServiceAwareDefinition} should use this helper method to ensure they support
441     * configured executor services in the same coherent way.
442     *
443     * @param routeContext   the route context
444     * @param name           name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService}
445     *                       is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}.
446     * @param definition     the node definition which may leverage executor service.
447     * @param useDefault     whether to fallback and use a default thread pool, if no explicit configured
448     * @return the configured executor service, or <tt>null</tt> if none was configured.
449     * @throws IllegalArgumentException is thrown if lookup of executor service in {@link org.apache.camel.spi.Registry} was not found
450     */
451    public static ExecutorService getConfiguredExecutorService(RouteContext routeContext, String name,
452                                                               ExecutorServiceAwareDefinition<?> definition,
453                                                               boolean useDefault) throws IllegalArgumentException {
454        ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager();
455        ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext());
456
457        // prefer to use explicit configured executor on the definition
458        if (definition.getExecutorService() != null) {
459            return definition.getExecutorService();
460        } else if (definition.getExecutorServiceRef() != null) {
461            // lookup in registry first and use existing thread pool if exists
462            ExecutorService answer = lookupExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef());
463            if (answer == null) {
464                throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " not found in registry or as a thread pool profile.");
465            }
466            return answer;
467        } else if (useDefault) {
468            return manager.newDefaultThreadPool(definition, name);
469        }
470
471        return null;
472    }
473
474    /**
475     * Will lookup in {@link org.apache.camel.spi.Registry} for a {@link ScheduledExecutorService} registered with the given
476     * <tt>executorServiceRef</tt> name.
477     * <p/>
478     * This method will lookup for configured thread pool in the following order
479     * <ul>
480     *   <li>from the {@link org.apache.camel.spi.Registry} if found</li>
481     *   <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li>
482     *   <li>if none found, then <tt>null</tt> is returned.</li>
483     * </ul>
484     * @param routeContext   the route context
485     * @param name           name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService}
486     *                       is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}.
487     * @param source         the source to use the thread pool
488     * @param executorServiceRef reference name of the thread pool
489     * @return the executor service, or <tt>null</tt> if none was found.
490     */
491    public static ScheduledExecutorService lookupScheduledExecutorServiceRef(RouteContext routeContext, String name,
492                                                                             Object source, String executorServiceRef) {
493
494        ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager();
495        ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext());
496        ObjectHelper.notNull(executorServiceRef, "executorServiceRef");
497
498        // lookup in registry first and use existing thread pool if exists
499        ScheduledExecutorService answer = routeContext.getCamelContext().getRegistry().lookupByNameAndType(executorServiceRef, ScheduledExecutorService.class);
500        if (answer == null) {
501            // then create a thread pool assuming the ref is a thread pool profile id
502            answer = manager.newScheduledThreadPool(source, name, executorServiceRef);
503        }
504        return answer;
505    }
506
507    /**
508     * Will lookup and get the configured {@link java.util.concurrent.ScheduledExecutorService} from the given definition.
509     * <p/>
510     * This method will lookup for configured thread pool in the following order
511     * <ul>
512     *   <li>from the definition if any explicit configured executor service.</li>
513     *   <li>from the {@link org.apache.camel.spi.Registry} if found</li>
514     *   <li>from the known list of {@link org.apache.camel.spi.ThreadPoolProfile ThreadPoolProfile(s)}.</li>
515     *   <li>if none found, then <tt>null</tt> is returned.</li>
516     * </ul>
517     * The various {@link ExecutorServiceAwareDefinition} should use this helper method to ensure they support
518     * configured executor services in the same coherent way.
519     *
520     * @param routeContext   the rout context
521     * @param name           name which is appended to the thread name, when the {@link java.util.concurrent.ExecutorService}
522     *                       is created based on a {@link org.apache.camel.spi.ThreadPoolProfile}.
523     * @param definition     the node definition which may leverage executor service.
524     * @param useDefault     whether to fallback and use a default thread pool, if no explicit configured
525     * @return the configured executor service, or <tt>null</tt> if none was configured.
526     * @throws IllegalArgumentException is thrown if the found instance is not a ScheduledExecutorService type,
527     * or lookup of executor service in {@link org.apache.camel.spi.Registry} was not found
528     */
529    public static ScheduledExecutorService getConfiguredScheduledExecutorService(RouteContext routeContext, String name,
530                                                               ExecutorServiceAwareDefinition<?> definition,
531                                                               boolean useDefault) throws IllegalArgumentException {
532        ExecutorServiceManager manager = routeContext.getCamelContext().getExecutorServiceManager();
533        ObjectHelper.notNull(manager, "ExecutorServiceManager", routeContext.getCamelContext());
534
535        // prefer to use explicit configured executor on the definition
536        if (definition.getExecutorService() != null) {
537            ExecutorService executorService = definition.getExecutorService();
538            if (executorService instanceof ScheduledExecutorService) {
539                return (ScheduledExecutorService) executorService;
540            }
541            throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " is not an ScheduledExecutorService instance");
542        } else if (definition.getExecutorServiceRef() != null) {
543            ScheduledExecutorService answer = lookupScheduledExecutorServiceRef(routeContext, name, definition, definition.getExecutorServiceRef());
544            if (answer == null) {
545                throw new IllegalArgumentException("ExecutorServiceRef " + definition.getExecutorServiceRef() + " not found in registry or as a thread pool profile.");
546            }
547            return answer;
548        } else if (useDefault) {
549            return manager.newDefaultScheduledThreadPool(definition, name);
550        }
551
552        return null;
553    }
554
555    /**
556     * Inspects the given definition and resolves any property placeholders from its properties.
557     * <p/>
558     * This implementation will check all the getter/setter pairs on this instance and for all the values
559     * (which is a String type) will be property placeholder resolved.
560     *
561     * @param routeContext the route context
562     * @param definition   the definition
563     * @throws Exception is thrown if property placeholders was used and there was an error resolving them
564     * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String)
565     * @see org.apache.camel.component.properties.PropertiesComponent
566     * @deprecated use {@link #resolvePropertyPlaceholders(org.apache.camel.CamelContext, Object)}
567     */
568    @Deprecated
569    public static void resolvePropertyPlaceholders(RouteContext routeContext, Object definition) throws Exception {
570        resolvePropertyPlaceholders(routeContext.getCamelContext(), definition);
571    }
572
573    /**
574     * Inspects the given definition and resolves any property placeholders from its properties.
575     * <p/>
576     * This implementation will check all the getter/setter pairs on this instance and for all the values
577     * (which is a String type) will be property placeholder resolved.
578     *
579     * @param camelContext the Camel context
580     * @param definition   the definition
581     * @throws Exception is thrown if property placeholders was used and there was an error resolving them
582     * @see org.apache.camel.CamelContext#resolvePropertyPlaceholders(String)
583     * @see org.apache.camel.component.properties.PropertiesComponent
584     */
585    public static void resolvePropertyPlaceholders(CamelContext camelContext, Object definition) throws Exception {
586        LOG.trace("Resolving property placeholders for: {}", definition);
587
588        // find all getter/setter which we can use for property placeholders
589        Map<String, Object> properties = new HashMap<String, Object>();
590        IntrospectionSupport.getProperties(definition, properties, null);
591
592        ProcessorDefinition<?> processorDefinition = null;
593        if (definition instanceof ProcessorDefinition) {
594            processorDefinition = (ProcessorDefinition<?>) definition;
595        }
596        // include additional properties which have the Camel placeholder QName
597        // and when the definition parameter is this (otherAttributes belong to this)
598        if (processorDefinition != null && processorDefinition.getOtherAttributes() != null) {
599            for (QName key : processorDefinition.getOtherAttributes().keySet()) {
600                if (Constants.PLACEHOLDER_QNAME.equals(key.getNamespaceURI())) {
601                    String local = key.getLocalPart();
602                    Object value = processorDefinition.getOtherAttributes().get(key);
603                    if (value != null && value instanceof String) {
604                        // enforce a properties component to be created if none existed
605                        CamelContextHelper.lookupPropertiesComponent(camelContext, true);
606
607                        // value must be enclosed with placeholder tokens
608                        String s = (String) value;
609                        String prefixToken = camelContext.getPropertyPrefixToken();
610                        String suffixToken = camelContext.getPropertySuffixToken();
611                        if (prefixToken == null) {
612                            throw new IllegalArgumentException("Property with name [" + local + "] uses property placeholders; however, no properties component is configured.");
613                        }
614
615                        if (!s.startsWith(prefixToken)) {
616                            s = prefixToken + s;
617                        }
618                        if (!s.endsWith(suffixToken)) {
619                            s = s + suffixToken;
620                        }
621                        value = s;
622                    }
623                    properties.put(local, value);
624                }
625            }
626        }
627
628        if (!properties.isEmpty()) {
629            LOG.trace("There are {} properties on: {}", properties.size(), definition);
630            // lookup and resolve properties for String based properties
631            for (Map.Entry<String, Object> entry : properties.entrySet()) {
632                // the name is always a String
633                String name = entry.getKey();
634                Object value = entry.getValue();
635                if (value instanceof String) {
636                    // value must be a String, as a String is the key for a property placeholder
637                    String text = (String) value;
638                    text = camelContext.resolvePropertyPlaceholders(text);
639                    if (text != value) {
640                        // invoke setter as the text has changed
641                        boolean changed = IntrospectionSupport.setProperty(camelContext.getTypeConverter(), definition, name, text);
642                        if (!changed) {
643                            throw new IllegalArgumentException("No setter to set property: " + name + " to: " + text + " on: " + definition);
644                        }
645                        if (LOG.isDebugEnabled()) {
646                            LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, text});
647                        }
648                    }
649                }
650            }
651        }
652    }
653
654    /**
655     * Inspects the given definition and resolves known fields
656     * <p/>
657     * This implementation will check all the getter/setter pairs on this instance and for all the values
658     * (which is a String type) will check if it refers to a known field (such as on Exchange).
659     *
660     * @param definition   the definition
661     */
662    public static void resolveKnownConstantFields(Object definition) throws Exception {
663        LOG.trace("Resolving known fields for: {}", definition);
664
665        // find all String getter/setter
666        Map<String, Object> properties = new HashMap<String, Object>();
667        IntrospectionSupport.getProperties(definition, properties, null);
668
669        if (!properties.isEmpty()) {
670            LOG.trace("There are {} properties on: {}", properties.size(), definition);
671
672            // lookup and resolve known constant fields for String based properties
673            for (Map.Entry<String, Object> entry : properties.entrySet()) {
674                String name = entry.getKey();
675                Object value = entry.getValue();
676                if (value instanceof String) {
677                    // we can only resolve String typed values
678                    String text = (String) value;
679
680                    // is the value a known field (currently we only support constants from Exchange.class)
681                    if (text.startsWith("Exchange.")) {
682                        String field = ObjectHelper.after(text, "Exchange.");
683                        String constant = ObjectHelper.lookupConstantFieldValue(Exchange.class, field);
684                        if (constant != null) {
685                            // invoke setter as the text has changed
686                            IntrospectionSupport.setProperty(definition, name, constant);
687                            if (LOG.isDebugEnabled()) {
688                                LOG.debug("Changed property [{}] from: {} to: {}", new Object[]{name, value, constant});
689                            }
690                        } else {
691                            throw new IllegalArgumentException("Constant field with name: " + field + " not found on Exchange.class");
692                        }
693                    }
694                }
695            }
696        }
697    }
698
699}