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