You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2023/08/30 17:57:19 UTC
[camel] branch main updated: Blueprint (#11240)
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new c51b05200fe Blueprint (#11240)
c51b05200fe is described below
commit c51b05200feb5bac00cb0d22716062bbe9746208
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Aug 30 19:43:46 2023 +0200
Blueprint (#11240)
CAMEL-19806: camel-jbang - Allow to load OSGi <blueprint> with embedded <camelContext>
CAMEL-19807: Move spring XML <beans> logic to its own class
---
.../apache/camel/catalog/schemas/camel-spring.xsd | 1 +
.../apache/camel/model/app/BeansDefinition.java | 14 +
.../java/org/apache/camel/xml/in/ModelParser.java | 14 +-
.../java/org/apache/camel/xml/out/ModelWriter.java | 3 +-
.../java/org/apache/camel/xml/in/BaseParser.java | 70 +++--
.../org/apache/camel/yaml/out/ModelWriter.java | 3 +-
.../apache/camel/dsl/jbang/core/commands/Run.java | 2 +-
.../java/org/apache/camel/main/KameletMain.java | 293 ++-----------------
.../java/org/apache/camel/main/util/XmlHelper.java | 12 +
.../xml/blueprint/BlueprintXmlBeansHandler.java | 224 +++++++++++++++
.../main/xml/spring/SpringXmlBeansHandler.java | 316 +++++++++++++++++++++
.../apache/camel/dsl/xml/io/XmlModelParser.java | 20 +-
.../camel/dsl/xml/io/XmlRoutesBuilderLoader.java | 15 +-
...eansLoadTest.java => XmlBlueprintLoadTest.java} | 13 +-
.../camel/dsl/xml/io/XmlSpringBeansLoadTest.java | 8 +-
.../apache/camel/dsl/xml/io/blueprintRoutes.xml | 50 ++++
.../packaging/ModelXmlParserGeneratorMojo.java | 4 +-
17 files changed, 742 insertions(+), 320 deletions(-)
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index b4bdabc4b57..4572bd2431d 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -13583,6 +13583,7 @@ org.apache.camel.builder.RouteBuilder.
</xs:element>
<xs:element maxOccurs="unbounded" minOccurs="0" name="bean" type="tns:registryBeanDefinition"/>
<xs:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
+ <xs:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
<xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:restConfiguration"/>
<xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:rest"/>
<xs:element maxOccurs="unbounded" minOccurs="0" ref="tns:routeConfiguration"/>
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
index debe7c0a9f0..a8230ccff11 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/app/BeansDefinition.java
@@ -52,6 +52,7 @@ import org.apache.camel.spi.annotations.ExternalSchemaElement;
"componentScanning",
"beans",
"springBeans",
+ "blueprintBeans",
"restConfigurations",
"rests",
"routeConfigurations",
@@ -82,6 +83,11 @@ public class BeansDefinition {
documentElement = "beans")
@XmlAnyElement
private List<Element> springBeans = new ArrayList<>();
+ @ExternalSchemaElement(names = { "bean" },
+ namespace = "http://www.osgi.org/xmlns/blueprint/v1.0.0",
+ documentElement = "blueprint")
+ @XmlAnyElement
+ private List<Element> blueprintBeans = new ArrayList<>();
// the order comes from <camelContext> (org.apache.camel.spring.xml.CamelContextFactoryBean)
// to make things less confusing, as it's not easy to simply tell JAXB to use <xsd:choice maxOccurs="unbounded">
@@ -127,6 +133,14 @@ public class BeansDefinition {
this.springBeans = springBeans;
}
+ public List<Element> getBlueprintBeans() {
+ return blueprintBeans;
+ }
+
+ public void setBlueprintBeans(List<Element> blueprintBeans) {
+ this.blueprintBeans = blueprintBeans;
+ }
+
public List<RestConfigurationDefinition> getRestConfigurations() {
return restConfigurations;
}
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 074c655829e..d8890ea2ce6 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -1568,7 +1568,7 @@ public class ModelParser extends BaseParser {
}
public Optional<ApplicationDefinition> parseApplicationDefinition()
throws IOException, XmlPullParserException {
- String tag = getNextTag("beans", "camel");
+ String tag = getNextTag("beans", "blueprint", "camel");
if (tag != null) {
return Optional.of(doParseApplicationDefinition());
}
@@ -1580,7 +1580,7 @@ public class ModelParser extends BaseParser {
}
public Optional<BeansDefinition> parseBeansDefinition()
throws IOException, XmlPullParserException {
- String tag = getNextTag("beans", "camel");
+ String tag = getNextTag("beans", "blueprint", "camel");
if (tag != null) {
return Optional.of(doParseBeansDefinition());
}
@@ -1588,7 +1588,15 @@ public class ModelParser extends BaseParser {
}
protected <T extends BeansDefinition> ElementHandler<T> beansDefinitionElementHandler() {
return (def, key) -> {
- if ("http://www.springframework.org/schema/beans".equals(parser.getNamespace())) {
+ if ("http://www.osgi.org/xmlns/blueprint/v1.0.0".equals(parser.getNamespace())) {
+ Element el = doParseDOMElement("blueprint", "http://www.osgi.org/xmlns/blueprint/v1.0.0", def.getBlueprintBeans());
+ if (el != null) {
+ doAddElement(el, def.getBlueprintBeans(), def::setBlueprintBeans);
+ return true;
+ }
+ return false;
+ }
+ else if ("http://www.springframework.org/schema/beans".equals(parser.getNamespace())) {
Element el = doParseDOMElement("beans", "http://www.springframework.org/schema/beans", def.getSpringBeans());
if (el != null) {
doAddElement(el, def.getSpringBeans(), def::setSpringBeans);
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 3b2487ee6d6..2e89e6409c4 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -2519,9 +2519,10 @@ public class ModelWriter extends BaseWriter {
throws IOException {
doWriteList(null, "route", def.getRoutes(), this::doWriteRouteDefinition);
domElements(def.getSpringBeans());
- doWriteList(null, "restConfiguration", def.getRestConfigurations(), this::doWriteRestConfigurationDefinition);
+ domElements(def.getBlueprintBeans());
doWriteList(null, "component-scan", def.getComponentScanning(), this::doWriteComponentScanDefinition);
doWriteList(null, "bean", def.getBeans(), this::doWriteRegistryBeanDefinition);
+ doWriteList(null, "restConfiguration", def.getRestConfigurations(), this::doWriteRestConfigurationDefinition);
doWriteList(null, "rest", def.getRests(), this::doWriteRestDefinition);
doWriteList(null, "routeConfiguration", def.getRouteConfigurations(), this::doWriteRouteConfigurationDefinition);
doWriteList(null, "routeTemplate", def.getRouteTemplates(), this::doWriteRouteTemplateDefinition);
diff --git a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
index b811d62c339..5d1a2a7bf55 100644
--- a/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
+++ b/core/camel-xml-io/src/main/java/org/apache/camel/xml/in/BaseParser.java
@@ -16,12 +16,29 @@
*/
package org.apache.camel.xml.in;
+import org.apache.camel.LineNumberAware;
+import org.apache.camel.model.language.ExpressionDefinition;
+import org.apache.camel.spi.NamespaceAware;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.URISupport;
+import org.apache.camel.xml.io.MXParser;
+import org.apache.camel.xml.io.XmlPullParser;
+import org.apache.camel.xml.io.XmlPullParserException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -31,29 +48,11 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import javax.xml.XMLConstants;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.parsers.ParserConfigurationException;
-
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.Text;
-
-import org.apache.camel.LineNumberAware;
-import org.apache.camel.model.language.ExpressionDefinition;
-import org.apache.camel.spi.NamespaceAware;
-import org.apache.camel.spi.Resource;
-import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.URISupport;
-import org.apache.camel.xml.io.MXParser;
-import org.apache.camel.xml.io.XmlPullParser;
-import org.apache.camel.xml.io.XmlPullParserException;
-
public class BaseParser {
protected final MXParser parser;
protected String namespace;
- protected String secondNamespace = "";
+ protected Set<String> secondaryNamespaces = new HashSet<>();
protected Resource resource;
public BaseParser(Resource resource) throws IOException, XmlPullParserException {
@@ -88,8 +87,8 @@ public class BaseParser {
this.namespace = namespace != null ? namespace : "";
}
- public void addSecondNamespace(String secondNamespace) {
- this.secondNamespace = secondNamespace;
+ public void addSecondaryNamespace(String namespace) {
+ this.secondaryNamespaces.add(namespace);
}
protected <T> T doParse(
@@ -368,6 +367,20 @@ public class BaseParser {
return pn;
}
+ protected String getNextTag(String name, String name2, String name3) throws XmlPullParserException, IOException {
+ if (parser.nextTag() != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("Expected starting tag");
+ }
+
+ String pn = parser.getName();
+ boolean match = Objects.equals(name, pn) || Objects.equals(name2, pn) || Objects.equals(name3, pn);
+ if (!match || !matchNamespace(namespace, parser.getNamespace(), null, false)) {
+ return ""; // empty tag
+ }
+
+ return pn;
+ }
+
protected void handleOtherAttribute(Object definition, String name, String ns, String val) throws XmlPullParserException {
// Ignore
if ("http://www.w3.org/2001/XMLSchema-instance".equals(ns)) {
@@ -465,15 +478,22 @@ public class BaseParser {
}
protected boolean matchNamespace(String ns, boolean optional) {
- return matchNamespace(ns, namespace, secondNamespace, optional);
+ return matchNamespace(ns, namespace, secondaryNamespaces, optional);
}
- protected static boolean matchNamespace(String ns, String namespace, String namespace2, boolean optional) {
+ protected static boolean matchNamespace(String ns, String namespace, Set<String> secondaryNamespaces, boolean optional) {
if (optional && ns.isEmpty()) {
return true;
}
-
- return Objects.equals(ns, namespace) || Objects.equals(ns, namespace2);
+ if (Objects.equals(ns, namespace)) {
+ return true;
+ }
+ for (String second : secondaryNamespaces) {
+ if (Objects.equals(ns, second)) {
+ return true;
+ }
+ }
+ return false;
}
}
diff --git a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
index 3ab7d56d7d0..3010257af60 100644
--- a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
+++ b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
@@ -2519,9 +2519,10 @@ public class ModelWriter extends BaseWriter {
throws IOException {
doWriteList(null, "route", def.getRoutes(), this::doWriteRouteDefinition);
domElements(def.getSpringBeans());
- doWriteList(null, "restConfiguration", def.getRestConfigurations(), this::doWriteRestConfigurationDefinition);
+ domElements(def.getBlueprintBeans());
doWriteList(null, "component-scan", def.getComponentScanning(), this::doWriteComponentScanDefinition);
doWriteList(null, "bean", def.getBeans(), this::doWriteRegistryBeanDefinition);
+ doWriteList(null, "restConfiguration", def.getRestConfigurations(), this::doWriteRestConfigurationDefinition);
doWriteList(null, "rest", def.getRests(), this::doWriteRestDefinition);
doWriteList(null, "routeConfiguration", def.getRouteConfigurations(), this::doWriteRouteConfigurationDefinition);
doWriteList(null, "routeTemplate", def.getRouteTemplates(), this::doWriteRouteTemplateDefinition);
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
index 39d8b128c1f..44e39f5de38 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Run.java
@@ -93,7 +93,7 @@ public class Run extends CamelCommand {
"templatedRoute", "templatedRoutes",
"rest", "rests",
"routeConfiguration",
- "beans", "camel"
+ "beans", "blueprint", "camel"
};
private static final Set<String> ACCEPTED_XML_ROOT_ELEMENTS
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
index 7cc52d1045f..c436be5703f 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/KameletMain.java
@@ -16,24 +16,13 @@
*/
package org.apache.camel.main;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
-import java.util.StringJoiner;
import java.util.TreeMap;
-import java.util.function.Supplier;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import org.w3c.dom.Document;
@@ -70,8 +59,8 @@ import org.apache.camel.main.download.PackageNameSourceLoader;
import org.apache.camel.main.download.TypeConverterLoaderDownloadListener;
import org.apache.camel.main.injection.AnnotationDependencyInjection;
import org.apache.camel.main.util.ExtraFilesClassLoader;
-import org.apache.camel.model.Model;
-import org.apache.camel.model.app.RegistryBeanDefinition;
+import org.apache.camel.main.xml.blueprint.BlueprintXmlBeansHandler;
+import org.apache.camel.main.xml.spring.SpringXmlBeansHandler;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.CliConnector;
import org.apache.camel.spi.CliConnectorFactory;
@@ -87,29 +76,10 @@ import org.apache.camel.spi.RoutesLoader;
import org.apache.camel.spi.UriFactoryResolver;
import org.apache.camel.startup.jfr.FlightRecorderStartupStepRecorder;
import org.apache.camel.support.DefaultContextReloadStrategy;
-import org.apache.camel.support.ObjectHelper;
import org.apache.camel.support.PluginHelper;
import org.apache.camel.support.RouteOnDemandReloadStrategy;
import org.apache.camel.support.service.ServiceHelper;
import org.apache.camel.tooling.maven.MavenGav;
-import org.apache.camel.util.StringHelper;
-import org.springframework.beans.MutablePropertyValues;
-import org.springframework.beans.PropertyValue;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.CannotLoadBeanClassException;
-import org.springframework.beans.factory.SmartFactoryBean;
-import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.config.BeanReference;
-import org.springframework.beans.factory.config.ConstructorArgumentValues;
-import org.springframework.beans.factory.config.TypedStringValue;
-import org.springframework.beans.factory.support.AbstractBeanDefinition;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-import org.springframework.beans.factory.support.GenericBeanDefinition;
-import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
-import org.springframework.core.io.AbstractResource;
-import org.springframework.core.io.Resource;
-import org.springframework.core.metrics.StartupStep;
/**
* A Main class for booting up Camel with Kamelet in standalone mode.
@@ -118,8 +88,6 @@ public class KameletMain extends MainCommandLineSupport {
public static final String DEFAULT_KAMELETS_LOCATION = "classpath:/kamelets,github:apache:camel-kamelets/kamelets";
- private static final Pattern SPRING_PATTERN = Pattern.compile("(\\$\\{.*?})"); // non-greedy mode
-
protected final MainRegistry registry = new MainRegistry();
private boolean download = true;
private String repos;
@@ -132,10 +100,8 @@ public class KameletMain extends MainCommandLineSupport {
private DownloadListener downloadListener;
private DependencyDownloaderClassLoader classLoader;
- // when preparing spring-based beans, we may have problems loading classes which are provided with Java DSL
- // that's why some beans should be processed later
- private final List<String> delayedBeans = new LinkedList<>();
- private Set<String> infraBeanNames;
+ private final SpringXmlBeansHandler springXmlBeansHandler = new SpringXmlBeansHandler();
+ private final BlueprintXmlBeansHandler blueprintXmlBeansHandler = new BlueprintXmlBeansHandler();
public KameletMain() {
configureInitialProperties(DEFAULT_KAMELETS_LOCATION);
@@ -693,250 +659,33 @@ public class KameletMain extends MainCommandLineSupport {
@Override
protected void preProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) {
- // camel-kamelet-main has access to Spring libraries, so we can grab XML documents representing
- // actual Spring Beans and read them using Spring's BeanFactory to populate Camel registry
- final Map<String, Document> xmls = new TreeMap<>();
+ final Map<String, Document> springXmls = new TreeMap<>();
+ final Map<String, Document> blueprintXmls = new TreeMap<>();
- Map<String, Document> springBeansDocs = registry.findByTypeWithName(Document.class);
- if (springBeansDocs != null) {
- springBeansDocs.forEach((id, doc) -> {
+ Map<String, Document> xmlDocs = registry.findByTypeWithName(Document.class);
+ if (xmlDocs != null) {
+ xmlDocs.forEach((id, doc) -> {
if (id.startsWith("camel-xml-io-dsl-spring-xml:")) {
- xmls.put(id, doc);
+ springXmls.put(id, doc);
+ } else if (id.startsWith("camel-xml-io-dsl-blueprint-xml:")) {
+ blueprintXmls.put(id, doc);
}
});
}
-
- if (!xmls.isEmpty()) {
- processSpringBeans(camelContext, config, xmls);
+ if (!springXmls.isEmpty()) {
+ // camel-kamelet-main has access to Spring libraries, so we can grab XML documents representing
+ // actual Spring Beans and read them using Spring's BeanFactory to populate Camel registry
+ springXmlBeansHandler.processSpringBeans(camelContext, config, springXmls);
+ }
+ if (!blueprintXmls.isEmpty()) {
+ blueprintXmlBeansHandler.processBlueprintBeans(camelContext, config, blueprintXmls);
}
- }
-
- private void processSpringBeans(
- CamelContext camelContext, MainConfigurationProperties config, final Map<String, Document> xmls) {
- // we _could_ create something like org.apache.camel.spring.spi.ApplicationContextBeanRepository, but
- // wrapping DefaultListableBeanFactory and use it as one of the
- // org.apache.camel.support.DefaultRegistry.repositories, but for now let's use it to populate
- // Spring registry and then copy the beans (whether the scope is)
- final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
- beanFactory.setAllowCircularReferences(true); // for now
- beanFactory.setBeanClassLoader(classLoader);
- beanFactory.setBeanExpressionResolver((value, beanExpressionContext) -> extractValue(value, true));
- registry.bind("SpringBeanFactory", beanFactory);
-
- // register some existing beans (the list may change)
- // would be nice to keep the documentation up to date: docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
- infraBeanNames = Set.of("CamelContext", "MainConfiguration");
- beanFactory.registerSingleton("CamelContext", camelContext);
- beanFactory.registerSingleton("MainConfiguration", config);
- // ...
-
- // instead of generating an MX parser for spring-beans.xsd and use it to read the docs, we can simply
- // pass w3c Documents directly to Spring
- final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
- xmls.forEach((id, doc) -> {
- reader.registerBeanDefinitions(doc, new AbstractResource() {
- @Override
- public String getFilename() {
- if (id.startsWith("camel-xml-io-dsl-spring-xml:")) {
- // this is a camel bean via camel-xml-io-dsl
- return StringHelper.afterLast(id, ":");
- }
- return null;
- }
-
- @Override
- public String getDescription() {
- return id;
- }
-
- @Override
- public InputStream getInputStream() throws IOException {
- return new ByteArrayInputStream(new byte[0]);
- }
- });
- });
-
- // for full interaction between Spring ApplicationContext and its BeanFactory see
- // org.springframework.context.support.AbstractApplicationContext.refresh()
- // see org.springframework.context.support.AbstractApplicationContext.prepareBeanFactory() to check
- // which extra/infra beans are added
- beanFactory.freezeConfiguration();
-
- List<String> beanNames = Arrays.asList(beanFactory.getBeanDefinitionNames());
-
- // Trigger initialization of all non-lazy singleton beans...
- instantiateAndRegisterBeans(beanFactory, beanNames);
}
@Override
protected void postProcessCamelRegistry(CamelContext camelContext, MainConfigurationProperties config) {
- if (delayedBeans.isEmpty()) {
- return;
- }
-
- DefaultListableBeanFactory beanFactory
- = registry.lookupByNameAndType("SpringBeanFactory", DefaultListableBeanFactory.class);
-
- // we have some beans with classes that we couldn't load before. now, after loading the routes
- // we may have the needed class definitions
- for (String beanName : delayedBeans) {
- BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
- if (bd instanceof AbstractBeanDefinition abd) {
- if (!abd.hasBeanClass()) {
- Class<?> c = camelContext.getClassResolver().resolveClass(abd.getBeanClassName());
- abd.setBeanClass(c);
- }
- }
- }
-
- instantiateAndRegisterBeans(beanFactory, delayedBeans);
- }
-
- private void instantiateAndRegisterBeans(DefaultListableBeanFactory beanFactory, List<String> beanNames) {
- List<String> instantiatedBeanNames = new LinkedList<>();
-
- for (String beanName : beanNames) {
- BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
- if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
- try {
- if (beanFactory.isFactoryBean(beanName)) {
- Object bean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
- if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
- beanFactory.getBean(beanName);
- instantiatedBeanNames.add(beanName);
- }
- } else {
- beanFactory.getBean(beanName);
- instantiatedBeanNames.add(beanName);
- }
- } catch (CannotLoadBeanClassException ignored) {
- // we'll try to resolve later
- delayedBeans.add(beanName);
- }
- }
- }
-
- // Trigger post-initialization callback for all applicable beans...
- for (String beanName : instantiatedBeanNames) {
- Object singletonInstance = beanFactory.getSingleton(beanName);
- if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
- StartupStep smartInitialize = beanFactory.getApplicationStartup()
- .start("spring.beans.smart-initialize")
- .tag("beanName", beanName);
- smartSingleton.afterSingletonsInstantiated();
- smartInitialize.end();
- }
- }
-
- for (String name : instantiatedBeanNames) {
- if (infraBeanNames.contains(name)) {
- continue;
- }
- BeanDefinition def = beanFactory.getBeanDefinition(name);
- if (def.isSingleton()) {
- // just grab the singleton and put into registry
- registry.bind(name, beanFactory.getBean(name));
- } else {
- // rely on the bean factory to implement prototype scope
- registry.bind(name, (Supplier<Object>) () -> beanFactory.getBean(name));
- }
-
- // register bean into model (as a BeanRegistry that allows Camel DSL to know about these beans)
- Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
- if (model != null) {
- RegistryBeanDefinition rrd = new RegistryBeanDefinition();
- if (def instanceof GenericBeanDefinition gbd) {
- // set camel resource to refer to the source file
- Resource res = gbd.getResource();
- if (res != null) {
- String fn = res.getFilename();
- if (fn != null) {
- rrd.setResource(camelContext.getCamelContextExtension().getContextPlugin(ResourceLoader.class)
- .resolveResource("file:" + fn));
- }
- }
- }
- rrd.setType(def.getBeanClassName());
- rrd.setName(name);
- model.addRegistryBean(rrd);
-
- // constructor arguments
- ConstructorArgumentValues ctr = def.getConstructorArgumentValues();
- StringJoiner sj = new StringJoiner(", ");
- for (ConstructorArgumentValues.ValueHolder v : ctr.getIndexedArgumentValues().values()) {
- Object val = v.getValue();
- if (val instanceof TypedStringValue tsv) {
- sj.add("'" + extractValue(tsv.getValue(), false) + "'");
- } else if (val instanceof BeanReference br) {
- sj.add("'#bean:" + br.getBeanName() + "'");
- }
- }
- if (sj.length() > 0) {
- rrd.setType("#class:" + def.getBeanClassName() + "(" + sj + ")");
- }
- // property values
- if (def.hasPropertyValues()) {
- Map<String, Object> properties = new LinkedHashMap<>();
- rrd.setProperties(properties);
-
- MutablePropertyValues values = def.getPropertyValues();
- for (PropertyValue v : values) {
- String key = v.getName();
- PropertyValue src = v.getOriginalPropertyValue();
- Object val = src.getValue();
- if (val instanceof TypedStringValue tsv) {
- properties.put(key, extractValue(tsv.getValue(), false));
- } else if (val instanceof BeanReference br) {
- properties.put(key, "#bean:" + br.getBeanName());
- } else if (val instanceof List) {
- int i = 0;
- Iterator<?> it = ObjectHelper.createIterator(val);
- while (it.hasNext()) {
- String k = key + "[" + i + "]";
- val = it.next();
- if (val instanceof TypedStringValue tsv) {
- properties.put(k, extractValue(tsv.getValue(), false));
- } else if (val instanceof BeanReference br) {
- properties.put(k, "#bean:" + br.getBeanName());
- }
- i++;
- }
- } else if (val instanceof Map) {
- Map<TypedStringValue, Object> map = (Map) val;
- for (Map.Entry<TypedStringValue, Object> entry : map.entrySet()) {
- String k = key + "[" + entry.getKey().getValue() + "]";
- val = entry.getValue();
- if (val instanceof TypedStringValue tsv) {
- properties.put(k, extractValue(tsv.getValue(), false));
- } else if (val instanceof BeanReference br) {
- properties.put(k, "#bean:" + br.getBeanName());
- }
- }
- }
- }
- }
- }
- }
- }
-
- protected String extractValue(String val, boolean resolve) {
- // spring placeholder prefix
- if (val != null && val.contains("${")) {
- Matcher matcher = SPRING_PATTERN.matcher(val);
- while (matcher.find()) {
- String group = matcher.group(1);
- String replace = "{{" + group.substring(2, group.length() - 1) + "}}";
- val = matcher.replaceFirst(replace);
- // we changed so reset matcher so it can find more
- matcher.reset(val);
- }
- }
-
- if (resolve && camelContext != null) {
- // if running camel then resolve property placeholders from beans
- val = camelContext.resolvePropertyPlaceholders(val);
- }
- return val;
+ springXmlBeansHandler.createAndRegisterBeans(camelContext);
+ blueprintXmlBeansHandler.createAndRegisterBeans(camelContext);
}
@Override
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/util/XmlHelper.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/util/XmlHelper.java
index 52b843abfcf..0a362f18334 100644
--- a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/util/XmlHelper.java
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/util/XmlHelper.java
@@ -20,6 +20,8 @@ import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
+import org.w3c.dom.Node;
+
import org.apache.camel.util.ObjectHelper;
public final class XmlHelper {
@@ -60,4 +62,14 @@ public final class XmlHelper {
return factory;
}
+ public static String getAttribute(Node node, String key) {
+ if (node != null && node.hasAttributes()) {
+ Node attr = node.getAttributes().getNamedItem(key);
+ if (attr != null) {
+ return attr.getNodeValue();
+ }
+ }
+ return null;
+ }
+
}
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java
new file mode 100644
index 00000000000..bcdf6ab97fd
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/blueprint/BlueprintXmlBeansHandler.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.main.xml.blueprint;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.main.MainConfigurationProperties;
+import org.apache.camel.main.util.XmlHelper;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.app.RegistryBeanDefinition;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.spi.ResourceLoader;
+import org.apache.camel.support.PropertyBindingSupport;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Used for parsing and discovering legacy OSGi <blueprint> XML to make it runnable on camel-jbang, and for tooling to
+ * migrate this to modern Camel DSL in plain Camel XML or YAML DSL.
+ */
+public class BlueprintXmlBeansHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(BlueprintXmlBeansHandler.class);
+ private static final Pattern BLUEPRINT_PATTERN = Pattern.compile("\\$\\{(.*?)}"); // non-greedy mode
+
+ // when preparing blueprint-based beans, we may have problems loading classes which are provided with Java DSL
+ // that's why some beans should be processed later
+ private final Map<String, Node> delayedBeans = new LinkedHashMap<>();
+ private final Map<String, Resource> resources = new LinkedHashMap<>();
+ private final List<RegistryBeanDefinition> delayedRegistrations = new ArrayList<>();
+
+ /**
+ * Parses the XML documents and discovers blueprint beans, which will be created manually via Camel.
+ */
+ public void processBlueprintBeans(
+ CamelContext camelContext, MainConfigurationProperties config, final Map<String, Document> xmls) {
+
+ LOG.debug("Loading beans from classic OSGi <blueprint> XML");
+
+ xmls.forEach((id, doc) -> {
+ if (id.startsWith("camel-xml-io-dsl-blueprint-xml:")) {
+ // this is a camel bean via camel-xml-io-dsl
+ String fileName = StringHelper.afterLast(id, ":");
+ discoverBeans(camelContext, fileName, doc);
+ }
+ });
+ }
+
+ /**
+ * Invoked at later stage to create and register Blueprint beans into Camel {@link org.apache.camel.spi.Registry}.
+ */
+ public void createAndRegisterBeans(CamelContext camelContext) {
+ LOG.info("Discovered {} OSGi <blueprint> XML beans", delayedBeans.size());
+
+ for (Map.Entry<String, Node> entry : delayedBeans.entrySet()) {
+ String id = entry.getKey();
+ Node n = entry.getValue();
+ RegistryBeanDefinition def = createBeanModel(camelContext, id, n);
+ LOG.info("Creating bean: {}", def);
+ registerBeanDefinition(camelContext, def, true);
+ }
+
+ if (!delayedRegistrations.isEmpty()) {
+ // some of the beans were not available yet, so we have to try register them now
+ for (RegistryBeanDefinition def : delayedRegistrations) {
+ LOG.info("Creating bean (2nd-try): {}", def);
+ registerBeanDefinition(camelContext, def, false);
+ }
+ delayedRegistrations.clear();
+ }
+
+ }
+
+ private RegistryBeanDefinition createBeanModel(CamelContext camelContext, String name, Node node) {
+ RegistryBeanDefinition rrd = new RegistryBeanDefinition();
+ rrd.setResource(resources.get(name));
+ rrd.setType(XmlHelper.getAttribute(node, "class"));
+ rrd.setName(name);
+
+ // constructor arguments
+ StringJoiner sj = new StringJoiner(", ");
+ NodeList props = node.getChildNodes();
+ for (int i = 0; i < props.getLength(); i++) {
+ Node child = props.item(i);
+ // assume the args are in order (1, 2)
+ if ("argument".equals(child.getNodeName())) {
+ String val = XmlHelper.getAttribute(child, "value");
+ String ref = XmlHelper.getAttribute(child, "ref");
+ if (val != null) {
+ sj.add("'" + extractValue(camelContext, val, false) + "'");
+ } else if (ref != null) {
+ sj.add("'#bean:" + extractValue(camelContext, ref, false) + "'");
+ }
+ }
+ }
+ if (sj.length() > 0) {
+ rrd.setType("#class:" + rrd.getType() + "(" + sj + ")");
+ }
+
+ // property values
+ Map<String, Object> properties = new LinkedHashMap<>();
+ props = node.getChildNodes();
+ for (int i = 0; i < props.getLength(); i++) {
+ Node child = props.item(i);
+ // assume the args are in order (1, 2)
+ if ("property".equals(child.getNodeName())) {
+ String key = XmlHelper.getAttribute(child, "name");
+ String val = XmlHelper.getAttribute(child, "value");
+ String ref = XmlHelper.getAttribute(child, "ref");
+
+ // TODO: List/Map properties
+ if (key != null && val != null) {
+ properties.put(key, extractValue(camelContext, val, false));
+ } else if (key != null && ref != null) {
+ properties.put(key, extractValue(camelContext, "#bean:" + ref, false));
+ }
+ }
+ }
+ if (!properties.isEmpty()) {
+ rrd.setProperties(properties);
+ }
+
+ return rrd;
+ }
+
+ private void discoverBeans(CamelContext camelContext, String fileName, Document dom) {
+ Resource resource = camelContext.getCamelContextExtension().getContextPlugin(ResourceLoader.class)
+ .resolveResource("file:" + fileName);
+
+ NodeList beans = dom.getElementsByTagName("bean");
+ for (int i = 0; i < beans.getLength(); i++) {
+ Node n = beans.item(i);
+ if (n.hasAttributes()) {
+ String id = XmlHelper.getAttribute(n, "id");
+ if (id != null) {
+ delayedBeans.put(id, n);
+ resources.put(id, resource);
+ }
+ }
+ }
+ }
+
+ protected String extractValue(CamelContext camelContext, String val, boolean resolve) {
+ // blueprint placeholder prefix
+ if (val != null && val.contains("${")) {
+ Matcher matcher = BLUEPRINT_PATTERN.matcher(val);
+ while (matcher.find()) {
+ String replace = "{{" + matcher.group(1) + "}}";
+ val = matcher.replaceFirst(replace);
+ // we changed so reset matcher so it can find more
+ matcher.reset(val);
+ }
+ }
+
+ if (resolve && camelContext != null) {
+ // if running camel then resolve property placeholders from beans
+ val = camelContext.resolvePropertyPlaceholders(val);
+ }
+ return val;
+ }
+
+ /**
+ * Try to instantiate bean from the definition.
+ */
+ private void registerBeanDefinition(CamelContext camelContext, RegistryBeanDefinition def, boolean delayIfFailed) {
+ String type = def.getType();
+ String name = def.getName();
+ if (name == null || name.trim().isEmpty()) {
+ name = type;
+ }
+ if (type != null) {
+ if (!type.startsWith("#")) {
+ type = "#class:" + type;
+ }
+ try {
+ final Object target = PropertyBindingSupport.resolveBean(camelContext, type);
+
+ if (def.getProperties() != null && !def.getProperties().isEmpty()) {
+ PropertyBindingSupport.setPropertiesOnTarget(camelContext, target, def.getProperties());
+ }
+ camelContext.getRegistry().unbind(name);
+ camelContext.getRegistry().bind(name, target);
+
+ // register bean in model
+ Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
+ model.addRegistryBean(def);
+
+ } catch (Exception e) {
+ if (delayIfFailed) {
+ delayedRegistrations.add(def);
+ } else {
+ LOG.warn("Error creating bean: {} due to: {}. This exception is ignored.", type, e.getMessage(), e);
+ }
+ }
+ }
+ }
+
+}
diff --git a/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java
new file mode 100644
index 00000000000..f6c4d7eba43
--- /dev/null
+++ b/dsl/camel-kamelet-main/src/main/java/org/apache/camel/main/xml/spring/SpringXmlBeansHandler.java
@@ -0,0 +1,316 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.main.xml.spring;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.w3c.dom.Document;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.main.MainConfigurationProperties;
+import org.apache.camel.model.Model;
+import org.apache.camel.model.app.RegistryBeanDefinition;
+import org.apache.camel.spi.ResourceLoader;
+import org.apache.camel.support.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.CannotLoadBeanClassException;
+import org.springframework.beans.factory.SmartFactoryBean;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanReference;
+import org.springframework.beans.factory.config.ConstructorArgumentValues;
+import org.springframework.beans.factory.config.TypedStringValue;
+import org.springframework.beans.factory.support.AbstractBeanDefinition;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.beans.factory.support.GenericBeanDefinition;
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
+import org.springframework.core.io.AbstractResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.metrics.StartupStep;
+
+/**
+ * Used for parsing and discovering legacy Spring XML <beans> to make it runnable on camel-jbang, and for tooling to
+ * migrate this to modern Camel DSL in plain Camel XML or YAML DSL.
+ */
+public class SpringXmlBeansHandler {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SpringXmlBeansHandler.class);
+ private static final Pattern SPRING_PATTERN = Pattern.compile("\\$\\{(.*?)}"); // non-greedy mode
+
+ // when preparing spring-based beans, we may have problems loading classes which are provided with Java DSL
+ // that's why some beans should be processed later
+ private final List<String> delayedBeans = new LinkedList<>();
+ // register some existing beans (the list may change)
+ // would be nice to keep the documentation up to date: docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+ private final Set<String> infraBeanNames = Set.of("CamelContext", "MainConfiguration");
+
+ /**
+ * Parses the XML documents and discovers spring beans, which will be created by Spring {@link BeanFactory}.
+ */
+ public void processSpringBeans(
+ CamelContext camelContext, MainConfigurationProperties config, final Map<String, Document> xmls) {
+
+ LOG.debug("Loading beans from classic Spring <beans> XML");
+
+ // we _could_ create something like org.apache.camel.spring.spi.ApplicationContextBeanRepository, but
+ // wrapping DefaultListableBeanFactory and use it as one of the
+ // org.apache.camel.support.DefaultRegistry.repositories, but for now let's use it to populate
+ // Spring registry and then copy the beans (whether the scope is)
+ final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
+ beanFactory.setAllowCircularReferences(true); // for now
+ beanFactory.setBeanClassLoader(camelContext.getApplicationContextClassLoader());
+ beanFactory.setBeanExpressionResolver((value, beanExpressionContext) -> extractValue(camelContext, value, true));
+ camelContext.getRegistry().bind("SpringBeanFactory", beanFactory);
+
+ beanFactory.registerSingleton("CamelContext", camelContext);
+ beanFactory.registerSingleton("MainConfiguration", config);
+ // ...
+
+ // instead of generating an MX parser for spring-beans.xsd and use it to read the docs, we can simply
+ // pass w3c Documents directly to Spring
+ final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
+ xmls.forEach((id, doc) -> {
+ reader.registerBeanDefinitions(doc, new AbstractResource() {
+ @Override
+ public String getFilename() {
+ if (id.startsWith("camel-xml-io-dsl-spring-xml:")) {
+ // this is a camel bean via camel-xml-io-dsl
+ return StringHelper.afterLast(id, ":");
+ }
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return id;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return new ByteArrayInputStream(new byte[0]);
+ }
+ });
+ });
+
+ // for full interaction between Spring ApplicationContext and its BeanFactory see
+ // org.springframework.context.support.AbstractApplicationContext.refresh()
+ // see org.springframework.context.support.AbstractApplicationContext.prepareBeanFactory() to check
+ // which extra/infra beans are added
+ beanFactory.freezeConfiguration();
+
+ List<String> beanNames = Arrays.asList(beanFactory.getBeanDefinitionNames());
+
+ // Trigger initialization of all non-lazy singleton beans...
+ instantiateAndRegisterBeans(camelContext, beanFactory, beanNames);
+ }
+
+ /**
+ * Invoked at later stage to create and register Spring beans into Camel {@link org.apache.camel.spi.Registry}.
+ */
+ public void createAndRegisterBeans(CamelContext camelContext) {
+ if (delayedBeans.isEmpty()) {
+ return;
+ }
+
+ DefaultListableBeanFactory beanFactory
+ = camelContext.getRegistry().lookupByNameAndType("SpringBeanFactory", DefaultListableBeanFactory.class);
+
+ // we have some beans with classes that we couldn't load before. now, after loading the routes
+ // we may have the needed class definitions
+ for (String beanName : delayedBeans) {
+ BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
+ if (bd instanceof AbstractBeanDefinition abd) {
+ if (!abd.hasBeanClass()) {
+ Class<?> c = camelContext.getClassResolver().resolveClass(abd.getBeanClassName());
+ abd.setBeanClass(c);
+ }
+ }
+ }
+
+ instantiateAndRegisterBeans(camelContext, beanFactory, delayedBeans);
+ }
+
+ private void instantiateAndRegisterBeans(
+ CamelContext camelContext, DefaultListableBeanFactory beanFactory, List<String> beanNames) {
+ List<String> instantiatedBeanNames = new LinkedList<>();
+
+ for (String beanName : beanNames) {
+ BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
+ if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
+ try {
+ if (beanFactory.isFactoryBean(beanName)) {
+ Object bean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
+ if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
+ beanFactory.getBean(beanName);
+ instantiatedBeanNames.add(beanName);
+ }
+ } else {
+ beanFactory.getBean(beanName);
+ instantiatedBeanNames.add(beanName);
+ }
+ } catch (CannotLoadBeanClassException ignored) {
+ // we'll try to resolve later
+ delayedBeans.add(beanName);
+ }
+ }
+ }
+
+ // Trigger post-initialization callback for all applicable beans...
+ for (String beanName : instantiatedBeanNames) {
+ Object singletonInstance = beanFactory.getSingleton(beanName);
+ if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
+ StartupStep smartInitialize = beanFactory.getApplicationStartup()
+ .start("spring.beans.smart-initialize")
+ .tag("beanName", beanName);
+ smartSingleton.afterSingletonsInstantiated();
+ smartInitialize.end();
+ }
+ }
+
+ for (String name : instantiatedBeanNames) {
+ if (infraBeanNames.contains(name)) {
+ continue;
+ }
+ BeanDefinition def = beanFactory.getBeanDefinition(name);
+ if (def.isSingleton()) {
+ // just grab the singleton and put into registry
+ camelContext.getRegistry().bind(name, beanFactory.getBean(name));
+ } else {
+ // rely on the bean factory to implement prototype scope
+ camelContext.getRegistry().bind(name, (Supplier<Object>) () -> beanFactory.getBean(name));
+ }
+
+ addBeanToCamelModel(camelContext, name, def);
+ }
+ }
+
+ private void addBeanToCamelModel(CamelContext camelContext, String name, BeanDefinition def) {
+ // register bean into model (as a BeanRegistry that allows Camel DSL to know about these beans)
+ Model model = camelContext.getCamelContextExtension().getContextPlugin(Model.class);
+ if (model != null) {
+ RegistryBeanDefinition rrd = new RegistryBeanDefinition();
+ if (def instanceof GenericBeanDefinition gbd) {
+ // set camel resource to refer to the source file
+ Resource res = gbd.getResource();
+ if (res != null) {
+ String fn = res.getFilename();
+ if (fn != null) {
+ rrd.setResource(camelContext.getCamelContextExtension().getContextPlugin(ResourceLoader.class)
+ .resolveResource("file:" + fn));
+ }
+ }
+ }
+ rrd.setType(def.getBeanClassName());
+ rrd.setName(name);
+ model.addRegistryBean(rrd);
+
+ // constructor arguments
+ ConstructorArgumentValues ctr = def.getConstructorArgumentValues();
+ StringJoiner sj = new StringJoiner(", ");
+ for (ConstructorArgumentValues.ValueHolder v : ctr.getIndexedArgumentValues().values()) {
+ Object val = v.getValue();
+ if (val instanceof TypedStringValue tsv) {
+ sj.add("'" + extractValue(camelContext, tsv.getValue(), false) + "'");
+ } else if (val instanceof BeanReference br) {
+ sj.add("'#bean:" + extractValue(camelContext, br.getBeanName(), false) + "'");
+ }
+ }
+ if (sj.length() > 0) {
+ rrd.setType("#class:" + def.getBeanClassName() + "(" + sj + ")");
+ }
+ // property values
+ if (def.hasPropertyValues()) {
+ Map<String, Object> properties = new LinkedHashMap<>();
+ rrd.setProperties(properties);
+
+ MutablePropertyValues values = def.getPropertyValues();
+ for (PropertyValue v : values) {
+ String key = v.getName();
+ PropertyValue src = v.getOriginalPropertyValue();
+ Object val = src.getValue();
+ if (val instanceof TypedStringValue tsv) {
+ properties.put(key, extractValue(camelContext, tsv.getValue(), false));
+ } else if (val instanceof BeanReference br) {
+ properties.put(key, "#bean:" + extractValue(camelContext, br.getBeanName(), false));
+ } else if (val instanceof List) {
+ int i = 0;
+ Iterator<?> it = ObjectHelper.createIterator(val);
+ while (it.hasNext()) {
+ String k = key + "[" + i + "]";
+ val = it.next();
+ if (val instanceof TypedStringValue tsv) {
+ properties.put(k, extractValue(camelContext, tsv.getValue(), false));
+ } else if (val instanceof BeanReference br) {
+ properties.put(k, "#bean:" + extractValue(camelContext, br.getBeanName(), false));
+ }
+ i++;
+ }
+ } else if (val instanceof Map) {
+ Map<TypedStringValue, Object> map = (Map) val;
+ for (Map.Entry<TypedStringValue, Object> entry : map.entrySet()) {
+ String k = key + "[" + entry.getKey().getValue() + "]";
+ val = entry.getValue();
+ if (val instanceof TypedStringValue tsv) {
+ properties.put(k, extractValue(camelContext, tsv.getValue(), false));
+ } else if (val instanceof BeanReference br) {
+ properties.put(k, "#bean:" + extractValue(camelContext, br.getBeanName(), false));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected String extractValue(CamelContext camelContext, String val, boolean resolve) {
+ // spring placeholder prefix
+ if (val != null && val.contains("${")) {
+ Matcher matcher = SPRING_PATTERN.matcher(val);
+ while (matcher.find()) {
+ String replace = "{{" + matcher.group(1) + "}}";
+ val = matcher.replaceFirst(replace);
+ // we changed so reset matcher so it can find more
+ matcher.reset(val);
+ }
+ }
+
+ if (resolve && camelContext != null) {
+ // if running camel then resolve property placeholders from beans
+ val = camelContext.resolvePropertyPlaceholders(val);
+ }
+ return val;
+ }
+
+}
diff --git a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlModelParser.java b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlModelParser.java
index b440e0a90ce..fde9a848027 100644
--- a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlModelParser.java
+++ b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlModelParser.java
@@ -23,23 +23,31 @@ import org.apache.camel.xml.in.ModelParser;
import org.apache.camel.xml.io.XmlPullParserException;
/**
- * XML {@link ModelParser} that supports loading classic Spring XML <beans> with embedded <camelContext>, with limited
- * parsing, to discover <routes> inside <camelContext>.
+ * XML {@link ModelParser} that supports loading:
+ * <ul>
+ * <li>Standard Camel XML DSL</li>
+ * <li>Classic Spring XML <beans> with embedded <camelContext> (limited parsing, to discover <routes> inside
+ * <camelContext>)</li>
+ * <li>Legacy OSGi <blueprint> with embedded <camelContext> (limited parsing, to discover <routes> inside
+ * <camelContext>)</li>
+ * </ul>
*/
public class XmlModelParser extends ModelParser {
private static final String SPRING_NS = "http://camel.apache.org/schema/spring";
+ private static final String BLUEPRINT_NS = "http://camel.apache.org/schema/blueprint";
public XmlModelParser(Resource input, String namespace) throws IOException, XmlPullParserException {
super(input, namespace);
- addSecondNamespace(SPRING_NS);
+ addSecondaryNamespace(SPRING_NS);
+ addSecondaryNamespace(BLUEPRINT_NS);
}
@Override
protected boolean handleUnexpectedElement(String namespace, String name) throws XmlPullParserException {
- // accept embedded <camelContext> inside Spring XML <beans> files, so we can discover
- // embedded <routes> inside this <camelContext>.
- if ("camelContext".equals(name) && SPRING_NS.equals(namespace)) {
+ // accept embedded <camelContext> inside Spring XML <beans> files or OSGi <blueprint> files,
+ // so we can discover embedded <routes> inside this <camelContext>.
+ if ("camelContext".equals(name) && (SPRING_NS.equals(namespace) || BLUEPRINT_NS.equals(namespace))) {
return true;
}
return super.handleUnexpectedElement(namespace, name);
diff --git a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
index bae6983dbac..3dace301432 100644
--- a/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
+++ b/dsl/camel-xml-io-dsl/src/main/java/org/apache/camel/dsl/xml/io/XmlRoutesBuilderLoader.java
@@ -89,7 +89,7 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
XmlStreamInfo xmlInfo = xmlInfo(resource);
if (xmlInfo.isValid()) {
String root = xmlInfo.getRootElementName();
- if ("beans".equals(root) || "camel".equals(root)) {
+ if ("beans".equals(root) || "blueprint".equals(root) || "camel".equals(root)) {
new XmlModelParser(resource, xmlInfo.getRootElementNamespace())
.parseBeansDefinition()
.ifPresent(bd -> {
@@ -116,7 +116,7 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
public void configure() throws Exception {
String resourceLocation = input.getLocation();
switch (xmlInfo.getRootElementName()) {
- case "beans", "camel" -> {
+ case "beans", "blueprint", "camel" -> {
BeansDefinition def = camelAppCache.get(resourceLocation);
if (def != null) {
configureCamel(def);
@@ -298,6 +298,17 @@ public class XmlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
String id = String.format("camel-xml-io-dsl-spring-xml:%05d:%s", counter.incrementAndGet(), resource.getLocation());
getCamelContext().getRegistry().bind(id, doc);
}
+
+ // <s:bean> elements - all the elements in single BeansDefinition have
+ // one parent org.w3c.dom.Document - and this is what we collect from each resource
+ if (!app.getBlueprintBeans().isEmpty()) {
+ Document doc = app.getBlueprintBeans().get(0).getOwnerDocument();
+ // bind as Document, to be picked up later - bean id allows nice sorting
+ // (can also be single ID - documents will get collected in LinkedHashMap, so we'll be fine)
+ String id = String.format("camel-xml-io-dsl-blueprint-xml:%05d:%s", counter.incrementAndGet(),
+ resource.getLocation());
+ getCamelContext().getRegistry().bind(id, doc);
+ }
}
/**
diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlBlueprintLoadTest.java
similarity index 72%
copy from dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java
copy to dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlBlueprintLoadTest.java
index a28258f7d62..ce3f61d0629 100644
--- a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java
+++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlBlueprintLoadTest.java
@@ -19,20 +19,23 @@ package org.apache.camel.dsl.xml.io;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.Resource;
import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-public class XmlSpringBeansLoadTest {
+public class XmlBlueprintLoadTest {
@Test
public void testLoadRoutesBuilderFromXml() throws Exception {
try (DefaultCamelContext context = new DefaultCamelContext()) {
- // load spring XML <beans> with embedded <camelContext>
+ // load OSGi blueprint XML <blueprint> with embedded <camelContext>
Resource resource = PluginHelper.getResourceLoader(context).resolveResource(
- "/org/apache/camel/dsl/xml/io/springBeans.xml");
+ "/org/apache/camel/dsl/xml/io/blueprintRoutes.xml");
- PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+ Assertions.assertDoesNotThrow(() -> {
+ // should be able to parse the file and not fail (camel-jbang supports creating spring beans)
+ PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+ });
- // should be able to parse the file and not fail (camel-jbang supports creating spring beans)
}
}
diff --git a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java
index a28258f7d62..c5f7ba6b226 100644
--- a/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java
+++ b/dsl/camel-xml-io-dsl/src/test/java/org/apache/camel/dsl/xml/io/XmlSpringBeansLoadTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.dsl.xml.io;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.spi.Resource;
import org.apache.camel.support.PluginHelper;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class XmlSpringBeansLoadTest {
@@ -30,9 +31,10 @@ public class XmlSpringBeansLoadTest {
Resource resource = PluginHelper.getResourceLoader(context).resolveResource(
"/org/apache/camel/dsl/xml/io/springBeans.xml");
- PluginHelper.getRoutesLoader(context).loadRoutes(resource);
-
- // should be able to parse the file and not fail (camel-jbang supports creating spring beans)
+ Assertions.assertDoesNotThrow(() -> {
+ // should be able to parse the file and not fail (camel-jbang supports creating spring beans)
+ PluginHelper.getRoutesLoader(context).loadRoutes(resource);
+ });
}
}
diff --git a/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/blueprintRoutes.xml b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/blueprintRoutes.xml
new file mode 100644
index 00000000000..96515d8c48f
--- /dev/null
+++ b/dsl/camel-xml-io-dsl/src/test/resources/org/apache/camel/dsl/xml/io/blueprintRoutes.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="
+ http://www.osgi.org/xmlns/blueprint/v1.0.0 https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd">
+
+ <!-- spring beans -->
+ <bean id="orderService" class="com.foo.OrderService">
+ <argument index="0" value="true"/>
+ <argument index="1" ref="office"/>
+ </bean>
+ <!-- uses blueprint property placeholder ${xxx} syntax -->
+ <bean id="office" class="com.foo.Address">
+ <property name="zip" value="${zipCode}"/>
+ <property name="street" value="${streetName}"/>
+ </bean>
+
+ <!-- embed Camel with routes -->
+ <camelContext xmlns="http://camel.apache.org/schema/blueprint">
+
+ <route>
+ <from uri="timer:xml?period={{time:1000}}"/>
+ <setBody>
+ <simple>${random(1000)}</simple>
+ </setBody>
+ <bean ref="orderService"/>
+ <log message="${body}"/>
+ </route>
+
+ </camelContext>
+
+</blueprint>
diff --git a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelXmlParserGeneratorMojo.java b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelXmlParserGeneratorMojo.java
index 36db5315cf6..c2a5c7cb5ed 100644
--- a/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelXmlParserGeneratorMojo.java
+++ b/tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelXmlParserGeneratorMojo.java
@@ -594,7 +594,9 @@ public class ModelXmlParserGeneratorMojo extends AbstractGeneratorMojo {
.setName("parse" + name)
.addThrows(IOException.class)
.addThrows(XML_PULL_PARSER_EXCEPTION)
- .setBody(String.format("String tag = getNextTag(\"%s\", \"%s\");", "beans", "camel"),
+ .setBody(
+ String.format("String tag = getNextTag(\"%s\", \"%s\", \"%s\");", "beans", "blueprint",
+ "camel"),
"if (tag != null) {",
String.format(" return Optional.of(doParse%s());", name),
"}",