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.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.StringJoiner;
027import java.util.concurrent.ConcurrentHashMap;
028import java.util.function.Function;
029
030import org.apache.camel.CamelContext;
031import org.apache.camel.ExtendedCamelContext;
032import org.apache.camel.model.DataFormatDefinition;
033import org.apache.camel.model.FaultToleranceConfigurationDefinition;
034import org.apache.camel.model.HystrixConfigurationDefinition;
035import org.apache.camel.model.Model;
036import org.apache.camel.model.ModelCamelContext;
037import org.apache.camel.model.ModelLifecycleStrategy;
038import org.apache.camel.model.ProcessorDefinition;
039import org.apache.camel.model.ProcessorDefinitionHelper;
040import org.apache.camel.model.Resilience4jConfigurationDefinition;
041import org.apache.camel.model.RouteDefinition;
042import org.apache.camel.model.RouteFilters;
043import org.apache.camel.model.RouteTemplateDefinition;
044import org.apache.camel.model.RouteTemplateParameterDefinition;
045import org.apache.camel.model.cloud.ServiceCallConfigurationDefinition;
046import org.apache.camel.model.rest.RestDefinition;
047import org.apache.camel.model.transformer.TransformerDefinition;
048import org.apache.camel.model.validator.ValidatorDefinition;
049import org.apache.camel.spi.ModelReifierFactory;
050import org.apache.camel.util.AntPathMatcher;
051
052public class DefaultModel implements Model {
053
054    private final CamelContext camelContext;
055
056    private ModelReifierFactory modelReifierFactory = new DefaultModelReifierFactory();
057    private final List<ModelLifecycleStrategy> modelLifecycleStrategies = new ArrayList<>();
058    private final List<RouteDefinition> routeDefinitions = new ArrayList<>();
059    private final List<RouteTemplateDefinition> routeTemplateDefinitions = new ArrayList<>();
060    private final List<RestDefinition> restDefinitions = new ArrayList<>();
061    private final Map<String, RouteTemplateDefinition.Converter> routeTemplateConverters = new ConcurrentHashMap<>();
062    private Map<String, DataFormatDefinition> dataFormats = new HashMap<>();
063    private List<TransformerDefinition> transformers = new ArrayList<>();
064    private List<ValidatorDefinition> validators = new ArrayList<>();
065    private final Map<String, ServiceCallConfigurationDefinition> serviceCallConfigurations = new ConcurrentHashMap<>();
066    private final Map<String, HystrixConfigurationDefinition> hystrixConfigurations = new ConcurrentHashMap<>();
067    private final Map<String, Resilience4jConfigurationDefinition> resilience4jConfigurations = new ConcurrentHashMap<>();
068    private final Map<String, FaultToleranceConfigurationDefinition> faultToleranceConfigurations = new ConcurrentHashMap<>();
069    private Function<RouteDefinition, Boolean> routeFilter;
070
071    public DefaultModel(CamelContext camelContext) {
072        this.camelContext = camelContext;
073    }
074
075    public CamelContext getCamelContext() {
076        return camelContext;
077    }
078
079    @Override
080    public void addModelLifecycleStrategy(ModelLifecycleStrategy modelLifecycleStrategy) {
081        this.modelLifecycleStrategies.add(modelLifecycleStrategy);
082    }
083
084    @Override
085    public List<ModelLifecycleStrategy> getModelLifecycleStrategies() {
086        return modelLifecycleStrategies;
087    }
088
089    @Override
090    public synchronized void addRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
091        if (routeDefinitions == null || routeDefinitions.isEmpty()) {
092            return;
093        }
094        List<RouteDefinition> list = new ArrayList<>();
095        routeDefinitions.forEach(r -> {
096            if (routeFilter == null || routeFilter.apply(r)) {
097                list.add(r);
098            }
099        });
100
101        removeRouteDefinitions(list);
102        list.forEach(r -> {
103            modelLifecycleStrategies.forEach(s -> s.onAddRouteDefinition(r));
104            this.routeDefinitions.add(r);
105        });
106        if (shouldStartRoutes()) {
107            getCamelContext().adapt(ModelCamelContext.class).startRouteDefinitions(list);
108        }
109    }
110
111    @Override
112    public void addRouteDefinition(RouteDefinition routeDefinition) throws Exception {
113        addRouteDefinitions(Collections.singletonList(routeDefinition));
114    }
115
116    @Override
117    public synchronized void removeRouteDefinitions(Collection<RouteDefinition> routeDefinitions) throws Exception {
118        for (RouteDefinition routeDefinition : routeDefinitions) {
119            removeRouteDefinition(routeDefinition);
120        }
121    }
122
123    @Override
124    public synchronized void removeRouteDefinition(RouteDefinition routeDefinition) throws Exception {
125        RouteDefinition toBeRemoved = routeDefinition;
126        String id = routeDefinition.getId();
127        if (id != null) {
128            // remove existing route
129            camelContext.getRouteController().stopRoute(id);
130            camelContext.removeRoute(id);
131            toBeRemoved = getRouteDefinition(id);
132        }
133        for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
134            s.onRemoveRouteDefinition(toBeRemoved);
135        }
136        this.routeDefinitions.remove(toBeRemoved);
137    }
138
139    @Override
140    public synchronized List<RouteDefinition> getRouteDefinitions() {
141        return routeDefinitions;
142    }
143
144    @Override
145    public synchronized RouteDefinition getRouteDefinition(String id) {
146        for (RouteDefinition route : routeDefinitions) {
147            if (route.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()).equals(id)) {
148                return route;
149            }
150        }
151        return null;
152    }
153
154    @Override
155    public List<RouteTemplateDefinition> getRouteTemplateDefinitions() {
156        return routeTemplateDefinitions;
157    }
158
159    @Override
160    public RouteTemplateDefinition getRouteTemplateDefinition(String id) {
161        for (RouteTemplateDefinition route : routeTemplateDefinitions) {
162            if (route.idOrCreate(camelContext.adapt(ExtendedCamelContext.class).getNodeIdFactory()).equals(id)) {
163                return route;
164            }
165        }
166        return null;
167    }
168
169    @Override
170    public void addRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
171        if (routeTemplateDefinitions == null || routeTemplateDefinitions.isEmpty()) {
172            return;
173        }
174        routeTemplateDefinitions.forEach(r -> {
175            modelLifecycleStrategies.forEach(s -> s.onAddRouteTemplateDefinition(r));
176            this.routeTemplateDefinitions.add(r);
177        });
178    }
179
180    @Override
181    public void addRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
182        addRouteTemplateDefinitions(Collections.singletonList(routeTemplateDefinition));
183    }
184
185    @Override
186    public void removeRouteTemplateDefinitions(Collection<RouteTemplateDefinition> routeTemplateDefinitions) throws Exception {
187        for (RouteTemplateDefinition r : routeTemplateDefinitions) {
188            removeRouteTemplateDefinition(r);
189        }
190    }
191
192    @Override
193    public void removeRouteTemplateDefinition(RouteTemplateDefinition routeTemplateDefinition) throws Exception {
194        for (ModelLifecycleStrategy s : modelLifecycleStrategies) {
195            s.onRemoveRouteTemplateDefinition(routeTemplateDefinition);
196        }
197        routeTemplateDefinitions.remove(routeTemplateDefinition);
198    }
199
200    @Override
201    public void addRouteTemplateDefinitionConverter(String templateIdPattern, RouteTemplateDefinition.Converter converter) {
202        routeTemplateConverters.put(templateIdPattern, converter);
203    }
204
205    @Override
206    public String addRouteFromTemplate(final String routeId, final String routeTemplateId, final Map<String, Object> parameters)
207            throws Exception {
208        RouteTemplateDefinition target = null;
209        for (RouteTemplateDefinition def : routeTemplateDefinitions) {
210            if (routeTemplateId.equals(def.getId())) {
211                target = def;
212                break;
213            }
214        }
215        if (target == null) {
216            throw new IllegalArgumentException("Cannot find RouteTemplate with id " + routeTemplateId);
217        }
218
219        final Map<String, Object> prop = new HashMap<>();
220        // include default values first from the template (and validate that we have inputs for all required parameters)
221        if (target.getTemplateParameters() != null) {
222            StringJoiner templatesBuilder = new StringJoiner(", ");
223
224            for (RouteTemplateParameterDefinition temp : target.getTemplateParameters()) {
225                if (temp.getDefaultValue() != null) {
226                    prop.put(temp.getName(), temp.getDefaultValue());
227                } else {
228                    // this is a required parameter do we have that as input
229                    if (!parameters.containsKey(temp.getName())) {
230                        templatesBuilder.add(temp.getName());
231                    }
232                }
233            }
234            if (templatesBuilder.length() > 0) {
235                throw new IllegalArgumentException(
236                        "Route template " + routeTemplateId + " the following mandatory parameters must be provided: "
237                                                   + templatesBuilder.toString());
238            }
239        }
240
241        // then override with user parameters
242        if (parameters != null) {
243            prop.putAll(parameters);
244        }
245
246        RouteTemplateDefinition.Converter converter = RouteTemplateDefinition.Converter.DEFAULT_CONVERTER;
247
248        for (Map.Entry<String, RouteTemplateDefinition.Converter> entry : routeTemplateConverters.entrySet()) {
249            final String key = entry.getKey();
250            final String templateId = target.getId();
251
252            if ("*".equals(key) || templateId.equals(key)) {
253                converter = entry.getValue();
254                break;
255            } else if (AntPathMatcher.INSTANCE.match(key, templateId)) {
256                converter = entry.getValue();
257                break;
258            } else if (templateId.matches(key)) {
259                converter = entry.getValue();
260                break;
261            }
262        }
263
264        RouteDefinition def = converter.apply(target, prop);
265        if (routeId != null) {
266            def.setId(routeId);
267        }
268        def.setTemplateParameters(prop);
269        addRouteDefinition(def);
270        return def.getId();
271    }
272
273    @Override
274    public synchronized List<RestDefinition> getRestDefinitions() {
275        return restDefinitions;
276    }
277
278    @Override
279    public synchronized void addRestDefinitions(Collection<RestDefinition> restDefinitions, boolean addToRoutes)
280            throws Exception {
281        if (restDefinitions == null || restDefinitions.isEmpty()) {
282            return;
283        }
284
285        this.restDefinitions.addAll(restDefinitions);
286        if (addToRoutes) {
287            // rests are also routes so need to add them there too
288            for (final RestDefinition restDefinition : restDefinitions) {
289                List<RouteDefinition> routeDefinitions = restDefinition.asRouteDefinition(camelContext);
290                addRouteDefinitions(routeDefinitions);
291            }
292        }
293    }
294
295    @Override
296    public ServiceCallConfigurationDefinition getServiceCallConfiguration(String serviceName) {
297        if (serviceName == null) {
298            serviceName = "";
299        }
300
301        return serviceCallConfigurations.get(serviceName);
302    }
303
304    @Override
305    public void setServiceCallConfiguration(ServiceCallConfigurationDefinition configuration) {
306        serviceCallConfigurations.put("", configuration);
307    }
308
309    @Override
310    public void setServiceCallConfigurations(List<ServiceCallConfigurationDefinition> configurations) {
311        if (configurations != null) {
312            for (ServiceCallConfigurationDefinition configuration : configurations) {
313                serviceCallConfigurations.put(configuration.getId(), configuration);
314            }
315        }
316    }
317
318    @Override
319    public void addServiceCallConfiguration(String serviceName, ServiceCallConfigurationDefinition configuration) {
320        serviceCallConfigurations.put(serviceName, configuration);
321    }
322
323    @Override
324    public HystrixConfigurationDefinition getHystrixConfiguration(String id) {
325        if (id == null) {
326            id = "";
327        }
328
329        return hystrixConfigurations.get(id);
330    }
331
332    @Override
333    public void setHystrixConfiguration(HystrixConfigurationDefinition configuration) {
334        hystrixConfigurations.put("", configuration);
335    }
336
337    @Override
338    public void setHystrixConfigurations(List<HystrixConfigurationDefinition> configurations) {
339        if (configurations != null) {
340            for (HystrixConfigurationDefinition configuration : configurations) {
341                hystrixConfigurations.put(configuration.getId(), configuration);
342            }
343        }
344    }
345
346    @Override
347    public void addHystrixConfiguration(String id, HystrixConfigurationDefinition configuration) {
348        hystrixConfigurations.put(id, configuration);
349    }
350
351    @Override
352    public Resilience4jConfigurationDefinition getResilience4jConfiguration(String id) {
353        if (id == null) {
354            id = "";
355        }
356
357        return resilience4jConfigurations.get(id);
358    }
359
360    @Override
361    public void setResilience4jConfiguration(Resilience4jConfigurationDefinition configuration) {
362        resilience4jConfigurations.put("", configuration);
363    }
364
365    @Override
366    public void setResilience4jConfigurations(List<Resilience4jConfigurationDefinition> configurations) {
367        if (configurations != null) {
368            for (Resilience4jConfigurationDefinition configuration : configurations) {
369                resilience4jConfigurations.put(configuration.getId(), configuration);
370            }
371        }
372    }
373
374    @Override
375    public void addResilience4jConfiguration(String id, Resilience4jConfigurationDefinition configuration) {
376        resilience4jConfigurations.put(id, configuration);
377    }
378
379    @Override
380    public FaultToleranceConfigurationDefinition getFaultToleranceConfiguration(String id) {
381        if (id == null) {
382            id = "";
383        }
384
385        return faultToleranceConfigurations.get(id);
386    }
387
388    @Override
389    public void setFaultToleranceConfiguration(FaultToleranceConfigurationDefinition configuration) {
390        faultToleranceConfigurations.put("", configuration);
391    }
392
393    @Override
394    public void setFaultToleranceConfigurations(List<FaultToleranceConfigurationDefinition> configurations) {
395        if (configurations != null) {
396            for (FaultToleranceConfigurationDefinition configuration : configurations) {
397                faultToleranceConfigurations.put(configuration.getId(), configuration);
398            }
399        }
400    }
401
402    @Override
403    public void addFaultToleranceConfiguration(String id, FaultToleranceConfigurationDefinition configuration) {
404        faultToleranceConfigurations.put(id, configuration);
405    }
406
407    @Override
408    public DataFormatDefinition resolveDataFormatDefinition(String name) {
409        // lookup type and create the data format from it
410        DataFormatDefinition type = lookup(camelContext, name, DataFormatDefinition.class);
411        if (type == null && getDataFormats() != null) {
412            type = getDataFormats().get(name);
413        }
414        return type;
415    }
416
417    @SuppressWarnings("rawtypes")
418    @Override
419    public ProcessorDefinition<?> getProcessorDefinition(String id) {
420        for (RouteDefinition route : getRouteDefinitions()) {
421            Iterator<ProcessorDefinition> it
422                    = ProcessorDefinitionHelper.filterTypeInOutputs(route.getOutputs(), ProcessorDefinition.class);
423            while (it.hasNext()) {
424                ProcessorDefinition<?> proc = it.next();
425                if (id.equals(proc.getId())) {
426                    return proc;
427                }
428            }
429        }
430        return null;
431    }
432
433    @Override
434    public <T extends ProcessorDefinition<T>> T getProcessorDefinition(String id, Class<T> type) {
435        ProcessorDefinition<?> answer = getProcessorDefinition(id);
436        if (answer != null) {
437            return type.cast(answer);
438        }
439        return null;
440    }
441
442    @Override
443    public Map<String, DataFormatDefinition> getDataFormats() {
444        return dataFormats;
445    }
446
447    @Override
448    public void setDataFormats(Map<String, DataFormatDefinition> dataFormats) {
449        this.dataFormats = dataFormats;
450    }
451
452    @Override
453    public List<TransformerDefinition> getTransformers() {
454        return transformers;
455    }
456
457    @Override
458    public void setTransformers(List<TransformerDefinition> transformers) {
459        this.transformers = transformers;
460    }
461
462    @Override
463    public List<ValidatorDefinition> getValidators() {
464        return validators;
465    }
466
467    @Override
468    public void setValidators(List<ValidatorDefinition> validators) {
469        this.validators = validators;
470    }
471
472    @Override
473    public void setRouteFilterPattern(String include, String exclude) {
474        setRouteFilter(RouteFilters.filterByPattern(include, exclude));
475    }
476
477    @Override
478    public Function<RouteDefinition, Boolean> getRouteFilter() {
479        return routeFilter;
480    }
481
482    @Override
483    public void setRouteFilter(Function<RouteDefinition, Boolean> routeFilter) {
484        this.routeFilter = routeFilter;
485    }
486
487    @Override
488    public ModelReifierFactory getModelReifierFactory() {
489        return modelReifierFactory;
490    }
491
492    @Override
493    public void setModelReifierFactory(ModelReifierFactory modelReifierFactory) {
494        this.modelReifierFactory = modelReifierFactory;
495    }
496
497    /**
498     * Should we start newly added routes?
499     */
500    protected boolean shouldStartRoutes() {
501        return camelContext.isStarted() && !camelContext.isStarting();
502    }
503
504    private static <T> T lookup(CamelContext context, String ref, Class<T> type) {
505        try {
506            return context.getRegistry().lookupByNameAndType(ref, type);
507        } catch (Exception e) {
508            // need to ignore not same type and return it as null
509            return null;
510        }
511    }
512
513}