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.StringJoiner;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.function.Function;
028
029import org.apache.camel.CamelContext;
030import org.apache.camel.Exchange;
031import org.apache.camel.Expression;
032import org.apache.camel.FailedToCreateRouteFromTemplateException;
033import org.apache.camel.RouteTemplateContext;
034import org.apache.camel.model.BeanFactoryDefinition;
035import org.apache.camel.model.DataFormatDefinition;
036import org.apache.camel.model.DefaultRouteTemplateContext;
037import org.apache.camel.model.FaultToleranceConfigurationDefinition;
038import org.apache.camel.model.FromDefinition;
039import org.apache.camel.model.Model;
040import org.apache.camel.model.ModelCamelContext;
041import org.apache.camel.model.ModelLifecycleStrategy;
042import org.apache.camel.model.ProcessorDefinition;
043import org.apache.camel.model.ProcessorDefinitionHelper;
044import org.apache.camel.model.Resilience4jConfigurationDefinition;
045import org.apache.camel.model.RouteConfigurationDefinition;
046import org.apache.camel.model.RouteDefinition;
047import org.apache.camel.model.RouteDefinitionHelper;
048import org.apache.camel.model.RouteFilters;
049import org.apache.camel.model.RouteTemplateBeanDefinition;
050import org.apache.camel.model.RouteTemplateDefinition;
051import org.apache.camel.model.RouteTemplateParameterDefinition;
052import org.apache.camel.model.RoutesDefinition;
053import org.apache.camel.model.TemplatedRouteBeanDefinition;
054import org.apache.camel.model.TemplatedRouteDefinition;
055import org.apache.camel.model.TemplatedRouteParameterDefinition;
056import org.apache.camel.model.ToDefinition;
057import org.apache.camel.model.app.RegistryBeanDefinition;
058import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
059import org.apache.camel.model.rest.RestDefinition;
060import org.apache.camel.model.transformer.TransformerDefinition;
061import org.apache.camel.model.validator.ValidatorDefinition;
062import org.apache.camel.spi.ExchangeFactory;
063import org.apache.camel.spi.Language;
064import org.apache.camel.spi.ModelReifierFactory;
065import org.apache.camel.spi.NodeIdFactory;
066import org.apache.camel.spi.RouteTemplateLoaderListener;
067import org.apache.camel.spi.RouteTemplateParameterSource;
068import org.apache.camel.spi.ScriptingLanguage;
069import org.apache.camel.support.CamelContextHelper;
070import org.apache.camel.support.PatternHelper;
071import org.apache.camel.support.PropertyBindingSupport;
072import org.apache.camel.support.RouteTemplateHelper;
073import org.apache.camel.support.ScriptHelper;
074import org.apache.camel.util.AntPathMatcher;
075import org.apache.camel.util.ObjectHelper;
076import org.apache.camel.util.StringHelper;
077import org.apache.camel.util.function.Suppliers;
078
079public class DefaultModel implements Model {
080
081    private final CamelContext camelContext;
082
083    private ModelReifierFactory modelReifierFactory = new DefaultModelReifierFactory();
084    private final List<ModelLifecycleStrategy> modelLifecycleStrategies = new ArrayList<>();
085    private final List<RouteConfigurationDefinition> routesConfigurations = new ArrayList<>();
086    private final List<RouteDefinition> routeDefinitions = new ArrayList<>();
087    private final List<RouteTemplateDefinition> routeTemplateDefinitions = new ArrayList<>();
088    private final List<RestDefinition> restDefinitions = new ArrayList<>();
089    private final Map<String, RouteTemplateDefinition.Converter> routeTemplateConverters = new ConcurrentHashMap<>();
090    private Map<String, DataFormatDefinition> dataFormats = new HashMap<>();
091    private List<TransformerDefinition> transformers = new ArrayList<>();
092    private List<ValidatorDefinition> validators = new ArrayList<>();
093    // XML and YAML DSL allows to declare beans in the DSL
094    private final List<RegistryBeanDefinition> beans = new ArrayList<>();
095    private final Map<String, ServiceCallConfigurationDefinition> serviceCallConfigurations = new ConcurrentHashMap<>();
096    private final Map<String, Resilience4jConfigurationDefinition> resilience4jConfigurations = new ConcurrentHashMap<>();
097    private final Map<String, FaultToleranceConfigurationDefinition> faultToleranceConfigurations = new ConcurrentHashMap<>();
098    private Function<RouteDefinition, Boolean> routeFilter;
099
100    public DefaultModel(CamelContext camelContext) {
101        this.camelContext = camelContext;
102    }
103
104    public CamelContext getCamelContext() {
105        return camelContext;
106    }
107
108    @Override
109    public void addModelLifecycleStrategy(ModelLifecycleStrategy modelLifecycleStrategy) {
110        // avoid adding double which can happen with spring xml on spring boot
111        if (!this.modelLifecycleStrategies.contains(modelLifecycleStrategy)) {
112            this.modelLifecycleStrategies.add(modelLifecycleStrategy);
113        }
114    }
115
116    @Override
117    public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
118        return modelLifecycleStrategies;
119    }
120
121    @Override
122    public void addRouteConfiguration(RouteConfigurationDefinition routesConfiguration) {
123        // Ensure that the route configuration should be included
124        if (routesConfiguration == null || !includedRouteConfiguration(routesConfiguration)) {
125            return;
126        }
127        // only add if not already exists (route-loader may let Java DSL add route configuration twice
128        // because it extends RouteBuilder as base class)
129        if (!this.routesConfigurations.contains(routesConfiguration)) {
130            // check that there is no id clash
131            if (routesConfiguration.getId() != null) {
132                boolean clash = this.routesConfigurations.stream()
133                        .anyMatch(r -> ObjectHelper.equal(r.getId(), routesConfiguration.getId()));
134                if (clash) {
135                    throw new IllegalArgumentException(
136                            "Route configuration already exists with id: " + routesConfiguration.getId());
137                }
138            }
139            this.routesConfigurations.add(routesConfiguration);
140        }
141    }
142
143    @Override
144    public void addRouteConfigurations(List<RouteConfigurationDefinition> routesConfigurations) {
145        if (routesConfigurations == null || routesConfigurations.isEmpty()) {
146            return;
147        }
148        // only add if not already exists (route-loader may let Java DSL add route configuration twice
149        // because it extends RouteBuilder as base class)
150        for (RouteConfigurationDefinition rc : routesConfigurations) {
151            addRouteConfiguration(rc);
152        }
153    }
154
155    @Override
156    public List<RouteConfigurationDefinition> getRouteConfigurationDefinitions() {
157        return routesConfigurations;
158    }
159
160    @Override
161    public synchronized RouteConfigurationDefinition getRouteConfigurationDefinition(String id) {
162        for (RouteConfigurationDefinition def : routesConfigurations) {
163            if (def.idOrCreate(camelContext.getCamelContextExtension().getContextPlugin(NodeIdFactory.class)).equals(id)) {
164                return def;
165            }
166        }
167        // you can have a global route configuration that has no ID assigned
168        return routesConfigurations.stream().filter(c -> c.getId() == null).findFirst().orElse(null);
169    }
170
171    @Override
172    public void removeRouteConfiguration(RouteConfigurationDefinition routeConfigurationDefinition) throws Exception {
173        RouteConfigurationDefinition toBeRemoved = getRouteConfigurationDefinition(routeConfigurationDefinition.getId());
174        this.routesConfigurations.remove(toBeRemoved);
175    }
176
177    @Override
178    public synchronized void addRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
179        if (routeDefinitions == null || routeDefinitions.isEmpty()) {
180            return;
181        }
182
183        List<RouteDefinition> list;
184        if (routeFilter == null) {
185            list = new ArrayList<>(routeDefinitions);
186        } else {
187            list = new ArrayList<>();
188            for (RouteDefinition r : routeDefinitions) {
189                if (routeFilter.apply(r)) {
190                    list.add(r);
191                }
192            }
193        }
194
195        removeRouteDefinitions(list);
196
197        // special if rest-dsl is inlining routes
198        if (camelContext.getRestConfiguration().isInlineRoutes()) {
199            List<RouteDefinition> allRoutes = new ArrayList<>();
200            allRoutes.addAll(list);
201            allRoutes.addAll(this.routeDefinitions);
202
203            List<RouteDefinition> toBeRemoved = new ArrayList<>();
204            Map<String, RouteDefinition> directs = new HashMap<>();
205            for (RouteDefinition r : allRoutes) {
206                // does the route start with direct, which is candidate for rest-dsl
207                FromDefinition from = r.getInput();
208                if (from != null) {
209                    String uri = from.getEndpointUri();
210                    if (uri != null && uri.startsWith("direct:")) {
211                        directs.put(uri, r);
212                    }
213                }
214            }
215            for (RouteDefinition r : allRoutes) {
216                // loop all rest routes
217                FromDefinition from = r.getInput();
218                if (from != null) {
219                    String uri = from.getEndpointUri();
220                    if (uri != null && uri.startsWith("rest:")) {
221                        ProcessorDefinition<?> def = r.getOutputs().get(0);
222                        if (def instanceof ToDefinition) {
223                            ToDefinition to = (ToDefinition) def;
224                            String toUri = to.getEndpointUri();
225                            RouteDefinition toBeInlined = directs.get(toUri);
226                            if (toBeInlined != null) {
227                                toBeRemoved.add(toBeInlined);
228                                // inline by replacing the outputs
229                                r.getOutputs().clear();
230                                r.getOutputs().addAll(toBeInlined.getOutputs());
231                            }
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            ((ModelCamelContext) getCamelContext()).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.getCamelContextExtension().getContextPlugin(NodeIdFactory.class)).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.getCamelContextExtension().getContextPlugin(NodeIdFactory.class)).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            props.putAll(beanFactory.getProperties());
545        }
546        if (beanFactory.getPropertyDefinitions() != null) {
547            beanFactory.getPropertyDefinitions().forEach(p -> props.put(p.getKey(), p.getValue()));
548        }
549        if (beanFactory.getBeanSupplier() != null) {
550            if (props.isEmpty()) {
551                // bean class is optional for supplier
552                if (beanFactory.getBeanClass() != null) {
553                    routeTemplateContext.bind(beanFactory.getName(), beanFactory.getBeanClass(), beanFactory.getBeanSupplier());
554                } else {
555                    routeTemplateContext.bind(beanFactory.getName(), beanFactory.getBeanSupplier());
556                }
557            }
558        } else if (beanFactory.getScript() != null && beanFactory.getScriptLanguage() != null) {
559            final CamelContext camelContext = routeTemplateContext.getCamelContext();
560            final Language lan = camelContext.resolveLanguage(beanFactory.getScriptLanguage());
561            final Class<?> clazz;
562            if (beanFactory.getBeanClass() != null) {
563                clazz = beanFactory.getBeanClass();
564            } else if (beanFactory.getType() != null) {
565                String fqn = beanFactory.getType();
566                if (fqn.contains(":")) {
567                    fqn = StringHelper.after(fqn, ":");
568                }
569                clazz = camelContext.getClassResolver().resolveMandatoryClass(fqn);
570            } else {
571                clazz = Object.class;
572            }
573            final String script = beanFactory.getScript();
574            final ScriptingLanguage slan = lan instanceof ScriptingLanguage ? (ScriptingLanguage) lan : null;
575            if (slan != null) {
576                // scripting language should be evaluated with route template context as binding
577                // and memorize so the script is only evaluated once and the local bean is the same
578                // if a route template refers to the local bean multiple times
579                routeTemplateContext.bind(beanFactory.getName(), clazz, Suppliers.memorize(() -> {
580                    Map<String, Object> bindings = new HashMap<>();
581                    // use rtx as the short-hand name, as context would imply its CamelContext
582                    bindings.put("rtc", routeTemplateContext);
583                    Object local = slan.evaluate(script, bindings, Object.class);
584                    if (!props.isEmpty()) {
585                        PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
586                    }
587                    return local;
588                }));
589            } else {
590                // exchange based languages needs a dummy exchange to be evaluated
591                // and memorize so the script is only evaluated once and the local bean is the same
592                // if a route template refers to the local bean multiple times
593                routeTemplateContext.bind(beanFactory.getName(), clazz, Suppliers.memorize(() -> {
594                    ExchangeFactory ef = camelContext.getCamelContextExtension().getExchangeFactory();
595                    Exchange dummy = ef.create(false);
596                    try {
597                        String text = ScriptHelper.resolveOptionalExternalScript(camelContext, dummy, script);
598                        if (text != null) {
599                            Expression exp = lan.createExpression(text);
600                            Object local = exp.evaluate(dummy, clazz);
601                            if (!props.isEmpty()) {
602                                PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
603                            }
604                            return local;
605                        } else {
606                            return null;
607                        }
608                    } finally {
609                        ef.release(dummy);
610                    }
611                }));
612            }
613        } else if (beanFactory.getBeanClass() != null
614                || beanFactory.getType() != null && beanFactory.getType().startsWith("#class:")) {
615            // if there is a factory method then the class/bean should be created in a different way
616            String className = null;
617            String factoryMethod = null;
618            String parameters = null;
619            if (beanFactory.getType() != null) {
620                className = beanFactory.getType().substring(7);
621                if (className.endsWith(")") && className.indexOf('(') != -1) {
622                    parameters = StringHelper.after(className, "(");
623                    parameters = parameters.substring(0, parameters.length() - 1); // clip last )
624                    className = StringHelper.before(className, "(");
625                }
626                if (className != null && className.indexOf('#') != -1) {
627                    factoryMethod = StringHelper.after(className, "#");
628                    className = StringHelper.before(className, "#");
629                }
630            }
631            if (className != null && (factoryMethod != null || parameters != null)) {
632                final CamelContext camelContext = routeTemplateContext.getCamelContext();
633                final Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(className);
634                Class<?> fc = null;
635                if (factoryMethod != null) {
636                    String typeOrRef = StringHelper.before(factoryMethod, ":");
637                    if (typeOrRef != null) {
638                        // use another class with factory method
639                        factoryMethod = StringHelper.after(factoryMethod, ":");
640                        // special to support factory method parameters
641                        Object existing = camelContext.getRegistry().lookupByName(typeOrRef);
642                        if (existing != null) {
643                            fc = existing.getClass();
644                        } else {
645                            fc = camelContext.getClassResolver().resolveMandatoryClass(typeOrRef);
646                        }
647                    }
648                }
649                final Class<?> factoryClass = fc;
650                final String fqn = className;
651                final String fm = factoryMethod;
652                final String fp = parameters;
653                routeTemplateContext.bind(beanFactory.getName(), Object.class, Suppliers.memorize(() -> {
654                    // resolve placeholders in parameters
655                    String params = camelContext.resolvePropertyPlaceholders(fp);
656                    try {
657                        Object local;
658                        if (fm != null) {
659                            if (fp != null) {
660                                // special to support factory method parameters
661                                Class<?> target = factoryClass != null ? factoryClass : clazz;
662                                local = PropertyBindingSupport.newInstanceFactoryParameters(camelContext, target, fm, params);
663                            } else {
664                                local = camelContext.getInjector().newInstance(clazz, factoryClass, fm);
665                            }
666                            if (local == null) {
667                                throw new IllegalStateException(
668                                        "Cannot create bean instance using factory method: " + fqn + "#" + fm);
669                            }
670                        } else {
671                            // special to support constructor parameters
672                            local = PropertyBindingSupport.newInstanceConstructorParameters(camelContext, clazz, params);
673                        }
674                        if (!props.isEmpty()) {
675                            PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
676                        }
677                        return local;
678                    } catch (Exception e) {
679                        throw new IllegalStateException(
680                                "Cannot create bean: " + beanFactory.getType());
681                    }
682                }));
683            } else {
684                final CamelContext camelContext = routeTemplateContext.getCamelContext();
685                Class<?> clazz = beanFactory.getBeanClass() != null
686                        ? beanFactory.getBeanClass() : camelContext.getClassResolver().resolveMandatoryClass(className);
687                // we only have the bean class so we use that to create a new bean via the injector
688                // and memorize so the bean is only created once and the local bean is the same
689                // if a route template refers to the local bean multiple times
690                routeTemplateContext.bind(beanFactory.getName(), clazz,
691                        Suppliers.memorize(() -> {
692                            Object local = camelContext.getInjector().newInstance(clazz);
693                            if (!props.isEmpty()) {
694                                PropertyBindingSupport.setPropertiesOnTarget(camelContext, local, props);
695                            }
696                            return local;
697                        }));
698            }
699        } else if (beanFactory.getType() != null && beanFactory.getType().startsWith("#type:")) {
700            final CamelContext camelContext = routeTemplateContext.getCamelContext();
701            Class<?> clazz = camelContext.getClassResolver().resolveMandatoryClass(beanFactory.getType().substring(6));
702            Object found = camelContext.getRegistry().mandatoryFindSingleByType(clazz);
703            // do not set properties when using #type as it uses an existing shared bean
704            routeTemplateContext.bind(beanFactory.getName(), clazz, found);
705        } else {
706            // invalid syntax for the local bean, so lets report an exception
707            throw new IllegalArgumentException(
708                    "Route template local bean: " + beanFactory.getName() + " has invalid type syntax: " + beanFactory.getType()
709                                               + ". To refer to a class then prefix the value with #class such as: #class:fullyQualifiedClassName");
710        }
711    }
712
713    @Override
714    public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
715            throws Exception {
716        ObjectHelper.notNull(templatedRouteDefinition, "templatedRouteDefinition");
717
718        final RouteTemplateContext routeTemplateContext = new DefaultRouteTemplateContext(camelContext);
719        // Load the parameters into the context
720        final List<TemplatedRouteParameterDefinition> parameters = templatedRouteDefinition.getParameters();
721        if (parameters != null) {
722            for (TemplatedRouteParameterDefinition parameterDefinition : parameters) {
723                routeTemplateContext.setParameter(parameterDefinition.getName(), parameterDefinition.getValue());
724            }
725        }
726        // Bind the beans into the context
727        final List<TemplatedRouteBeanDefinition> beans = templatedRouteDefinition.getBeans();
728        if (beans != null) {
729            for (TemplatedRouteBeanDefinition beanDefinition : beans) {
730                bind(beanDefinition, routeTemplateContext);
731            }
732        }
733        // Add the route
734        addRouteFromTemplate(templatedRouteDefinition.getRouteId(), templatedRouteDefinition.getRouteTemplateRef(),
735                templatedRouteDefinition.getPrefixId(), routeTemplateContext);
736    }
737
738    @Override
739    public synchronized List<RestDefinition> getRestDefinitions() {
740        return restDefinitions;
741    }
742
743    @Override
744    public synchronized void addRestDefinitions(Collection<RestDefinition> restDefinitions, boolean addToRoutes)
745            throws Exception {
746        if (restDefinitions == null || restDefinitions.isEmpty()) {
747            return;
748        }
749
750        this.restDefinitions.addAll(restDefinitions);
751        if (addToRoutes) {
752            // rests are also routes so need to add them there too
753            for (final RestDefinition restDefinition : restDefinitions) {
754                List<RouteDefinition> routeDefinitions = restDefinition.asRouteDefinition(camelContext);
755                addRouteDefinitions(routeDefinitions);
756            }
757        }
758    }
759
760    @Override
761    public ServiceCallConfigurationDefinition getServiceCallConfiguration(String serviceName) {
762        if (serviceName == null) {
763            serviceName = "";
764        }
765
766        return serviceCallConfigurations.get(serviceName);
767    }
768
769    @Override
770    public void setServiceCallConfiguration(ServiceCallConfigurationDefinition configuration) {
771        serviceCallConfigurations.put("", configuration);
772    }
773
774    @Override
775    public void setServiceCallConfigurations(List<ServiceCallConfigurationDefinition> configurations) {
776        if (configurations != null) {
777            for (ServiceCallConfigurationDefinition configuration : configurations) {
778                serviceCallConfigurations.put(configuration.getId(), configuration);
779            }
780        }
781    }
782
783    @Override
784    public void addServiceCallConfiguration(String serviceName, ServiceCallConfigurationDefinition configuration) {
785        serviceCallConfigurations.put(serviceName, configuration);
786    }
787
788    @Override
789    public Resilience4jConfigurationDefinition getResilience4jConfiguration(String id) {
790        if (id == null) {
791            id = "";
792        }
793
794        return resilience4jConfigurations.get(id);
795    }
796
797    @Override
798    public void setResilience4jConfiguration(Resilience4jConfigurationDefinition configuration) {
799        resilience4jConfigurations.put("", configuration);
800    }
801
802    @Override
803    public void setResilience4jConfigurations(List<Resilience4jConfigurationDefinition> configurations) {
804        if (configurations != null) {
805            for (Resilience4jConfigurationDefinition configuration : configurations) {
806                resilience4jConfigurations.put(configuration.getId(), configuration);
807            }
808        }
809    }
810
811    @Override
812    public void addResilience4jConfiguration(String id, Resilience4jConfigurationDefinition configuration) {
813        resilience4jConfigurations.put(id, configuration);
814    }
815
816    @Override
817    public FaultToleranceConfigurationDefinition getFaultToleranceConfiguration(String id) {
818        if (id == null) {
819            id = "";
820        }
821
822        return faultToleranceConfigurations.get(id);
823    }
824
825    @Override
826    public void setFaultToleranceConfiguration(FaultToleranceConfigurationDefinition configuration) {
827        faultToleranceConfigurations.put("", configuration);
828    }
829
830    @Override
831    public void setFaultToleranceConfigurations(List<FaultToleranceConfigurationDefinition> configurations) {
832        if (configurations != null) {
833            for (FaultToleranceConfigurationDefinition configuration : configurations) {
834                faultToleranceConfigurations.put(configuration.getId(), configuration);
835            }
836        }
837    }
838
839    @Override
840    public void addFaultToleranceConfiguration(String id, FaultToleranceConfigurationDefinition configuration) {
841        faultToleranceConfigurations.put(id, configuration);
842    }
843
844    @Override
845    public DataFormatDefinition resolveDataFormatDefinition(String name) {
846        // lookup type and create the data format from it
847        DataFormatDefinition type = lookup(camelContext, name, DataFormatDefinition.class);
848        if (type == null && getDataFormats() != null) {
849            type = getDataFormats().get(name);
850        }
851        return type;
852    }
853
854    @SuppressWarnings("rawtypes")
855    @Override
856    public ProcessorDefinition<?> getProcessorDefinition(String id) {
857        for (RouteDefinition route : getRouteDefinitions()) {
858            Collection<ProcessorDefinition> col
859                    = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class);
860            for (ProcessorDefinition proc : col) {
861                String pid = proc.getId();
862                // match direct by ids
863                if (id.equals(pid)) {
864                    return proc;
865                }
866                // try to match via node prefix id
867                if (proc.getNodePrefixId() != null) {
868                    pid = proc.getNodePrefixId() + pid;
869                    if (id.equals(pid)) {
870                        return proc;
871                    }
872                }
873            }
874        }
875        return null;
876    }
877
878    @Override
879    public <T extends ProcessorDefinition<T>> T getProcessorDefinition(String id, Class<T> type) {
880        ProcessorDefinition<?> answer = getProcessorDefinition(id);
881        if (answer != null) {
882            return type.cast(answer);
883        }
884        return null;
885    }
886
887    @Override
888    public Map<String, DataFormatDefinition> getDataFormats() {
889        return dataFormats;
890    }
891
892    @Override
893    public void setDataFormats(Map<String, DataFormatDefinition> dataFormats) {
894        this.dataFormats = dataFormats;
895    }
896
897    @Override
898    public List<TransformerDefinition> getTransformers() {
899        return transformers;
900    }
901
902    @Override
903    public void setTransformers(List<TransformerDefinition> transformers) {
904        this.transformers = transformers;
905    }
906
907    @Override
908    public List<ValidatorDefinition> getValidators() {
909        return validators;
910    }
911
912    @Override
913    public void setValidators(List<ValidatorDefinition> validators) {
914        this.validators = validators;
915    }
916
917    @Override
918    public void setRouteFilterPattern(String include, String exclude) {
919        setRouteFilter(RouteFilters.filterByPattern(include, exclude));
920    }
921
922    @Override
923    public Function<RouteDefinition, Boolean> getRouteFilter() {
924        return routeFilter;
925    }
926
927    @Override
928    public void setRouteFilter(Function<RouteDefinition, Boolean> routeFilter) {
929        this.routeFilter = routeFilter;
930    }
931
932    @Override
933    public ModelReifierFactory getModelReifierFactory() {
934        return modelReifierFactory;
935    }
936
937    @Override
938    public void setModelReifierFactory(ModelReifierFactory modelReifierFactory) {
939        this.modelReifierFactory = modelReifierFactory;
940    }
941
942    @Override
943    public void addRegistryBean(RegistryBeanDefinition bean) {
944        // remove exiting bean with same name to update
945        beans.removeIf(b -> bean.getName().equals(b.getName()));
946        beans.add(bean);
947    }
948
949    @Override
950    public List<RegistryBeanDefinition> getRegistryBeans() {
951        return beans;
952    }
953
954    /**
955     * Should we start newly added routes?
956     */
957    protected boolean shouldStartRoutes() {
958        return camelContext.isStarted() && !camelContext.isStarting();
959    }
960
961    private static <T> T lookup(CamelContext context, String ref, Class<T> type) {
962        try {
963            return context.getRegistry().lookupByNameAndType(ref, type);
964        } catch (Exception e) {
965            // need to ignore not same type and return it as null
966            return null;
967        }
968    }
969
970    /**
971     * Indicates whether the route configuration should be included according to the precondition.
972     *
973     * @param  definition the definition of the route configuration to check.
974     * @return            {@code true} if the route configuration should be included, {@code false} otherwise.
975     */
976    private boolean includedRouteConfiguration(RouteConfigurationDefinition definition) {
977        return PreconditionHelper.included(definition, camelContext);
978    }
979}