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