diff --git a/camel-k-runtime-bom/pom.xml b/camel-k-runtime-bom/pom.xml index 72d30a07d..400859bc5 100644 --- a/camel-k-runtime-bom/pom.xml +++ b/camel-k-runtime-bom/pom.xml @@ -142,6 +142,11 @@ camel-k-runtime-servlet ${project.version} + + org.apache.camel.k + camel-k-runtime-webhook + ${project.version} + org.apache.camel.k camel-k-runtime-knative diff --git a/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java index e8374a22f..3bf5be177 100644 --- a/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java +++ b/camel-k-runtime-core/src/main/java/org/apache/camel/k/support/RuntimeSupport.java @@ -207,4 +207,5 @@ public static RoutesLoader lookupLoaderFromResource(CamelContext context, String return loader; } + } diff --git a/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/Application.java b/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/Application.java index 7725bab09..f5cc5718e 100644 --- a/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/Application.java +++ b/camel-k-runtime-main/src/main/java/org/apache/camel/k/main/Application.java @@ -36,7 +36,7 @@ public final class Application { private Application() { } - + public static void main(String[] args) throws Exception { ApplicationRuntime runtime = new ApplicationRuntime(); runtime.setProperties(PropertiesSupport.loadProperties()); diff --git a/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java b/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java index d4ec53672..d85ef53c4 100644 --- a/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java +++ b/camel-k-runtime-main/src/test/java/org/apache/camel/k/main/RuntimeTest.java @@ -116,4 +116,5 @@ public void testLoadJavaSourceWithBeans() throws Exception { assertThat(b).hasFieldOrPropertyWithValue("name", "my-bean-name"); }); } + } diff --git a/camel-k-runtime-webhook/pom.xml b/camel-k-runtime-webhook/pom.xml new file mode 100644 index 000000000..102587656 --- /dev/null +++ b/camel-k-runtime-webhook/pom.xml @@ -0,0 +1,170 @@ + + + + + org.apache.camel.k + camel-k-runtime-parent + 1.0.6-SNAPSHOT + + 4.0.0 + + camel-k-runtime-webhook + + + + + + + + + + + org.apache.camel + camel-core-engine + provided + + + org.apache.camel + camel-webhook + + + org.apache.camel.k + camel-k-runtime-core + + + + + + + + + + org.apache.camel + camel-test + test + + + org.apache.camel + camel-endpointdsl + test + + + org.apache.camel + camel-properties + test + + + org.apache.camel + camel-log + test + + + org.apache.camel + camel-timer + test + + + org.apache.camel + camel-undertow + test + + + org.apache.camel + camel-rest + test + + + org.apache.camel + camel-properties + test + + + org.apache.camel + camel-main + test + + + org.apache.camel.k + camel-k-loader-js + test + + + org.apache.camel.k + camel-k-runtime-main + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + test + + + org.apache.logging.log4j + log4j-slf4j-impl + ${log4j2.version} + test + + + + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + jandex + + + + + + + + diff --git a/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookAction.java b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookAction.java new file mode 100644 index 000000000..1b7dd58f1 --- /dev/null +++ b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookAction.java @@ -0,0 +1,21 @@ +/* + * 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.webhook; + +public enum WebhookAction { + REGISTER, UNREGISTER +} diff --git a/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookContextCustomizer.java b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookContextCustomizer.java new file mode 100644 index 000000000..875422349 --- /dev/null +++ b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookContextCustomizer.java @@ -0,0 +1,43 @@ +/* + * 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.webhook; + +import org.apache.camel.CamelContext; +import org.apache.camel.k.ContextCustomizer; + +public class WebhookContextCustomizer implements ContextCustomizer { + + private WebhookAction action; + + public WebhookContextCustomizer() { + } + + public WebhookAction getAction() { + return action; + } + + public void setAction(WebhookAction action) { + this.action = action; + } + + @Override + public void apply(CamelContext camelContext) { + if (action != null) { + camelContext.addRoutePolicyFactory(new WebhookRoutePolicyFactory(action)); + } + } +} diff --git a/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookRoutePolicyFactory.java b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookRoutePolicyFactory.java new file mode 100644 index 000000000..bae04da3d --- /dev/null +++ b/camel-k-runtime-webhook/src/main/java/org/apache/camel/k/webhook/WebhookRoutePolicyFactory.java @@ -0,0 +1,101 @@ +/* + * 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.webhook; + +import org.apache.camel.CamelContext; +import org.apache.camel.NamedNode; +import org.apache.camel.Route; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.component.webhook.WebhookCapableEndpoint; +import org.apache.camel.component.webhook.WebhookEndpoint; +import org.apache.camel.spi.RoutePolicy; +import org.apache.camel.spi.RoutePolicyFactory; +import org.apache.camel.support.RoutePolicySupport; + +/** + * A RoutePolicyFactory that does not start any route but only registers/unregisters the webhook endpoints when enabled. + */ +public class WebhookRoutePolicyFactory implements RoutePolicyFactory { + + private final WebhookAction action; + + public WebhookRoutePolicyFactory(WebhookAction action) { + this.action = action; + } + + @Override + public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, NamedNode route) { + if (action != null) { + return new WebhookRoutePolicy(camelContext, action); + } + return null; + } + + private class WebhookRoutePolicy extends RoutePolicySupport { + + private final CamelContext context; + + private final WebhookAction action; + + public WebhookRoutePolicy(CamelContext context, WebhookAction action) { + this.context = context; + this.action = action; + } + + @Override + public void onInit(Route route) { + super.onInit(route); + route.getRouteContext().setAutoStartup(false); + + if (route.getEndpoint() instanceof WebhookEndpoint) { + WebhookEndpoint webhook = (WebhookEndpoint)route.getEndpoint(); + if (webhook.getConfiguration() != null && webhook.getConfiguration().isWebhookAutoRegister()) { + throw new IllegalStateException( + "Webhook auto-register is enabled on endpoint " + webhook + ": it must be disabled when the WebhookRoutePolicy is active"); + } + executeWebhookAction(webhook.getEndpoint()); + } + } + + @Override + protected void doStart() throws Exception { + super.doStart(); + context.getExecutorServiceManager().newThread("terminator", context::stop).start(); + } + + private void executeWebhookAction(WebhookCapableEndpoint endpoint) { + switch (this.action) { + case REGISTER: + try { + endpoint.registerWebhook(); + } catch (Exception ex) { + throw new RuntimeCamelException("Unable to register webhook for endpoint " + endpoint, ex); + } + return; + case UNREGISTER: + try { + endpoint.unregisterWebhook(); + } catch (Exception ex) { + throw new RuntimeCamelException("Unable to unregister webhook for endpoint " + endpoint, ex); + } + return; + default: + throw new UnsupportedOperationException("Unsupported webhook action type: " + this.action); + } + } + } +} diff --git a/camel-k-runtime-webhook/src/main/resources/META-INF/services/org/apache/camel/k/customizer/webhook b/camel-k-runtime-webhook/src/main/resources/META-INF/services/org/apache/camel/k/customizer/webhook new file mode 100644 index 000000000..7c4e87b9f --- /dev/null +++ b/camel-k-runtime-webhook/src/main/resources/META-INF/services/org/apache/camel/k/customizer/webhook @@ -0,0 +1,18 @@ +# +# 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. +# + +class=org.apache.camel.k.webhook.WebhookContextCustomizer diff --git a/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/DummyWebhookComponent.java b/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/DummyWebhookComponent.java new file mode 100644 index 000000000..dafb48b64 --- /dev/null +++ b/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/DummyWebhookComponent.java @@ -0,0 +1,102 @@ +/* + * 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.webhook; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.camel.Consumer; +import org.apache.camel.Endpoint; +import org.apache.camel.Processor; +import org.apache.camel.Producer; +import org.apache.camel.component.webhook.WebhookCapableEndpoint; +import org.apache.camel.component.webhook.WebhookConfiguration; +import org.apache.camel.support.DefaultComponent; +import org.apache.camel.support.DefaultConsumer; +import org.apache.camel.support.DefaultEndpoint; + +public class DummyWebhookComponent extends DefaultComponent { + + private final Runnable onRegister; + + private final Runnable onUnregister; + + public DummyWebhookComponent(Runnable onRegister, Runnable onUnregister) { + this.onRegister = onRegister; + this.onUnregister = onUnregister; + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map parameters) throws Exception { + return new DummyWebhookComponent.DummyWebhookEndpoint(); + } + + class DummyWebhookEndpoint extends DefaultEndpoint implements WebhookCapableEndpoint { + + @Override + protected String createEndpointUri() { + return "dummy"; + } + + @Override + public Producer createProducer() throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + public Consumer createConsumer(Processor processor) throws Exception { + return new DummyWebhookConsumer(this, processor); + } + + @Override + public Processor createWebhookHandler(Processor next) { + return next; + } + + @Override + public void registerWebhook() throws Exception { + onRegister.run(); + } + + @Override + public void unregisterWebhook() throws Exception { + onUnregister.run(); + } + + @Override + public void setWebhookConfiguration(WebhookConfiguration webhookConfiguration) { + + } + + @Override + public List getWebhookMethods() { + return Collections.singletonList("POST"); + } + + class DummyWebhookConsumer extends DefaultConsumer { + public DummyWebhookConsumer(Endpoint endpoint, Processor processor) { + super(endpoint, processor); + } + + @Override + protected void doStart() throws Exception { + throw new IllegalStateException("Webhook consumer must never be started"); + } + } + } +} 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 new file mode 100644 index 000000000..ab4c53f43 --- /dev/null +++ b/camel-k-runtime-webhook/src/test/java/org/apache/camel/k/webhook/WebhookTest.java @@ -0,0 +1,153 @@ +/* + * 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.webhook; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.apache.camel.CamelContext; +import org.apache.camel.Consumer; +import org.apache.camel.FailedToCreateRouteException; +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.spi.RoutePolicy; +import org.apache.camel.spi.RoutePolicyFactory; +import org.apache.camel.support.RoutePolicySupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WebhookTest { + + private ApplicationRuntime runtime; + + @BeforeEach + public void setUp() { + this.runtime = new ApplicationRuntime(); + runtime.addListener(RoutesConfigurer.forRoutes("classpath:webhook.js")); + runtime.addListener(new ContextConfigurer()); + } + + @ParameterizedTest + @EnumSource(WebhookAction.class) + public void testWebhookRegistration(WebhookAction action) throws Exception { + Properties properties = new Properties(); + properties.setProperty("camel.component.webhook.configuration.webhook-auto-register", "false"); + properties.setProperty("customizer.webhook.enabled", "true"); + properties.setProperty("customizer.webhook.action", action.name().toLowerCase()); + runtime.setProperties(properties); + + CountDownLatch operation = new CountDownLatch(1); + Map registerCounters = new HashMap<>(); + Arrays.stream(WebhookAction.values()).forEach(v -> registerCounters.put(v, new AtomicInteger())); + + DummyWebhookComponent dummy = new DummyWebhookComponent(() -> { + registerCounters.get(WebhookAction.REGISTER).incrementAndGet(); + operation.countDown(); + }, () -> { + registerCounters.get(WebhookAction.UNREGISTER).incrementAndGet(); + operation.countDown(); + } + ); + runtime.getCamelContext().addComponent("dummy", dummy); + + AtomicBoolean routeStarted = new AtomicBoolean(); + runtime.getCamelContext().addRoutePolicyFactory(new RoutePolicyFactory() { + @Override + public RoutePolicy createRoutePolicy(CamelContext camelContext, String routeId, NamedNode route) { + return new RoutePolicySupport() { + @Override + public void onStart(Route route) { + routeStarted.set(true); + super.onStart(route); + } + + @Override + public void startRoute(Route route) throws Exception { + routeStarted.set(true); + super.startRoute(route); + } + + @Override + public void startConsumer(Consumer consumer) throws Exception { + routeStarted.set(true); + super.startConsumer(consumer); + } + }; + } + }); + + runtime.run(); + + operation.await(15, TimeUnit.SECONDS); + for (WebhookAction a : WebhookAction.values()) { + if (a == action) { + assertThat(registerCounters.get(a)).hasValue(1); + } else { + assertThat(registerCounters.get(a)).hasValue(0); + } + } + + assertThat(routeStarted).isFalse(); + } + + @ParameterizedTest() + @EnumSource(WebhookAction.class) + public void testRegistrationFailure(WebhookAction action) throws Exception { + Properties properties = new Properties(); + properties.setProperty("camel.component.webhook.configuration.webhook-auto-register", "false"); + properties.setProperty("customizer.webhook.enabled", "true"); + properties.setProperty("customizer.webhook.action", action.name()); + runtime.setProperties(properties); + + DummyWebhookComponent dummy = new DummyWebhookComponent(() -> { + throw new RuntimeException("dummy error"); + }, () -> { + throw new RuntimeException("dummy error"); + }); + runtime.getCamelContext().addComponent("dummy", dummy); + Assertions.assertThrows(FailedToCreateRouteException.class, runtime::run); + } + + @Test + public void testAutoRegistrationNotDisabled() throws Exception { + Properties properties = new Properties(); + properties.setProperty("customizer.webhook.enabled", "true"); + properties.setProperty("customizer.webhook.action", WebhookAction.REGISTER.name()); + runtime.setProperties(properties); + + DummyWebhookComponent dummy = new DummyWebhookComponent(() -> { + }, () -> { + }); + runtime.getCamelContext().addComponent("dummy", dummy); + Assertions.assertThrows(FailedToCreateRouteException.class, runtime::run); + } + +} diff --git a/camel-k-runtime-webhook/src/test/resources/log4j2-test.xml b/camel-k-runtime-webhook/src/test/resources/log4j2-test.xml new file mode 100644 index 000000000..48bdd0c49 --- /dev/null +++ b/camel-k-runtime-webhook/src/test/resources/log4j2-test.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/camel-k-runtime-webhook/src/test/resources/webhook.js b/camel-k-runtime-webhook/src/test/resources/webhook.js new file mode 100644 index 000000000..ef57ca4a6 --- /dev/null +++ b/camel-k-runtime-webhook/src/test/resources/webhook.js @@ -0,0 +1,21 @@ +/* + * 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. + */ + +from('webhook:dummy') + .to('log:info') + +from('timer:tick').to('log:info') diff --git a/pom.xml b/pom.xml index 5f8ef8b0f..ba7b1667f 100644 --- a/pom.xml +++ b/pom.xml @@ -219,6 +219,7 @@ camel-k-runtime-health camel-k-runtime-servlet camel-k-runtime-knative + camel-k-runtime-webhook camel-k-runtime-bom examples diff --git a/tooling/camel-k-maven-plugin/src/main/java/org/apache/camel/k/tooling/maven/processors/CatalogProcessor3x.java b/tooling/camel-k-maven-plugin/src/main/java/org/apache/camel/k/tooling/maven/processors/CatalogProcessor3x.java index cd03407d3..05d4b871a 100644 --- a/tooling/camel-k-maven-plugin/src/main/java/org/apache/camel/k/tooling/maven/processors/CatalogProcessor3x.java +++ b/tooling/camel-k-maven-plugin/src/main/java/org/apache/camel/k/tooling/maven/processors/CatalogProcessor3x.java @@ -238,6 +238,21 @@ public void process(MavenProject project, CamelCatalog catalog, Map d.getGroupId().equals("org.apache.camel") && d.getArtifactId().equals("camel-servlet") ); }); + assertThat(artifactMap.get("camel-k-runtime-webhook")).satisfies(a -> { + assertThat(a.getDependencies()).anyMatch( + d -> d.getGroupId().equals("org.apache.camel") && d.getArtifactId().equals("camel-webhook") + ); + }); assertThat(artifactMap.get("camel-k-runtime-knative")).satisfies(a -> { assertThat(a.getDependencies()).anyMatch(