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..7479a586c 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.support.SourcesSupport 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 = SourcesSupport.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 = SourcesSupport.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..433a8622e 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.support.SourcesSupport.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..a97070bf9 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.support.SourcesSupport; 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 = SourcesSupport.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 = SourcesSupport.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 = SourcesSupport.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 = SourcesSupport.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 = SourcesSupport.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 = SourcesSupport.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..bd92fe93d 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.support.SourcesSupport; 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 = SourcesSupport.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..c3199dbd0 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.support.SourcesSupport; 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); + SourcesSupport.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..38be00067 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.support.SourcesSupport; 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); + SourcesSupport.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..0bdbd63d6 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.support.SourcesSupport 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 = SourcesSupport.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 = SourcesSupport.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..cc19fde75 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.support.SourcesSupport.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/pom.xml b/camel-k-main/camel-k-runtime-main/pom.xml index ad8438fd2..79b93296a 100644 --- a/camel-k-main/camel-k-runtime-main/pom.xml +++ b/camel-k-main/camel-k-runtime-main/pom.xml @@ -148,6 +148,11 @@ camel-k-runtime-knative test + + org.apache.camel.k + camel-kamelet + test + 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..e7301576f 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 @@ -17,6 +17,7 @@ package org.apache.camel.k.main; import java.util.List; +import java.util.Map; import org.apache.camel.CamelContext; import org.apache.camel.Route; @@ -24,9 +25,12 @@ import org.apache.camel.component.knative.spi.Knative; import org.apache.camel.component.knative.spi.KnativeEnvironment; import org.apache.camel.k.Runtime; +import org.apache.camel.k.SourceType; 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.main.support.MyBean; +import org.apache.camel.k.support.SourcesSupport; import org.apache.camel.k.test.AvailablePortFinder; import org.apache.camel.model.ModelCamelContext; import org.apache.camel.model.ToDefinition; @@ -55,7 +59,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(SourcesSupport.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 +77,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(SourcesSupport.forRoutes("classpath:routes.xml", "classpath:rests.xml")); runtime.addListener(Runtime.Phase.Started, r -> { CamelContext context = r.getCamelContext(); @@ -93,16 +97,55 @@ void testLoadRouteWithExpression() throws Exception { )); runtime.addListener(new ContextConfigurer()); - runtime.addListener(RoutesConfigurer.forRoutes("classpath:routes-with-expression.xml")); - runtime.addListener(Runtime.Phase.Started, r -> runtime.stop()); + runtime.addListener(SourcesSupport.forRoutes("classpath:routes-with-expression.xml")); + runtime.addListener(Runtime.Phase.Started, 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(SourcesSupport.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(); @@ -111,6 +154,70 @@ public void testLoadJavaSource() throws Exception { }); } + @Test + public void testLoadTemplates() throws Exception { + runtime.setInitialProperties( + "camel.k.sources[0].id", "my-template", + "camel.k.sources[0].location", "classpath:MyRouteTemplate.java", + "camel.k.sources[0].type", SourceType.template.name(), + "camel.k.sources[0].property-names[0]", "message" + ); + runtime.addListener(new SourcesConfigurer()); + runtime.addListener(Runtime.Phase.Started, r -> { + CamelContext context = runtime.getCamelContext(); + + assertThat(context.adapt(ModelCamelContext.class).getRouteTemplateDefinitions()).hasSize(1); + assertThat(context.adapt(ModelCamelContext.class).getRouteDefinitions()).isEmpty(); + + context.addRouteFromTemplate("myRoute", "my-template", Map.of("message", "test")); + + assertThat(context.getRoutes()) + .hasSize(1) + .first().hasFieldOrPropertyWithValue("id", "myRoute"); + + r.stop(); + }); + runtime.run(); + } + + @Test + public void testLoadJavaSourceWithKamelet() throws Exception { + runtime.setInitialProperties( + // template + "camel.k.sources[0].id", "my-template", + "camel.k.sources[0].location", "classpath:MyRouteTemplate.java", + "camel.k.sources[0].type", SourceType.template.name(), + "camel.k.sources[0].property-names[0]", "message", + // route + "camel.k.sources[1].location", "classpath:MyRoutesWithKamelets.java", + "camel.k.sources[1].type", SourceType.source.name(), + // props + "camel.kamelet.my-template.message", "default-message" + ); + runtime.addListener(new SourcesConfigurer()); + runtime.addListener(Runtime.Phase.Started, r -> { + CamelContext context = runtime.getCamelContext(); + + // templates + assertThat(context.adapt(ModelCamelContext.class).getRouteTemplateDefinitions()).hasSize(1); + + // 2 routes defined in MyRoutesWithKamelets + // 2 routes materialized from templates by camel-kamelet + assertThat(context.adapt(ModelCamelContext.class).getRouteDefinitions()).hasSize(4); + + assertThat(context.getRoutes()) + .hasSize(4) + .extracting(Route::getId) + .containsExactlyInAnyOrder( + "k1", "k2", // routes from MyRoutesWithKamelets + "myKamelet1", "myKamelet2" // routes from templates + ); + + r.stop(); + }); + runtime.run(); + } + @Test public void testLoadJavaSourceWrap() throws Exception { KnativeComponent component = new KnativeComponent(); @@ -123,8 +230,8 @@ 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(Runtime.Phase.Started, r -> runtime.stop()); + runtime.addListener(SourcesSupport.forRoutes("classpath:MyRoutesWithBeans.java?interceptors=knative-source")); + runtime.addListener(Runtime.Phase.Started, Runtime::stop); runtime.run(); assertThat(runtime.getRegistry().lookupByName("my-bean")).isInstanceOfSatisfying(MyBean.class, b -> { diff --git a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/MyBean.java b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/MyBean.java similarity index 96% rename from camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/MyBean.java rename to camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/MyBean.java index 2069e084c..650709d30 100644 --- a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/MyBean.java +++ b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/MyBean.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.k.main; +package org.apache.camel.k.main.support; public class MyBean { private final String name; diff --git a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/TestCustomizer.java b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/TestCustomizer.java similarity index 97% rename from camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/TestCustomizer.java rename to camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/TestCustomizer.java index 46761011a..c5e17700c 100644 --- a/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/TestCustomizer.java +++ b/camel-k-main/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/support/TestCustomizer.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.camel.k.main; +package org.apache.camel.k.main.support; import org.apache.camel.CamelContext; import org.apache.camel.k.ContextCustomizer; diff --git a/camel-k-main/camel-k-runtime-main/src/test/resources/META-INF/services/org/apache/camel/k/customizer/test b/camel-k-main/camel-k-runtime-main/src/test/resources/META-INF/services/org/apache/camel/k/customizer/test index a393d0e62..26544f0df 100644 --- a/camel-k-main/camel-k-runtime-main/src/test/resources/META-INF/services/org/apache/camel/k/customizer/test +++ b/camel-k-main/camel-k-runtime-main/src/test/resources/META-INF/services/org/apache/camel/k/customizer/test @@ -15,4 +15,4 @@ # limitations under the License. # -class=org.apache.camel.k.main.TestCustomizer \ No newline at end of file +class=org.apache.camel.k.main.support.TestCustomizer \ No newline at end of file diff --git a/camel-k-main/camel-k-runtime-main/src/test/resources/MyRouteTemplate.java b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRouteTemplate.java new file mode 100644 index 000000000..b70f3090f --- /dev/null +++ b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRouteTemplate.java @@ -0,0 +1,29 @@ +/* + * 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. + */ + +import org.apache.camel.BindToRegistry; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.main.support.MyBean; + +public class MyRouteTemplate extends RouteBuilder { + @Override + public void configure() throws Exception { + from("direct:{{routeId}}") + .setBody().constant("{{message}}") + .to("log:{{routeId}}"); + } +} \ No newline at end of file diff --git a/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithBeans.java b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithBeans.java index 0659a75a2..003e97ca6 100644 --- a/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithBeans.java +++ b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithBeans.java @@ -17,6 +17,7 @@ import org.apache.camel.BindToRegistry; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.main.support.MyBean; public class MyRoutesWithBeans extends RouteBuilder { @Override @@ -28,7 +29,7 @@ public void configure() throws Exception { } @BindToRegistry("my-bean") - public org.apache.camel.k.main.MyBean createMyBean() { - return new org.apache.camel.k.main.MyBean("my-bean-name"); + public MyBean createMyBean() { + return new MyBean("my-bean-name"); } } \ No newline at end of file diff --git a/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithKamelets.java b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithKamelets.java new file mode 100644 index 000000000..7e1f4b1e1 --- /dev/null +++ b/camel-k-main/camel-k-runtime-main/src/test/resources/MyRoutesWithKamelets.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +import org.apache.camel.BindToRegistry; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.k.main.support.MyBean; + +public class MyRoutesWithKamelets extends RouteBuilder { + @Override + public void configure() throws Exception { + from("direct:k1") + .routeId("k1") + .to("kamelet:my-template/myKamelet1?message=my-message"); + from("direct:k2") + .routeId("k2") + .to("kamelet:my-template/myKamelet2"); + } +} \ No newline at end of file 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..7d41d8a37 --- /dev/null +++ b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/SourceDefinitionConfigurer.java @@ -0,0 +1,90 @@ +/* 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 "id": + case "Id": target.setId(property(camelContext, java.lang.String.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 "propertynames": + case "PropertyNames": target.setPropertyNames(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("Id", java.lang.String.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("PropertyNames", 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 "id": + case "Id": return target.getId(); + 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 "propertynames": + case "PropertyNames": return target.getPropertyNames(); + 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/SourcesConfigurerConfigurer.java b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurerConfigurer.java new file mode 100644 index 000000000..a9faffa78 --- /dev/null +++ b/camel-k-runtime-core/src/generated/java/org/apache/camel/k/listener/SourcesConfigurerConfigurer.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.SourcesConfigurer; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +@SuppressWarnings("unchecked") +public class SourcesConfigurerConfigurer 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.SourcesConfigurer target = (org.apache.camel.k.listener.SourcesConfigurer) 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.SourcesConfigurer target = (org.apache.camel.k.listener.SourcesConfigurer) 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/SourcesConfigurer b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfigurer new file mode 100644 index 000000000..618e94371 --- /dev/null +++ b/camel-k-runtime-core/src/generated/resources/META-INF/services/org/apache/camel/configurer/SourcesConfigurer @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.k.listener.SourcesConfigurerConfigurer 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..beae5129f 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 @@ -25,12 +25,15 @@ import java.util.Optional; import org.apache.camel.CamelContext; +import org.apache.camel.spi.HasId; -public interface Source { +public interface Source extends HasId { String getName(); String getLanguage(); + SourceType getType(); Optional getLoader(); List getInterceptors(); + List getPropertyNames(); 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..330ab1397 --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceDefinition.java @@ -0,0 +1,232 @@ +/* + * 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.spi.IdAware; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.URISupport; + +@Configurer +public class SourceDefinition implements IdAware { + private String id; + private String name; + private String language; + private String loader; + private List interceptors; + private SourceType type; + private List propertyNames; + private String location; + private byte[] content; + private boolean compressed; + + @Override + public String getId() { + return id; + } + + /** + * Sets the id + */ + @Override + public void setId(String id) { + this.id = id; + } + + 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 getPropertyNames() { + return propertyNames; + } + + /** + * The list of properties names the source requires (used only for templates). + */ + public void setPropertyNames(List propertyNames) { + this.propertyNames = propertyNames; + } + + 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; + } + + @Override + public String toString() { + return "SourceDefinition{" + + "name='" + name + '\'' + + ", language='" + language + '\'' + + ", loader='" + loader + '\'' + + ", interceptors=" + interceptors + + ", type=" + type + + ", propertiesNames=" + propertyNames + + ", location='" + location + '\'' + + ", content=" + (content != null ? "<...>" : null) + + ", 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 id = (String) params.get("id"); + 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.id = id; + 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 id, String name, String language, String loader, List interceptors, byte[] content) { + SourceDefinition answer = new SourceDefinition(); + answer.id = id; + 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/SourceLoader.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceLoader.java index 8fe13338e..923b3eefc 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceLoader.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/SourceLoader.java @@ -101,11 +101,14 @@ interface Interceptor { /** * Invoked before the source is materialized top a RoutesBuilder. */ - void beforeLoad(SourceLoader loader, Source source); + default void beforeLoad(SourceLoader loader, Source source) { + } /** * Invoked after the source is materialized and before is added to the runtime. */ - Result afterLoad(SourceLoader loader, Source source, Result result); + default Result afterLoad(SourceLoader loader, Source source, Result result) { + return result; + } } } 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..ed9717a28 --- /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..239f5055c 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,119 @@ 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(null, 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(null, 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(null, 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; - } + return new Source() { + @Override + public String getId() { + return ObjectHelper.supplyIfEmpty(definition.getId(), this::getName); + } - @Override - public Optional getLoader() { - return Optional.ofNullable(loader); - } + @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, "."); - @Override - public List getInterceptors() { - return interceptors; - } + 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 getPropertyNames() { + return ObjectHelper.supplyIfEmpty(definition.getPropertyNames(), 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/RoutesConfigurer.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java deleted file mode 100644 index c701621e8..000000000 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/RoutesConfigurer.java +++ /dev/null @@ -1,107 +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.k.listener; - -import java.util.List; - -import org.apache.camel.RuntimeCamelException; -import org.apache.camel.k.Constants; -import org.apache.camel.k.Runtime; -import org.apache.camel.k.RuntimeAware; -import org.apache.camel.k.Source; -import org.apache.camel.k.SourceLoader; -import org.apache.camel.k.Sources; -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 RoutesConfigurer() { - super(Runtime.Phase.ConfigureRoutes); - } - - @Override - protected void accept(Runtime runtime) { - 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; - } - - load(runtime, routes.split(",", -1)); - } - - protected void load(Runtime runtime, String[] routes) { - for (String route: routes) { - if (ObjectHelper.isEmpty(route)) { - continue; - } - - 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() { - @Override - protected void accept(Runtime runtime) { - load(runtime, routes); - } - }; - } - - public static SourceLoader load(Runtime runtime, Source source) { - final List interceptors = RuntimeSupport.loadInterceptors(runtime.getCamelContext(), source); - final SourceLoader loader = RuntimeSupport.loaderFor(runtime.getCamelContext(), source); - - try { - for (SourceLoader.Interceptor interceptor: interceptors) { - if (interceptor instanceof RuntimeAware) { - ((RuntimeAware) interceptor).setRuntime(runtime); - } - - interceptor.beforeLoad(loader, source); - } - - SourceLoader.Result result = loader.load(runtime, source); - for (SourceLoader.Interceptor interceptor: interceptors) { - result = interceptor.afterLoad(loader, source, result); - } - - result.builder().ifPresent(runtime::addRoutes); - result.configuration().ifPresent(runtime::addConfiguration); - } catch (Exception e) { - throw RuntimeCamelException.wrapRuntimeCamelException(e); - } - - return loader; - } -} diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java new file mode 100644 index 000000000..5545947ff --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java @@ -0,0 +1,77 @@ +/* + * 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.Constants; +import org.apache.camel.k.Runtime; +import org.apache.camel.k.SourceDefinition; +import org.apache.camel.k.support.PropertiesSupport; +import org.apache.camel.k.support.SourcesSupport; +import org.apache.camel.spi.Configurer; +import org.apache.camel.util.ObjectHelper; + +@Configurer +public class SourcesConfigurer extends AbstractPhaseListener { + public static final String CAMEL_K_PREFIX = "camel.k."; + public static final String CAMEL_K_SOURCES_PREFIX = "camel.k.sources["; + + private SourceDefinition[] sources; + + public SourcesConfigurer() { + super(Runtime.Phase.ConfigureRoutes); + } + + public SourceDefinition[] getSources() { + return sources; + } + + public void setSources(SourceDefinition[] sources) { + this.sources = sources; + } + + @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.isNotEmpty(routes)) { + SourcesSupport.loadSources(runtime, routes.split(",")); + } + + // + // load routes from properties + // + // In order not to load any unwanted property, the filer remove any + // property that can't be bound to this configurer. + // + PropertiesSupport.bindProperties( + runtime.getCamelContext(), + this, + k -> k.startsWith(CAMEL_K_SOURCES_PREFIX), + CAMEL_K_PREFIX); + + if (ObjectHelper.isNotEmpty(this.getSources())) { + SourcesSupport.loadSources(runtime, this.getSources()); + } + } + +} 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..bd57ae651 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,34 +26,84 @@ 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; import java.util.Properties; import java.util.Set; +import java.util.function.Predicate; 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; +import org.apache.camel.util.StringHelper; public final class PropertiesSupport { private PropertiesSupport() { } - @SuppressWarnings("unchecked") - public static boolean bindProperties(CamelContext context, Object target, String prefix) { + public static T bindProperties(CamelContext context, T target, String prefix) { + return bindProperties(context, target, prefix, false); + } + + public static T bindProperties(CamelContext context, T target, String prefix, boolean stripPrefix) { + return bindProperties(context, target, k -> k.startsWith(prefix), prefix, stripPrefix); + } + + public static T bindProperties(CamelContext context, T target, Predicate filter, String prefix) { + return bindProperties(context, target, filter, prefix, false); + } + + public static T bindProperties(CamelContext context, T target, Predicate filter, String prefix, boolean stripPrefix) { final PropertiesComponent component = context.getPropertiesComponent(); - final Properties properties = component.loadProperties(k -> k.startsWith(prefix)); + final Properties propertiesWithPrefix = component.loadProperties(filter); + final Map properties = new HashMap<>(); + + propertiesWithPrefix.stringPropertyNames().forEach( + name -> properties.put( + // PropertyBindingSupport does not support dash properties + StringHelper.dashToCamelCase(stripPrefix ? name.substring(prefix.length()) : name), + propertiesWithPrefix.getProperty(name)) + ); + + 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(); + } + + 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"; + } - return PropertyBindingSupport.build() + // see if there is a configurer for it + configurer = context.adapt(ExtendedCamelContext.class) + .getConfigurerResolver() + .resolvePropertyConfigurer(name, context); + } + + PropertyBindingSupport.build() + .withIgnoreCase(true) .withCamelContext(context) .withTarget(target) - .withProperties((Map)properties) - .withRemoveParameters(false) - .withOptionPrefix(prefix) + .withProperties(properties) + .withRemoveParameters(true) + .withOptionPrefix(stripPrefix ? null : prefix) + .withConfigurer(configurer) .bind(); + + return target; } public static String resolveApplicationPropertiesLocation() { diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/SourcesSupport.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/SourcesSupport.java new file mode 100644 index 000000000..7d6fa6463 --- /dev/null +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/SourcesSupport.java @@ -0,0 +1,164 @@ +/* + * 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.List; + +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.builder.RouteBuilderLifecycleStrategy; +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.SourceType; +import org.apache.camel.k.Sources; +import org.apache.camel.k.listener.AbstractPhaseListener; +import org.apache.camel.k.listener.SourcesConfigurer; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.model.RouteTemplateDefinition; +import org.apache.camel.util.ObjectHelper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class SourcesSupport { + private static final Logger LOGGER = LoggerFactory.getLogger(SourcesConfigurer.class); + + private SourcesSupport() { + } + + public static Runtime.Listener forRoutes(String... sources) { + return new AbstractPhaseListener(Runtime.Phase.ConfigureRoutes) { + @Override + protected void accept(Runtime runtime) { + loadSources(runtime, sources); + } + }; + } + + public static Runtime.Listener forRoutes(SourceDefinition... definitions) { + return new AbstractPhaseListener(Runtime.Phase.ConfigureRoutes) { + @Override + protected void accept(Runtime runtime) { + loadSources(runtime, definitions); + } + }; + } + + public static void loadSources(Runtime runtime, String... routes) { + for (String route: routes) { + if (ObjectHelper.isEmpty(route)) { + continue; + } + + LOGGER.debug("Loading routes from: {}", route); + + try { + load(runtime, Sources.fromURI(route)); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeCamelException(e); + } + } + } + + public static void loadSources(Runtime runtime, SourceDefinition... definitions) { + for (SourceDefinition definition: definitions) { + LOGGER.debug("Loading routes from: {}", definition); + + load(runtime, Sources.fromDefinition(definition)); + } + } + + public static SourceLoader load(Runtime runtime, Source source) { + final SourceLoader loader = RuntimeSupport.loaderFor(runtime.getCamelContext(), source); + final List interceptors = source.getType() == SourceType.source + ? sourceInterceptors(runtime, source) + : templateInterceptors(runtime, source); + + try { + for (SourceLoader.Interceptor interceptor: interceptors) { + if (interceptor instanceof RuntimeAware) { + ((RuntimeAware) interceptor).setRuntime(runtime); + } + + interceptor.beforeLoad(loader, source); + } + + SourceLoader.Result result = loader.load(runtime, source); + + for (SourceLoader.Interceptor interceptor: interceptors) { + result = interceptor.afterLoad(loader, source, result); + } + + result.builder().ifPresent(runtime::addRoutes); + result.configuration().ifPresent(runtime::addConfiguration); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeCamelException(e); + } + + return loader; + } + + private static List sourceInterceptors(Runtime runtime, Source source) { + return RuntimeSupport.loadInterceptors(runtime.getCamelContext(), source); + } + + private static List templateInterceptors(Runtime runtime, Source source) { + if (!source.getInterceptors().isEmpty()) { + LOGGER.warn("Interceptors associated to the route template {} will be ignored", source.getName()); + } + + return List.of( + new SourceLoader.Interceptor() { + @Override + public SourceLoader.Result afterLoad(SourceLoader loader, Source source, SourceLoader.Result result) { + RouteBuilder builder = result.builder() + .map(RouteBuilder.class::cast) + .orElseThrow(() -> new IllegalArgumentException("Unexpected routes builder type")); + + builder.addLifecycleInterceptor(new RouteBuilderLifecycleStrategy() { + @Override + public void afterConfigure(RouteBuilder builder) { + List routes = builder.getRouteCollection().getRoutes(); + List templates = builder.getRouteTemplateCollection().getRouteTemplates(); + + if (routes.size() != 1) { + throw new IllegalArgumentException("There should be a single route definition, got " + routes.size()); + } + if (!templates.isEmpty()) { + throw new IllegalArgumentException("There should not be any template, got " + templates.size()); + } + + // create a new template from the source + RouteTemplateDefinition templatesDefinition = builder.getRouteTemplateCollection().routeTemplate(source.getId()); + templatesDefinition.setRoute(routes.get(0)); + + source.getPropertyNames().forEach(templatesDefinition::templateParameter); + + // remove all routes definitions as they have been translated + // in the related route template + routes.clear(); + } + }); + + return SourceLoader.Result.on(builder); + } + } + ); + } +} 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..6a91b581b --- /dev/null +++ b/camel-k-runtime-core/src/test/java/org/apache/camel/k/support/PropertiesSupportTest.java @@ -0,0 +1,96 @@ +/* + * 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.List; +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.SourcesConfigurer; +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 propertiesAreBoundToSourcesConfigurer() { + 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", + "camel.k.sources[1].property-names[0]", "foo", + "camel.k.sources[1].property-names[1]", "bar" + )); + + SourcesConfigurer configuration = new SourcesConfigurer(); + + PropertiesSupport.bindProperties( + context, + configuration, + k -> k.startsWith(SourcesConfigurer.CAMEL_K_SOURCES_PREFIX), + SourcesConfigurer.CAMEL_K_PREFIX); + + assertThat(configuration.getSources()) + .hasSize(2) + .anyMatch(byNameAndLocation("MyRoutesWithBeans", "classpath:MyRoutesWithBeans.java") + .and(d -> d.getPropertyNames() == null)) + .anyMatch(byNameAndLocation("MyRoutesConfig", "classpath:MyRoutesConfig.java") + .and(d -> d.getPropertyNames() != null && d.getPropertyNames().containsAll(List.of("foo", "bar")))); + } + + @Test + public void propertiesWithGapsAreBoundToSourcesConfigurer() { + 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" + )); + + SourcesConfigurer configuration = new SourcesConfigurer(); + + PropertiesSupport.bindProperties( + context, + configuration, + k -> k.startsWith(SourcesConfigurer.CAMEL_K_SOURCES_PREFIX), + SourcesConfigurer.CAMEL_K_PREFIX); + + 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..5c30e159c 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,8 +22,8 @@ import org.apache.camel.CamelContext; import org.apache.camel.component.mock.MockEndpoint; -import org.apache.camel.k.listener.RoutesConfigurer; import org.apache.camel.k.main.ApplicationRuntime; +import org.apache.camel.k.support.SourcesSupport; import org.apache.camel.support.LifecycleStrategySupport; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -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(SourcesSupport.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..c92199bbe 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,8 +41,8 @@ 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.support.RuntimeSupport; +import org.apache.camel.k.support.SourcesSupport; 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 = SourcesSupport.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..88a265b99 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,8 +27,8 @@ 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.loader.yaml.YamlSourceLoader; +import org.apache.camel.k.support.SourcesSupport; import org.apache.camel.model.FromDefinition; import org.apache.camel.model.ProcessorDefinition; import org.apache.camel.model.RouteDefinition; @@ -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 = SourcesSupport.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..a41d21eeb 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.support.SourcesSupport; 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 = SourcesSupport.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..010e85a01 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,8 +30,8 @@ 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.main.ApplicationRuntime; +import org.apache.camel.k.support.SourcesSupport; import org.apache.camel.spi.RoutePolicy; import org.apache.camel.spi.RoutePolicyFactory; import org.apache.camel.support.RoutePolicySupport; @@ -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(SourcesSupport.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(SourcesSupport.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(SourcesSupport.forRoutes("classpath:webhook.js")); Assertions.assertThrows(FailedToCreateRouteException.class, runtime::run); } diff --git a/camel-kamelet/pom.xml b/camel-kamelet/pom.xml index 33e09c34f..e459ea9cb 100644 --- a/camel-kamelet/pom.xml +++ b/camel-kamelet/pom.xml @@ -21,7 +21,7 @@ org.apache.camel.k camel-k-runtime-parent - 1.5.0-SNAPSHOT + 1.5.1-SNAPSHOT 4.0.0 @@ -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/generated/resources/org/apache/camel/component/kamelet/kamelet.json b/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json index 93ef4969b..50552af50 100644 --- a/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json +++ b/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json @@ -11,7 +11,7 @@ "supportLevel": "Preview", "groupId": "org.apache.camel.k", "artifactId": "camel-kamelet", - "version": "1.5.0-SNAPSHOT", + "version": "1.5.1-SNAPSHOT", "scheme": "kamelet", "extendsScheme": "", "syntax": "kamelet:templateId\/routeId", 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..bc3cb8c92 100644 --- a/camel-kamelet/src/test/resources/log4j2-test.xml +++ b/camel-kamelet/src/test/resources/log4j2-test.xml @@ -26,11 +26,13 @@ + + - + diff --git a/examples/camel-k-runtime-example-groovy/data/application.properties b/examples/camel-k-runtime-example-groovy/data/application.properties index ac98d6e1d..3be9292e4 100644 --- a/examples/camel-k-runtime-example-groovy/data/application.properties +++ b/examples/camel-k-runtime-example-groovy/data/application.properties @@ -16,7 +16,7 @@ ## --------------------------------------------------------------------------- # -# Logging +# logging # logging.level.org.apache.camel.k = DEBUG @@ -27,5 +27,14 @@ camel.main.name = camel-k camel.main.stream-caching-enabled = true camel.main.stream-caching-spool-directory = ${java.io.tmpdir}/camel-k +# +# camel-k - sources +# +camel.k.sources[0].name = routes +camel.k.sources[0].language = groovy +camel.k.sources[0].location = file:{{data.dir}}/routes.groovy +# +# misc +# message = test diff --git a/examples/camel-k-runtime-example-groovy/pom.xml b/examples/camel-k-runtime-example-groovy/pom.xml index 8514eb9da..661f2b90b 100644 --- a/examples/camel-k-runtime-example-groovy/pom.xml +++ b/examples/camel-k-runtime-example-groovy/pom.xml @@ -69,8 +69,8 @@ ${project.basedir}/data/application.properties - camel.k.routes - file:${project.basedir}/data/routes.groovy + data.dir + ${project.basedir}/data diff --git a/tooling/camel-k-test/src/main/java/org/apache/camel/k/test/CamelKTestSupport.java b/tooling/camel-k-test/src/main/java/org/apache/camel/k/test/CamelKTestSupport.java new file mode 100644 index 000000000..05c6e64bc --- /dev/null +++ b/tooling/camel-k-test/src/main/java/org/apache/camel/k/test/CamelKTestSupport.java @@ -0,0 +1,48 @@ +/* + * 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.test; + +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +public final class CamelKTestSupport { + private CamelKTestSupport() { + } + + public static Properties asProperties(String... properties) { + if ((properties.length & 1) != 0) { + throw new InternalError("length is odd"); + } + + Properties answer = new Properties(); + for (int i = 0; i < properties.length; i += 2) { + answer.setProperty( + Objects.requireNonNull(properties[i]), + Objects.requireNonNull(properties[i + 1])); + } + + return answer; + } + + public static Properties asProperties(Map properties) { + Properties answer = new Properties(); + answer.putAll(properties); + + return answer; + } +}