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.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.function.Function;
027
028import org.apache.camel.CamelContext;
029import org.apache.camel.Expression;
030import org.apache.camel.FailedToStartRouteException;
031import org.apache.camel.LoggingLevel;
032import org.apache.camel.Predicate;
033import org.apache.camel.Processor;
034import org.apache.camel.Route;
035import org.apache.camel.RouteTemplateContext;
036import org.apache.camel.StartupStep;
037import org.apache.camel.ValueHolder;
038import org.apache.camel.api.management.JmxSystemPropertyKeys;
039import org.apache.camel.impl.engine.DefaultExecutorServiceManager;
040import org.apache.camel.impl.engine.RouteService;
041import org.apache.camel.impl.engine.SimpleCamelContext;
042import org.apache.camel.impl.engine.TransformerKey;
043import org.apache.camel.impl.engine.ValidatorKey;
044import org.apache.camel.impl.scan.AssignableToPackageScanFilter;
045import org.apache.camel.impl.scan.InvertingPackageScanFilter;
046import org.apache.camel.model.DataFormatDefinition;
047import org.apache.camel.model.FaultToleranceConfigurationDefinition;
048import org.apache.camel.model.Model;
049import org.apache.camel.model.ModelCamelContext;
050import org.apache.camel.model.ModelLifecycleStrategy;
051import org.apache.camel.model.ProcessorDefinition;
052import org.apache.camel.model.ProcessorDefinitionHelper;
053import org.apache.camel.model.Resilience4jConfigurationDefinition;
054import org.apache.camel.model.RouteConfigurationDefinition;
055import org.apache.camel.model.RouteDefinition;
056import org.apache.camel.model.RouteDefinitionHelper;
057import org.apache.camel.model.RouteTemplateDefinition;
058import org.apache.camel.model.RouteTemplatesDefinition;
059import org.apache.camel.model.RoutesDefinition;
060import org.apache.camel.model.TemplatedRouteDefinition;
061import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
062import org.apache.camel.model.language.ExpressionDefinition;
063import org.apache.camel.model.rest.RestDefinition;
064import org.apache.camel.model.rest.RestsDefinition;
065import org.apache.camel.model.transformer.TransformerDefinition;
066import org.apache.camel.model.validator.ValidatorDefinition;
067import org.apache.camel.spi.BeanRepository;
068import org.apache.camel.spi.DataFormat;
069import org.apache.camel.spi.DataType;
070import org.apache.camel.spi.ExecutorServiceManager;
071import org.apache.camel.spi.LocalBeanRepositoryAware;
072import org.apache.camel.spi.ModelReifierFactory;
073import org.apache.camel.spi.ModelToXMLDumper;
074import org.apache.camel.spi.ModelToYAMLDumper;
075import org.apache.camel.spi.PackageScanClassResolver;
076import org.apache.camel.spi.PropertiesComponent;
077import org.apache.camel.spi.Registry;
078import org.apache.camel.spi.StartupStepRecorder;
079import org.apache.camel.spi.Transformer;
080import org.apache.camel.spi.UuidGenerator;
081import org.apache.camel.spi.Validator;
082import org.apache.camel.support.CamelContextHelper;
083import org.apache.camel.support.DefaultRegistry;
084import org.apache.camel.support.LocalBeanRegistry;
085import org.apache.camel.support.PluginHelper;
086import org.apache.camel.support.SimpleUuidGenerator;
087import org.apache.camel.util.ObjectHelper;
088import org.apache.camel.util.OrderedLocationProperties;
089import org.apache.camel.util.StopWatch;
090import org.apache.camel.util.StringHelper;
091import org.apache.camel.util.concurrent.NamedThreadLocal;
092import org.slf4j.Logger;
093import org.slf4j.LoggerFactory;
094
095/**
096 * Represents the context used to configure routes and the policies to use.
097 */
098public class DefaultCamelContext extends SimpleCamelContext implements ModelCamelContext {
099
100    // global options that can be set on CamelContext as part of concurrent testing
101    // which means options should be isolated via thread-locals and not a static instance
102    // use a HashMap to store only JDK classes in the thread-local so there will not be any Camel classes leaking
103    private static final ThreadLocal<Map<String, Object>> OPTIONS = new NamedThreadLocal<>("CamelContextOptions", HashMap::new);
104    private static final String OPTION_NO_START = "OptionNoStart";
105    private static final String OPTION_DISABLE_JMX = "OptionDisableJMX";
106    private static final String OPTION_EXCLUDE_ROUTES = "OptionExcludeRoutes";
107
108    private static final Logger LOG = LoggerFactory.getLogger(DefaultCamelContext.class);
109    private static final UuidGenerator UUID = new SimpleUuidGenerator();
110
111    private Model model = new DefaultModel(this);
112
113    /**
114     * Creates the {@link ModelCamelContext} using {@link org.apache.camel.support.DefaultRegistry} as registry.
115     * <p/>
116     * Use one of the other constructors to force use an explicit registry.
117     */
118    public DefaultCamelContext() {
119        this(true);
120    }
121
122    /**
123     * Creates the {@link CamelContext} using the given {@link BeanRepository} as first-choice repository, and the
124     * {@link org.apache.camel.support.SimpleRegistry} as fallback, via the {@link DefaultRegistry} implementation.
125     *
126     * @param repository the bean repository.
127     */
128    public DefaultCamelContext(BeanRepository repository) {
129        this(new DefaultRegistry(repository));
130    }
131
132    /**
133     * Creates the {@link ModelCamelContext} using the given registry
134     *
135     * @param registry the registry
136     */
137    public DefaultCamelContext(Registry registry) {
138        this();
139        getCamelContextExtension().setRegistry(registry);
140    }
141
142    public DefaultCamelContext(boolean init) {
143        super(init);
144        if (isDisableJmx()) {
145            disableJMX();
146        }
147    }
148
149    @Override
150    protected void doStop() throws Exception {
151        super.doStop();
152        OPTIONS.remove();
153    }
154
155    @Override
156    protected void doDumpRoutes() {
157        if ("yaml".equalsIgnoreCase(getDumpRoutes())) {
158            doDumpRoutesAsYaml();
159        } else {
160            // xml is default
161            doDumpRoutesAsXml();
162        }
163    }
164
165    protected void doDumpRoutesAsXml() {
166        final ModelToXMLDumper dumper = PluginHelper.getModelToXMLDumper(this);
167
168        int size = getRouteDefinitions().size();
169        if (size > 0) {
170            LOG.info("Dumping {} routes as XML", size);
171            // for XML to output nicely all routes in one XML then lets put them into <routes>
172            RoutesDefinition def = new RoutesDefinition();
173            def.setRoutes(getRouteDefinitions());
174            try {
175                String xml = dumper.dumpModelAsXml(this, def, true);
176                // lets separate routes with empty line
177                xml = StringHelper.replaceFirst(xml, "xmlns=\"http://camel.apache.org/schema/spring\">",
178                        "xmlns=\"http://camel.apache.org/schema/spring\">\n");
179                xml = xml.replace("</route>", "</route>\n");
180                LOG.info("\n\n{}\n", xml);
181            } catch (Exception e) {
182                LOG.warn("Error dumping routes to XML due to {}. This exception is ignored.", e.getMessage(), e);
183            }
184        }
185
186        size = getRestDefinitions().size();
187        if (size > 0) {
188            LOG.info("Dumping {} rests as XML", size);
189            // for XML to output nicely all routes in one XML then lets put them into <routes>
190            RestsDefinition def = new RestsDefinition();
191            def.setRests(getRestDefinitions());
192            try {
193                String xml = dumper.dumpModelAsXml(this, def, true);
194                // lets separate rests with empty line
195                xml = StringHelper.replaceFirst(xml, "xmlns=\"http://camel.apache.org/schema/spring\">",
196                        "xmlns=\"http://camel.apache.org/schema/spring\">\n");
197                xml = xml.replace("</rest>", "</rest>\n");
198                LOG.info("\n\n{}\n", xml);
199            } catch (Exception e) {
200                LOG.warn("Error dumping rests to XML due to {}. This exception is ignored.", e.getMessage(), e);
201            }
202        }
203
204        size = getRouteTemplateDefinitions().size();
205        if (size > 0) {
206            LOG.info("Dumping {} route templates as XML", size);
207            // for XML to output nicely all routes in one XML then lets put them into <routes>
208            RouteTemplatesDefinition def = new RouteTemplatesDefinition();
209            def.setRouteTemplates(getRouteTemplateDefinitions());
210            try {
211                String xml = dumper.dumpModelAsXml(this, def, true);
212                // lets separate rests with empty line
213                xml = StringHelper.replaceFirst(xml, "xmlns=\"http://camel.apache.org/schema/spring\">",
214                        "xmlns=\"http://camel.apache.org/schema/spring\">\n");
215                xml = xml.replace("</routeTemplate>", "</routeTemplate>\n");
216                LOG.info("\n\n{}\n", xml);
217            } catch (Exception e) {
218                LOG.warn("Error dumping route-templates to XML due to {}. This exception is ignored.", e.getMessage(), e);
219            }
220        }
221    }
222
223    protected void doDumpRoutesAsYaml() {
224        final ModelToYAMLDumper dumper = PluginHelper.getModelToYAMLDumper(this);
225
226        int size = getRouteDefinitions().size();
227        if (size > 0) {
228            LOG.info("Dumping {} routes as YAML", size);
229            RoutesDefinition def = new RoutesDefinition();
230            def.setRoutes(getRouteDefinitions());
231            try {
232                String yaml = dumper.dumpModelAsYaml(this, def, true, false);
233                LOG.info("\n\n{}\n", yaml);
234            } catch (Exception e) {
235                LOG.warn("Error dumping routes to YAML due to {}. This exception is ignored.", e.getMessage(), e);
236            }
237        }
238
239        size = getRestDefinitions().size();
240        if (size > 0) {
241            LOG.info("Dumping {} rests as YAML", size);
242            RestsDefinition def = new RestsDefinition();
243            def.setRests(getRestDefinitions());
244            try {
245                String taml = dumper.dumpModelAsYaml(this, def, true, false);
246                LOG.info("\n\n{}\n", taml);
247            } catch (Exception e) {
248                LOG.warn("Error dumping rests to YAML due to {}. This exception is ignored.", e.getMessage(), e);
249            }
250        }
251
252        size = getRouteTemplateDefinitions().size();
253        if (size > 0) {
254            LOG.info("Dumping {} route templates as YAML", size);
255            RouteTemplatesDefinition def = new RouteTemplatesDefinition();
256            def.setRouteTemplates(getRouteTemplateDefinitions());
257            try {
258                String yaml = dumper.dumpModelAsYaml(this, def, true, false);
259                LOG.info("\n\n{}\n", yaml);
260            } catch (Exception e) {
261                LOG.warn("Error dumping route-templates to YAML due to {}. This exception is ignored.", e.getMessage(), e);
262            }
263        }
264    }
265
266    public static void setNoStart(boolean b) {
267        getOptions().put(OPTION_NO_START, b);
268    }
269
270    public static boolean isNoStart() {
271        return (Boolean) getOptions().getOrDefault(OPTION_NO_START, Boolean.FALSE);
272    }
273
274    public static void setDisableJmx(boolean b) {
275        getOptions().put(OPTION_DISABLE_JMX, b);
276    }
277
278    public static boolean isDisableJmx() {
279        return (Boolean) getOptions().getOrDefault(OPTION_DISABLE_JMX, Boolean.getBoolean(JmxSystemPropertyKeys.DISABLED));
280    }
281
282    @Override
283    public String getTestExcludeRoutes() {
284        return getExcludeRoutes();
285    }
286
287    public static String getExcludeRoutes() {
288        return (String) getOptions().get(OPTION_EXCLUDE_ROUTES);
289    }
290
291    public static void setExcludeRoutes(String s) {
292        getOptions().put(OPTION_EXCLUDE_ROUTES, s);
293    }
294
295    public static void clearOptions() {
296        OPTIONS.get().clear();
297    }
298
299    private static Map<String, Object> getOptions() {
300        return OPTIONS.get();
301    }
302
303    @Override
304    public void start() {
305        // for example from unit testing we want to start Camel later (manually)
306        if (isNoStart()) {
307            LOG.trace("Ignoring start() as NO_START is true");
308            return;
309        }
310
311        if (!isStarted() && !isStarting()) {
312            StopWatch watch = new StopWatch();
313            super.start();
314            LOG.debug("start() took {} millis", watch.taken());
315        } else {
316            // ignore as Camel is already started
317            LOG.trace("Ignoring start() as Camel is already started");
318        }
319    }
320
321    @Override
322    protected PackageScanClassResolver createPackageScanClassResolver() {
323        PackageScanClassResolver resolver = super.createPackageScanClassResolver();
324        String excluded = getExcludeRoutes();
325        if (ObjectHelper.isNotEmpty(excluded)) {
326            Set<Class<?>> excludedClasses = new HashSet<>();
327            for (String str : excluded.split(",")) {
328                excludedClasses.add(getClassResolver().resolveClass(str));
329            }
330            resolver.addFilter(new InvertingPackageScanFilter(new AssignableToPackageScanFilter(excludedClasses)));
331        }
332        return resolver;
333    }
334
335    @Override
336    public void disposeModel() {
337        LOG.debug("Disposing Model on CamelContext");
338        model = null;
339    }
340
341    @Override
342    public void addModelLifecycleStrategy(ModelLifecycleStrategy modelLifecycleStrategy) {
343        model.addModelLifecycleStrategy(modelLifecycleStrategy);
344    }
345
346    @Override
347    public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
348        return model.getModelLifecycleStrategies();
349    }
350
351    @Override
352    public void addRouteConfiguration(RouteConfigurationDefinition routesConfiguration) {
353        model.addRouteConfiguration(routesConfiguration);
354    }
355
356    @Override
357    public void addRouteConfigurations(List<RouteConfigurationDefinition> routesConfigurations) {
358        model.addRouteConfigurations(routesConfigurations);
359    }
360
361    @Override
362    public List<RouteConfigurationDefinition> getRouteConfigurationDefinitions() {
363        return model.getRouteConfigurationDefinitions();
364    }
365
366    @Override
367    public RouteConfigurationDefinition getRouteConfigurationDefinition(String id) {
368        return model.getRouteConfigurationDefinition(id);
369    }
370
371    @Override
372    public void removeRouteConfiguration(RouteConfigurationDefinition routeConfigurationDefinition) throws Exception {
373        model.removeRouteConfiguration(routeConfigurationDefinition);
374    }
375
376    @Override
377    public List<RouteDefinition> getRouteDefinitions() {
378        return model.getRouteDefinitions();
379    }
380
381    @Override
382    public RouteDefinition getRouteDefinition(String id) {
383        return model.getRouteDefinition(id);
384    }
385
386    @Override
387    public void addRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
388        model.addRouteDefinitions(routeDefinitions);
389    }
390
391    @Override
392    public void addRouteDefinition(RouteDefinition routeDefinition) throws Exception {
393        model.addRouteDefinition(routeDefinition);
394    }
395
396    @Override
397    public void removeRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
398        if (!isLockModel()) {
399            model.removeRouteDefinitions(routeDefinitions);
400        }
401    }
402
403    @Override
404    public void removeRouteDefinition(RouteDefinition routeDefinition) throws Exception {
405        if (!isLockModel()) {
406            model.removeRouteDefinition(routeDefinition);
407        }
408    }
409
410    @Override
411    public List<RouteTemplateDefinition> getRouteTemplateDefinitions() {
412        return model.getRouteTemplateDefinitions();
413    }
414
415    @Override
416    public RouteTemplateDefinition getRouteTemplateDefinition(String id) {
417        return model.getRouteTemplateDefinition(id);
418    }
419
420    @Override
421    public void addRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
422        model.addRouteTemplateDefinitions(routeTemplateDefinitions);
423    }
424
425    @Override
426    public void addRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
427
428        model.addRouteTemplateDefinition(routeTemplateDefinition);
429    }
430
431    @Override
432    public void removeRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
433
434        if (!isLockModel()) {
435            model.removeRouteTemplateDefinitions(routeTemplateDefinitions);
436        }
437    }
438
439    @Override
440    public void removeRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
441        if (!isLockModel()) {
442            model.removeRouteTemplateDefinition(routeTemplateDefinition);
443        }
444    }
445
446    @Override
447    public void removeRouteTemplateDefinitions(String pattern) throws Exception {
448        if (!isLockModel()) {
449            model.removeRouteTemplateDefinitions(pattern);
450        }
451    }
452
453    @Override
454    public void addRouteTemplateDefinitionConverter(String templateIdPattern, RouteTemplateDefinition.Converter converter) {
455
456        model.addRouteTemplateDefinitionConverter(templateIdPattern, converter);
457    }
458
459    @Override
460    public String addRouteFromTemplate(String routeId, String routeTemplateId, Map<String, Object> parameters)
461            throws Exception {
462
463        return model.addRouteFromTemplate(routeId, routeTemplateId, parameters);
464    }
465
466    @Override
467    public String addRouteFromTemplate(String routeId, String routeTemplateId, String prefixId, Map<String, Object> parameters)
468            throws Exception {
469
470        return model.addRouteFromTemplate(routeId, routeTemplateId, prefixId, parameters);
471    }
472
473    @Override
474    public String addRouteFromTemplate(
475            String routeId, String routeTemplateId, String prefixId, RouteTemplateContext routeTemplateContext)
476            throws Exception {
477
478        return model.addRouteFromTemplate(routeId, routeTemplateId, prefixId, routeTemplateContext);
479    }
480
481    @Override
482    public void addRouteFromTemplatedRoute(TemplatedRouteDefinition templatedRouteDefinition)
483            throws Exception {
484
485        model.addRouteFromTemplatedRoute(templatedRouteDefinition);
486    }
487
488    @Override
489    public void removeRouteTemplates(String pattern) throws Exception {
490
491        if (!isLockModel()) {
492            model.removeRouteTemplateDefinitions(pattern);
493        }
494    }
495
496    @Override
497    public List<RestDefinition> getRestDefinitions() {
498        return model.getRestDefinitions();
499    }
500
501    @Override
502    public void addRestDefinitions(Collection<RestDefinition> restDefinitions, boolean addToRoutes) throws Exception {
503        model.addRestDefinitions(restDefinitions, addToRoutes);
504    }
505
506    @Override
507    public void setDataFormats(Map<String, DataFormatDefinition> dataFormats) {
508        model.setDataFormats(dataFormats);
509    }
510
511    @Override
512    public Map<String, DataFormatDefinition> getDataFormats() {
513        return model.getDataFormats();
514    }
515
516    @Override
517    public DataFormatDefinition resolveDataFormatDefinition(String name) {
518        return model.resolveDataFormatDefinition(name);
519    }
520
521    @Override
522    public ProcessorDefinition<?> getProcessorDefinition(String id) {
523        return model.getProcessorDefinition(id);
524    }
525
526    @Override
527    public <T extends ProcessorDefinition<T>> T getProcessorDefinition(String id, Class<T> type) {
528        return model.getProcessorDefinition(id, type);
529    }
530
531    @Override
532    public void setValidators(List<ValidatorDefinition> validators) {
533        model.setValidators(validators);
534    }
535
536    @Override
537    public Resilience4jConfigurationDefinition getResilience4jConfiguration(String id) {
538        return model.getResilience4jConfiguration(id);
539    }
540
541    @Override
542    public void setResilience4jConfiguration(Resilience4jConfigurationDefinition configuration) {
543        model.setResilience4jConfiguration(configuration);
544    }
545
546    @Override
547    public void setResilience4jConfigurations(List<Resilience4jConfigurationDefinition> configurations) {
548        model.setResilience4jConfigurations(configurations);
549    }
550
551    @Override
552    public void addResilience4jConfiguration(String id, Resilience4jConfigurationDefinition configuration) {
553        model.addResilience4jConfiguration(id, configuration);
554    }
555
556    @Override
557    public FaultToleranceConfigurationDefinition getFaultToleranceConfiguration(String id) {
558        return model.getFaultToleranceConfiguration(id);
559    }
560
561    @Override
562    public void setFaultToleranceConfiguration(FaultToleranceConfigurationDefinition configuration) {
563        model.setFaultToleranceConfiguration(configuration);
564    }
565
566    @Override
567    public void setFaultToleranceConfigurations(List<FaultToleranceConfigurationDefinition> configurations) {
568        model.setFaultToleranceConfigurations(configurations);
569    }
570
571    @Override
572    public void addFaultToleranceConfiguration(String id, FaultToleranceConfigurationDefinition configuration) {
573        model.addFaultToleranceConfiguration(id, configuration);
574    }
575
576    @Override
577    public List<ValidatorDefinition> getValidators() {
578        return model.getValidators();
579    }
580
581    @Override
582    public void setTransformers(List<TransformerDefinition> transformers) {
583        model.setTransformers(transformers);
584    }
585
586    @Override
587    public List<TransformerDefinition> getTransformers() {
588        return model.getTransformers();
589    }
590
591    @Override
592    public ServiceCallConfigurationDefinition getServiceCallConfiguration(String serviceName) {
593        return model.getServiceCallConfiguration(serviceName);
594    }
595
596    @Override
597    public void setServiceCallConfiguration(ServiceCallConfigurationDefinition configuration) {
598        model.setServiceCallConfiguration(configuration);
599    }
600
601    @Override
602    public void setServiceCallConfigurations(List<ServiceCallConfigurationDefinition> configurations) {
603        model.setServiceCallConfigurations(configurations);
604    }
605
606    @Override
607    public void addServiceCallConfiguration(String serviceName, ServiceCallConfigurationDefinition configuration) {
608        model.addServiceCallConfiguration(serviceName, configuration);
609    }
610
611    @Override
612    public void setRouteFilterPattern(String include, String exclude) {
613        model.setRouteFilterPattern(include, exclude);
614    }
615
616    @Override
617    public void setRouteFilter(Function<RouteDefinition, Boolean> filter) {
618        model.setRouteFilter(filter);
619    }
620
621    @Override
622    public Function<RouteDefinition, Boolean> getRouteFilter() {
623        return model.getRouteFilter();
624    }
625
626    @Override
627    public ModelReifierFactory getModelReifierFactory() {
628        return model.getModelReifierFactory();
629    }
630
631    @Override
632    public void setModelReifierFactory(ModelReifierFactory modelReifierFactory) {
633        model.setModelReifierFactory(modelReifierFactory);
634    }
635
636    @Override
637    protected void bindDataFormats() throws Exception {
638        // eager lookup data formats and bind to registry so the dataformats can
639        // be looked up and used
640        if (model != null) {
641            for (Map.Entry<String, DataFormatDefinition> e : model.getDataFormats().entrySet()) {
642                String id = e.getKey();
643                DataFormatDefinition def = e.getValue();
644                LOG.debug("Creating Dataformat with id: {} and definition: {}", id, def);
645                DataFormat df = model.getModelReifierFactory().createDataFormat(this, def);
646                addService(df, true);
647                getRegistry().bind(id, df);
648            }
649        }
650    }
651
652    @Override
653    protected synchronized void shutdownRouteService(RouteService routeService) throws Exception {
654        if (model != null) {
655            RouteDefinition rd = model.getRouteDefinition(routeService.getId());
656            if (rd != null) {
657                model.getRouteDefinitions().remove(rd);
658            }
659        }
660        super.shutdownRouteService(routeService);
661    }
662
663    @Override
664    protected boolean isStreamCachingInUse() throws Exception {
665        boolean streamCachingInUse = super.isStreamCachingInUse();
666        if (!streamCachingInUse) {
667            for (RouteDefinition route : model.getRouteDefinitions()) {
668                Boolean routeCache = CamelContextHelper.parseBoolean(this, route.getStreamCache());
669                if (routeCache != null && routeCache) {
670                    streamCachingInUse = true;
671                    break;
672                }
673            }
674        }
675        return streamCachingInUse;
676    }
677
678    @Override
679    public void startRouteDefinitions() throws Exception {
680        List<RouteDefinition> routeDefinitions = model.getRouteDefinitions();
681        if (routeDefinitions != null) {
682            // defensive copy of routes to be started as kamelets
683            // can add route definitions from existing routes
684            List<RouteDefinition> toBeStarted = new ArrayList<>(routeDefinitions);
685            startRouteDefinitions(toBeStarted);
686        }
687    }
688
689    @Override
690    public void removeRouteDefinitionsFromTemplate() throws Exception {
691        List<RouteDefinition> toBeRemoved = new ArrayList<>();
692        for (RouteDefinition rd : model.getRouteDefinitions()) {
693            if (rd.isTemplate() != null && rd.isTemplate()) {
694                toBeRemoved.add(rd);
695            }
696        }
697        removeRouteDefinitions(toBeRemoved);
698    }
699
700    public void startRouteDefinitions(List<RouteDefinition> routeDefinitions) throws Exception {
701        // indicate we are staring the route using this thread so
702        // we are able to query this if needed
703        boolean alreadyStartingRoutes = isStartingRoutes();
704        if (!alreadyStartingRoutes) {
705            setStartingRoutes(true);
706        }
707
708        PropertiesComponent pc = getCamelContextReference().getPropertiesComponent();
709        // route templates supports binding beans that are local for the template only
710        // in this local mode then we need to check for side-effects (see further)
711        LocalBeanRepositoryAware localBeans = null;
712        if (getCamelContextReference().getRegistry() instanceof LocalBeanRepositoryAware) {
713            localBeans = (LocalBeanRepositoryAware) getCamelContextReference().getRegistry();
714        }
715        try {
716            RouteDefinitionHelper.forceAssignIds(getCamelContextReference(), routeDefinitions);
717            List<RouteDefinition> routeDefinitionsToRemove = null;
718            for (RouteDefinition routeDefinition : routeDefinitions) {
719                // assign ids to the routes and validate that the id's is all unique
720                String duplicate = RouteDefinitionHelper.validateUniqueIds(routeDefinition, routeDefinitions,
721                        routeDefinition.getNodePrefixId());
722                if (duplicate != null) {
723                    throw new FailedToStartRouteException(
724                            routeDefinition.getId(),
725                            "duplicate id detected: " + duplicate + ". Please correct ids to be unique among all your routes.");
726                }
727
728                // if the route definition was created via a route template then we need to prepare its parameters when the route is being created and started
729                if (routeDefinition.isTemplate() != null && routeDefinition.isTemplate()
730                        && routeDefinition.getTemplateParameters() != null) {
731
732                    // apply configurer if any present
733                    if (routeDefinition.getRouteTemplateContext().getConfigurer() != null) {
734                        routeDefinition.getRouteTemplateContext().getConfigurer()
735                                .accept(routeDefinition.getRouteTemplateContext());
736                    }
737
738                    // copy parameters/bean repository to not cause side effect
739                    Map<Object, Object> params = new HashMap<>(routeDefinition.getTemplateParameters());
740                    LocalBeanRegistry bbr
741                            = (LocalBeanRegistry) routeDefinition.getRouteTemplateContext().getLocalBeanRepository();
742                    LocalBeanRegistry bbrCopy = new LocalBeanRegistry();
743
744                    // make all bean in the bean repository use unique keys (need to add uuid counter)
745                    // so when the route template is used again to create another route, then there is
746                    // no side-effect from previously used values that Camel may use in its endpoint
747                    // registry and elsewhere
748                    if (bbr != null && !bbr.isEmpty()) {
749                        for (Map.Entry<Object, Object> param : params.entrySet()) {
750                            Object value = param.getValue();
751                            if (value instanceof String) {
752                                String oldKey = (String) value;
753                                boolean clash = bbr.keys().stream().anyMatch(k -> k.equals(oldKey));
754                                if (clash) {
755                                    String newKey = oldKey + "-" + UUID.generateUuid();
756                                    LOG.debug(
757                                            "Route: {} re-assigning local-bean id: {} to: {} to ensure ids are globally unique",
758                                            routeDefinition.getId(), oldKey, newKey);
759                                    bbrCopy.put(newKey, bbr.remove(oldKey));
760                                    param.setValue(newKey);
761                                }
762                            }
763                        }
764                        // the remainder of the local beans must also have their ids made global unique
765                        for (Map.Entry<String, Map<Class<?>, Object>> entry : bbr.entrySet()) {
766                            String oldKey = entry.getKey();
767                            String newKey = oldKey + "-" + UUID.generateUuid();
768                            LOG.debug(
769                                    "Route: {} re-assigning local-bean id: {} to: {} to ensure ids are globally unique",
770                                    routeDefinition.getId(), oldKey, newKey);
771                            bbrCopy.put(newKey, entry.getValue());
772                            if (!params.containsKey(oldKey)) {
773                                // if a bean was bound as local bean with a key and it was not defined as template parameter
774                                // then store it as if it was a template parameter with same key=value which allows us
775                                // to use this local bean in the route without any problem such as:
776                                //   to("bean:{{myBean}}")
777                                // and myBean is the local bean id.
778                                params.put(oldKey, newKey);
779                            }
780                        }
781                    }
782
783                    OrderedLocationProperties prop = new OrderedLocationProperties();
784                    if (routeDefinition.getTemplateDefaultParameters() != null) {
785                        // need to keep track if a parameter is set as default value or end user configured value
786                        params.forEach((k, v) -> {
787                            Object dv = routeDefinition.getTemplateDefaultParameters().get(k);
788                            prop.put(routeDefinition.getLocation(), k, v, dv);
789                        });
790                    } else {
791                        prop.putAll(routeDefinition.getLocation(), params);
792                    }
793                    pc.setLocalProperties(prop);
794
795                    // we need to shadow the bean registry on the CamelContext with the local beans from the route template context
796                    if (localBeans != null && bbrCopy != null) {
797                        localBeans.setLocalBeanRepository(bbrCopy);
798                    }
799
800                    // need to reset auto assigned ids, so there is no clash when creating routes
801                    ProcessorDefinitionHelper.resetAllAutoAssignedNodeIds(routeDefinition);
802                    // must re-init parent when created from a template
803                    RouteDefinitionHelper.initParent(routeDefinition);
804                }
805                // Check if the route is included
806                if (includedRoute(routeDefinition)) {
807                    // must ensure route is prepared, before we can start it
808                    if (!routeDefinition.isPrepared()) {
809                        RouteDefinitionHelper.prepareRoute(getCamelContextReference(), routeDefinition);
810                        routeDefinition.markPrepared();
811                    }
812
813                    StartupStepRecorder recorder
814                            = getCamelContextReference().getCamelContextExtension().getStartupStepRecorder();
815                    StartupStep step = recorder.beginStep(Route.class, routeDefinition.getRouteId(), "Create Route");
816                    Route route = model.getModelReifierFactory().createRoute(this, routeDefinition);
817                    recorder.endStep(step);
818
819                    RouteService routeService = new RouteService(route);
820                    startRouteService(routeService, true);
821                } else {
822                    // Add the definition to the list of definitions to remove as the route is excluded
823                    if (routeDefinitionsToRemove == null) {
824                        routeDefinitionsToRemove = new ArrayList<>(routeDefinitions.size());
825                    }
826                    routeDefinitionsToRemove.add(routeDefinition);
827                }
828
829                // clear local after the route is created via the reifier
830                pc.setLocalProperties(null);
831                if (localBeans != null) {
832                    localBeans.setLocalBeanRepository(null);
833                }
834            }
835            if (routeDefinitionsToRemove != null) {
836                // Remove all the excluded routes
837                model.removeRouteDefinitions(routeDefinitionsToRemove);
838            }
839        } finally {
840            if (!alreadyStartingRoutes) {
841                setStartingRoutes(false);
842            }
843            pc.setLocalProperties(null);
844            if (localBeans != null) {
845                localBeans.setLocalBeanRepository(null);
846            }
847        }
848    }
849
850    @Override
851    protected ExecutorServiceManager createExecutorServiceManager() {
852        return new DefaultExecutorServiceManager(this);
853    }
854
855    @Override
856    public Processor createErrorHandler(Route route, Processor processor) throws Exception {
857        return model.getModelReifierFactory().createErrorHandler(route, processor);
858    }
859
860    @Override
861    public Expression createExpression(ExpressionDefinition definition) {
862        return model.getModelReifierFactory().createExpression(this, definition);
863    }
864
865    @Override
866    public Predicate createPredicate(ExpressionDefinition definition) {
867        return model.getModelReifierFactory().createPredicate(this, definition);
868    }
869
870    @Override
871    public void registerValidator(ValidatorDefinition def) {
872        model.getValidators().add(def);
873        Validator validator = model.getModelReifierFactory().createValidator(this, def);
874        getValidatorRegistry().put(createValidatorKey(def), validator);
875    }
876
877    private static ValueHolder<String> createValidatorKey(ValidatorDefinition def) {
878        return new ValidatorKey(new DataType(def.getType()));
879    }
880
881    @Override
882    public void registerTransformer(TransformerDefinition def) {
883        model.getTransformers().add(def);
884        Transformer transformer = model.getModelReifierFactory().createTransformer(this, def);
885        getTransformerRegistry().put(createTransformerKey(def), transformer);
886    }
887
888    @Override
889    protected boolean removeRoute(String routeId, LoggingLevel loggingLevel) throws Exception {
890        // synchronize on model first to avoid deadlock with concurrent 'addRoutes' calls:
891        synchronized (model) {
892            synchronized (this) {
893                boolean removed = super.removeRoute(routeId, loggingLevel);
894                if (removed) {
895                    // must also remove the route definition
896                    RouteDefinition def = getRouteDefinition(routeId);
897                    if (def != null) {
898                        removeRouteDefinition(def);
899                    }
900                }
901                return removed;
902            }
903        }
904    }
905
906    @Override
907    public boolean removeRoute(String routeId) throws Exception {
908        // synchronize on model first to avoid deadlock with concurrent 'addRoutes' calls:
909        synchronized (model) {
910            return super.removeRoute(routeId);
911        }
912    }
913
914    /**
915     * Indicates whether the route should be included according to the precondition.
916     *
917     * @param  definition the definition of the route to check.
918     * @return            {@code true} if the route should be included, {@code false} otherwise.
919     */
920    private boolean includedRoute(RouteDefinition definition) {
921        return PreconditionHelper.included(definition, this);
922    }
923
924    private static ValueHolder<String> createTransformerKey(TransformerDefinition def) {
925        if (ObjectHelper.isNotEmpty(def.getScheme())) {
926            return ObjectHelper.isNotEmpty(def.getName())
927                    ? new TransformerKey(def.getScheme() + ":" + def.getName()) : new TransformerKey(def.getScheme());
928        }
929        if (ObjectHelper.isNotEmpty(def.getName())) {
930            return new TransformerKey(def.getName());
931        } else {
932            return new TransformerKey(new DataType(def.getFromType()), new DataType(def.getToType()));
933        }
934    }
935
936}