From 16a524b2dfce7e2ceaef75c0f276e8bd68429c3f Mon Sep 17 00:00:00 2001 From: lburgazzoli Date: Mon, 10 Aug 2020 18:53:27 +0200 Subject: [PATCH] kamelets: create a camel-kamelet component #375 --- .../camel/k/loader/groovy/LoaderTest.groovy | 6 +- .../loader/groovy/dsl/IntegrationTest.groovy | 2 +- .../camel/k/loader/java/RoutesLoaderTest.java | 14 +- .../camel/k/loader/js/RoutesLoaderTest.java | 4 +- .../k/loader/js/dsl/IntegrationTest.java | 4 +- .../k/loader/kotlin/itests/LoaderTest.java | 4 +- .../camel/k/loader/kotlin/LoaderTest.kt | 6 +- .../k/loader/kotlin/dsl/IntegrationTest.kt | 2 +- .../camel/k/main/ApplicationRuntime.java | 11 - .../org/apache/camel/k/main/RuntimeTest.java | 55 ++++- .../quarkus/deployment/ExtensionTest.java | 4 +- .../deployment/DeploymentProcessor.java | 17 +- camel-k-runtime-core/pom.xml | 39 ++++ .../camel/k/SourceDefinitionConfigurer.java | 85 ++++++++ .../SourcesConfigurationConfigurer.java | 45 ++++ .../apache/camel/configurer/SourceDefinition | 2 + .../camel/configurer/SourcesConfiguration | 2 + .../main/java/org/apache/camel/k/Source.java | 2 + .../org/apache/camel/k/SourceDefinition.java | 199 ++++++++++++++++++ .../java/org/apache/camel/k/SourceType.java | 22 ++ .../main/java/org/apache/camel/k/Sources.java | 197 ++++++----------- .../k/listener/SourcesConfiguration.java | 33 +++ ...Configurer.java => SourcesConfigurer.java} | 68 +++--- .../camel/k/support/PropertiesSupport.java | 54 ++++- .../org.apache.camel.k.Runtime$Listener | 2 +- .../java/org/apache/camel/k/SourceTest.java | 10 + .../k/support/PropertiesSupportTest.java | 83 ++++++++ .../org/apache/camel/k/cron/CronTest.java | 4 +- .../KnativeSinkBindingCustomizerTest.java | 4 +- .../yaml/parser/KnativeConverterTest.java | 4 +- .../KnativeSourceRoutesLoaderTest.java | 9 +- .../apache/camel/k/webhook/WebhookTest.java | 8 +- camel-kamelet/pom.xml | 15 ++ .../camel/component/kamelet/Kamelet.java | 72 +++++++ .../component/kamelet/KameletComponent.java | 120 ++++------- .../component/kamelet/KameletEndpoint.java | 10 +- .../KameletAddAfterCamelStartedTest.java | 76 ------- .../component/kamelet/KameletBasicTest.java | 84 ++++++++ .../kamelet/KameletPropertiesTest.java | 86 ++++++++ .../camel/component/kamelet/KameletTest.java | 74 ------- .../src/test/resources/log4j2-test.xml | 2 + .../camel/k/test/CamelKTestSupport.java | 48 +++++ 42 files changed, 1121 insertions(+), 467 deletions(-) create mode 100644 camel-k-runtime-core/src/generated/java/org/apache/camel/k/SourceDefinitionConfigurer.java create mode 100644 camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurationConfigurer.java create mode 100644 camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourceDefinition create mode 100644 camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfiguration create mode 100644 camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceDefinition.java create mode 100644 camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceType.java create mode 100644 camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfiguration.java rename camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/{RoutesConfigurer.java => SourcesConfigurer.java} (61%) create mode 100644 camel-k-runtime-core/src/test/java/org/apache/camel/k/support/PropertiesSupportTest.java delete mode 100644 camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletAddAfterCamelStartedTest.java create mode 100644 camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletBasicTest.java create mode 100644 camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java delete mode 100644 camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletTest.java create mode 100644 tooling/camel-k-test/src/main/java/org/apache/camel/k/test/CamelKTestSupport.java diff --git a/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/LoaderTest.groovy b/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/LoaderTest.groovy index c637fda28..1ce2b095d 100644 --- a/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/LoaderTest.groovy +++ b/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/LoaderTest.groovy @@ -22,7 +22,7 @@ import org.apache.camel.builder.RouteBuilder import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.k.Runtime import org.apache.camel.k.Sources -import org.apache.camel.k.listener.RoutesConfigurer +import org.apache.camel.k.listener.SourcesConfigurer import org.apache.camel.model.FromDefinition import org.apache.camel.model.ToDefinition import spock.lang.Specification @@ -35,7 +35,7 @@ class LoaderTest extends Specification { def source = Sources.fromURI("classpath:routes.groovy") when: - def loader = RoutesConfigurer.load(runtime, source) + def loader = SourcesConfigurer.load(runtime, source) then: loader instanceof GroovySourceLoader @@ -60,7 +60,7 @@ class LoaderTest extends Specification { def source = Sources.fromURI("classpath:routes-with-endpoint-dsl.groovy") when: - def loader = RoutesConfigurer.load(runtime, source) + def loader = SourcesConfigurer.load(runtime, source) then: loader instanceof GroovySourceLoader diff --git a/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/dsl/IntegrationTest.groovy b/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/dsl/IntegrationTest.groovy index 11f29acca..e2319a92b 100644 --- a/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/dsl/IntegrationTest.groovy +++ b/camel-k-loader-groovy/src/test/groovy/org/apache/camel/k/loader/groovy/dsl/IntegrationTest.groovy @@ -36,7 +36,7 @@ import spock.lang.Specification import javax.sql.DataSource -import static org.apache.camel.k.listener.RoutesConfigurer.forRoutes +import static org.apache.camel.k.listener.SourcesConfigurer.forRoutes class IntegrationTest extends Specification { diff --git a/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java b/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java index 2519babc0..c6e41a574 100644 --- a/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java +++ b/camel-k-loader-java/src/test/java/org/apache/camel/k/loader/java/RoutesLoaderTest.java @@ -30,7 +30,7 @@ import org.apache.camel.k.Source; import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.model.ProcessDefinition; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.SetBodyDefinition; @@ -47,7 +47,7 @@ public class RoutesLoaderTest { public void testLoadJavaWithNestedClass() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:MyRoutesWithNestedClass.java"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(JavaSourceLoader.class); assertThat(runtime.builders).hasSize(1); @@ -69,7 +69,7 @@ public void testLoadJavaWithNestedClass() throws Exception { public void testLoadJavaWithRestConfiguration() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:MyRoutesWithRestConfiguration.java"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(JavaSourceLoader.class); assertThat(runtime.builders).hasSize(1); @@ -84,7 +84,7 @@ public void testLoadJavaWithRestConfiguration() throws Exception { public void testLoadJavaConfiguration() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:MyRoutesConfig.java"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(JavaSourceLoader.class); assertThat(runtime.builders).isEmpty(); @@ -95,7 +95,7 @@ public void testLoadJavaConfiguration() throws Exception { public void testLoadJavaWithModel() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:MyRoutesWithModel.java"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(JavaSourceLoader.class); assertThat(runtime.builders).hasSize(1); @@ -114,7 +114,7 @@ public void testLoadJavaWithModel() throws Exception { public void testLoadJavaWithNestedType() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:MyRoutesWithNestedTypes.java"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(JavaSourceLoader.class); assertThat(runtime.builders).hasSize(1); @@ -129,7 +129,7 @@ public void testLoadJavaWithNestedType() throws Exception { public void testLoaders(String location, Class type) throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI(location); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(type); assertThat(runtime.builders).hasSize(1); diff --git a/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/RoutesLoaderTest.java b/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/RoutesLoaderTest.java index 846daad03..b32013089 100644 --- a/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/RoutesLoaderTest.java +++ b/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/RoutesLoaderTest.java @@ -29,7 +29,7 @@ import org.apache.camel.k.Source; import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.model.RouteDefinition; import org.apache.camel.model.ToDefinition; import org.junit.jupiter.params.ParameterizedTest; @@ -44,7 +44,7 @@ public class RoutesLoaderTest { public void testLoaders(String location, Class type) throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI(location); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(type); assertThat(runtime.builders).hasSize(1); diff --git a/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/dsl/IntegrationTest.java b/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/dsl/IntegrationTest.java index 326ddc5c1..e815b0939 100644 --- a/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/dsl/IntegrationTest.java +++ b/camel-k-loader-js/src/test/java/org/apache/camel/k/loader/js/dsl/IntegrationTest.java @@ -23,7 +23,7 @@ import org.apache.camel.component.seda.SedaComponent; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.k.Runtime; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.ModelCamelContext; import org.apache.camel.model.RouteDefinition; @@ -55,7 +55,7 @@ public void shutDown() { } private void configureRoutes(String... routes) { - RoutesConfigurer.forRoutes(routes).accept(Runtime.Phase.ConfigureRoutes, runtime); + SourcesConfigurer.forRoutes(routes).accept(Runtime.Phase.ConfigureRoutes, runtime); } @Test diff --git a/camel-k-loader-kotlin/camel-k-loader-kotlin-itests/src/test/java/org/apache/camel/k/loader/kotlin/itests/LoaderTest.java b/camel-k-loader-kotlin/camel-k-loader-kotlin-itests/src/test/java/org/apache/camel/k/loader/kotlin/itests/LoaderTest.java index bc25de785..9f368b5a9 100644 --- a/camel-k-loader-kotlin/camel-k-loader-kotlin-itests/src/test/java/org/apache/camel/k/loader/kotlin/itests/LoaderTest.java +++ b/camel-k-loader-kotlin/camel-k-loader-kotlin-itests/src/test/java/org/apache/camel/k/loader/kotlin/itests/LoaderTest.java @@ -21,7 +21,7 @@ import org.apache.camel.k.Runtime; import org.apache.camel.k.Source; import org.apache.camel.k.Sources; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -33,7 +33,7 @@ public void testLoad() throws Exception { final Runtime runtime = Runtime.on(context); final Source source = Sources.fromURI("classpath:routes.kts"); - RoutesConfigurer.load(runtime, source); + SourcesConfigurer.load(runtime, source); try { context.start(); diff --git a/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/LoaderTest.kt b/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/LoaderTest.kt index d6840fae0..12badb565 100644 --- a/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/LoaderTest.kt +++ b/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/LoaderTest.kt @@ -22,7 +22,7 @@ import org.apache.camel.builder.RouteBuilder import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.k.Runtime import org.apache.camel.k.Sources -import org.apache.camel.k.listener.RoutesConfigurer +import org.apache.camel.k.listener.SourcesConfigurer import org.apache.camel.model.ProcessDefinition import org.apache.camel.model.ToDefinition import org.assertj.core.api.Assertions.assertThat @@ -35,7 +35,7 @@ class LoaderTest { fun `load routes`() { val runtime = TestRuntime() val source = Sources.fromURI("classpath:routes.kts") - val loader = RoutesConfigurer.load(runtime, source) + val loader = SourcesConfigurer.load(runtime, source) assertThat(loader).isInstanceOf(KotlinSourceLoader::class.java) assertThat(runtime.builders).hasSize(1) @@ -56,7 +56,7 @@ class LoaderTest { fun `load routes with endpoint dsl`() { val runtime = TestRuntime() val source = Sources.fromURI("classpath:routes-with-endpoint-dsl.kts") - val loader = RoutesConfigurer.load(runtime, source) + val loader = SourcesConfigurer.load(runtime, source) assertThat(loader).isInstanceOf(KotlinSourceLoader::class.java) assertThat(runtime.builders).hasSize(1) diff --git a/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/dsl/IntegrationTest.kt b/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/dsl/IntegrationTest.kt index 95c0da7e8..b9529415e 100644 --- a/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/dsl/IntegrationTest.kt +++ b/camel-k-loader-kotlin/camel-k-loader-kotlin/src/test/kotlin/org/apache/camel/k/loader/kotlin/dsl/IntegrationTest.kt @@ -24,7 +24,7 @@ import org.apache.camel.component.log.LogComponent import org.apache.camel.component.seda.SedaComponent import org.apache.camel.impl.DefaultCamelContext import org.apache.camel.k.Runtime -import org.apache.camel.k.listener.RoutesConfigurer.forRoutes +import org.apache.camel.k.listener.SourcesConfigurer.forRoutes import org.apache.camel.language.bean.BeanLanguage import org.apache.camel.model.ModelCamelContext import org.apache.camel.model.rest.GetVerbDefinition diff --git a/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java b/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java index 7d68d0e5a..5ba138f92 100644 --- a/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java +++ b/camel-k-main/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/ApplicationRuntime.java @@ -30,14 +30,12 @@ import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.Runtime; -import org.apache.camel.k.support.PropertiesSupport; import org.apache.camel.main.BaseMainSupport; import org.apache.camel.main.MainSupport; import org.apache.camel.main.RoutesCollector; import org.apache.camel.model.RouteTemplatesDefinition; import org.apache.camel.model.RoutesDefinition; import org.apache.camel.model.rest.RestsDefinition; -import org.apache.camel.spi.HasId; import org.apache.camel.util.function.ThrowingConsumer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -103,15 +101,6 @@ public void addListeners(Iterable listeners) { } public void addListener(Runtime.Listener listener) { - if (listener instanceof HasId) { - String id = ((HasId) listener).getId(); - if (!id.endsWith(".")) { - id = id + "."; - } - - PropertiesSupport.bindProperties(getCamelContext(), listener, id); - } - LOGGER.info("Add listener: {}", listener); this.listeners.add(listener); diff --git a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java index 59681e641..591755b3b 100644 --- a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java +++ b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java @@ -26,7 +26,7 @@ import org.apache.camel.k.Runtime; import org.apache.camel.k.http.PlatformHttpServiceContextCustomizer; import org.apache.camel.k.listener.ContextConfigurer; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.test.AvailablePortFinder; import org.apache.camel.model.ModelCamelContext; import org.apache.camel.model.ToDefinition; @@ -55,7 +55,7 @@ public void cleanUp() throws Exception { @Test void testLoadMultipleRoutes() throws Exception { runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:r1.js", "classpath:r2.mytype?language=js")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:r1.js", "classpath:r2.mytype?language=js")); runtime.addListener(Runtime.Phase.Started, r -> { CamelContext context = r.getCamelContext(); List routes = context.getRoutes(); @@ -73,7 +73,7 @@ void testLoadMultipleRoutes() throws Exception { @Test void testLoadRouteAndRest() throws Exception { runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes.xml", "classpath:rests.xml")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:routes.xml", "classpath:rests.xml")); runtime.addListener(Runtime.Phase.Started, r -> { CamelContext context = r.getCamelContext(); @@ -93,16 +93,55 @@ void testLoadRouteWithExpression() throws Exception { )); runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes-with-expression.xml")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:routes-with-expression.xml")); runtime.addListener(Runtime.Phase.Started, r -> runtime.stop()); runtime.run(); } @Test public void testLoadJavaSource() throws Exception { - ApplicationRuntime runtime = new ApplicationRuntime(); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:MyRoutesWithBeans.java", "classpath:MyRoutesConfig.java")); - runtime.addListener(Runtime.Phase.Started, r -> runtime.stop()); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:MyRoutesWithBeans.java", "classpath:MyRoutesConfig.java")); + runtime.addListener(Runtime.Phase.Started, r -> { + assertThat(runtime.getCamelContext().getRoutes()).hasSize(1); + assertThat(runtime.getRegistry().lookupByName("my-processor")).isNotNull(); + assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> { + assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name"); + }); + r.stop(); + }); + runtime.run(); + } + + @Test + public void testLoadJavaSourceFromProperties() throws Exception { + runtime.setInitialProperties( + "camel.k.sources[0].name", "MyRoutesWithBeans", + "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java", + "camel.k.sources[0].language", "java", + "camel.k.sources[1].name", "MyRoutesConfig", + "camel.k.sources[1].location", "classpath:MyRoutesConfig.java", + "camel.k.sources[1].language", "java" + ); + runtime.addListener(new SourcesConfigurer()); + runtime.addListener(Runtime.Phase.Started, r -> { + assertThat(runtime.getCamelContext().getRoutes()).hasSize(1); + assertThat(runtime.getRegistry().lookupByName("my-processor")).isNotNull(); + assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> { + assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name"); + }); + r.stop(); + }); + runtime.run(); + } + + @Test + public void testLoadJavaSourceFromSimpleProperties() throws Exception { + runtime.setInitialProperties( + "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java", + "camel.k.sources[1].location", "classpath:MyRoutesConfig.java" + ); + runtime.addListener(new SourcesConfigurer()); + runtime.addListener(Runtime.Phase.Started, Runtime::stop); runtime.run(); assertThat(runtime.getRegistry().lookupByName("my-processor")).isNotNull(); @@ -123,7 +162,7 @@ public void testLoadJavaSourceWrap() throws Exception { phsc.apply(runtime.getCamelContext()); runtime.getCamelContext().addComponent("knative", component); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:MyRoutesWithBeans.java?interceptors=knative-source")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:MyRoutesWithBeans.java?interceptors=knative-source")); runtime.addListener(Runtime.Phase.Started, r -> runtime.stop()); runtime.run(); diff --git a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java index bbf417c85..b653dc2e9 100644 --- a/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java +++ b/camel-k-quarkus/camel-k-quarkus-itests/camel-k-quarkus-itests-core/src/test/java/org/apache/camel/k/core/quarkus/deployment/ExtensionTest.java @@ -24,7 +24,7 @@ import io.restassured.path.json.JsonPath; import org.apache.camel.k.CompositeClassloader; import org.apache.camel.k.listener.ContextConfigurer; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -45,7 +45,7 @@ public void testServices() { assertThat(p.getList("services", String.class)).contains( ContextConfigurer.class.getName(), - RoutesConfigurer.class.getName() + SourcesConfigurer.class.getName() ); } diff --git a/camel-k-quarkus/camel-k-runtime-quarkus/deployment/src/main/java/org/apache/camel/k/quarkus/deployment/DeploymentProcessor.java b/camel-k-quarkus/camel-k-runtime-quarkus/deployment/src/main/java/org/apache/camel/k/quarkus/deployment/DeploymentProcessor.java index 3136a69f9..076ca3cb1 100644 --- a/camel-k-quarkus/camel-k-runtime-quarkus/deployment/src/main/java/org/apache/camel/k/quarkus/deployment/DeploymentProcessor.java +++ b/camel-k-quarkus/camel-k-runtime-quarkus/deployment/src/main/java/org/apache/camel/k/quarkus/deployment/DeploymentProcessor.java @@ -26,11 +26,8 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import org.apache.camel.k.Runtime; import org.apache.camel.k.quarkus.ApplicationRecorder; -import org.apache.camel.quarkus.core.deployment.spi.CamelServiceDestination; -import org.apache.camel.quarkus.core.deployment.spi.CamelServicePatternBuildItem; import org.apache.camel.quarkus.main.CamelMainApplication; import org.apache.camel.quarkus.main.deployment.spi.CamelMainListenerBuildItem; -import org.apache.camel.spi.HasId; public class DeploymentProcessor { @BuildStep @@ -42,19 +39,7 @@ public ReflectiveClassBuildItem reflectiveClasses() { @BuildStep CamelMainListenerBuildItem registerListener(ApplicationRecorder recorder) { List listeners = new ArrayList<>(); - ServiceLoader.load(Runtime.Listener.class).forEach(listener -> { - if (listener instanceof HasId) { - String id = ((HasId) listener).getId(); - if (!id.endsWith(".")) { - id = id + "."; - } - - // TODO: this has to be done at runtime - //PropertiesSupport.bindProperties(getCamelContext(), listener, id); - } - - listeners.add(listener); - }); + ServiceLoader.load(Runtime.Listener.class).forEach(listeners::add); return new CamelMainListenerBuildItem(recorder.createMainListener(listeners)); } diff --git a/camel-k-runtime-core/pom.xml b/camel-k-runtime-core/pom.xml index b9bfeb0a1..608ff3e67 100644 --- a/camel-k-runtime-core/pom.xml +++ b/camel-k-runtime-core/pom.xml @@ -93,6 +93,45 @@ + + + org.apache.camel + camel-package-maven-plugin + ${camel.version} + + + generate-configurer + process-classes + + generate-configurer + + + + + + org.codehaus.mojo + build-helper-maven-plugin + ${build-helper-maven-plugin.version} + + + generate-sources + + add-source + add-resource + + + + src/generated/java + + + + src/generated/resources + + + + + + diff --git a/camel-k-runtime-core/src/generated/java/org/apache/camel/k/SourceDefinitionConfigurer.java b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/SourceDefinitionConfigurer.java new file mode 100644 index 000000000..9ce8c5ff7 --- /dev/null +++ b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/SourceDefinitionConfigurer.java @@ -0,0 +1,85 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.k; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.k.SourceDefinition; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class SourceDefinitionConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.k.SourceDefinition target = (org.apache.camel.k.SourceDefinition) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "compressed": + case "Compressed": target.setCompressed(property(camelContext, boolean.class, value)); return true; + case "content": + case "Content": target.setContent(property(camelContext, byte[].class, value)); return true; + case "interceptors": + case "Interceptors": target.setInterceptors(property(camelContext, java.util.List.class, value)); return true; + case "language": + case "Language": target.setLanguage(property(camelContext, java.lang.String.class, value)); return true; + case "loader": + case "Loader": target.setLoader(property(camelContext, java.lang.String.class, value)); return true; + case "location": + case "Location": target.setLocation(property(camelContext, java.lang.String.class, value)); return true; + case "name": + case "Name": target.setName(property(camelContext, java.lang.String.class, value)); return true; + case "propertiesnames": + case "PropertiesNames": target.setPropertiesNames(property(camelContext, java.util.List.class, value)); return true; + case "type": + case "Type": target.setType(property(camelContext, org.apache.camel.k.SourceType.class, value)); return true; + default: return false; + } + } + + @Override + public Map getAllOptions(Object target) { + Map answer = new CaseInsensitiveMap(); + answer.put("Compressed", boolean.class); + answer.put("Content", byte[].class); + answer.put("Interceptors", java.util.List.class); + answer.put("Language", java.lang.String.class); + answer.put("Loader", java.lang.String.class); + answer.put("Location", java.lang.String.class); + answer.put("Name", java.lang.String.class); + answer.put("PropertiesNames", java.util.List.class); + answer.put("Type", org.apache.camel.k.SourceType.class); + return answer; + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.k.SourceDefinition target = (org.apache.camel.k.SourceDefinition) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "compressed": + case "Compressed": return target.isCompressed(); + case "content": + case "Content": return target.getContent(); + case "interceptors": + case "Interceptors": return target.getInterceptors(); + case "language": + case "Language": return target.getLanguage(); + case "loader": + case "Loader": return target.getLoader(); + case "location": + case "Location": return target.getLocation(); + case "name": + case "Name": return target.getName(); + case "propertiesnames": + case "PropertiesNames": return target.getPropertiesNames(); + case "type": + case "Type": return target.getType(); + default: return null; + } + } +} + diff --git a/camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurationConfigurer.java b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurationConfigurer.java new file mode 100644 index 000000000..0116df607 --- /dev/null +++ b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurationConfigurer.java @@ -0,0 +1,45 @@ +/* Generated by camel build tools - do NOT edit this file! */ +package org.apache.camel.k.listener; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.GeneratedPropertyConfigurer; +import org.apache.camel.spi.PropertyConfigurerGetter; +import org.apache.camel.util.CaseInsensitiveMap; +import org.apache.camel.k.listener.SourcesConfiguration; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class SourcesConfigurationConfigurer extends org.apache.camel.support.component.PropertyConfigurerSupport implements GeneratedPropertyConfigurer, PropertyConfigurerGetter { + + @Override + public boolean configure(CamelContext camelContext, Object obj, String name, Object value, boolean ignoreCase) { + org.apache.camel.k.listener.SourcesConfiguration target = (org.apache.camel.k.listener.SourcesConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "sources": + case "Sources": target.setSources(property(camelContext, org.apache.camel.k.SourceDefinition[].class, value)); return true; + default: return false; + } + } + + @Override + public Map getAllOptions(Object target) { + Map answer = new CaseInsensitiveMap(); + answer.put("Sources", org.apache.camel.k.SourceDefinition[].class); + return answer; + } + + @Override + public Object getOptionValue(Object obj, String name, boolean ignoreCase) { + org.apache.camel.k.listener.SourcesConfiguration target = (org.apache.camel.k.listener.SourcesConfiguration) obj; + switch (ignoreCase ? name.toLowerCase() : name) { + case "sources": + case "Sources": return target.getSources(); + default: return null; + } + } +} + diff --git a/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourceDefinition b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourceDefinition new file mode 100644 index 000000000..ac318bd4f --- /dev/null +++ b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourceDefinition @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.k.SourceDefinitionConfigurer diff --git a/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfiguration b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfiguration new file mode 100644 index 000000000..9bceaa5bf --- /dev/null +++ b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfiguration @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.k.listener.SourcesConfigurationConfigurer diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/Source.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/Source.java index a14a17d6d..0195e8692 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/Source.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/Source.java @@ -29,8 +29,10 @@ public interface Source { String getName(); String getLanguage(); + SourceType getType(); Optional getLoader(); List getInterceptors(); + List getPropertiesNames(); InputStream resolveAsInputStream(CamelContext ctx); default Reader resolveAsReader(CamelContext ctx) { diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceDefinition.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceDefinition.java new file mode 100644 index 000000000..03bdfd4fc --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceDefinition.java @@ -0,0 +1,199 @@ +/* + * 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.k; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.camel.k.support.StringSupport; +import org.apache.camel.spi.Configurer; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.URISupport; + +@Configurer +public class SourceDefinition { + private String name; + private String language; + private String loader; + private List interceptors; + private SourceType type; + private List propertiesNames; + private String location; + private byte[] content; + private boolean compressed; + + public String getName() { + return name; + } + + /** + * The name of the source. + */ + public void setName(String name) { + this.name = name; + } + + public String getLanguage() { + return language; + } + + /** + * The language use to define the source. + */ + public void setLanguage(String language) { + this.language = language; + } + + public String getLoader() { + return loader; + } + + /** + * The {@link SourceLoader} that should be used to load the content of the source. + */ + public void setLoader(String loader) { + this.loader = loader; + } + + public List getInterceptors() { + return interceptors; + } + + /** + * The {@link org.apache.camel.k.SourceLoader.Interceptor} that should be applied. + */ + public void setInterceptors(List interceptors) { + this.interceptors = interceptors; + } + + public SourceType getType() { + return type; + } + + /** + * The {@link SourceType} of the source. + */ + public void setType(SourceType type) { + this.type = type; + } + + public List getPropertiesNames() { + return propertiesNames; + } + + /** + * The list of properties names the source requires (used only for templates). + */ + public void setPropertiesNames(List propertiesNames) { + this.propertiesNames = propertiesNames; + } + + public String getLocation() { + return location; + } + + /** + * The location of the source. + */ + public void setLocation(String location) { + this.location = location; + } + + public byte[] getContent() { + return content; + } + + /** + * The content of the source. + */ + public void setContent(byte[] content) { + this.content = content; + } + + public boolean isCompressed() { + return compressed; + } + + /** + * If the content of the source is compressed. + */ + public void setCompressed(boolean compressed) { + this.compressed = compressed; + } + + // *********************************** + // + // Helpers + // + // *********************************** + + public static SourceDefinition fromURI(String uri) throws Exception { + final String location = StringSupport.substringBefore(uri, "?"); + + if (!location.startsWith(Constants.SCHEME_PREFIX_CLASSPATH) && !location.startsWith(Constants.SCHEME_PREFIX_FILE)) { + throw new IllegalArgumentException("No valid resource format, expected scheme:path, found " + uri); + } + + final String query = StringSupport.substringAfter(uri, "?"); + final Map params = URISupport.parseQuery(query); + final String languageName = (String) params.get("language"); + final String compression = (String) params.get("compression"); + final String loader = (String) params.get("loader"); + final String interceptors = (String) params.get("interceptors"); + + String language = languageName; + if (ObjectHelper.isEmpty(language)) { + language = StringSupport.substringAfterLast(location, ":"); + language = StringSupport.substringAfterLast(language, "."); + } + if (ObjectHelper.isEmpty(language)) { + throw new IllegalArgumentException("Unknown language " + language); + } + + String name = (String) params.get("name"); + if (name == null) { + name = StringSupport.substringAfter(location, ":"); + name = StringSupport.substringBeforeLast(name, "."); + + if (name.contains("/")) { + name = StringSupport.substringAfterLast(name, "/"); + } + } + + SourceDefinition answer = new SourceDefinition(); + answer.location = location; + answer.name = name; + answer.language = language; + answer.loader = loader; + answer.interceptors = interceptors != null ? List.of(interceptors.split(",")) : Collections.emptyList(); + answer.compressed = Boolean.parseBoolean(compression); + + return answer; + } + + public static SourceDefinition fromBytes(String name, String language, String loader, List interceptors, byte[] content) { + SourceDefinition answer = new SourceDefinition(); + answer.name = name; + answer.language = language; + answer.loader = loader; + answer.interceptors = interceptors != null ? interceptors : Collections.emptyList(); + answer.content = content; + + return answer; + } +} diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceType.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceType.java new file mode 100644 index 000000000..ead909913 --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceType.java @@ -0,0 +1,22 @@ +/* + * 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.k; + +public enum SourceType { + SOURCE, + TEMPLATE +} diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/Sources.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/Sources.java index 976d65206..3fc62008d 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/Sources.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/Sources.java @@ -18,181 +18,114 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.zip.GZIPInputStream; import org.apache.camel.CamelContext; import org.apache.camel.k.support.StringSupport; -import org.apache.camel.spi.ClassResolver; import org.apache.camel.support.ResourceHelper; import org.apache.camel.util.ObjectHelper; -import org.apache.camel.util.URISupport; public final class Sources { private Sources() { } public static Source fromURI(String uri) throws Exception { - return new URI(uri); + return fromDefinition(SourceDefinition.fromURI(uri)); } public static Source fromBytes(String name, String language, String loader, List interceptors, byte[] content) { - return new InMemory(name, language, loader, interceptors, content); + return fromDefinition(SourceDefinition.fromBytes(name, language, loader, interceptors, content)); } public static Source fromBytes(String name, String language, String loader, byte[] content) { - return new InMemory(name, language, loader, content); + return fromDefinition(SourceDefinition.fromBytes(name, language, loader, null, content)); } public static Source fromBytes(String language, byte[] content) { - return new InMemory(UUID.randomUUID().toString(), language, null, content); + return fromDefinition(SourceDefinition.fromBytes(UUID.randomUUID().toString(), language, null, null, content)); } - private static final class InMemory implements Source { - private final String name; - private final String language; - private final String loader; - private final List interceptors; - private final byte[] content; - - public InMemory(String name, String language, String loader, byte[] content) { - this.name = name; - this.language = language; - this.loader = loader; - this.interceptors = Collections.emptyList(); - this.content = content; + public static Source fromDefinition(SourceDefinition definition) { + if (definition.getLocation() == null && definition.getContent() == null) { + throw new IllegalArgumentException("Either the source location or the source content should be set"); } - public InMemory(String name, String language, String loader, List interceptors, byte[] content) { - this.name = name; - this.language = language; - this.loader = loader; - this.interceptors = new ArrayList<>(interceptors); - this.content = content; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getLanguage() { - return language; - } - - @Override - public Optional getLoader() { - return Optional.ofNullable(loader); - } - - @Override - public List getInterceptors() { - return interceptors; - } + return new Source() { + @Override + public String getName() { + String answer = definition.getName(); + if (ObjectHelper.isEmpty(answer) && ObjectHelper.isNotEmpty(definition.getLocation())) { + answer = StringSupport.substringAfter(definition.getLocation(), ":"); + answer = StringSupport.substringBeforeLast(answer, "."); + + if (answer.contains("/")) { + answer = StringSupport.substringAfterLast(answer, "/"); + } + } - @Override - public InputStream resolveAsInputStream(CamelContext ctx) { - if (content == null) { - throw new IllegalArgumentException("No content defined"); + return answer; } - return new ByteArrayInputStream(this.content); - } - } - - private static final class URI implements Source { - private final String location; - private final String name; - private final String language; - private final String loader; - private final List interceptors; - private final boolean compressed; - - private URI(String uri) throws Exception { - final String location = StringSupport.substringBefore(uri, "?"); + @Override + public String getLanguage() { + String answer = definition.getLanguage(); + if (ObjectHelper.isEmpty(answer) && ObjectHelper.isNotEmpty(definition.getLocation())) { + answer = StringSupport.substringAfterLast(definition.getLocation(), ":"); + answer = StringSupport.substringAfterLast(answer, "."); + } - if (!location.startsWith(Constants.SCHEME_PREFIX_CLASSPATH) && !location.startsWith(Constants.SCHEME_PREFIX_FILE)) { - throw new IllegalArgumentException("No valid resource format, expected scheme:path, found " + uri); + return answer; } - final String query = StringSupport.substringAfter(uri, "?"); - final Map params = URISupport.parseQuery(query); - final String languageName = (String) params.get("language"); - final String compression = (String) params.get("compression"); - final String loader = (String) params.get("loader"); - final String interceptors = (String) params.get("interceptors"); - - String language = languageName; - if (ObjectHelper.isEmpty(language)) { - language = StringSupport.substringAfterLast(location, ":"); - language = StringSupport.substringAfterLast(language, "."); - } - if (ObjectHelper.isEmpty(language)) { - throw new IllegalArgumentException("Unknown language " + language); + @Override + public SourceType getType() { + return ObjectHelper.supplyIfEmpty(definition.getType(), () -> SourceType.SOURCE); } - String name = (String) params.get("name"); - if (name == null) { - name = StringSupport.substringAfter(location, ":"); - name = StringSupport.substringBeforeLast(name, "."); - - if (name.contains("/")) { - name = StringSupport.substringAfterLast(name, "/"); - } + @Override + public Optional getLoader() { + return Optional.ofNullable(definition.getLoader()); } - this.location = location; - this.name = name; - this.language = language; - this.loader = loader; - this.interceptors = interceptors != null ? Arrays.asList(interceptors.split(",", -1)) : Collections.emptyList(); - this.compressed = Boolean.parseBoolean(compression); - } - - @Override - public String getName() { - return name; - } - - @Override - public String getLanguage() { - return language; - } - - @Override - public Optional getLoader() { - return Optional.ofNullable(loader); - } - - @Override - public List getInterceptors() { - return interceptors; - } - - @Override - public InputStream resolveAsInputStream(CamelContext ctx) { - if (location == null) { - throw new IllegalArgumentException("Cannot resolve null URI"); + @Override + public List getInterceptors() { + return ObjectHelper.supplyIfEmpty(definition.getInterceptors(), Collections::emptyList); } - try { - final ClassResolver cr = ctx.getClassResolver(); - final InputStream is = ResourceHelper.resolveResourceAsInputStream(cr, location); + @Override + public List getPropertiesNames() { + return ObjectHelper.supplyIfEmpty(definition.getPropertiesNames(), Collections::emptyList); + } - return compressed - ? new GZIPInputStream(Base64.getDecoder().wrap(is)) - : is; - } catch (Exception e) { - throw new RuntimeException(e); + /** + * Read the content of the source as {@link InputStream}. + * + * @param ctx the {@link CamelContext} + * @return the {@link InputStream} representing the source content + */ + @Override + public InputStream resolveAsInputStream(CamelContext ctx) { + try { + InputStream is; + + if (definition.getContent() != null) { + is = new ByteArrayInputStream(definition.getContent()); + } else { + is = ResourceHelper.resolveMandatoryResourceAsInputStream(ctx, definition.getLocation()); + } + + return definition.isCompressed() + ? new GZIPInputStream(Base64.getDecoder().wrap(is)) + : is; + } catch (Exception e) { + throw new RuntimeException(e); + } } - } + }; } } diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfiguration.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfiguration.java new file mode 100644 index 000000000..d1f708e90 --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfiguration.java @@ -0,0 +1,33 @@ +/* + * 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.k.listener; + +import org.apache.camel.k.SourceDefinition; +import org.apache.camel.spi.Configurer; + +@Configurer +public class SourcesConfiguration { + private SourceDefinition[] sources; + + public SourceDefinition[] getSources() { + return sources; + } + + public void setSources(SourceDefinition[] sources) { + this.sources = sources; + } +} diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java similarity index 61% rename from camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java rename to camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java index c701621e8..9873bdd9e 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java @@ -23,57 +23,77 @@ import org.apache.camel.k.Runtime; import org.apache.camel.k.RuntimeAware; import org.apache.camel.k.Source; +import org.apache.camel.k.SourceDefinition; import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; +import org.apache.camel.k.support.PropertiesSupport; import org.apache.camel.k.support.RuntimeSupport; import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class RoutesConfigurer extends AbstractPhaseListener { - private static final Logger LOGGER = LoggerFactory.getLogger(RoutesConfigurer.class); +public class SourcesConfigurer extends AbstractPhaseListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SourcesConfigurer.class); - public RoutesConfigurer() { + public SourcesConfigurer() { super(Runtime.Phase.ConfigureRoutes); } @Override protected void accept(Runtime runtime) { + // + // load routes from env var for backward compatibility + // String routes = System.getProperty(Constants.PROPERTY_CAMEL_K_ROUTES); - if (ObjectHelper.isEmpty(routes)) { routes = System.getenv(Constants.ENV_CAMEL_K_ROUTES); } - if (ObjectHelper.isEmpty(routes)) { - LOGGER.warn("No routes found in {} environment variable", Constants.ENV_CAMEL_K_ROUTES); - return; + if (ObjectHelper.isNotEmpty(routes)) { + LOGGER.info("Loading routes from environment"); + forRoutes(routes.split(",")).accept(runtime); } - load(runtime, routes.split(",", -1)); - } + // + // load routes from properties + // + SourcesConfiguration configuration = new SourcesConfiguration(); + PropertiesSupport.bindProperties(runtime.getCamelContext(), configuration, "camel.k."); - protected void load(Runtime runtime, String[] routes) { - for (String route: routes) { - if (ObjectHelper.isEmpty(route)) { - continue; - } + if (ObjectHelper.isNotEmpty(configuration.getSources())) { + LOGGER.info("Loading routes from properties"); + forRoutes(configuration.getSources()).accept(runtime); + } + } - try { - load(runtime, Sources.fromURI(route)); - } catch (Exception e) { - throw RuntimeCamelException.wrapRuntimeCamelException(e); + public static SourcesConfigurer forRoutes(String... routes) { + return new SourcesConfigurer() { + @Override + protected void accept(Runtime runtime) { + for (String route: routes) { + if (ObjectHelper.isEmpty(route)) { + continue; + } + + LOGGER.info("Loading routes from: {}", route); + + try { + load(runtime, Sources.fromURI(route)); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeCamelException(e); + } + } } - - LOGGER.info("Loading routes from: {}", route); - } + }; } - public static RoutesConfigurer forRoutes(String... routes) { - return new RoutesConfigurer() { + public static SourcesConfigurer forRoutes(SourceDefinition... definitions) { + return new SourcesConfigurer() { @Override protected void accept(Runtime runtime) { - load(runtime, routes); + for (SourceDefinition definition: definitions) { + load(runtime, Sources.fromDefinition(definition)); + } } }; } diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PropertiesSupport.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PropertiesSupport.java index e1e7996e4..3e2584f18 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PropertiesSupport.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/PropertiesSupport.java @@ -26,6 +26,7 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; @@ -33,27 +34,70 @@ import java.util.Set; import org.apache.camel.CamelContext; +import org.apache.camel.Component; +import org.apache.camel.ExtendedCamelContext; import org.apache.camel.k.Constants; import org.apache.camel.spi.PropertiesComponent; +import org.apache.camel.spi.PropertyConfigurer; import org.apache.camel.support.PropertyBindingSupport; +import org.apache.camel.support.service.ServiceHelper; import org.apache.camel.util.ObjectHelper; public final class PropertiesSupport { private PropertiesSupport() { } + public static T bindProperties(CamelContext context, T target, String prefix) { + return bindProperties(context, target, prefix, false); + } + @SuppressWarnings("unchecked") - public static boolean bindProperties(CamelContext context, Object target, String prefix) { + public static T bindProperties(CamelContext context, T target, String prefix, boolean stripPrefix) { final PropertiesComponent component = context.getPropertiesComponent(); - final Properties properties = component.loadProperties(k -> k.startsWith(prefix)); + final Properties propertiesWithPrefix = component.loadProperties(k -> k.startsWith(prefix)); + final Map properties; + + if (stripPrefix) { + properties = new HashMap<>(); + + propertiesWithPrefix.stringPropertyNames().forEach( + name -> properties.put( + stripPrefix ? name.substring(prefix.length()) : name, + propertiesWithPrefix.getProperty(name)) + ); + } else { + properties = (Map)propertiesWithPrefix; + } + + PropertyConfigurer configurer = null; + if (target instanceof Component) { + // the component needs to be initialized to have the configurer ready + ServiceHelper.initService(target); + configurer = ((Component) target).getComponentPropertyConfigurer(); + } - return PropertyBindingSupport.build() + if (configurer == null) { + String name = target.getClass().getSimpleName(); + if (target instanceof ExtendedCamelContext) { + // special for camel context itself as we have an extended configurer + name = "ExtendedCamelContext"; + } + + // see if there is a configurer for it + configurer = context.adapt(ExtendedCamelContext.class) + .getConfigurerResolver().resolvePropertyConfigurer(name, context); + } + + PropertyBindingSupport.build() .withCamelContext(context) .withTarget(target) - .withProperties((Map)properties) + .withProperties(properties) .withRemoveParameters(false) - .withOptionPrefix(prefix) + .withOptionPrefix(stripPrefix ? null : prefix) + .withConfigurer(configurer) .bind(); + + return target; } public static String resolveApplicationPropertiesLocation() { diff --git a/camel-k-runtime-core/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener b/camel-k-runtime-core/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener index 9caf5beb2..f0c50b74f 100644 --- a/camel-k-runtime-core/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener +++ b/camel-k-runtime-core/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener @@ -16,5 +16,5 @@ # org.apache.camel.k.listener.ContextConfigurer -org.apache.camel.k.listener.RoutesConfigurer +org.apache.camel.k.listener.SourcesConfigurer org.apache.camel.k.listener.PropertiesConfigurer diff --git a/camel-k-runtime-core/src/test/java/org/apache/camel/k/SourceTest.java b/camel-k-runtime-core/src/test/java/org/apache/camel/k/SourceTest.java index 4191de29e..87279cffb 100644 --- a/camel-k-runtime-core/src/test/java/org/apache/camel/k/SourceTest.java +++ b/camel-k-runtime-core/src/test/java/org/apache/camel/k/SourceTest.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; public class SourceTest { @@ -42,4 +43,13 @@ public void testUnsupportedLanguage() { ); } + @Test + public void sourceCanBeContructedFromLocation() { + SourceDefinition definition = new SourceDefinition(); + definition.setLocation("classpath:MyRoutes.java"); + + assertThat(Sources.fromDefinition(definition)) + .hasFieldOrPropertyWithValue("name", "MyRoutes") + .hasFieldOrPropertyWithValue("language", "java"); + } } diff --git a/camel-k-runtime-core/src/test/java/org/apache/camel/k/support/PropertiesSupportTest.java b/camel-k-runtime-core/src/test/java/org/apache/camel/k/support/PropertiesSupportTest.java new file mode 100644 index 000000000..10af11a6c --- /dev/null +++ b/camel-k-runtime-core/src/test/java/org/apache/camel/k/support/PropertiesSupportTest.java @@ -0,0 +1,83 @@ +/* + * 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.k.support; + +import java.util.Objects; +import java.util.function.Predicate; + +import org.apache.camel.CamelContext; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.k.SourceDefinition; +import org.apache.camel.k.listener.SourcesConfiguration; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.k.test.CamelKTestSupport.asProperties; +import static org.assertj.core.api.Assertions.assertThat; + +public class PropertiesSupportTest { + @Test + public void propertiesAreBoundToSourcesConfiguration() { + CamelContext context = new DefaultCamelContext(); + context.getPropertiesComponent().setInitialProperties(asProperties( + "camel.k.sources[0].name", "MyRoutesWithBeans", + "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java", + "camel.k.sources[1].name", "MyRoutesConfig", + "camel.k.sources[1].location", "classpath:MyRoutesConfig.java" + )); + + SourcesConfiguration configuration = new SourcesConfiguration(); + + PropertiesSupport.bindProperties(context, configuration, "camel.k."); + + assertThat(configuration.getSources()) + .hasSize(2) + .anyMatch(byNameAndLocation("MyRoutesWithBeans", "classpath:MyRoutesWithBeans.java")) + .anyMatch(byNameAndLocation("MyRoutesConfig", "classpath:MyRoutesConfig.java")); + } + + @Test + public void propertiesWithGapsAreBoundToSourcesConfiguration() { + CamelContext context = new DefaultCamelContext(); + context.getPropertiesComponent().setInitialProperties(asProperties( + "camel.k.sources[0].name", "MyRoutesWithBeans", + "camel.k.sources[0].location", "classpath:MyRoutesWithBeans.java", + "camel.k.sources[2].name", "MyRoutesConfig", + "camel.k.sources[2].location", "classpath:MyRoutesConfig.java" + )); + + SourcesConfiguration configuration = new SourcesConfiguration(); + + PropertiesSupport.bindProperties(context, configuration, "camel.k."); + + assertThat(configuration.getSources()) + .hasSize(3) + .filteredOn(Objects::nonNull) + .hasSize(2) + .anyMatch(byNameAndLocation("MyRoutesWithBeans", "classpath:MyRoutesWithBeans.java")) + .anyMatch(byNameAndLocation("MyRoutesConfig", "classpath:MyRoutesConfig.java")); + } + + // *************************** + // + // Helpers + // + // *************************** + + private static Predicate byNameAndLocation(String name, String location) { + return def -> Objects.equals(def.getName(), name) && Objects.equals(def.getLocation(), location); + } +} diff --git a/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java b/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java index d90427a13..163cd347c 100644 --- a/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java +++ b/camel-k-runtime-cron/src/test/java/org/apache/camel/k/cron/CronTest.java @@ -22,7 +22,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.main.ApplicationRuntime; import org.apache.camel.support.LifecycleStrategySupport; import org.junit.jupiter.params.ParameterizedTest; @@ -39,7 +39,7 @@ public void testCronTimerActivation(String routes, String cronOverride) throws E runtime.setProperties( "loader.interceptor.cron.overridable-components", cronOverride ); - runtime.addListener(RoutesConfigurer.forRoutes(routes)); + runtime.addListener(SourcesConfigurer.forRoutes(routes)); // To check auto-termination of Camel context CountDownLatch termination = new CountDownLatch(1); diff --git a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/customizer/KnativeSinkBindingCustomizerTest.java b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/customizer/KnativeSinkBindingCustomizerTest.java index 7dab46d51..1bb8ccb5a 100644 --- a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/customizer/KnativeSinkBindingCustomizerTest.java +++ b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/customizer/KnativeSinkBindingCustomizerTest.java @@ -41,7 +41,7 @@ import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; import org.apache.camel.k.http.PlatformHttpServiceContextCustomizer; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.support.RuntimeSupport; import org.apache.camel.k.test.AvailablePortFinder; import org.junit.jupiter.api.Test; @@ -106,7 +106,7 @@ public void testWrapLoaderWithSyntheticServiceDefinition() throws Exception { RuntimeSupport.configureContextCustomizers(runtime); Source source = Sources.fromBytes("groovy", "from('direct:start').setBody().header('MyHeader').to('knative://endpoint/mySynk')".getBytes(StandardCharsets.UTF_8)); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader.getSupportedLanguages()).contains(source.getLanguage()); assertThat(runtime.builders).hasSize(1); diff --git a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/yaml/parser/KnativeConverterTest.java b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/yaml/parser/KnativeConverterTest.java index fe818cd85..cb2fac1d6 100644 --- a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/yaml/parser/KnativeConverterTest.java +++ b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/knative/yaml/parser/KnativeConverterTest.java @@ -27,7 +27,7 @@ import org.apache.camel.k.Source; import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.loader.yaml.YamlSourceLoader; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.ProcessorDefinition; @@ -43,7 +43,7 @@ public class KnativeConverterTest { public void testLoadRoutes() throws Exception { TestRuntime runtime = new TestRuntime(); Source source = Sources.fromURI("classpath:route.yaml"); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader).isInstanceOf(YamlSourceLoader.class); assertThat(runtime.builders).hasSize(1); diff --git a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/loader/knative/KnativeSourceRoutesLoaderTest.java b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/loader/knative/KnativeSourceRoutesLoaderTest.java index 513005eba..bb30e582e 100644 --- a/camel-k-runtime-knative/src/test/java/org/apache/camel/k/loader/knative/KnativeSourceRoutesLoaderTest.java +++ b/camel-k-runtime-knative/src/test/java/org/apache/camel/k/loader/knative/KnativeSourceRoutesLoaderTest.java @@ -18,10 +18,7 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Properties; import java.util.UUID; import java.util.stream.Stream; @@ -30,8 +27,6 @@ import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.knative.KnativeComponent; import org.apache.camel.component.knative.KnativeConstants; -import org.apache.camel.component.knative.spi.CloudEvent; -import org.apache.camel.component.knative.spi.CloudEvents; import org.apache.camel.component.knative.spi.Knative; import org.apache.camel.component.knative.spi.KnativeEnvironment; import org.apache.camel.component.mock.MockEndpoint; @@ -41,7 +36,7 @@ import org.apache.camel.k.SourceLoader; import org.apache.camel.k.Sources; import org.apache.camel.k.http.PlatformHttpServiceContextCustomizer; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.test.AvailablePortFinder; import org.apache.camel.model.ModelCamelContext; import org.apache.camel.model.RouteDefinition; @@ -84,7 +79,7 @@ public void testWrapLoader(String uri) throws Exception { context.addComponent(KnativeConstants.SCHEME, component); Source source = Sources.fromURI(uri); - SourceLoader loader = RoutesConfigurer.load(runtime, source); + SourceLoader loader = SourcesConfigurer.load(runtime, source); assertThat(loader.getSupportedLanguages()).contains(source.getLanguage()); assertThat(runtime.builders).hasSize(1); diff --git a/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/WebhookTest.java b/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/WebhookTest.java index 768e4f64e..ae16ec322 100644 --- a/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/WebhookTest.java +++ b/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/WebhookTest.java @@ -30,7 +30,7 @@ import org.apache.camel.NamedNode; import org.apache.camel.Route; import org.apache.camel.k.listener.ContextConfigurer; -import org.apache.camel.k.listener.RoutesConfigurer; +import org.apache.camel.k.listener.SourcesConfigurer; import org.apache.camel.k.main.ApplicationRuntime; import org.apache.camel.spi.RoutePolicy; import org.apache.camel.spi.RoutePolicyFactory; @@ -71,7 +71,7 @@ public void testWebhookRegistration(WebhookAction action) throws Exception { AtomicBoolean routeStarted = new AtomicBoolean(); runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:webhook.js")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:webhook.js")); runtime.getCamelContext().addRoutePolicyFactory(new RoutePolicyFactory() { @Override public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, NamedNode route) { @@ -132,7 +132,7 @@ public void testRegistrationFailure(WebhookAction action) throws Exception { ); runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:webhook.js")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:webhook.js")); Assertions.assertThrows(FailedToCreateRouteException.class, runtime::run); } @@ -146,7 +146,7 @@ public void testAutoRegistrationNotDisabled() throws Exception { runtime.getCamelContext().addComponent("dummy", new DummyWebhookComponent()); runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:webhook.js")); + runtime.addListener(SourcesConfigurer.forRoutes("classpath:webhook.js")); Assertions.assertThrows(FailedToCreateRouteException.class, runtime::run); } diff --git a/camel-kamelet/pom.xml b/camel-kamelet/pom.xml index 33e09c34f..306c224a1 100644 --- a/camel-kamelet/pom.xml +++ b/camel-kamelet/pom.xml @@ -76,6 +76,21 @@ camel-log test + + org.apache.camel + camel-mock + test + + + org.apache.camel + camel-mock + test + + + org.apache.camel + camel-test-junit5 + test + diff --git a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java index 043f44eb2..3712c7c20 100644 --- a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java +++ b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java @@ -16,10 +16,26 @@ */ package org.apache.camel.component.kamelet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.function.Predicate; +import org.apache.camel.CamelContext; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.spi.PropertiesComponent; +import org.apache.camel.util.StringHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public final class Kamelet { + private static final Logger LOGGER = LoggerFactory.getLogger(Kamelet.class); + public static final String SCHEME = "kamelet"; + public static final String PROPERTIES_PREFIX = "camel.kamelet."; private Kamelet() { } @@ -27,4 +43,60 @@ private Kamelet() { public static Predicate startsWith(String prefix) { return item -> item.startsWith(prefix); } + + public static void createRouteForEndpoint(KameletEndpoint endpoint) { + try { + LOGGER.debug("Creating route from template {}", endpoint.getTemplateId()); + + ModelCamelContext context = endpoint.getCamelContext().adapt(ModelCamelContext.class); + String id = context.addRouteFromTemplate(endpoint.getRouteId(), endpoint.getTemplateId(), endpoint.getKameletProperties()); + RouteDefinition def = context.getRouteDefinition(id); + if (!def.isPrepared()) { + context.startRouteDefinitions(List.of(def)); + } + + LOGGER.debug("Route {} created from template {}", id, endpoint.getTemplateId()); + } catch (Exception e) { + throw new RuntimeCamelException(e); + } + } + + public static String extractTemplateId(CamelContext context, String remaining) { + String answer = StringHelper.before(remaining, "/"); + if (answer == null) { + answer = remaining; + } + + return answer; + } + + public static String extractRouteId(CamelContext context, String remaining) { + String answer = StringHelper.after(remaining, "/"); + if (answer == null) { + answer = extractTemplateId(context, remaining) + "-" + context.getUuidGenerator().generateUuid(); + } + + return answer; + } + + public static Map extractKameletProperties(CamelContext context, String... elements) { + PropertiesComponent pc = context.getPropertiesComponent(); + Map properties = new HashMap<>(); + String prefix = Kamelet.PROPERTIES_PREFIX; + + for (String element: elements) { + if (element == null) { + continue; + } + + prefix = prefix + element + "."; + + Properties prefixed = pc.loadProperties(Kamelet.startsWith(prefix)); + for (String name : prefixed.stringPropertyNames()) { + properties.put(name.substring(prefix.length()), prefixed.getProperty(name)); + } + } + + return properties; + } } diff --git a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java index 83898a2ef..0f8233f08 100644 --- a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java +++ b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java @@ -17,7 +17,6 @@ package org.apache.camel.component.kamelet; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -25,16 +24,17 @@ import org.apache.camel.Endpoint; import org.apache.camel.RuntimeCamelException; import org.apache.camel.model.ModelCamelContext; -import org.apache.camel.model.RouteDefinition; import org.apache.camel.spi.CamelEvent; +import org.apache.camel.spi.ManagementStrategy; import org.apache.camel.spi.annotations.Component; import org.apache.camel.support.DefaultComponent; import org.apache.camel.support.EventNotifierSupport; import org.apache.camel.support.service.ServiceHelper; -import org.apache.camel.util.StringHelper; @Component(Kamelet.SCHEME) public class KameletComponent extends DefaultComponent { + private volatile EventHandler notifier; + public KameletComponent() { this(null); } @@ -43,15 +43,10 @@ public KameletComponent(CamelContext context) { super(context); } - // use as temporary to keep track of created kamelet endpoints during startup as we need to defer - // create routes from templates until camel context has finished loading all routes and whatnot - private final List endpoints = new ArrayList<>(); - private volatile RouteTemplateEventNotifier notifier; - @Override protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { - final String templateId = extractTemplateId(remaining); - final String routeId = extractRouteId(remaining); + final String templateId = Kamelet.extractTemplateId(getCamelContext(), remaining); + final String routeId = Kamelet.extractRouteId(getCamelContext(), remaining); // // The properties for the kamelets are determined by global properties @@ -62,10 +57,10 @@ protected Endpoint createEndpoint(String uri, String remaining, Map kameletProperties = extractKameletProperties(templateId, routeId); + Map kameletProperties = Kamelet.extractKameletProperties(getCamelContext(), templateId, routeId); kameletProperties.putAll(parameters); - kameletProperties.putIfAbsent("templateId", templateId); - kameletProperties.putIfAbsent("routeId", routeId); + kameletProperties.put("templateId", templateId); + kameletProperties.put("routeId", routeId); // Remaining parameter should be related to the route and to avoid the // parameters validation to fail, we need to clear the parameters map. @@ -79,54 +74,15 @@ protected Endpoint createEndpoint(String uri, String remaining, Map extractKameletProperties(String... elements) { - Map properties = new HashMap<>(); - String prefix = "camel.kamelet."; - - for (String element: elements) { - if (element == null) { - continue; - } - - prefix = prefix + element + "."; - - properties.putAll( - (Map)getCamelContext().getPropertiesComponent().loadProperties(Kamelet.startsWith(prefix)) - ); - - } - - return properties; - } - @Override protected void doInit() throws Exception { super.doInit(); if (!getCamelContext().isRunAllowed()) { - // setup event listener which must be started to get triggered during initialization of camel context - notifier = new RouteTemplateEventNotifier(this); + notifier = new EventHandler(); + ServiceHelper.startService(notifier); - getCamelContext().getManagementStrategy().addEventNotifier(notifier); + getManagementStrategy().addEventNotifier(notifier); } } @@ -134,54 +90,59 @@ protected void doInit() throws Exception { protected void doStop() throws Exception { if (notifier != null) { ServiceHelper.stopService(notifier); - getCamelContext().getManagementStrategy().removeEventNotifier(notifier); + + getManagementStrategy().removeEventNotifier(notifier); notifier = null; } + super.doStop(); } void onEndpointAdd(KameletEndpoint endpoint) { if (notifier == null) { try { - addRouteFromTemplate(endpoint); + Kamelet.createRouteForEndpoint(endpoint); } catch (Exception e) { throw RuntimeCamelException.wrapRuntimeException(e); } } else { // remember endpoints as we defer adding routes for them till later - this.endpoints.add(endpoint); + notifier.track(endpoint); } } - void addRouteFromTemplate(KameletEndpoint endpoint) throws Exception { - ModelCamelContext context = endpoint.getCamelContext().adapt(ModelCamelContext.class); - String id = context.addRouteFromTemplate(endpoint.getRouteId(), endpoint.getTemplateId(), endpoint.getKameletProperties()); - RouteDefinition def = context.getRouteDefinition(id); - if (!def.isPrepared()) { - List list = new ArrayList<>(1); - list.add(def); - context.startRouteDefinitions(list); - } + private ManagementStrategy getManagementStrategy() { + return getCamelContext().adapt(ModelCamelContext.class).getManagementStrategy(); } - private static class RouteTemplateEventNotifier extends EventNotifierSupport { - - private final KameletComponent component; - - public RouteTemplateEventNotifier(KameletComponent component) { - this.component = component; + /* + * This EventNotifier is used to keep track of created kamelet endpoints during startup as + * we need to defer create routes from templates until camel context has finished loading + * all routes and whatnot. + * + * Once the camel context is initialized all the endpoint tracked by this EventNotifier will + * be used to create route s from templates. + */ + private class EventHandler extends EventNotifierSupport { + private final List endpoints; + + public EventHandler() { + this.endpoints = new ArrayList<>(); } @Override public void notify(CamelEvent event) throws Exception { - for (KameletEndpoint endpoint : component.endpoints) { - component.addRouteFromTemplate(endpoint); + for (KameletEndpoint endpoint : endpoints) { + Kamelet.createRouteForEndpoint(endpoint); } - component.endpoints.clear(); + + endpoints.clear(); + // we were only needed during initializing/starting up camel, so remove after use ServiceHelper.stopService(this); - component.getCamelContext().getManagementStrategy().removeEventNotifier(this); - component.notifier = null; + + getManagementStrategy().removeEventNotifier(this); + notifier = null; } @Override @@ -192,5 +153,8 @@ public boolean isEnabled(CamelEvent event) { return event instanceof CamelEvent.CamelContextInitializedEvent; } + public void track(KameletEndpoint endpoint) { + this.endpoints.add(endpoint); + } } } diff --git a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java index 760964732..cae7cd4e3 100644 --- a/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java +++ b/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletEndpoint.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.kamelet; +import java.util.Collections; import java.util.Map; import org.apache.camel.AsyncCallback; @@ -32,6 +33,7 @@ import org.apache.camel.support.DefaultConsumer; import org.apache.camel.support.DefaultEndpoint; import org.apache.camel.support.service.ServiceHelper; +import org.apache.camel.util.ObjectHelper; @UriEndpoint( firstVersion = "3.5.0", @@ -60,9 +62,13 @@ public KameletEndpoint( super(uri, component); + ObjectHelper.notNull(templateId, "template id"); + ObjectHelper.notNull(routeId, "route id"); + ObjectHelper.notNull(kameletProperties, "kamelet properties"); + this.templateId = templateId; this.routeId = routeId; - this.kameletProperties = kameletProperties; + this.kameletProperties = Collections.unmodifiableMap(kameletProperties); this.kameletUri = "direct:" + routeId; } @@ -98,7 +104,7 @@ public Consumer createConsumer(Processor processor) throws Exception { @Override protected void doInit() throws Exception { super.doInit(); - // only need to add during init phase + getComponent().onEndpointAdd(this); } diff --git a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletAddAfterCamelStartedTest.java b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletAddAfterCamelStartedTest.java deleted file mode 100644 index fdc9dc676..000000000 --- a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletAddAfterCamelStartedTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.component.kamelet; - -import java.util.UUID; - -import org.apache.camel.CamelContext; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.impl.DefaultCamelContext; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -import static org.assertj.core.api.Assertions.assertThat; - -public class KameletAddAfterCamelStartedTest { - private static final Logger LOGGER = LoggerFactory.getLogger(KameletAddAfterCamelStartedTest.class); - - @Test - public void test() throws Exception { - String body = UUID.randomUUID().toString(); - - CamelContext context = new DefaultCamelContext(); - context.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { - routeTemplate("setBody") - .templateParameter("bodyValue") - .from("direct:{{routeId}}") - .setBody().constant("{{bodyValue}}"); - } - }); - - /* - context.addRouteFromTemplate("setBody") - .routeId("test") - .parameter("routeId", "test") - .parameter("bodyValue", body) - .build(); - */ - - // start camel here and add routes with kamelts later - context.start(); - - context.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { - // routes - from("direct:template") - .toF("kamelet:setBody/test?bodyValue=%s", body) - .to("log:1"); - } - }); - - assertThat( - context.createFluentProducerTemplate().to("direct:template").withBody("test").request(String.class) - ).isEqualTo(body); - - context.stop(); - } -} diff --git a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletBasicTest.java b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletBasicTest.java new file mode 100644 index 000000000..a7363ba82 --- /dev/null +++ b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletBasicTest.java @@ -0,0 +1,84 @@ +/* + * 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.component.kamelet; + +import java.util.UUID; + +import org.apache.camel.Exchange; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class KameletBasicTest extends CamelTestSupport { + @Test + public void canProduceToKamelets() { + String body = UUID.randomUUID().toString(); + + assertThat( + fluentTemplate.toF("kamelet:setBody/test?bodyValue=%s", body).request(String.class) + ).isEqualTo(body); + } + + @Test + public void canConsumeFromKamelets() { + assertThat( + consumer.receiveBody("kamelet:tick", Integer.class) + ).isEqualTo(1); + } + + @Test + public void kameletsCanBeCreatedAfterContextIsStarted() throws Exception { + String body = UUID.randomUUID().toString(); + + RouteBuilder.addRoutes(context, b -> { + b.from("direct:template") + .toF("kamelet:setBody/test?bodyValue=%s", body); + }); + + assertThat( + fluentTemplate.to("direct:template").request(String.class) + ).isEqualTo(body); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + routeTemplate("setBody") + .templateParameter("bodyValue") + .from("direct:{{routeId}}") + .setBody().constant("{{bodyValue}}"); + + routeTemplate("tick") + .from("timer:{{routeId}}?repeatCount=1&delay=-1") + .setBody().exchangeProperty(Exchange.TIMER_COUNTER) + .to("direct:{{routeId}}"); + } + }; + } +} diff --git a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java new file mode 100644 index 000000000..d33a15bb1 --- /dev/null +++ b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletPropertiesTest.java @@ -0,0 +1,86 @@ +/* + * 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.component.kamelet; + +import java.util.Properties; + +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.apache.http.annotation.Obsolete; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.k.test.CamelKTestSupport.asProperties; +import static org.assertj.core.api.Assertions.assertThat; + +public class KameletPropertiesTest extends CamelTestSupport { + @Test + public void propertiesAreTakenFromRouteId() throws Exception { + assertThat( + fluentTemplate + .to("kamelet:setBody/test") + .request(String.class) + ).isEqualTo("from-route"); + } + + @Test + public void propertiesAreTakenFromTemplateId() throws Exception { + assertThat( + fluentTemplate + .to("kamelet:setBody") + .request(String.class) + ).isEqualTo("from-template"); + } + + @Test + public void propertiesAreTakenFromURI() { + assertThat( + fluentTemplate + .to("kamelet:setBody?bodyValue={{bodyValue}}") + .request(String.class) + ).isEqualTo("from-uri"); + } + + // ********************************************** + // + // test set-up + // + // ********************************************** + + @Override + protected Properties useOverridePropertiesWithPropertiesComponent() { + return asProperties( + "bodyValue", "from-uri", + Kamelet.PROPERTIES_PREFIX + "setBody.bodyValue", "from-template", + Kamelet.PROPERTIES_PREFIX + "setBody.test.bodyValue", "from-route" + ); + } + + @Obsolete + protected RoutesBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + // template + routeTemplate("setBody") + .templateParameter("bodyValue") + .from("direct:{{routeId}}") + .setBody().constant("{{bodyValue}}"); + } + }; + } +} diff --git a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletTest.java b/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletTest.java deleted file mode 100644 index 32634a0d8..000000000 --- a/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletTest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.component.kamelet; - -import java.util.UUID; - -import org.apache.camel.CamelContext; -import org.apache.camel.builder.RouteBuilder; -import org.apache.camel.impl.DefaultCamelContext; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -public class KameletTest { - private static final Logger LOGGER = LoggerFactory.getLogger(KameletTest.class); - - @Test - public void test() throws Exception { - String body = UUID.randomUUID().toString(); - - CamelContext context = new DefaultCamelContext(); - context.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { - routeTemplate("setBody") - .templateParameter("bodyValue") - .from("direct:{{routeId}}") - .setBody().constant("{{bodyValue}}"); - } - }); - - /* - context.addRouteFromTemplate("setBody") - .routeId("test") - .parameter("routeId", "test") - .parameter("bodyValue", body) - .build(); - */ - - context.addRoutes(new RouteBuilder() { - @Override - public void configure() throws Exception { - // routes - from("direct:template") - .toF("kamelet:setBody/test?bodyValue=%s", body) - .to("log:1"); - } - }); - - context.start(); - - assertThat( - context.createFluentProducerTemplate().to("direct:template").withBody("test").request(String.class) - ).isEqualTo(body); - - context.stop(); - } -} diff --git a/camel-kamelet/src/test/resources/log4j2-test.xml b/camel-kamelet/src/test/resources/log4j2-test.xml index 486a0f041..9d74ce158 100644 --- a/camel-kamelet/src/test/resources/log4j2-test.xml +++ b/camel-kamelet/src/test/resources/log4j2-test.xml @@ -26,6 +26,8 @@ + +