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.impl;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.StringJoiner;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.function.Function;
029
030import org.apache.camel.CamelContext;
031import org.apache.camel.Component;
032import org.apache.camel.Exchange;
033import org.apache.camel.Expression;
034import org.apache.camel.ExtendedCamelContext;
035import org.apache.camel.FailedToCreateRouteFromTemplateException;
036import org.apache.camel.NoSuchBeanException;
037import org.apache.camel.PropertyBindingException;
038import org.apache.camel.RouteTemplateContext;
039import org.apache.camel.model.BeanFactoryDefinition;
040import org.apache.camel.model.DataFormatDefinition;
041import org.apache.camel.model.DefaultRouteTemplateContext;
042import org.apache.camel.model.FaultToleranceConfigurationDefinition;
043import org.apache.camel.model.FromDefinition;
044import org.apache.camel.model.Model;
045import org.apache.camel.model.ModelCamelContext;
046import org.apache.camel.model.ModelLifecycleStrategy;
047import org.apache.camel.model.ProcessorDefinition;
048import org.apache.camel.model.ProcessorDefinitionHelper;
049import org.apache.camel.model.Resilience4jConfigurationDefinition;
050import org.apache.camel.model.RouteConfigurationDefinition;
051import org.apache.camel.model.RouteDefinition;
052import org.apache.camel.model.RouteDefinitionHelper;
053import org.apache.camel.model.RouteFilters;
054import org.apache.camel.model.RouteTemplateBeanDefinition;
055import org.apache.camel.model.RouteTemplateDefinition;
056import org.apache.camel.model.RouteTemplateParameterDefinition;
057import org.apache.camel.model.RoutesDefinition;
058import org.apache.camel.model.TemplatedRouteBeanDefinition;
059import org.apache.camel.model.TemplatedRouteDefinition;
060import org.apache.camel.model.TemplatedRouteParameterDefinition;
061import org.apache.camel.model.ToDefinition;
062import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
063import org.apache.camel.model.rest.RestDefinition;
064import org.apache.camel.model.transformer.TransformerDefinition;
065import org.apache.camel.model.validator.ValidatorDefinition;
066import org.apache.camel.spi.ExchangeFactory;
067import org.apache.camel.spi.Language;
068import org.apache.camel.spi.ModelReifierFactory;
069import org.apache.camel.spi.PropertyConfigurer;
070import org.apache.camel.spi.RouteTemplateLoaderListener;
071import org.apache.camel.spi.RouteTemplateParameterSource;
072import org.apache.camel.spi.ScriptingLanguage;
073import org.apache.camel.support.CamelContextHelper;
074import org.apache.camel.support.PatternHelper;
075import org.apache.camel.support.PropertyBindingSupport;
076import org.apache.camel.support.RouteTemplateHelper;
077import org.apache.camel.support.ScriptHelper;
078import org.apache.camel.support.service.ServiceHelper;
079import org.apache.camel.util.AntPathMatcher;
080import org.apache.camel.util.ObjectHelper;
081import org.apache.camel.util.StringHelper;
082import org.apache.camel.util.function.Suppliers;
083
084public class DefaultModel implements Model {
085
086    private final CamelContext camelContext;
087
088    private ModelReifierFactory modelReifierFactory = new DefaultModelReifierFactory();
089    private final List<ModelLifecycleStrategy> modelLifecycleStrategies = new ArrayList<>();
090    private final List<RouteConfigurationDefinition> routesConfigurations = new ArrayList<>();
091    private final List<RouteDefinition> routeDefinitions = new ArrayList<>();
092    private final List<RouteTemplateDefinition> routeTemplateDefinitions = new ArrayList<>();
093    private final List<RestDefinition> restDefinitions = new ArrayList<>();
094    private final Map<String, RouteTemplateDefinition.Converter> routeTemplateConverters = new ConcurrentHashMap<>();
095    private Map<String, DataFormatDefinition> dataFormats = new HashMap<>();
096    private List<TransformerDefinition> transformers = new ArrayList<>();
097    private List<ValidatorDefinition> validators = new ArrayList<>();
098    private final Map<String, ServiceCallConfigurationDefinition> serviceCallConfigurations = new ConcurrentHashMap<>();
099    private final Map<String, Resilience4jConfigurationDefinition> resilience4jConfigurations = new ConcurrentHashMap<>();
100    private final Map<String, FaultToleranceConfigurationDefinition> faultToleranceConfigurations = new ConcurrentHashMap<>();
101    private Function<RouteDefinition, Boolean> routeFilter;
102
103    public DefaultModel(CamelContext camelContext) {
104        this.camelContext = camelContext;
105    }
106
107    public CamelContext getCamelContext() {
108        return camelContext;
109    }
110
111    @Override
112    public void addModelLifecycleStrategy(ModelLifecycleStrategy modelLifecycleStrategy) {
113        // avoid adding double which can happen with spring xml on spring boot
114        if (!this.modelLifecycleStrategies.contains(modelLifecycleStrategy)) {
115            this.modelLifecycleStrategies.add(modelLifecycleStrategy);
116        }
117    }
118
119    @Override
120    public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
121        return modelLifecycleStrategies;
122    }
123
124    @Override
125    public void addRouteConfiguration(RouteConfigurationDefinition routesConfiguration) {
126        // Ensure that the route configuration should be included
127        if (routesConfiguration == null || !includedRouteConfiguration(routesConfiguration)) {
128            return;
129        }
130        // only add if not already exists (route-loader may let Java DSL add route configuration twice
131        // because it extends RouteBuilder as base class)
132        if (!this.routesConfigurations.contains(routesConfiguration)) {
133            // check that there is no id clash
134            if (routesConfiguration.getId() != null) {
135                boolean clash = this.routesConfigurations.stream()
136                        .anyMatch(r -> ObjectHelper.equal(r.getId(), routesConfiguration.getId()));
137                if (clash) {
138                    throw new IllegalArgumentException(
139                            "Route configuration already exists with id: " + routesConfiguration.getId());
140                }
141            }
142            this.routesConfigurations.add(routesConfiguration);
143        }
144    }
145
146    @Override
147    public void addRouteConfigurations(List<RouteConfigurationDefinition> routesConfigurations) {
148        if (routesConfigurations == null || routesConfigurations.isEmpty()) {
149            return;
150        }
151        // only add if not already exists (route-loader may let Java DSL add route configuration twice
152        // because it extends RouteBuilder as base class)
153        for (RouteConfigurationDefinition rc : routesConfigurations) {
154            addRouteConfiguration(rc);
155        }
156    }
157
158    @Override
159    public List<RouteConfigurationDefinition> getRouteConfigurationDefinitions() {
160        return routesConfigurations;
161    }
162
163    @Override
164    public synchronized RouteConfigurationDefinition getRouteConfigurationDefinition(String id) {
165        for (RouteConfigurationDefinition def : routesConfigurations) {
166            if (def.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()).equals(id)) {
167                return def;
168            }
169        }
170        // you can have a global route configuration that has no ID assigned
171        return routesConfigurations.stream().filter(c -> c.getId() == null).findFirst().orElse(null);
172    }
173
174    @Override
175    public void removeRouteConfiguration(RouteConfigurationDefinition routeConfigurationDefinition) throws Exception {
176        RouteConfigurationDefinition toBeRemoved = getRouteConfigurationDefinition(routeConfigurationDefinition.getId());
177        this.routesConfigurations.remove(toBeRemoved);
178    }
179
180    @Override
181    public synchronized void addRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
182        if (routeDefinitions == null || routeDefinitions.isEmpty()) {
183            return;
184        }
185
186        List<RouteDefinition> list;
187        if (routeFilter == null) {
188            list = new ArrayList<>(routeDefinitions);
189        } else {
190            list = new ArrayList<>();
191            for (RouteDefinition r : routeDefinitions) {
192                if (routeFilter.apply(r)) {
193                    list.add(r);
194                }
195            }
196        }
197
198        removeRouteDefinitions(list);
199
200        // special if rest-dsl is inlining routes
201        if (camelContext.getRestConfiguration().isInlineRoutes()) {
202            List<RouteDefinition> allRoutes = new ArrayList<>();
203            allRoutes.addAll(list);
204            allRoutes.addAll(this.routeDefinitions);
205
206            List<RouteDefinition> toBeRemoved = new ArrayList<>();
207            Map<String, RouteDefinition> directs = new HashMap<>();
208            for (RouteDefinition r : allRoutes) {
209                // does the route start with direct, which is candidate for rest-dsl
210                FromDefinition from = r.getInput();
211                if (from != null) {
212                    String uri = from.getEndpointUri();
213                    if (uri != null && uri.startsWith("direct:")) {
214                        directs.put(uri, r);
215                    }
216                }
217            }
218            for (RouteDefinition r : allRoutes) {
219                // loop all rest routes
220                FromDefinition from = r.getInput();
221                if (from != null) {
222                    String uri = from.getEndpointUri();
223                    if (uri != null && uri.startsWith("rest:")) {
224                        ToDefinition to = (ToDefinition) r.getOutputs().get(0);
225                        String toUri = to.getEndpointUri();
226                        RouteDefinition toBeInlined = directs.get(toUri);
227                        if (toBeInlined != null) {
228                            toBeRemoved.add(toBeInlined);
229                            // inline by replacing the outputs
230                            r.getOutputs().clear();
231                            r.getOutputs().addAll(toBeInlined.getOutputs());
232                        }
233                    }
234                }
235            }
236            // remove all the routes that was inlined
237            list.removeAll(toBeRemoved);
238            this.routeDefinitions.removeAll(toBeRemoved);
239        }
240
241        for (RouteDefinition r : list) {
242            for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
243                s.onAddRouteDefinition(r);
244            }
245            this.routeDefinitions.add(r);
246        }
247
248        if (shouldStartRoutes()) {
249            getCamelContext().adapt(ModelCamelContext.class).startRouteDefinitions(list);
250        }
251    }
252
253    @Override
254    public void addRouteDefinition(RouteDefinition routeDefinition) throws Exception {
255        addRouteDefinitions(Collections.singletonList(routeDefinition));
256    }
257
258    @Override
259    public synchronized void removeRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
260        for (RouteDefinition routeDefinition : routeDefinitions) {
261            removeRouteDefinition(routeDefinition);
262        }
263    }
264
265    @Override
266    public synchronized void removeRouteDefinition(RouteDefinition routeDefinition) throws Exception {
267        RouteDefinition toBeRemoved = routeDefinition;
268        String id = routeDefinition.getId();
269        if (id != null) {
270            // remove existing route
271            camelContext.getRouteController().stopRoute(id);
272            camelContext.removeRoute(id);
273            toBeRemoved = getRouteDefinition(id);
274        }
275        for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
276            s.onRemoveRouteDefinition(toBeRemoved);
277        }
278        this.routeDefinitions.remove(toBeRemoved);
279    }
280
281    @Override
282    public synchronized void removeRouteTemplateDefinitions(String pattern) throws Exception {
283        for (RouteTemplateDefinition def : new ArrayList<>(routeTemplateDefinitions)) {
284            if (PatternHelper.matchPattern(def.getId(), pattern)) {
285                removeRouteTemplateDefinition(def);
286            }
287        }
288    }
289
290    @Override
291    public synchronized List<RouteDefinition> getRouteDefinitions() {
292        return routeDefinitions;
293    }
294
295    @Override
296    public synchronized RouteDefinition getRouteDefinition(String id) {
297        for (RouteDefinition route : routeDefinitions) {
298            if (route.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()).equals(id)) {
299                return route;
300            }
301        }
302        return null;
303    }
304
305    @Override
306    public List<RouteTemplateDefinition> getRouteTemplateDefinitions() {
307        return routeTemplateDefinitions;
308    }
309
310    @Override
311    public RouteTemplateDefinition getRouteTemplateDefinition(String id) {
312        for (RouteTemplateDefinition route : routeTemplateDefinitions) {
313            if (route.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()).equals(id)) {
314                return route;
315            }
316        }
317        return null;
318    }
319
320    @Override
321    public void addRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
322        if (routeTemplateDefinitions == null || routeTemplateDefinitions.isEmpty()) {
323            return;
324        }
325
326        for (RouteTemplateDefinition r : routeTemplateDefinitions) {
327            for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
328                s.onAddRouteTemplateDefinition(r);
329            }
330            this.routeTemplateDefinitions.add(r);
331        }
332    }
333
334    @Override
335    public void addRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
336        addRouteTemplateDefinitions(Collections.singletonList(routeTemplateDefinition));
337    }
338
339    @Override
340    public void removeRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
341        for (RouteTemplateDefinition r : routeTemplateDefinitions) {
342            removeRouteTemplateDefinition(r);
343        }
344    }
345
346    @Override
347    public void removeRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
348        for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
349            s.onRemoveRouteTemplateDefinition(routeTemplateDefinition);
350        }
351        routeTemplateDefinitions.remove(routeTemplateDefinition);
352    }
353
354    @Override
355    public void addRouteTemplateDefinitionConverter(String templateIdPattern, RouteTemplateDefinition.Converter converter) {
356        routeTemplateConverters.put(templateIdPattern, converter);
357    }
358
359    @Override
360    @Deprecated
361    public String addRouteFromTemplate(final String routeId, final String routeTemplateId, final Map<String, Object> parameters)
362            throws Exception {
363        RouteTemplateContext rtc = new DefaultRouteTemplateContext(camelContext);
364        if (parameters != null) {
365            parameters.forEach(rtc::setParameter);
366        }
367        return addRouteFromTemplate(routeId, routeTemplateId, null, rtc);
368    }
369
370    @Override
371    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
372            throws Exception {
373        RouteTemplateContext rtc = new DefaultRouteTemplateContext(camelContext);
374        if (parameters != null) {
375            parameters.forEach(rtc::setParameter);
376        }
377        return addRouteFromTemplate(routeId, routeTemplateId, prefixId, rtc);
378    }
379
380    public String addRouteFromTemplate(String routeId, String routeTemplateId, RouteTemplateContext routeTemplateContext)
381            throws Exception {
382        return addRouteFromTemplate(routeId, routeTemplateId, null, routeTemplateContext);
383    }
384
385    @Override
386    public String addRouteFromTemplate(
387            String routeId, String routeTemplateId, String prefixId,
388            RouteTemplateContext routeTemplateContext)
389            throws Exception {
390
391        RouteTemplateDefinition target = null;
392        for (RouteTemplateDefinition def : routeTemplateDefinitions) {
393            if (routeTemplateId.equals(def.getId())) {
394                target = def;
395                break;
396            }
397        }
398        if (target == null) {
399            // if the route template has a location parameter, then try to load route templates from the location
400            // and look up again
401            Object location = routeTemplateContext.getParameters().get(RouteTemplateParameterSource.LOCATION);
402            if (location != null) {
403                RouteTemplateLoaderListener listener
404                        = CamelContextHelper.findSingleByType(getCamelContext(), RouteTemplateLoaderListener.class);
405                RouteTemplateHelper.loadRouteTemplateFromLocation(getCamelContext(), listener, routeTemplateId,
406                        location.toString());
407            }
408            for (RouteTemplateDefinition def : routeTemplateDefinitions) {
409                if (routeTemplateId.equals(def.getId())) {
410                    target = def;
411                    break;
412                }
413            }
414        }
415        if (target == null) {
416            throw new IllegalArgumentException("Cannot find RouteTemplate with id " + routeTemplateId);
417        }
418
419        // support both camelCase and kebab-case keys
420        final Map<String, Object> prop = new HashMap<>();
421        final Map<String, Object> propDefaultValues = new HashMap<>();
422        // include default values first from the template (and validate that we have inputs for all required parameters)
423        if (target.getTemplateParameters() != null) {
424            StringJoiner templatesBuilder = new StringJoiner(", ");
425
426            for (RouteTemplateParameterDefinition temp : target.getTemplateParameters()) {
427                if (temp.getDefaultValue() != null) {
428                    addProperty(prop, temp.getName(), temp.getDefaultValue());
429                    addProperty(propDefaultValues, temp.getName(), temp.getDefaultValue());
430                } else {
431                    if (temp.isRequired() && !routeTemplateContext.hasParameter(temp.getName())) {
432                        // this is a required parameter which is missing
433                        templatesBuilder.add(temp.getName());
434                    }
435                }
436            }
437            if (templatesBuilder.length() > 0) {
438                throw new IllegalArgumentException(
439                        "Route template " + routeTemplateId + " the following mandatory parameters must be provided: "
440                                                   + templatesBuilder);
441            }
442        }
443
444        // then override with user parameters part 1
445        if (routeTemplateContext.getParameters() != null) {
446            routeTemplateContext.getParameters().forEach((k, v) -> addProperty(prop, k, v));
447        }
448        // route template context should include default template parameters from the target route template
449        // so it has all parameters available
450        if (target.getTemplateParameters() != null) {
451            for (RouteTemplateParameterDefinition temp : target.getTemplateParameters()) {
452                if (!routeTemplateContext.hasParameter(temp.getName()) && temp.getDefaultValue() != null) {
453                    routeTemplateContext.setParameter(temp.getName(), temp.getDefaultValue());
454                }
455            }
456        }
457
458        RouteTemplateDefinition.Converter converter = RouteTemplateDefinition.Converter.DEFAULT_CONVERTER;
459
460        for (Map.Entry<String, RouteTemplateDefinition.Converter> entry : routeTemplateConverters.entrySet()) {
461            final String key = entry.getKey();
462            final String templateId = target.getId();
463
464            if ("*".equals(key) || templateId.equals(key)) {
465                converter = entry.getValue();
466                break;
467            } else if (AntPathMatcher.INSTANCE.match(key, templateId)) {
468                converter = entry.getValue();
469                break;
470            } else if (templateId.matches(key)) {
471                converter = entry.getValue();
472                break;
473            }
474        }
475
476        RouteDefinition def = converter.apply(target, prop);
477        if (routeId != null) {
478            def.setId(routeId);
479        }
480        if (prefixId != null) {
481            def.setNodePrefixId(prefixId);
482        }
483        def.setTemplateParameters(prop);
484        def.setTemplateDefaultParameters(propDefaultValues);
485        def.setRouteTemplateContext(routeTemplateContext);
486
487        // setup local beans
488        if (target.getTemplateBeans() != null) {
489            addTemplateBeans(routeTemplateContext, target);
490        }
491
492        if (target.getConfigurer() != null) {
493            routeTemplateContext.setConfigurer(target.getConfigurer());
494        }
495
496        // assign ids to the routes and validate that the id's are all unique
497        String duplicate = RouteDefinitionHelper.validateUniqueIds(def, routeDefinitions, prefixId);
498        if (duplicate != null) {
499            throw new FailedToCreateRouteFromTemplateException(
500                    routeId, routeTemplateId,
501                    "duplicate id detected: " + duplicate + ". Please correct ids to be unique among all your routes.");
502        }
503
504        // must use route collection to prepare the created route to
505        // ensure its created correctly from the route template
506        RoutesDefinition routeCollection = new RoutesDefinition();
507        routeCollection.setCamelContext(camelContext);
508        routeCollection.setRoutes(getRouteDefinitions());
509        routeCollection.prepareRoute(def);
510
511        // add route and return the id it was assigned
512        addRouteDefinition(def);
513        return def.getId();
514    }
515
516    private static void addProperty(Map<String, Object> prop, String key, Object value) {
517        prop.put(key, value);
518        // support also camelCase and kebab-case because route templates (kamelets)
519        // can be defined using different key styles
520        key = StringHelper.dashToCamelCase(key);
521        prop.put(key, value);
522        key = StringHelper.camelCaseToDash(key);
523        prop.put(key, value);
524    }
525
526    private static void addTemplateBeans(RouteTemplateContext routeTemplateContext, RouteTemplateDefinition target)
527            throws Exception {
528        for (RouteTemplateBeanDefinition b : target.getTemplateBeans()) {
529            bind(b, routeTemplateContext);
530        }
531    }
532
533    /**
534     * Binds the bean factory to the repository (if possible).
535     *
536     * @param  beanFactory          the bean factory to bind.
537     * @param  routeTemplateContext the context into which the bean factory should be bound.
538     * @throws Exception            if an error occurs while trying to bind the bean factory
539     */
540    private static void bind(BeanFactoryDefinition<?, ?> beanFactory, RouteTemplateContext routeTemplateContext)
541            throws Exception {
542        final Map<String, Object> props = new HashMap<>();
543        if (beanFactory.getProperties() != null) {
544            beanFactory.getProperties().forEach(p -> props.put(p.getKey(), p.getValue()));
545        }
546        if (beanFactory.getBeanSupplier() != null) {
547            if (props.isEmpty()) {
548                // bean class is optional for supplier
549                if (beanFactory.getBeanClass() != null) {
550                    routeTemplateContext.bind(beanFactory.getName(), beanFactory.getBeanClass(), beanFactory.getBeanSupplier());
551                } else {
552                    routeTemplateContext.bind(beanFactory.getName(), beanFactory.getBeanSupplier());
553                }
554            }
555        } else if (beanFactory.getScript() != null) {
556            final String script = beanFactory.getScript();
557            final CamelContext camelContext = routeTemplateContext.getCamelContext();
558            final Language lan = camelContext.resolveLanguage(beanFactory.getType());
559            final Class<?> clazz;
560            if (beanFactory.getBeanType() != null) {
561                clazz = camelContext.getClassResolver().resolveMandatoryClass(beanFactory.getBeanType());
562            } else {
563                if (beanFactory.getBeanClass() != null) {
564                    clazz = beanFactory.getBeanClass();
565                } else {
566                    clazz = Object.class;
567                }
568            }
569            final ScriptingLanguage slan = lan instanceof ScriptingLanguage ? (ScriptingLanguage) lan : null;
570            if (slan != null) {
571                // scripting language should be evaluated with route template context as binding
572                // and memorize so the script is only evaluated once and the local bean is the same
573                // if a route template refers to the local bean multiple times
574                routeTemplateContext.bind(beanFactory.getName(), clazz, Suppliers.memorize(() -> {
575                    Map<String, Object> bindings = new HashMap<>();
576                    // use rtx as the short-hand name, as context would imply its CamelContext
577                    bindings.put("rtc", routeTemplateContext);
578                    Object local = slan.evaluate(script, bindings, clazz);
579                    if (!props.isEmpty()) {
580                        setPropertiesOnTarget(camelContext, local, props);
581                    }
582                    return local;
583                }));
584            } else {
585                // exchange based languages needs a dummy exchange to be evaluated
586                // and memorize so the script is only evaluated once and the local bean is the same
587                // if a route template refers to the local bean multiple times
588                routeTemplateContext.bind(beanFactory.getName(), clazz, Suppliers.memorize(() -> {
589                    ExchangeFactory ef = camelContext.adapt(ExtendedCamelContext.class).getExchangeFactory();
590                    Exchange dummy = ef.create(false);
591                    try {
592                        String text = ScriptHelper.resolveOptionalExternalScript(camelContext, dummy, script);
593                        if (text != null) {
594                            Expression exp = lan.createExpression(text);
595                            Object local = exp.evaluate(dummy, clazz);
596                            if (!props.isEmpty()) {
597                                setPropertiesOnTarget(camelContext, local, props);
598                            }
599                            return local;
600                        } else {
601                            return null;
602                        }
603                    } finally {
604                        ef.release(dummy);
605                    }
606                }));
607            }
608        } else if (beanFactory.getBeanClass() != null
609                || beanFactory.getType() != null && beanFactory.getType().startsWith("#class:")) {
610            // if there is a factory method then the class/bean should be created in a different way
611            String className = null;
612            String factoryMethod = null;
613            String parameters = null;
614            if (beanFactory.getType() != null) {
615                className = beanFactory.getType().substring(7);
616                if (className.endsWith(")") && className.indexOf('(') != -1) {
617                    parameters = StringHelper.after(className, "(");
618                    parameters = parameters.substring(0, parameters.length() - 1); // clip last )
619                    className = StringHelper.before(className, "(");
620                }
621                if (className != null && className.indexOf('#') != -1) {
622                    factoryMethod = StringHelper.after(className, "#");
623                    className = StringHelper.before(className, "#");
624                }
625            }
626            if (className != null && (factoryMethod != null || parameters != null)) {
627                final CamelContext camelContext = routeTemplateContext.getCamelContext();
628                final Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(className);
629                final String fqn = className;
630                final String fm = factoryMethod;
631                final String fp = parameters;
632                routeTemplateContext.bind(beanFactory.getName(), Object.class, Suppliers.memorize(() -> {
633                    // resolve placeholders in parameters
634                    String params = camelContext.resolvePropertyPlaceholders(fp);
635                    try {
636                        Object local;
637                        if (fm != null) {
638                            if (fp != null) {
639                                // special to support factory method parameters
640                                local = PropertyBindingSupport.newInstanceFactoryParameters(camelContext, clazz, fm, params);
641                            } else {
642                                local = camelContext.getInjector().newInstance(clazz, fm);
643                            }
644                            if (local == null) {
645                                throw new IllegalStateException(
646                                        "Cannot create bean instance using factory method: " + fqn + "#" + fm);
647                            }
648                        } else {
649                            // special to support constructor parameters
650                            local = PropertyBindingSupport.newInstanceConstructorParameters(camelContext, clazz, params);
651                        }
652                        if (!props.isEmpty()) {
653                            setPropertiesOnTarget(camelContext, local, props);
654                        }
655                        return local;
656                    } catch (Exception e) {
657                        throw new IllegalStateException(
658                                "Cannot create bean: " + beanFactory.getType());
659                    }
660                }));
661            } else {
662                final CamelContext camelContext = routeTemplateContext.getCamelContext();
663                Class<?> clazz = beanFactory.getBeanClass() != null
664                        ? beanFactory.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(className);
665                // we only have the bean class so we use that to create a new bean via the injector
666                // and memorize so the bean is only created once and the local bean is the same
667                // if a route template refers to the local bean multiple times
668                routeTemplateContext.bind(beanFactory.getName(), clazz,
669                        Suppliers.memorize(() -> {
670                            Object local = camelContext.getInjector().newInstance(clazz);
671                            if (!props.isEmpty()) {
672                                setPropertiesOnTarget(camelContext, local, props);
673                            }
674                            return local;
675                        }));
676            }
677        } else if (beanFactory.getType() != null && beanFactory.getType().startsWith("#type:")) {
678            final CamelContext camelContext = routeTemplateContext.getCamelContext();
679            Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(beanFactory.getType().substring(6));
680            Set<?> found = camelContext.getRegistry().findByType(clazz);
681            if (found == null || found.isEmpty()) {
682                throw new NoSuchBeanException(null, clazz.getName());
683            } else if (found.size() > 1) {
684                throw new NoSuchBeanException(
685                        "Found " + found.size() + " beans of type: " + clazz + ". Only one bean expected.");
686            } else {
687                // do not set properties when using #type as it uses an existing shared bean
688                routeTemplateContext.bind(beanFactory.getName(), clazz, found.iterator().next());
689            }
690        } else {
691            // invalid syntax for the local bean, so lets report an exception
692            throw new IllegalArgumentException(
693                    "Route template local bean: " + beanFactory.getName() + " has invalid type syntax: " + beanFactory.getType()
694                                               + ". To refer to a class then prefix the value with #class such as: #class:fullyQualifiedClassName");
695        }
696    }
697
698    /**
699     * Sets the properties to the given target.
700     *
701     * @param context    the context into which the properties must be set.
702     * @param target     the object to which the properties must be set.
703     * @param properties the properties to set.
704     */
705    private static void setPropertiesOnTarget(CamelContext context, Object target, Map<String, Object> properties) {
706        ObjectHelper.notNull(context, "context");
707        ObjectHelper.notNull(target, "target");
708        ObjectHelper.notNull(properties, "properties");
709
710        if (target instanceof CamelContext) {
711            throw new UnsupportedOperationException("Configuring the Camel Context is not supported");
712        }
713
714        PropertyConfigurer configurer = null;
715        if (target instanceof Component) {
716            // the component needs to be initialized to have the configurer ready
717            ServiceHelper.initService(target);
718            configurer = ((Component) target).getComponentPropertyConfigurer();
719        }
720
721        if (configurer == null) {
722            // see if there is a configurer for it
723            configurer = context.adapt(ExtendedCamelContext.class)
724                    .getConfigurerResolver()
725                    .resolvePropertyConfigurer(target.getClass().getSimpleName(), context);
726        }
727
728        try {
729            PropertyBindingSupport.build()
730                    .withMandatory(true)
731                    .withRemoveParameters(false)
732                    .withConfigurer(configurer)
733                    .withIgnoreCase(true)
734                    .withFlattenProperties(true)
735                    .bind(context, target, properties);
736        } catch (PropertyBindingException e) {
737            String key = e.getOptionKey();
738            if (key == null) {
739                String prefix = e.getOptionPrefix();
740                if (prefix != null && !prefix.endsWith(".")) {
741                    prefix = "." + prefix;
742                }
743
744                key = prefix != null
745                        ? prefix + "." + e.getPropertyName()
746                        : e.getPropertyName();
747            }
748
749            // enrich the error with more precise details with option prefix and key
750            throw new PropertyBindingException(
751                    e.getTarget(),
752                    e.getPropertyName(),
753                    e.getValue(),
754                    null,
755                    key,
756                    e.getCause());
757        }
758    }
759
760    @Override
761    public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
762            throws Exception {
763        ObjectHelper.notNull(templatedRouteDefinition, "templatedRouteDefinition");
764
765        final RouteTemplateContext routeTemplateContext = new DefaultRouteTemplateContext(camelContext);
766        // Load the parameters into the context
767        final List<TemplatedRouteParameterDefinition> parameters = templatedRouteDefinition.getParameters();
768        if (parameters != null) {
769            for (TemplatedRouteParameterDefinition parameterDefinition : parameters) {
770                routeTemplateContext.setParameter(parameterDefinition.getName(), parameterDefinition.getValue());
771            }
772        }
773        // Bind the beans into the context
774        final List<TemplatedRouteBeanDefinition> beans = templatedRouteDefinition.getBeans();
775        if (beans != null) {
776            for (TemplatedRouteBeanDefinition beanDefinition : beans) {
777                bind(beanDefinition, routeTemplateContext);
778            }
779        }
780        // Add the route
781        addRouteFromTemplate(templatedRouteDefinition.getRouteId(), templatedRouteDefinition.getRouteTemplateRef(),
782                templatedRouteDefinition.getPrefixId(), routeTemplateContext);
783    }
784
785    @Override
786    public synchronized List<RestDefinition> getRestDefinitions() {
787        return restDefinitions;
788    }
789
790    @Override
791    public synchronized void addRestDefinitions(Collection<RestDefinition> restDefinitions, boolean addToRoutes)
792            throws Exception {
793        if (restDefinitions == null || restDefinitions.isEmpty()) {
794            return;
795        }
796
797        this.restDefinitions.addAll(restDefinitions);
798        if (addToRoutes) {
799            // rests are also routes so need to add them there too
800            for (final RestDefinition restDefinition : restDefinitions) {
801                List<RouteDefinition> routeDefinitions = restDefinition.asRouteDefinition(camelContext);
802                addRouteDefinitions(routeDefinitions);
803            }
804        }
805    }
806
807    @Override
808    public ServiceCallConfigurationDefinition getServiceCallConfiguration(String serviceName) {
809        if (serviceName == null) {
810            serviceName = "";
811        }
812
813        return serviceCallConfigurations.get(serviceName);
814    }
815
816    @Override
817    public void setServiceCallConfiguration(ServiceCallConfigurationDefinition configuration) {
818        serviceCallConfigurations.put("", configuration);
819    }
820
821    @Override
822    public void setServiceCallConfigurations(List<ServiceCallConfigurationDefinition> configurations) {
823        if (configurations != null) {
824            for (ServiceCallConfigurationDefinition configuration : configurations) {
825                serviceCallConfigurations.put(configuration.getId(), configuration);
826            }
827        }
828    }
829
830    @Override
831    public void addServiceCallConfiguration(String serviceName, ServiceCallConfigurationDefinition configuration) {
832        serviceCallConfigurations.put(serviceName, configuration);
833    }
834
835    @Override
836    public Resilience4jConfigurationDefinition getResilience4jConfiguration(String id) {
837        if (id == null) {
838            id = "";
839        }
840
841        return resilience4jConfigurations.get(id);
842    }
843
844    @Override
845    public void setResilience4jConfiguration(Resilience4jConfigurationDefinition configuration) {
846        resilience4jConfigurations.put("", configuration);
847    }
848
849    @Override
850    public void setResilience4jConfigurations(List<Resilience4jConfigurationDefinition> configurations) {
851        if (configurations != null) {
852            for (Resilience4jConfigurationDefinition configuration : configurations) {
853                resilience4jConfigurations.put(configuration.getId(), configuration);
854            }
855        }
856    }
857
858    @Override
859    public void addResilience4jConfiguration(String id, Resilience4jConfigurationDefinition configuration) {
860        resilience4jConfigurations.put(id, configuration);
861    }
862
863    @Override
864    public FaultToleranceConfigurationDefinition getFaultToleranceConfiguration(String id) {
865        if (id == null) {
866            id = "";
867        }
868
869        return faultToleranceConfigurations.get(id);
870    }
871
872    @Override
873    public void setFaultToleranceConfiguration(FaultToleranceConfigurationDefinition configuration) {
874        faultToleranceConfigurations.put("", configuration);
875    }
876
877    @Override
878    public void setFaultToleranceConfigurations(List<FaultToleranceConfigurationDefinition> configurations) {
879        if (configurations != null) {
880            for (FaultToleranceConfigurationDefinition configuration : configurations) {
881                faultToleranceConfigurations.put(configuration.getId(), configuration);
882            }
883        }
884    }
885
886    @Override
887    public void addFaultToleranceConfiguration(String id, FaultToleranceConfigurationDefinition configuration) {
888        faultToleranceConfigurations.put(id, configuration);
889    }
890
891    @Override
892    public DataFormatDefinition resolveDataFormatDefinition(String name) {
893        // lookup type and create the data format from it
894        DataFormatDefinition type = lookup(camelContext, name, DataFormatDefinition.class);
895        if (type == null && getDataFormats() != null) {
896            type = getDataFormats().get(name);
897        }
898        return type;
899    }
900
901    @SuppressWarnings("rawtypes")
902    @Override
903    public ProcessorDefinition<?> getProcessorDefinition(String id) {
904        for (RouteDefinition route : getRouteDefinitions()) {
905            Collection<ProcessorDefinition> col
906                    = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class);
907            for (ProcessorDefinition proc : col) {
908                if (id.equals(proc.getId())) {
909                    return proc;
910                }
911            }
912        }
913        return null;
914    }
915
916    @Override
917    public <T extends ProcessorDefinition<T>> T getProcessorDefinition(String id, Class<T> type) {
918        ProcessorDefinition<?> answer = getProcessorDefinition(id);
919        if (answer != null) {
920            return type.cast(answer);
921        }
922        return null;
923    }
924
925    @Override
926    public Map<String, DataFormatDefinition> getDataFormats() {
927        return dataFormats;
928    }
929
930    @Override
931    public void setDataFormats(Map<String, DataFormatDefinition> dataFormats) {
932        this.dataFormats = dataFormats;
933    }
934
935    @Override
936    public List<TransformerDefinition> getTransformers() {
937        return transformers;
938    }
939
940    @Override
941    public void setTransformers(List<TransformerDefinition> transformers) {
942        this.transformers = transformers;
943    }
944
945    @Override
946    public List<ValidatorDefinition> getValidators() {
947        return validators;
948    }
949
950    @Override
951    public void setValidators(List<ValidatorDefinition> validators) {
952        this.validators = validators;
953    }
954
955    @Override
956    public void setRouteFilterPattern(String include, String exclude) {
957        setRouteFilter(RouteFilters.filterByPattern(include, exclude));
958    }
959
960    @Override
961    public Function<RouteDefinition, Boolean> getRouteFilter() {
962        return routeFilter;
963    }
964
965    @Override
966    public void setRouteFilter(Function<RouteDefinition, Boolean> routeFilter) {
967        this.routeFilter = routeFilter;
968    }
969
970    @Override
971    public ModelReifierFactory getModelReifierFactory() {
972        return modelReifierFactory;
973    }
974
975    @Override
976    public void setModelReifierFactory(ModelReifierFactory modelReifierFactory) {
977        this.modelReifierFactory = modelReifierFactory;
978    }
979
980    /**
981     * Should we start newly added routes?
982     */
983    protected boolean shouldStartRoutes() {
984        return camelContext.isStarted() && !camelContext.isStarting();
985    }
986
987    private static <T> T lookup(CamelContext context, String ref, Class<T> type) {
988        try {
989            return context.getRegistry().lookupByNameAndType(ref, type);
990        } catch (Exception e) {
991            // need to ignore not same type and return it as null
992            return null;
993        }
994    }
995
996    /**
997     * Indicates whether the route configuration should be included according to the precondition.
998     *
999     * @param  definition the definition of the route configuration to check.
1000     * @return            {@code true} if the route configuration should be included, {@code false} otherwise.
1001     */
1002    private boolean includedRouteConfiguration(RouteConfigurationDefinition definition) {
1003        return PreconditionHelper.included(definition, camelContext);
1004    }
1005}