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