This is an automated email from the ASF dual-hosted git repository. lburgazzoli pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel-k-runtime.git
commit 36a11797d1a0c51f27928c2067b9f7cb430b05c5 Author: Pasquale Congiusti <pasquale.congiu...@gmail.com> AuthorDate: Tue Apr 13 12:45:48 2021 +0200 feat(core): default error handler * Introduced a new SourceType, errorHandler, that can be used as a source for spotting a default error handler that will be used in those routes that don't specify any * Forced the sources sorting in order to load errorHandler first, sources and templates * Added a check to make sure only one error handler is provided --- .../main/java/org/apache/camel/k/SourceType.java | 2 + .../k/listener/GlobalErrorHandlerConfigurer.java | 67 ------------ .../apache/camel/k/listener/SourcesConfigurer.java | 42 +++++++- .../org/apache/camel/k/support/SourcesSupport.java | 59 +++++++++-- .../services/org.apache.camel.k.Runtime$Listener | 1 - .../camel/k/listener/SourceConfigurerTest.java | 114 +++++++++++++++++++++ .../support/GlobalErrorHandlerConfigurerTest.java | 47 --------- 7 files changed, 206 insertions(+), 126 deletions(-) diff --git a/camel-k-core/api/src/main/java/org/apache/camel/k/SourceType.java b/camel-k-core/api/src/main/java/org/apache/camel/k/SourceType.java index ed9717a..243a7b8 100644 --- a/camel-k-core/api/src/main/java/org/apache/camel/k/SourceType.java +++ b/camel-k-core/api/src/main/java/org/apache/camel/k/SourceType.java @@ -17,6 +17,8 @@ package org.apache.camel.k; public enum SourceType { + // Order matters. We want the sources to be loaded following this enum order. + errorHandler, source, template } diff --git a/camel-k-core/support/src/main/java/org/apache/camel/k/listener/GlobalErrorHandlerConfigurer.java b/camel-k-core/support/src/main/java/org/apache/camel/k/listener/GlobalErrorHandlerConfigurer.java deleted file mode 100644 index 794f794..0000000 --- a/camel-k-core/support/src/main/java/org/apache/camel/k/listener/GlobalErrorHandlerConfigurer.java +++ /dev/null @@ -1,67 +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 org.apache.camel.ExtendedCamelContext; -import org.apache.camel.builder.DeadLetterChannelBuilder; -import org.apache.camel.k.Runtime; -import org.apache.camel.k.support.PropertiesSupport; -import org.apache.camel.spi.Configurer; -import org.apache.camel.util.ObjectHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -@Configurer -public class GlobalErrorHandlerConfigurer extends AbstractPhaseListener { - private static final Logger LOGGER = LoggerFactory.getLogger(GlobalErrorHandlerConfigurer.class); - - public static final String CAMEL_K_PREFIX = "camel.k."; - public static final String CAMEL_K_GLOBAL_ERROR_HANDLER_PREFIX = "camel.k.global-error-handler"; - - private String globalErrorHandler; - - public GlobalErrorHandlerConfigurer() { - super(Runtime.Phase.ConfigureRoutes); - } - - public String getGlobalErrorHandler(){ - return this.globalErrorHandler; - } - - public void setGlobalErrorHandler(String globalErrorHandler){ - this.globalErrorHandler = globalErrorHandler; - } - - @Override - protected void accept(Runtime runtime) { - PropertiesSupport.bindProperties( - runtime.getCamelContext(), - this, - k -> k.startsWith(CAMEL_K_GLOBAL_ERROR_HANDLER_PREFIX), - CAMEL_K_PREFIX); - if (ObjectHelper.isNotEmpty(this.globalErrorHandler)) { - loadGlobalErrorHandler(runtime, this.getGlobalErrorHandler()); - } - } - - public static void loadGlobalErrorHandler(Runtime runtime, String uri) { - LOGGER.info("Setting a default global error handler factory to deadletter channel {}", uri); - runtime.getCamelContext().adapt(ExtendedCamelContext.class) - .setErrorHandlerFactory(new DeadLetterChannelBuilder(uri)); - } - -} diff --git a/camel-k-core/support/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java b/camel-k-core/support/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java index 30c2efe..95ddd92 100644 --- a/camel-k-core/support/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java +++ b/camel-k-core/support/src/main/java/org/apache/camel/k/listener/SourcesConfigurer.java @@ -18,12 +18,16 @@ package org.apache.camel.k.listener; import org.apache.camel.k.Runtime; import org.apache.camel.k.SourceDefinition; +import org.apache.camel.k.SourceType; import org.apache.camel.k.support.Constants; 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; +import java.util.Arrays; +import java.util.Collections; + @Configurer public class SourcesConfigurer extends AbstractPhaseListener { public static final String CAMEL_K_PREFIX = "camel.k."; @@ -64,14 +68,44 @@ public class SourcesConfigurer extends AbstractPhaseListener { // property that can't be bound to this configurer. // PropertiesSupport.bindProperties( - runtime.getCamelContext(), - this, - k -> k.startsWith(CAMEL_K_SOURCES_PREFIX), - CAMEL_K_PREFIX); + runtime.getCamelContext(), + this, + k -> k.startsWith(CAMEL_K_SOURCES_PREFIX), + CAMEL_K_PREFIX); + + checkUniqueErrorHandler(); + sortSources(); if (ObjectHelper.isNotEmpty(this.getSources())) { SourcesSupport.loadSources(runtime, this.getSources()); } } + private void checkUniqueErrorHandler() { + checkUniqueErrorHandler(this.sources); + } + + static void checkUniqueErrorHandler(SourceDefinition[] sources) { + long errorHandlers = Arrays.stream(sources).filter(s -> s.getType() == SourceType.errorHandler).count(); + if ( errorHandlers > 1) { + throw new IllegalArgumentException("Expected only one error handler source type, got " + errorHandlers); + } + } + + private void sortSources() { + sortSources(this.getSources()); + } + + static void sortSources(SourceDefinition[] sources) { + // We must ensure the following source type order: errorHandler, source, template + Arrays.sort(sources, + (a, b) -> { + if (a.getType() == null) { + return SourceType.source.compareTo(b.getType()); + } else if (b.getType() == null) { + return a.getType().compareTo(SourceType.source); + } else return a.getType().compareTo(b.getType()); + }); + } + } diff --git a/camel-k-core/support/src/main/java/org/apache/camel/k/support/SourcesSupport.java b/camel-k-core/support/src/main/java/org/apache/camel/k/support/SourcesSupport.java index e393521..029b4d3 100644 --- a/camel-k-core/support/src/main/java/org/apache/camel/k/support/SourcesSupport.java +++ b/camel-k-core/support/src/main/java/org/apache/camel/k/support/SourcesSupport.java @@ -16,12 +16,10 @@ */ package org.apache.camel.k.support; -import java.util.Collection; -import java.util.List; - import org.apache.camel.ExtendedCamelContext; import org.apache.camel.RoutesBuilder; import org.apache.camel.RuntimeCamelException; +import org.apache.camel.builder.ErrorHandlerBuilder; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.builder.RouteBuilderLifecycleStrategy; import org.apache.camel.k.Runtime; @@ -37,6 +35,10 @@ import org.apache.camel.util.ObjectHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; + public final class SourcesSupport { private static final Logger LOGGER = LoggerFactory.getLogger(SourcesConfigurer.class); @@ -128,21 +130,64 @@ public final class SourcesSupport { } }); break; + case errorHandler: + if (!source.getInterceptors().isEmpty()) { + LOGGER.warn("Interceptors associated to the route template {} will be ignored", source.getName()); + } + + interceptors = List.of(new RouteBuilderLifecycleStrategy() { + @Override + public void afterConfigure(RouteBuilder builder) { + List<RouteDefinition> routes = builder.getRouteCollection().getRoutes(); + List<RouteTemplateDefinition> templates = builder.getRouteTemplateCollection().getRouteTemplates(); + + if (routes.size() > 0) { + throw new IllegalArgumentException("There should be no route definition, got " + routes.size()); + } + if (!templates.isEmpty()) { + throw new IllegalArgumentException("There should not be any template, got " + templates.size()); + } + + if (existErrorHandler(builder)) { + LOGGER.debug("Setting default error handler builder factory as {}", builder.getErrorHandlerBuilder()); + runtime.getCamelContext().adapt(ExtendedCamelContext.class).setErrorHandlerFactory(builder.getErrorHandlerBuilder()); + } + } + }); + break; default: throw new IllegalArgumentException("Unknown source type: " + source.getType()); } try { final Resource resource = Sources.asResource(runtime.getCamelContext(), source); - final ExtendedCamelContext ecc = runtime.getCamelContext(ExtendedCamelContext.class); + final ExtendedCamelContext ecc = runtime.getCamelContext(ExtendedCamelContext.class); final Collection<RoutesBuilder> builders = ecc.getRoutesLoader().findRoutesBuilders(resource); builders.stream() - .map(RouteBuilder.class::cast) - .peek(b -> interceptors.forEach(b::addLifecycleInterceptor)) - .forEach(runtime::addRoutes); + .map(RouteBuilder.class::cast) + .peek(b -> interceptors.forEach(b::addLifecycleInterceptor)) + .forEach(runtime::addRoutes); } catch (Exception e) { throw RuntimeCamelException.wrapRuntimeCamelException(e); } } + + static boolean existErrorHandler(RouteBuilder builder) { + //return builder.hasErrorHandlerBuilder(); + // TODO We need to replace the following workaround with the statement above once we switch to camel-3.10.0 or above + try { + Field f = RouteBuilder.class.getSuperclass().getDeclaredField("errorHandlerBuilder"); + f.setAccessible(true); + ErrorHandlerBuilder privateErrorHandlerBuilder = (ErrorHandlerBuilder) f.get(builder); + return privateErrorHandlerBuilder != null; + } catch (Exception e){ + throw new IllegalArgumentException("Something went wrong while checking the error handler builder", e); + } + } + + public static void loadErrorHandlerSource(Runtime runtime, SourceDefinition errorHandlerSourceDefinition) { + LOGGER.info("Loading error handler from: {}", errorHandlerSourceDefinition); + load(runtime, Sources.fromDefinition(errorHandlerSourceDefinition)); + } } diff --git a/camel-k-core/support/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener b/camel-k-core/support/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener index 9fc5078..f0c50b7 100644 --- a/camel-k-core/support/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener +++ b/camel-k-core/support/src/main/resources/META-INF/services/org.apache.camel.k.Runtime$Listener @@ -16,6 +16,5 @@ # org.apache.camel.k.listener.ContextConfigurer -org.apache.camel.k.listener.GlobalErrorHandlerConfigurer org.apache.camel.k.listener.SourcesConfigurer org.apache.camel.k.listener.PropertiesConfigurer diff --git a/camel-k-core/support/src/test/java/org/apache/camel/k/listener/SourceConfigurerTest.java b/camel-k-core/support/src/test/java/org/apache/camel/k/listener/SourceConfigurerTest.java new file mode 100644 index 0000000..ebdd22c --- /dev/null +++ b/camel-k-core/support/src/test/java/org/apache/camel/k/listener/SourceConfigurerTest.java @@ -0,0 +1,114 @@ +/* + * 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.CamelContext; +import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.k.support.PropertiesSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.k.test.CamelKTestSupport.asProperties; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class SourceConfigurerTest { + @Test + public void shouldLoadMultipleSources() { + CamelContext context = new DefaultCamelContext(); + context.getPropertiesComponent().setInitialProperties(asProperties( + "camel.k.sources[0].name", "sourceName0", + "camel.k.sources[0].location", "classpath:MyTemplate1.java", + "camel.k.sources[0].type", "template", + "camel.k.sources[1].name", "err1", + "camel.k.sources[1.location", "classpath:MyTemplate2.java", + "camel.k.sources[2].name", "err2", + "camel.k.sources[2].location", "classpath:Err2.java", + "camel.k.sources[2].type", "errorHandler" + )); + + SourcesConfigurer configuration = new SourcesConfigurer(); + + PropertiesSupport.bindProperties( + context, + configuration, + k -> k.startsWith(SourcesConfigurer.CAMEL_K_SOURCES_PREFIX), + SourcesConfigurer.CAMEL_K_PREFIX); + + assertThat(configuration.getSources().length).isEqualTo(3); + } + + @Test + public void shouldFailOnMultipleErrorHandlers() { + CamelContext context = new DefaultCamelContext(); + context.getPropertiesComponent().setInitialProperties(asProperties( + "camel.k.sources[0].name", "sourceName0", + "camel.k.sources[0].location", "classpath:MyTemplate1.java", + "camel.k.sources[0].type", "template", + "camel.k.sources[1].name", "err1", + "camel.k.sources[1.location", "classpath:Err1.java", + "camel.k.sources[1].type", "errorHandler", + "camel.k.sources[2].name", "err2", + "camel.k.sources[2].location", "classpath:Err2.java", + "camel.k.sources[2].type", "errorHandler" + )); + + SourcesConfigurer configuration = new SourcesConfigurer(); + + PropertiesSupport.bindProperties( + context, + configuration, + k -> k.startsWith(SourcesConfigurer.CAMEL_K_SOURCES_PREFIX), + SourcesConfigurer.CAMEL_K_PREFIX); + + assertThat(configuration.getSources().length).isEqualTo(3); + Assertions.assertThrows(IllegalArgumentException.class, () -> { + SourcesConfigurer.checkUniqueErrorHandler(configuration.getSources()); + }, "java.lang.IllegalArgumentException: Expected only one error handler source type, got 2"); + } + + @Test + public void shouldOrderSourcesByType() { + CamelContext context = new DefaultCamelContext(); + context.getPropertiesComponent().setInitialProperties(asProperties( + "camel.k.sources[0].name", "template1", + "camel.k.sources[0].type", "template", + "camel.k.sources[1].name", "source1", + "camel.k.sources[2].name", "source2", + "camel.k.sources[2].type", "source", + "camel.k.sources[3].name", "errorHandler1", + "camel.k.sources[3].type", "errorHandler" + )); + + SourcesConfigurer configuration = new SourcesConfigurer(); + + PropertiesSupport.bindProperties( + context, + configuration, + k -> k.startsWith(SourcesConfigurer.CAMEL_K_SOURCES_PREFIX), + SourcesConfigurer.CAMEL_K_PREFIX); + SourcesConfigurer.sortSources(configuration.getSources()); + + assertThat(configuration.getSources().length).isEqualTo(4); + + assertThat(configuration.getSources()[0].getName()).isEqualTo("errorHandler1"); + // Order for the same type does not matter + assertThat(configuration.getSources()[1].getName()).contains("source"); + assertThat(configuration.getSources()[2].getName()).contains("source"); + assertThat(configuration.getSources()[3].getName()).isEqualTo("template1"); + } +} diff --git a/camel-k-core/support/src/test/java/org/apache/camel/k/support/GlobalErrorHandlerConfigurerTest.java b/camel-k-core/support/src/test/java/org/apache/camel/k/support/GlobalErrorHandlerConfigurerTest.java deleted file mode 100644 index fdb7446..0000000 --- a/camel-k-core/support/src/test/java/org/apache/camel/k/support/GlobalErrorHandlerConfigurerTest.java +++ /dev/null @@ -1,47 +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.support; - -import org.apache.camel.CamelContext; -import org.apache.camel.impl.DefaultCamelContext; -import org.apache.camel.k.listener.GlobalErrorHandlerConfigurer; -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 GlobalErrorHandlerConfigurerTest { - - @Test - public void globalErrorHandlerIsBoundToSourcesConfigurer() { - CamelContext context = new DefaultCamelContext(); - context.getPropertiesComponent().setInitialProperties(asProperties( - "camel.k.global-error-handler", "someUriError" - )); - - GlobalErrorHandlerConfigurer configuration = new GlobalErrorHandlerConfigurer(); - - PropertiesSupport.bindProperties( - context, - configuration, - k -> k.startsWith(GlobalErrorHandlerConfigurer.CAMEL_K_GLOBAL_ERROR_HANDLER_PREFIX), - SourcesConfigurer.CAMEL_K_PREFIX); - - assertThat(configuration.getGlobalErrorHandler()).isEqualTo("someUriError"); - } -}