From edd3f90299e9395dea316d288d83bd5458c7c902 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Tue, 14 Feb 2012 17:06:30 -0500 Subject: [PATCH 1/3] INTSAMPLES-64 Poller Sample using custom Trigger For reference: https://jira.springsource.org/browse/INTSAMPLES-64 Shows how the polling period can be changed at runtime using a custom trigger. --- README.md | 1 + intermediate/dynamic-poller/README.md | 36 +++++ intermediate/dynamic-poller/pom.xml | 70 +++++++++ .../poller/DynamicPeriodicTrigger.java | 140 ++++++++++++++++++ .../integration/samples/poller/Main.java | 86 +++++++++++ .../spring-integration-context.xml | 21 +++ .../src/main/resources/log4j.xml | 28 ++++ 7 files changed, 382 insertions(+) create mode 100644 intermediate/dynamic-poller/README.md create mode 100644 intermediate/dynamic-poller/pom.xml create mode 100644 intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java create mode 100644 intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java create mode 100644 intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml create mode 100644 intermediate/dynamic-poller/src/main/resources/log4j.xml diff --git a/README.md b/README.md index 6eae87cc..985ad8ec 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ This is a good place to get started. The samples here are technically motivated This category targets developers who are already more familiar with the Spring Integration framework (past getting started), but need some more guidance while resolving more advanced technical problems that you have to deal with when switching to a Messaging architecture. For example, if you are looking for an answer on how to handle errors in various scenarios, or how to properly configure an **Aggregator** for the situations where some messages might not ever arrive for aggregation, or any other issue that goes beyond a basic understanding and configuration of a particular component to address "what else you can do?" types of problems, this would be the right place to find relevant examples. +* **dynamic-poller** - Example shows usage of a **Poller** with a custom **Trigger** to change polling periods at runtime * **async-gateway** - Example shows usage of an **Asynchronous Gateway** * **errorhandling** - Demonstrates basic **Error Handling** capabilities of Spring Integration * **file-processing** - Sample demonstrates how to wire a message flow to process files either sequentially (maintain the order) or concurrently (no order). diff --git a/intermediate/dynamic-poller/README.md b/intermediate/dynamic-poller/README.md new file mode 100644 index 00000000..e5dfe951 --- /dev/null +++ b/intermediate/dynamic-poller/README.md @@ -0,0 +1,36 @@ +Dynamic Poller Sample +===================== + +By default this application will (poll) print out the current system time every 5 seconds. From the command line you can enter a non-negative numeric value to change the polling period (in milliseconds) at runtime. + +Under the cover an **Inbound Channel Adapter** polls for the current system time. The **Poller** which is used by the **Inbound Channel Adapter** is configured with a custom trigger. The resulting message contains as payload the time in milliseconds and the message is sent to a **Logging Channel Adapter**, which will print the time to the command prompt. + +You can run the application by either + +* running the "Main" class from within STS (Right-click on Main class --> Run As --> Java Application) +* or from the command line using the [Exec Maven Plugin](http://mojo.codehaus.org/exec-maven-plugin/): + - mvn package exec:java + +You should see output like the following: + + INFO : org.springframework.integration.samples.poller.Main - + ========================================================== + + Welcome to the Spring Integration Dynamic Poller Sample! + + For more information please visit: + http://www.springsource.org/spring-integration + + ========================================================== + INFO : org.springframework.integration.samples.poller.Main - + ========================================================= + + Please press 'q + Enter' to quit the application. + + ========================================================= + Please enter a non-negative numeric value and press : INFO : org.springframework.integration.samples.poller - 1329257519165 + INFO : org.springframework.integration.samples.poller - 1329257524207 + INFO : org.springframework.integration.samples.poller - 1329257529208 + INFO : org.springframework.integration.samples.poller - 1329257534209 + INFO : org.springframework.integration.samples.poller - 1329257539210 + diff --git a/intermediate/dynamic-poller/pom.xml b/intermediate/dynamic-poller/pom.xml new file mode 100644 index 00000000..f998dfe1 --- /dev/null +++ b/intermediate/dynamic-poller/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + org.springframework.integration.samples + dynamic-poller + 2.1.0.BUILD-SNAPSHOT + jar + + dynamic-poller + http://www.springsource.org/spring-integration + + + UTF-8 + 2.1.0.RELEASE + 1.2.16 + 4.10 + org.springframework.integration.samples.poller.Main + + + + + org.springframework.integration + spring-integration-core + ${spring.integration.version} + + + log4j + log4j + ${log4j.version} + + + + junit + junit + ${junit.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.5 + 1.5 + -Xlint:all + true + true + + + + org.codehaus.mojo + exec-maven-plugin + 1.2 + + ${java.main.class} + + + + + + + repo.springsource.org.milestone + Spring Framework Maven Milestone Repository + https://repo.springsource.org/milestone + + + \ No newline at end of file diff --git a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java new file mode 100644 index 00000000..b0ad47d1 --- /dev/null +++ b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java @@ -0,0 +1,140 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed 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.springframework.integration.samples.poller; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import org.springframework.scheduling.Trigger; +import org.springframework.scheduling.TriggerContext; +import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.util.Assert; + + +/** + * This is a dynamically changeable {@link Trigger}. It is based on the + * {@link PeriodicTrigger} implementations. However, the fields of this dynamic + * trigger are not final and the properties can be inspected and set via + * explicit getters and setters. + * + * @author Gunnar Hillert + * + */ +public class DynamicPeriodicTrigger implements Trigger { + + private volatile long period; + + private volatile TimeUnit timeUnit; + + private volatile long initialDelay = 0; + + private volatile boolean fixedRate = false; + + /** + * Create a trigger with the given period in milliseconds. + */ + public DynamicPeriodicTrigger(long period) { + this(period, null); + } + + /** + * Create a trigger with the given period and time unit. The time unit will + * apply not only to the period but also to any 'initialDelay' value, if + * configured on this Trigger later via {@link #setInitialDelay(long)}. + */ + public DynamicPeriodicTrigger(long period, TimeUnit timeUnit) { + Assert.isTrue(period >= 0, "period must not be negative"); + this.timeUnit = (timeUnit != null) ? timeUnit : TimeUnit.MILLISECONDS; + this.period = this.timeUnit.toMillis(period); + } + + /** + * Specify the delay for the initial execution. It will be evaluated in + * terms of this trigger's {@link TimeUnit}. If no time unit was explicitly + * provided upon instantiation, the default is milliseconds. + */ + public void setInitialDelay(long initialDelay) { + this.initialDelay = this.timeUnit.toMillis(initialDelay); + } + + /** + * Specify whether the periodic interval should be measured between the + * scheduled start times rather than between actual completion times. + * The latter, "fixed delay" behavior, is the default. + */ + public void setFixedRate(boolean fixedRate) { + this.fixedRate = fixedRate; + } + + /** + * Returns the time after which a task should run again. + */ + public Date nextExecutionTime(TriggerContext triggerContext) { + if (triggerContext.lastScheduledExecutionTime() == null) { + return new Date(System.currentTimeMillis() + this.initialDelay); + } + else if (this.fixedRate) { + return new Date(triggerContext.lastScheduledExecutionTime().getTime() + this.period); + } + return new Date(triggerContext.lastCompletionTime().getTime() + this.period); + } + + public long getPeriod() { + return period; + } + + public void setPeriod(long period) { + this.period = period; + } + + public TimeUnit getTimeUnit() { + return timeUnit; + } + + public void setTimeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; + } + + public long getInitialDelay() { + return initialDelay; + } + + public boolean isFixedRate() { + return fixedRate; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DynamicPeriodicTrigger)) { + return false; + } + DynamicPeriodicTrigger other = (DynamicPeriodicTrigger) obj; + return this.fixedRate == other.fixedRate + && this.initialDelay == other.initialDelay + && this.period == other.period; + } + + @Override + public int hashCode() { + return (this.fixedRate ? 14 : 41) + + (int) (38 * this.period) + + (int) (43 * this.initialDelay); + } + +} diff --git a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java new file mode 100644 index 00000000..d4f5dd90 --- /dev/null +++ b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java @@ -0,0 +1,86 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed 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.springframework.integration.samples.poller; + +import java.util.Scanner; + +import org.apache.log4j.Logger; +import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * Starts the Spring Context and will initialize the Spring Integration routes. + * + * @author Gunnar Hillert + * @version 1.0 + * + */ +public final class Main { + + private static final Logger LOGGER = Logger.getLogger(Main.class); + + private Main() { } + + /** + * Load the Spring Integration Application Context + * + * @param args - command line arguments + */ + public static void main(final String... args) { + + LOGGER.info("\n==========================================================" + + "\n " + + "\n Welcome to the Spring Integration Dynamic Poller Sample! " + + "\n " + + "\n For more information please visit: " + + "\n http://www.springsource.org/spring-integration " + + "\n " + + "\n==========================================================" ); + + final AbstractApplicationContext context = + new ClassPathXmlApplicationContext("classpath:META-INF/spring/integration/*-context.xml"); + + context.registerShutdownHook(); + + final Scanner scanner = new Scanner(System.in); + + final DynamicPeriodicTrigger trigger = context.getBean(DynamicPeriodicTrigger.class); + + LOGGER.info("\n=========================================================" + + "\n " + + "\n Please press 'q + Enter' to quit the application. " + + "\n " + + "\n=========================================================" ); + + System.out.print("Please enter a non-negative numeric value and press : "); + + while (!scanner.hasNext("q")) { + + int triggerPeriod = scanner.nextInt(); + + System.out.println(String.format("Setting trigger period to '%s' ms", triggerPeriod)); + + trigger.setPeriod(triggerPeriod); + + System.out.print("Please enter a non-negative numeric value and press : "); + } + + LOGGER.info("Exiting application...bye."); + + System.exit(0); + + } +} diff --git a/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml b/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml new file mode 100644 index 00000000..15431c3b --- /dev/null +++ b/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/intermediate/dynamic-poller/src/main/resources/log4j.xml b/intermediate/dynamic-poller/src/main/resources/log4j.xml new file mode 100644 index 00000000..678b69b3 --- /dev/null +++ b/intermediate/dynamic-poller/src/main/resources/log4j.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 3e7d121dd4bcf65736bd7bbe0e593cc2e0eccdd8 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Wed, 15 Feb 2012 12:17:52 -0500 Subject: [PATCH 2/3] INTSAMPLES-64 - Code Review Changes * Better documentation in README.md * Better error handling * Using a better data-format --- intermediate/dynamic-poller/README.md | 18 +++++++------ .../poller/DynamicPeriodicTrigger.java | 3 +++ .../integration/samples/poller/Main.java | 25 ++++++++++++++----- .../spring-integration-context.xml | 2 +- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/intermediate/dynamic-poller/README.md b/intermediate/dynamic-poller/README.md index e5dfe951..99921203 100644 --- a/intermediate/dynamic-poller/README.md +++ b/intermediate/dynamic-poller/README.md @@ -3,9 +3,13 @@ Dynamic Poller Sample By default this application will (poll) print out the current system time every 5 seconds. From the command line you can enter a non-negative numeric value to change the polling period (in milliseconds) at runtime. -Under the cover an **Inbound Channel Adapter** polls for the current system time. The **Poller** which is used by the **Inbound Channel Adapter** is configured with a custom trigger. The resulting message contains as payload the time in milliseconds and the message is sent to a **Logging Channel Adapter**, which will print the time to the command prompt. +Under the cover an **Inbound Channel Adapter** polls for the current system time. The **Poller**, which is used by the **Inbound Channel Adapter**, is configured with a custom Trigger (*org.springframework.integration.samples.poller.DynamicPeriodicTrigger*). The resulting message contains as payload the time in milliseconds and the message is sent to a **Logging Channel Adapter**, which will print the time to the command prompt. -You can run the application by either +When changing the polling period, the change to the trigger will occur after the NEXT poll at the current rate. Therefore, if the current polling period is 60 seconds and you change it to 1 second, it can take up to 60 seconds to take effect, depending on when in the polling cycle you make the change. + +### Running the Application + +You can run the application by either: * running the "Main" class from within STS (Right-click on Main class --> Run As --> Java Application) * or from the command line using the [Exec Maven Plugin](http://mojo.codehaus.org/exec-maven-plugin/): @@ -28,9 +32,9 @@ You should see output like the following: Please press 'q + Enter' to quit the application. ========================================================= - Please enter a non-negative numeric value and press : INFO : org.springframework.integration.samples.poller - 1329257519165 - INFO : org.springframework.integration.samples.poller - 1329257524207 - INFO : org.springframework.integration.samples.poller - 1329257529208 - INFO : org.springframework.integration.samples.poller - 1329257534209 - INFO : org.springframework.integration.samples.poller - 1329257539210 + Please enter a non-negative numeric value and press : + INFO : org.springframework.integration.samples.poller - 2012-02-15 12:14:37.503 + INFO : org.springframework.integration.samples.poller - 2012-02-15 12:14:42.504 + INFO : org.springframework.integration.samples.poller - 2012-02-15 12:14:47.505 + diff --git a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java index b0ad47d1..c3954779 100644 --- a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java +++ b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java @@ -67,6 +67,7 @@ public class DynamicPeriodicTrigger implements Trigger { * provided upon instantiation, the default is milliseconds. */ public void setInitialDelay(long initialDelay) { + Assert.isTrue(initialDelay >= 0, "initialDelay must not be negative"); this.initialDelay = this.timeUnit.toMillis(initialDelay); } @@ -97,6 +98,7 @@ public class DynamicPeriodicTrigger implements Trigger { } public void setPeriod(long period) { + Assert.isTrue(period >= 0, "period must not be negative"); this.period = period; } @@ -105,6 +107,7 @@ public class DynamicPeriodicTrigger implements Trigger { } public void setTimeUnit(TimeUnit timeUnit) { + Assert.notNull(timeUnit, "timeUnit must not be null"); this.timeUnit = timeUnit; } diff --git a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java index d4f5dd90..187980b7 100644 --- a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java +++ b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/Main.java @@ -66,16 +66,29 @@ public final class Main { + "\n=========================================================" ); System.out.print("Please enter a non-negative numeric value and press : "); - - while (!scanner.hasNext("q")) { + + while (true) { - int triggerPeriod = scanner.nextInt(); + final String input = scanner.nextLine(); - System.out.println(String.format("Setting trigger period to '%s' ms", triggerPeriod)); + if("q".equals(input.trim())) { + break; + } - trigger.setPeriod(triggerPeriod); - + try { + + int triggerPeriod = Integer.valueOf(input); + + System.out.println(String.format("Setting trigger period to '%s' ms", triggerPeriod)); + + trigger.setPeriod(triggerPeriod); + + } catch (Exception e) { + LOGGER.error("An exception was caught: " + e); + } + System.out.print("Please enter a non-negative numeric value and press : "); + } LOGGER.info("Exiting application...bye."); diff --git a/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml b/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml index 15431c3b..9fd09af6 100644 --- a/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml +++ b/intermediate/dynamic-poller/src/main/resources/META-INF/spring/integration/spring-integration-context.xml @@ -7,7 +7,7 @@ xmlns:int="http://www.springframework.org/schema/integration" xmlns:task="http://www.springframework.org/schema/task"> - + From c53b236d77b43344af4265defd1de4933ea590e1 Mon Sep 17 00:00:00 2001 From: Gunnar Hillert Date: Wed, 15 Feb 2012 14:20:28 -0500 Subject: [PATCH 3/3] INTSAMPLES-64 - Refactored Constructor of DynamicPeriodicTrigger --- .../poller/DynamicPeriodicTrigger.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java index c3954779..8ce90ff3 100644 --- a/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java +++ b/intermediate/dynamic-poller/src/main/java/org/springframework/integration/samples/poller/DynamicPeriodicTrigger.java @@ -44,23 +44,32 @@ public class DynamicPeriodicTrigger implements Trigger { private volatile boolean fixedRate = false; /** - * Create a trigger with the given period in milliseconds. + * Create a trigger with the given period in milliseconds. The underlying + * {@link TimeUnit} will be initialized to TimeUnit.MILLISECONDS. + * + * @param period Must not be negative */ public DynamicPeriodicTrigger(long period) { - this(period, null); + this(period, TimeUnit.MILLISECONDS); } /** * Create a trigger with the given period and time unit. The time unit will * apply not only to the period but also to any 'initialDelay' value, if * configured on this Trigger later via {@link #setInitialDelay(long)}. + * + * @param period Must not be negative + * @param timeUnit Must not be null + * */ public DynamicPeriodicTrigger(long period, TimeUnit timeUnit) { Assert.isTrue(period >= 0, "period must not be negative"); - this.timeUnit = (timeUnit != null) ? timeUnit : TimeUnit.MILLISECONDS; + Assert.notNull(timeUnit, "timeUnit must not be null"); + + this.timeUnit = timeUnit; this.period = this.timeUnit.toMillis(period); } - + /** * Specify the delay for the initial execution. It will be evaluated in * terms of this trigger's {@link TimeUnit}. If no time unit was explicitly @@ -97,9 +106,16 @@ public class DynamicPeriodicTrigger implements Trigger { return period; } + /** + * Specify the period of the trigger. It will be evaluated in + * terms of this trigger's {@link TimeUnit}. If no time unit was explicitly + * provided upon instantiation, the default is milliseconds. + * + * @param period Must not be negative + */ public void setPeriod(long period) { Assert.isTrue(period >= 0, "period must not be negative"); - this.period = period; + this.period = this.timeUnit.toMillis(period); } public TimeUnit getTimeUnit() {