Repository: camel Updated Branches: refs/heads/master 45616e17a -> e68111ec1
http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTest.java b/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTest.java new file mode 100644 index 0000000..aa18956 --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTest.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.util.backoff; + +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; + +public class BackOffTest { + + @Test + public void testSimpleBackOff() { + final BackOff backOff = BackOff.builder().build(); + final BackOffContext context = new BackOffContext(backOff); + + long delay; + + for (int i = 1; i <= 5; i++) { + delay = context.next(); + Assert.assertEquals(i, context.getCurrentAttempts()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), delay); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), context.getCurrentDelay()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis() * i, context.getCurrentElapsedTime()); + } + } + + @Test + public void testBackOffWithMultiplier() { + final BackOff backOff = BackOff.builder().multiplier(1.5).build(); + final BackOffContext context = new BackOffContext(backOff); + + long delay = BackOff.DEFAULT_DELAY.toMillis(); + long oldDelay; + long elapsed = 0; + + for (int i = 1; i <= 5; i++) { + oldDelay = delay; + delay = context.next(); + elapsed += delay; + + Assert.assertEquals(i, context.getCurrentAttempts()); + Assert.assertEquals((long)(oldDelay * 1.5), delay); + Assert.assertEquals((long)(oldDelay * 1.5), context.getCurrentDelay()); + Assert.assertEquals(elapsed, context.getCurrentElapsedTime(), 0); + } + } + + @Test + public void testBackOffWithMaxAttempts() { + final BackOff backOff = BackOff.builder().maxAttempts(5L).build(); + final BackOffContext context = new BackOffContext(backOff); + + long delay; + + for (int i = 1; i <= 5; i++) { + delay = context.next(); + Assert.assertEquals(i, context.getCurrentAttempts()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), delay); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), context.getCurrentDelay()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis() * i, context.getCurrentElapsedTime()); + } + + delay = context.next(); + Assert.assertEquals(6, context.getCurrentAttempts()); + Assert.assertEquals(BackOff.NEVER, delay); + } + + @Test + public void testBackOffWithMaxTime() { + final BackOff backOff = BackOff.builder().maxElapsedTime(9, TimeUnit.SECONDS).build(); + final BackOffContext context = new BackOffContext(backOff); + + long delay; + + for (int i = 1; i <= 5; i++) { + delay = context.next(); + Assert.assertEquals(i, context.getCurrentAttempts()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), delay); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis(), context.getCurrentDelay()); + Assert.assertEquals(BackOff.DEFAULT_DELAY.toMillis() * i, context.getCurrentElapsedTime()); + } + + delay = context.next(); + Assert.assertEquals(6, context.getCurrentAttempts()); + Assert.assertEquals(BackOff.NEVER, delay); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTimerTest.java ---------------------------------------------------------------------- diff --git a/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTimerTest.java b/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTimerTest.java new file mode 100644 index 0000000..5c2cc9f --- /dev/null +++ b/camel-core/src/test/java/org/apache/camel/util/backoff/BackOffTimerTest.java @@ -0,0 +1,105 @@ +/** + * 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.util.backoff; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; +import org.junit.Test; + +public class BackOffTimerTest { + @Test + public void testBackOffTimer() throws Exception { + final AtomicInteger counter = new AtomicInteger(0); + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); + final BackOff backOff = BackOff.builder().delay(100).build(); + final BackOffTimer timer = new BackOffTimer(executor); + + timer.schedule( + backOff, + context -> { + Assert.assertEquals(counter.incrementAndGet(), context.getCurrentAttempts()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100 * counter.get(), context.getCurrentElapsedTime()); + + return counter.get() < 5; + } + ).thenAccept( + context -> { + Assert.assertEquals(5, counter.get()); + } + ).get(5, TimeUnit.SECONDS); + + executor.shutdownNow(); + } + + @Test + public void testBackOffTimerWithMaxAttempts() throws Exception { + final AtomicInteger counter = new AtomicInteger(0); + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); + final BackOff backOff = BackOff.builder().delay(100).maxAttempts(5L).build(); + final BackOffTimer timer = new BackOffTimer(executor); + + timer.schedule( + backOff, + context -> { + Assert.assertEquals(counter.incrementAndGet(), context.getCurrentAttempts()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100 * counter.get(), context.getCurrentElapsedTime()); + + return true; + } + ).thenAccept( + context -> { + Assert.assertEquals(5, counter.get()); + } + ).get(5, TimeUnit.SECONDS); + + executor.shutdownNow(); + } + + @Test + public void testBackOffTimerWithMaxElapsedTime() throws Exception { + final AtomicInteger counter = new AtomicInteger(0); + final ScheduledExecutorService executor = Executors.newScheduledThreadPool(3); + final BackOff backOff = BackOff.builder().delay(100).maxElapsedTime(400).build(); + final BackOffTimer timer = new BackOffTimer(executor); + + timer.schedule( + backOff, + context -> { + Assert.assertEquals(counter.incrementAndGet(), context.getCurrentAttempts()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100, context.getCurrentDelay()); + Assert.assertEquals(100 * counter.get(), context.getCurrentElapsedTime()); + + return true; + } + ).thenAccept( + context -> { + Assert.assertTrue(counter.get() <= 5); + } + ).get(5, TimeUnit.SECONDS); + + executor.shutdownNow(); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java ---------------------------------------------------------------------- diff --git a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java index 64e9937..7f134f3 100644 --- a/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java +++ b/components/camel-core-xml/src/main/java/org/apache/camel/core/xml/AbstractCamelContextFactoryBean.java @@ -102,6 +102,7 @@ import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.PackageScanFilter; import org.apache.camel.spi.ProcessorFactory; import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.RouteController; import org.apache.camel.spi.RoutePolicyFactory; import org.apache.camel.spi.RuntimeEndpointRegistry; import org.apache.camel.spi.ShutdownStrategy; @@ -339,6 +340,13 @@ public abstract class AbstractCamelContextFactoryBean<T extends ModelCamelContex } } + // Route controller + RouteController routeController = getBeanForType(RouteController.class); + if (clusterService != null) { + LOG.info("Using RouteController: " + routeController); + getContext().setRouteController(routeController); + } + // set the default thread pool profile if defined initThreadPoolProfiles(getContext()); http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java index 690c632..6605029 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/CamelAutoConfiguration.java @@ -46,6 +46,7 @@ import org.apache.camel.spi.LifecycleStrategy; import org.apache.camel.spi.ManagementNamingStrategy; import org.apache.camel.spi.ManagementStrategy; import org.apache.camel.spi.ReloadStrategy; +import org.apache.camel.spi.RouteController; import org.apache.camel.spi.RoutePolicyFactory; import org.apache.camel.spi.RuntimeEndpointRegistry; import org.apache.camel.spi.ShutdownStrategy; @@ -439,6 +440,13 @@ public class CamelAutoConfiguration { camelContext.setSSLContextParameters(sslContextParametersSupplier.get()); } + // Route controller + RouteController routeController = getSingleBeanOfType(applicationContext, RouteController.class); + if (routeController != null) { + LOG.info("Using RouteController: " + routeController); + camelContext.setRouteController(routeController); + } + // set the default thread pool profile if defined initThreadPoolProfiles(applicationContext, camelContext); } http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java index 9e86ed4..208f2df 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesEndpoint.java @@ -18,6 +18,8 @@ package org.apache.camel.spring.boot.actuate.endpoint; import java.util.Date; import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonInclude; @@ -27,6 +29,7 @@ import org.apache.camel.CamelContext; import org.apache.camel.Route; import org.apache.camel.StatefulService; import org.apache.camel.api.management.mbean.ManagedRouteMBean; +import org.apache.camel.spi.RouteError; import org.apache.camel.spring.boot.actuate.endpoint.CamelRoutesEndpoint.RouteEndpointInfo; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.actuate.endpoint.Endpoint; @@ -67,6 +70,30 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo return null; } + public void startRoute(String id) throws Exception { + camelContext.getRouteController().startRoute(id); + } + + public void stopRoute(String id, Optional<Long> timeout, Optional<TimeUnit> timeUnit, Optional<Boolean> abortAfterTimeout) throws Exception { + if (timeout.isPresent()) { + camelContext.getRouteController().stopRoute(id, timeout.get(), timeUnit.orElse(TimeUnit.SECONDS), abortAfterTimeout.orElse(Boolean.TRUE)); + } else { + camelContext.getRouteController().stopRoute(id); + } + } + + public void suspendRoute(String id, Optional<Long> timeout, Optional<TimeUnit> timeUnit) throws Exception { + if (timeout.isPresent()) { + camelContext.getRouteController().suspendRoute(id, timeout.get(), timeUnit.orElse(TimeUnit.SECONDS)); + } else { + camelContext.getRouteController().suspendRoute(id); + } + } + + public void resumeRoute(String id) throws Exception { + camelContext.getRouteController().resumeRoute(id); + } + /** * Container for exposing {@link org.apache.camel.Route} information as JSON. */ @@ -82,7 +109,7 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo private final long uptimeMillis; - private String status; + private final String status; public RouteEndpointInfo(Route route) { this.id = route.getId(); @@ -92,6 +119,8 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo if (route instanceof StatefulService) { this.status = ((StatefulService) route).getStatus().name(); + } else { + this.status = null; } } @@ -127,16 +156,12 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo public RouteDetailsEndpointInfo(final CamelContext camelContext, final Route route) { super(route); + if (camelContext.getManagementStrategy().getManagementAgent() != null) { - this.routeDetails = new RouteDetails(camelContext.getManagedRoute(route.getId(), - ManagedRouteMBean.class)); + this.routeDetails = new RouteDetails(camelContext.getManagedRoute(route.getId(), ManagedRouteMBean.class)); } } - public RouteDetails getRouteDetails() { - return routeDetails; - } - @JsonInclude(JsonInclude.Include.NON_EMPTY) static class RouteDetails { @@ -188,6 +213,10 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo private long totalProcessingTime; + private RouteError lastError; + + private boolean hasRouteController; + RouteDetails(ManagedRouteMBean managedRoute) { try { this.deltaProcessingTime = managedRoute.getDeltaProcessingTime(); @@ -214,6 +243,8 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo this.oldestInflightExchangeId = managedRoute.getOldestInflightExchangeId(); this.redeliveries = managedRoute.getRedeliveries(); this.totalProcessingTime = managedRoute.getTotalProcessingTime(); + this.lastError = managedRoute.getLastError(); + this.hasRouteController = managedRoute.getHasRouteController(); } catch (Exception e) { // Ignore } @@ -314,6 +345,14 @@ public class CamelRoutesEndpoint extends AbstractEndpoint<List<RouteEndpointInfo public long getTotalProcessingTime() { return totalProcessingTime; } + + public RouteError getLastError() { + return lastError; + } + + public boolean getHasRouteController() { + return hasRouteController; + } } } http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpoint.java b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpoint.java index c90e187..9933c78 100644 --- a/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpoint.java +++ b/components/camel-spring-boot/src/main/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpoint.java @@ -16,15 +16,23 @@ */ package org.apache.camel.spring.boot.actuate.endpoint; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + import org.springframework.boot.actuate.endpoint.mvc.ActuatorMediaTypes; import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestAttribute; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; /** * Adapter to expose {@link CamelRoutesEndpoint} as an {@link MvcEndpoint}. @@ -42,20 +50,157 @@ public class CamelRoutesMvcEndpoint extends EndpointMvcAdapter { this.delegate = delegate; } - @GetMapping(value = "/{id}", produces = { + // ******************************************** + // Endpoints + // ******************************************** + + @ResponseBody + @GetMapping( + value = "/{id}/info", + produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, - MediaType.APPLICATION_JSON_VALUE }) + MediaType.APPLICATION_JSON_VALUE + } + ) + public Object info( + @PathVariable String id) { + + return doIfEnabled(() -> { + Object result = delegate.getRouteDetailsInfo(id); + if (result == null) { + throw new NoSuchRouteException("No such route " + id); + } + + return result; + }); + } + @ResponseBody - public Object get(@PathVariable String id) { + @PostMapping( + value = "/{id}/stop", + produces = { + ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, + MediaType.APPLICATION_JSON_VALUE + } + ) + public Object stop( + @PathVariable String id, + @RequestAttribute(required = false) Long timeout, + @RequestAttribute(required = false) Boolean abortAfterTimeout) { + + return doIfEnabled(() -> { + try { + delegate.stopRoute( + id, + Optional.ofNullable(timeout), + Optional.of(TimeUnit.SECONDS), + Optional.ofNullable(abortAfterTimeout) + ); + } catch (Exception e) { + throw new GenericException("Error stopping route " + id, e); + } + + return ResponseEntity.ok().build(); + }); + } + + @ResponseBody + @PostMapping( + value = "/{id}/start", + produces = { + ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, + MediaType.APPLICATION_JSON_VALUE + } + ) + public Object start( + @PathVariable String id) { + + return doIfEnabled(() -> { + try { + delegate.startRoute(id); + } catch (Exception e) { + throw new GenericException("Error starting route " + id, e); + } + + return ResponseEntity.ok().build(); + }); + } + + @ResponseBody + @PostMapping( + value = "/{id}/suspend", + produces = { + ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, + MediaType.APPLICATION_JSON_VALUE + } + ) + public Object suspend( + @PathVariable String id, + @RequestAttribute(required = false) Long timeout) { + + return doIfEnabled(() -> { + try { + delegate.suspendRoute( + id, + Optional.ofNullable(timeout), + Optional.of(TimeUnit.SECONDS) + ); + } catch (Exception e) { + throw new GenericException("Error suspending route " + id, e); + } + + return ResponseEntity.ok().build(); + }); + } + + @ResponseBody + @PostMapping( + value = "/{id}/resume", + produces = { + ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, + MediaType.APPLICATION_JSON_VALUE + } + ) + public Object resume( + @PathVariable String id) { + + return doIfEnabled(() -> { + try { + delegate.resumeRoute(id); + } catch (Exception e) { + throw new GenericException("Error resuming route " + id, e); + } + + return ResponseEntity.ok().build(); + }); + } + + // ******************************************** + // Helpers + // ******************************************** + + private Object doIfEnabled(Supplier<Object> supplier) { if (!delegate.isEnabled()) { return getDisabledResponse(); } - Object result = delegate.getRouteDetailsInfo(id); - if (result == null) { - result = NOT_FOUND; + return supplier.get(); + } + + @SuppressWarnings("serial") + @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) + public static class GenericException extends RuntimeException { + public GenericException(String message, Throwable cause) { + super(message, cause); + } + } - return result; + @SuppressWarnings("serial") + @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such route") + public static class NoSuchRouteException extends RuntimeException { + public NoSuchRouteException(String message) { + super(message); + } } } http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/components/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpointTest.java ---------------------------------------------------------------------- diff --git a/components/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpointTest.java b/components/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpointTest.java index 63e47cf..9d752f2 100644 --- a/components/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpointTest.java +++ b/components/camel-spring-boot/src/test/java/org/apache/camel/spring/boot/actuate/endpoint/CamelRoutesMvcEndpointTest.java @@ -59,7 +59,7 @@ public class CamelRoutesMvcEndpointTest extends Assert { @Test public void testMvcRoutesEndpoint() throws Exception { - Object result = endpoint.get("foo-route"); + Object result = endpoint.info("foo-route"); assertTrue(result instanceof RouteDetailsEndpointInfo); assertEquals("foo-route", ((RouteDetailsEndpointInfo)result).getId()); http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/camel-example-spring-boot-routecontroller/pom.xml ---------------------------------------------------------------------- diff --git a/examples/camel-example-spring-boot-routecontroller/pom.xml b/examples/camel-example-spring-boot-routecontroller/pom.xml new file mode 100644 index 0000000..b30b67e --- /dev/null +++ b/examples/camel-example-spring-boot-routecontroller/pom.xml @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel.example</groupId> + <artifactId>examples</artifactId> + <version>2.20.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-example-spring-boot-routecontroller</artifactId> + <name>Camel :: Example :: Spring Boot :: Route Controller</name> + <description>An example showing how to work with Camel Route Controller and Spring Boot</description> + + <properties> + <category>Beginner</category> + + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <spring.boot-version>${spring-boot-version}</spring.boot-version> + </properties> + + <dependencyManagement> + <dependencies> + <!-- Spring Boot BOM --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-dependencies</artifactId> + <version>${spring.boot-version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + <!-- Camel BOM --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-boot-dependencies</artifactId> + <version>${project.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + + <dependency> + <groupId>org.jolokia</groupId> + <artifactId>jolokia-core</artifactId> + </dependency> + + <!-- Spring Boot --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-undertow</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-actuator</artifactId> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-configuration-processor</artifactId> + <optional>true</optional> + <version>${spring-boot-version}</version> + </dependency> + + <!-- Camel --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-undertow-starter</artifactId> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-stream-starter</artifactId> + </dependency> + + <!-- test --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-test-spring</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring-boot-version}</version> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>jdk9-build</id> + <activation> + <jdk>9</jdk> + </activation> + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <configuration> + <argLine>--add-modules java.xml.bind --add-opens java.base/java.lang=ALL-UNNAMED</argLine> + </configuration> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/camel-example-spring-boot-routecontroller/readme.adoc ---------------------------------------------------------------------- diff --git a/examples/camel-example-spring-boot-routecontroller/readme.adoc b/examples/camel-example-spring-boot-routecontroller/readme.adoc new file mode 100644 index 0000000..5b54d9c --- /dev/null +++ b/examples/camel-example-spring-boot-routecontroller/readme.adoc @@ -0,0 +1,44 @@ +# Camel Route Controller Example Spring Boot + +This example shows how to work with a simple Apache Camel application using Spring Boot and a Route Controller. + +## How to run + +You can run this example using + + mvn spring-boot:run + +Beside JMX you can use Spring Boot Endpoints to interact with the routes: + +* To get info about the routes ++ +[source] +---- +curl -XGET -s http://localhost:8080/camel/routes +---- + +* To get info about a route ++ +[source] +---- +curl -XGET -s http://localhost:8080/camel/routes/${id}/info +---- + +* To stop a route ++ +[source] +---- +curl -XPOST -s http://localhost:8080/camel/routes/{id}/stop +---- + +* To start a route ++ +[source] +---- +curl -XPOST -s http://localhost:8080/camel/routes/{id}/stop +---- + + +## More information + +You can find more information about Apache Camel at the website: http://camel.apache.org/ http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/Application.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/Application.java b/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/Application.java new file mode 100644 index 0000000..2a97fed --- /dev/null +++ b/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/Application.java @@ -0,0 +1,37 @@ +/** + * 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 sample.camel; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +//CHECKSTYLE:OFF +/** + * A sample Spring Boot application that starts the Camel routes. + */ +@SpringBootApplication +public class Application { + + /** + * A main method to start this application. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} +//CHECKSTYLE:ON http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/ApplicationRoutes.java ---------------------------------------------------------------------- diff --git a/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/ApplicationRoutes.java b/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/ApplicationRoutes.java new file mode 100644 index 0000000..bd1473a --- /dev/null +++ b/examples/camel-example-spring-boot-routecontroller/src/main/java/sample/camel/ApplicationRoutes.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 sample.camel; + +import org.apache.camel.builder.RouteBuilder; +import org.springframework.stereotype.Component; + +/** + * + */ +@Component +public class ApplicationRoutes extends RouteBuilder { + @Override + public void configure() throws Exception { + from("timer:foo?period=5s") + .id("foo") + .startupOrder(2) + .log("From timer (foo) ..."); + + from("timer:bar?period=5s") + .id("bar") + .startupOrder(1) + .log("From timer (bar) ..."); + + from("undertow:http://localhost:9011") + .id("undertow") + .log("From undertow ..."); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/camel-example-spring-boot-routecontroller/src/main/resources/application.properties ---------------------------------------------------------------------- diff --git a/examples/camel-example-spring-boot-routecontroller/src/main/resources/application.properties b/examples/camel-example-spring-boot-routecontroller/src/main/resources/application.properties new file mode 100644 index 0000000..89ea7d1 --- /dev/null +++ b/examples/camel-example-spring-boot-routecontroller/src/main/resources/application.properties @@ -0,0 +1,40 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +debug = false + +logging.level.org.springframework = INFO +logging.level.org.apache.camel.spring.boot = INFO +logging.level.org.apache.camel.impl = DEBUG +logging.level.org.apache.camel.util.backoff = DEBUG +logging.level.sample.camel = DEBUG + +endpoints.enabled = false +endpoints.jmx.enabled = false +endpoints.health.enabled = true +endpoints.camelroutes.path = /camel/routes +endpoints.camelroutes.enabled = true + +management.security.enabled = false + +camel.springboot.name = SampleSupervisingRouteController + +camel.supervising.controller.enabled = true +camel.supervising.controller.back-off.delay = 5s +camel.supervising.controller.back-off.max-attempts = 10 +camel.supervising.controller.routes.undertow.back-off.delay = 10s +camel.supervising.controller.routes.undertow.back-off.max-attempts = 3 http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/examples/pom.xml ---------------------------------------------------------------------- diff --git a/examples/pom.xml b/examples/pom.xml index ecbae00..d835d62 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -100,6 +100,7 @@ <module>camel-example-spring-boot-metrics</module> <module>camel-example-spring-boot-rest-jpa</module> <module>camel-example-spring-boot-rest-swagger</module> + <module>camel-example-spring-boot-routecontroller</module> <module>camel-example-spring-boot-servicecall</module> <module>camel-example-spring-cloud-servicecall</module> <module>camel-example-spring-javaconfig</module> http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerAutoConfiguration.java ---------------------------------------------------------------------- diff --git a/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerAutoConfiguration.java b/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerAutoConfiguration.java new file mode 100644 index 0000000..d872ec3 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerAutoConfiguration.java @@ -0,0 +1,79 @@ +/** + * 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.impl.springboot; + +import java.util.Map; +import java.util.Optional; + +import org.apache.camel.converter.TimePatternConverter; +import org.apache.camel.impl.SupervisingRouteController; +import org.apache.camel.impl.springboot.SupervisingRouteControllerConfiguration.BackOffConfiguration; +import org.apache.camel.impl.springboot.SupervisingRouteControllerConfiguration.RouteConfiguration; +import org.apache.camel.spi.RouteController; +import org.apache.camel.spring.boot.CamelAutoConfiguration; +import org.apache.camel.util.backoff.BackOff; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; + +@Configuration +@AutoConfigureBefore(CamelAutoConfiguration.class) +@ConditionalOnProperty(prefix = "camel.supervising.controller", name = "enabled") +@EnableConfigurationProperties(SupervisingRouteControllerConfiguration.class) +public class SupervisingRouteControllerAutoConfiguration { + @Autowired + private SupervisingRouteControllerConfiguration configuration; + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) + @ConditionalOnMissingBean + public RouteController routeController() { + SupervisingRouteController controller = new SupervisingRouteController(); + + controller.setDefaultBackOff(configureBackOff(Optional.empty(), configuration.getBackOff())); + + for (Map.Entry<String, RouteConfiguration> entry: configuration.getRoutes().entrySet()) { + controller.setBackOff( + entry.getKey(), + configureBackOff( + Optional.ofNullable(controller.getDefaultBackOff()), + entry.getValue().getBackOff() + ) + ); + } + + return controller; + } + + private BackOff configureBackOff(Optional<BackOff> template, BackOffConfiguration conf) { + final BackOff.Builder builder = template.map(t -> BackOff.builder().read(t)).orElseGet(BackOff::builder); + + Optional.ofNullable(conf.getDelay()).map(TimePatternConverter::toMilliSeconds).ifPresent(builder::delay); + Optional.ofNullable(conf.getMaxDelay()).map(TimePatternConverter::toMilliSeconds).ifPresent(builder::maxDelay); + Optional.ofNullable(conf.getMaxElapsedTime()).map(TimePatternConverter::toMilliSeconds).ifPresent(builder::maxElapsedTime); + Optional.ofNullable(conf.getMaxAttempts()).ifPresent(builder::maxAttempts); + Optional.ofNullable(conf.getMultiplier()).ifPresent(builder::multiplier); + + return builder.build(); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerConfiguration.java ---------------------------------------------------------------------- diff --git a/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerConfiguration.java b/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerConfiguration.java new file mode 100644 index 0000000..4460671 --- /dev/null +++ b/platforms/spring-boot/components-starter/camel-core-starter/src/main/java/org/apache/camel/impl/springboot/SupervisingRouteControllerConfiguration.java @@ -0,0 +1,109 @@ +/** + * 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.impl.springboot; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "camel.supervising.controller") +public class SupervisingRouteControllerConfiguration { + private boolean enabled; + private BackOffConfiguration backOff = new BackOffConfiguration(); + private Map<String, RouteConfiguration> routes = new HashMap<>(); + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public BackOffConfiguration getBackOff() { + return backOff; + } + + public Map<String, RouteConfiguration> getRoutes() { + return routes; + } + + // ***************************************** + // Configuration Classes + // ***************************************** + + public static class RouteConfiguration { + private BackOffConfiguration backOff; + + public BackOffConfiguration getBackOff() { + return backOff; + } + + public void setBackOff(BackOffConfiguration backOff) { + this.backOff = backOff; + } + } + + public static class BackOffConfiguration { + private String delay; + private String maxDelay; + private String maxElapsedTime; + private Long maxAttempts; + private Double multiplier; + + public String getDelay() { + return delay; + } + + public void setDelay(String delay) { + this.delay = delay; + } + + public String getMaxDelay() { + return maxDelay; + } + + public void setMaxDelay(String maxDelay) { + this.maxDelay = maxDelay; + } + + public String getMaxElapsedTime() { + return maxElapsedTime; + } + + public void setMaxElapsedTime(String maxElapsedTime) { + this.maxElapsedTime = maxElapsedTime; + } + + public Long getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(Long maxAttempts) { + this.maxAttempts = maxAttempts; + } + + public Double getMultiplier() { + return multiplier; + } + + public void setMultiplier(Double multiplier) { + this.multiplier = multiplier; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/e68111ec/platforms/spring-boot/components-starter/camel-core-starter/src/main/resources/META-INF/spring.factories ---------------------------------------------------------------------- diff --git a/platforms/spring-boot/components-starter/camel-core-starter/src/main/resources/META-INF/spring.factories b/platforms/spring-boot/components-starter/camel-core-starter/src/main/resources/META-INF/spring.factories index 253b608..656661c 100644 --- a/platforms/spring-boot/components-starter/camel-core-starter/src/main/resources/META-INF/spring.factories +++ b/platforms/spring-boot/components-starter/camel-core-starter/src/main/resources/META-INF/spring.factories @@ -29,6 +29,7 @@ org.apache.camel.impl.springboot.StringDataFormatAutoConfiguration,\ org.apache.camel.impl.springboot.ZipDataFormatAutoConfiguration,\ org.apache.camel.impl.springboot.GzipDataFormatAutoConfiguration,\ org.apache.camel.impl.springboot.SerializationDataFormatAutoConfiguration,\ +org.apache.camel.impl.springboot.SupervisingRouteControllerAutoConfiguration,\ org.apache.camel.language.constant.springboot.ConstantLanguageAutoConfiguration,\ org.apache.camel.language.simple.springboot.SimpleLanguageAutoConfiguration,\ org.apache.camel.language.ref.springboot.RefLanguageAutoConfiguration,\
