From 76bee761d4f3aac13cea132786c0fcac210cf035 Mon Sep 17 00:00:00 2001 From: abilan Date: Fri, 19 May 2023 10:50:10 -0400 Subject: [PATCH 1/2] GH-8625: Add Duration support for `` Fixes https://github.com/spring-projects/spring-integration/issues/8625 The duration can be represented in a ISO 8601 format, e.g. `PT10S`, `P1D` etc. The `` and `@Poller` don't support such a format. * Introduce a `PeriodicTriggerFactoryBean` to accept string values for trigger options and parse them manually before creating the target `PeriodicTrigger` * Use this `PeriodicTriggerFactoryBean` in the `PollerParser` and `AbstractMethodAnnotationPostProcessor` where we parse options for the `PeriodicTrigger` * Modify tests to ensure that feature works * Document the duration option * Add more cross-links into polling docs * Fix typos in the affected doc files * Add `-parameters` for compiler options since SF 6.1 does not support `-debug` anymore for method parameter names discovery --- build.gradle | 1 + .../integration/annotation/Poller.java | 14 +-- ...AbstractMethodAnnotationPostProcessor.java | 16 +-- .../config/PeriodicTriggerFactoryBean.java | 115 ++++++++++++++++++ .../integration/config/xml/PollerParser.java | 60 ++++----- .../integration/config/spring-integration.xsd | 11 +- .../config/xml/PollerParserTests.java | 10 +- .../config/xml/pollerWithReceiveTimeout.xml | 2 +- .../EnableIntegrationTests.properties | 2 +- src/reference/asciidoc/channel-adapter.adoc | 5 +- src/reference/asciidoc/endpoint.adoc | 53 ++++---- src/reference/asciidoc/polling-consumer.adoc | 2 + src/reference/asciidoc/whats-new.adoc | 3 + 13 files changed, 210 insertions(+), 84 deletions(-) create mode 100644 spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java diff --git a/build.gradle b/build.gradle index c9156eadfe1..a82aa151bd0 100644 --- a/build.gradle +++ b/build.gradle @@ -212,6 +212,7 @@ configure(javaProjects) { subproject -> compileJava { options.release = 17 + options.compilerArgs << '-parameters' } compileTestJava { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java index 792438061a4..83faf806a6a 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/Poller.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2023 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. @@ -65,16 +65,16 @@ String maxMessagesPerPoll() default ""; /** - * @return The fixed delay in milliseconds to create the - * {@link org.springframework.scheduling.support.PeriodicTrigger}. Can be specified as - * 'property placeholder', e.g. {@code ${poller.fixedDelay}}. + * @return The fixed delay in milliseconds or a {@link java.time.Duration} compliant string + * to create the {@link org.springframework.scheduling.support.PeriodicTrigger}. + * Can be specified as 'property placeholder', e.g. {@code ${poller.fixedDelay}}. */ String fixedDelay() default ""; /** - * @return The fixed rate in milliseconds to create the - * {@link org.springframework.scheduling.support.PeriodicTrigger} with - * {@code fixedRate}. Can be specified as 'property placeholder', e.g. + * @return The fixed rate in milliseconds or a {@link java.time.Duration} compliant string + * to create the {@link org.springframework.scheduling.support.PeriodicTrigger} with + * the {@code fixedRate} option. Can be specified as 'property placeholder', e.g. * {@code ${poller.fixedRate}}. */ String fixedRate() default ""; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java index abd651d10c0..bb075afb285 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/AbstractMethodAnnotationPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -18,7 +18,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -744,14 +743,11 @@ else if (StringUtils.hasText(cron)) { "The '@Poller' 'cron' attribute is mutually exclusive with other attributes."); trigger = new CronTrigger(cron); } - else if (StringUtils.hasText(fixedDelayValue)) { - Assert.state(!StringUtils.hasText(fixedRateValue), - "The '@Poller' 'fixedDelay' attribute is mutually exclusive with other attributes."); - trigger = new PeriodicTrigger(Duration.ofMillis(Long.parseLong(fixedDelayValue))); - } - else if (StringUtils.hasText(fixedRateValue)) { - trigger = new PeriodicTrigger(Duration.ofMillis(Long.parseLong(fixedRateValue))); - ((PeriodicTrigger) trigger).setFixedRate(true); + else if (StringUtils.hasText(fixedDelayValue) || StringUtils.hasText(fixedRateValue)) { + PeriodicTriggerFactoryBean periodicTriggerFactoryBean = new PeriodicTriggerFactoryBean(); + periodicTriggerFactoryBean.setFixedDelayValue(fixedDelayValue); + periodicTriggerFactoryBean.setFixedRateValue(fixedRateValue); + trigger = periodicTriggerFactoryBean.getObject(); } //'Trigger' can be null. 'PollingConsumer' does fallback to the 'new PeriodicTrigger(10)'. pollerMetadata.setTrigger(trigger); diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java new file mode 100644 index 00000000000..adad9fdd8ee --- /dev/null +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 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 + * + * https://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.config; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +import org.springframework.beans.factory.FactoryBean; +import org.springframework.lang.Nullable; +import org.springframework.scheduling.support.PeriodicTrigger; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * The {@link FactoryBean} to produce a {@link PeriodicTrigger} + * based on parsing string values for its options. + * This class is mostly driver by the XML configuration requirements for + * {@link Duration} value representations for the respective attributes. + * + * @author Artem Bilan + * + * @since 6.2 + */ +public class PeriodicTriggerFactoryBean implements FactoryBean { + + @Nullable + private String fixedDelayValue; + + @Nullable + private String fixedRateValue; + + @Nullable + private String initialDelayValue; + + @Nullable + private TimeUnit timeUnit; + + public void setFixedDelayValue(String fixedDelayValue) { + this.fixedDelayValue = fixedDelayValue; + } + + public void setFixedRateValue(String fixedRateValue) { + this.fixedRateValue = fixedRateValue; + } + + public void setInitialDelayValue(String initialDelayValue) { + this.initialDelayValue = initialDelayValue; + } + + public void setTimeUnit(TimeUnit timeUnit) { + this.timeUnit = timeUnit; + } + + @Override + public PeriodicTrigger getObject() { + boolean hasFixedDelay = StringUtils.hasText(this.fixedDelayValue); + boolean hasFixedRate = StringUtils.hasText(this.fixedRateValue); + + Assert.isTrue(hasFixedDelay ^ hasFixedRate, + "One of the 'fixedDelayValue' or 'fixedRateValue' property must be provided but not both."); + + TimeUnit timeUnitToUse = this.timeUnit; + if (timeUnitToUse == null) { + timeUnitToUse = TimeUnit.MILLISECONDS; + } + + Duration duration = toDuration(hasFixedDelay ? this.fixedDelayValue : this.fixedRateValue, timeUnitToUse); + + PeriodicTrigger periodicTrigger = new PeriodicTrigger(duration); + periodicTrigger.setFixedRate(hasFixedRate); + if (StringUtils.hasText(this.initialDelayValue)) { + periodicTrigger.setInitialDelay(toDuration(this.initialDelayValue, timeUnitToUse)); + } + return periodicTrigger; + } + + @Override + public Class getObjectType() { + return PeriodicTrigger.class; + } + + private static Duration toDuration(String value, TimeUnit timeUnit) { + if (isDurationString(value)) { + return Duration.parse(value); + } + return toDuration(Long.parseLong(value), timeUnit); + } + + private static boolean isDurationString(String value) { + return (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))); + } + + private static boolean isP(char ch) { + return (ch == 'P' || ch == 'p'); + } + + private static Duration toDuration(long value, TimeUnit timeUnit) { + return Duration.of(value, timeUnit.toChronoUnit()); + } + +} diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java index 0bbc2113f49..ec60a15c5b8 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/xml/PollerParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2023 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. @@ -28,9 +28,9 @@ import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.integration.channel.MessagePublishingErrorHandler; +import org.springframework.integration.config.PeriodicTriggerFactoryBean; import org.springframework.integration.scheduling.PollerMetadata; import org.springframework.scheduling.support.CronTrigger; -import org.springframework.scheduling.support.PeriodicTrigger; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; @@ -45,12 +45,15 @@ */ public class PollerParser extends AbstractBeanDefinitionParser { - private static final String MULTIPLE_TRIGGER_DEFINITIONS = "A cannot specify more than one trigger configuration."; + private static final String MULTIPLE_TRIGGER_DEFINITIONS = + "A cannot specify more than one trigger configuration."; private static final String NO_TRIGGER_DEFINITIONS = "A must have one and only one trigger configuration."; @Override - protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) throws BeanDefinitionStoreException { + protected String resolveId(Element element, AbstractBeanDefinition definition, ParserContext parserContext) + throws BeanDefinitionStoreException { + String id = super.resolveId(element, definition, parserContext); if (element.getAttribute("default").equals("true")) { if (parserContext.getRegistry().isBeanNameInUse(PollerMetadata.DEFAULT_POLLER_METADATA_BEAN_NAME)) { @@ -102,29 +105,30 @@ else if (adviceChainElement != null) { String errorChannel = element.getAttribute("error-channel"); if (StringUtils.hasText(errorChannel)) { - BeanDefinitionBuilder errorHandler = BeanDefinitionBuilder.genericBeanDefinition(MessagePublishingErrorHandler.class); + BeanDefinitionBuilder errorHandler = + BeanDefinitionBuilder.genericBeanDefinition(MessagePublishingErrorHandler.class); errorHandler.addPropertyReference("defaultErrorChannel", errorChannel); metadataBuilder.addPropertyValue("errorHandler", errorHandler.getBeanDefinition()); } return metadataBuilder.getBeanDefinition(); } - private void configureTrigger(Element pollerElement, BeanDefinitionBuilder targetBuilder, ParserContext parserContext) { + private void configureTrigger(Element pollerElement, BeanDefinitionBuilder targetBuilder, + ParserContext parserContext) { + String triggerAttribute = pollerElement.getAttribute("trigger"); String fixedRateAttribute = pollerElement.getAttribute("fixed-rate"); String fixedDelayAttribute = pollerElement.getAttribute("fixed-delay"); String cronAttribute = pollerElement.getAttribute("cron"); String timeUnit = pollerElement.getAttribute("time-unit"); - List triggerBeanNames = new ArrayList(); + List triggerBeanNames = new ArrayList<>(); if (StringUtils.hasText(triggerAttribute)) { trigger(pollerElement, parserContext, triggerAttribute, timeUnit, triggerBeanNames); } - if (StringUtils.hasText(fixedRateAttribute)) { - fixedRate(parserContext, fixedRateAttribute, timeUnit, triggerBeanNames); - } - if (StringUtils.hasText(fixedDelayAttribute)) { - fixedDelay(parserContext, fixedDelayAttribute, timeUnit, triggerBeanNames); + if (StringUtils.hasText(fixedRateAttribute) || StringUtils.hasText(fixedDelayAttribute)) { + period(parserContext, fixedDelayAttribute, fixedRateAttribute, pollerElement.getAttribute("initial-delay"), + timeUnit, triggerBeanNames); } if (StringUtils.hasText(cronAttribute)) { cron(pollerElement, parserContext, cronAttribute, timeUnit, triggerBeanNames); @@ -142,33 +146,20 @@ private void trigger(Element pollerElement, ParserContext parserContext, String List triggerBeanNames) { if (StringUtils.hasText(timeUnit)) { - parserContext.getReaderContext().error("The 'time-unit' attribute cannot be used with a 'trigger' reference.", pollerElement); + parserContext.getReaderContext() + .error("The 'time-unit' attribute cannot be used with a 'trigger' reference.", pollerElement); } triggerBeanNames.add(triggerAttribute); } - private void fixedRate(ParserContext parserContext, String fixedRateAttribute, String timeUnit, - List triggerBeanNames) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PeriodicTrigger.class); - builder.addConstructorArgValue(fixedRateAttribute); - if (StringUtils.hasText(timeUnit)) { - builder.addConstructorArgValue(timeUnit); - } - builder.addPropertyValue("fixedRate", Boolean.TRUE); - String triggerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( - builder.getBeanDefinition(), parserContext.getRegistry()); - triggerBeanNames.add(triggerBeanName); - } - - private void fixedDelay(ParserContext parserContext, String fixedDelayAttribute, String timeUnit, - List triggerBeanNames) { + private void period(ParserContext parserContext, String fixedDelayAttribute, String fixedRateAttribute, + String initialDelayAttribute, String timeUnit, List triggerBeanNames) { - BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PeriodicTrigger.class); - builder.addConstructorArgValue(fixedDelayAttribute); - if (StringUtils.hasText(timeUnit)) { - builder.addConstructorArgValue(timeUnit); - } - builder.addPropertyValue("fixedRate", Boolean.FALSE); + BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(PeriodicTriggerFactoryBean.class); + builder.addPropertyValue("fixedDelayValue", fixedDelayAttribute); + builder.addPropertyValue("fixedRateValue", fixedRateAttribute); + builder.addPropertyValue("timeUnit", timeUnit); + builder.addPropertyValue("initialDelayValue", initialDelayAttribute); String triggerBeanName = BeanDefinitionReaderUtils.registerWithGeneratedName( builder.getBeanDefinition(), parserContext.getRegistry()); triggerBeanNames.add(triggerBeanName); @@ -187,4 +178,5 @@ private void cron(Element pollerElement, ParserContext parserContext, String cro builder.getBeanDefinition(), parserContext.getRegistry()); triggerBeanNames.add(triggerBeanName); } + } diff --git a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration.xsd b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration.xsd index 66d33f72ff0..15a9ada77c4 100644 --- a/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration.xsd +++ b/spring-integration-core/src/main/resources/org/springframework/integration/config/spring-integration.xsd @@ -1983,7 +1983,7 @@ - Fixed delay trigger (in milliseconds). + Fixed delay trigger (decimal for time unit or Duration string). @@ -1997,7 +1997,14 @@ - Fixed rate trigger (in milliseconds). + Fixed rate trigger (decimal for time unit or Duration string). + + + + + + Periodic trigger initial delay (decimal for time unit or Duration string). + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerParserTests.java index 681aeb90278..4aa4b5477e1 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/PollerParserTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -16,7 +16,7 @@ package org.springframework.integration.config.xml; -import java.time.temporal.ChronoUnit; +import java.time.Duration; import java.util.HashMap; import org.aopalliance.aop.Advice; @@ -112,7 +112,9 @@ public void pollerWithReceiveTimeoutAndTimeunit() { PollerMetadata metadata = (PollerMetadata) poller; assertThat(metadata.getReceiveTimeout()).isEqualTo(1234); PeriodicTrigger trigger = (PeriodicTrigger) metadata.getTrigger(); - assertThat(TestUtils.getPropertyValue(trigger, "chronoUnit")).isEqualTo(ChronoUnit.SECONDS); + assertThat(trigger.getPeriodDuration()).isEqualTo(Duration.ofSeconds(5)); + assertThat(trigger.isFixedRate()).isTrue(); + assertThat(trigger.getInitialDelayDuration()).isEqualTo(Duration.ofSeconds(45)); context.close(); } @@ -123,7 +125,7 @@ public void pollerWithTriggerReference() { Object poller = context.getBean("poller"); assertThat(poller).isNotNull(); PollerMetadata metadata = (PollerMetadata) poller; - assertThat(metadata.getTrigger() instanceof TestTrigger).isTrue(); + assertThat(metadata.getTrigger()).isInstanceOf(TestTrigger.class); context.close(); } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/pollerWithReceiveTimeout.xml b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/pollerWithReceiveTimeout.xml index 8c8e9ea8d13..38ad3d6c445 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/config/xml/pollerWithReceiveTimeout.xml +++ b/spring-integration-core/src/test/java/org/springframework/integration/config/xml/pollerWithReceiveTimeout.xml @@ -15,6 +15,6 @@ SECONDS - + diff --git a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.properties b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.properties index 725e320a1cb..364e0b2eda7 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.properties +++ b/spring-integration-core/src/test/java/org/springframework/integration/configuration/EnableIntegrationTests.properties @@ -1,5 +1,5 @@ message.history.tracked.components=input, publishedChannel, annotationTestService* poller.maxMessagesPerPoll=10 -poller.interval=100 +poller.interval=PT0.1S poller.receiveTimeout=10000 global.wireTap.pattern=input diff --git a/src/reference/asciidoc/channel-adapter.adoc b/src/reference/asciidoc/channel-adapter.adoc index bc9e68e1a40..475e0a637b0 100644 --- a/src/reference/asciidoc/channel-adapter.adoc +++ b/src/reference/asciidoc/channel-adapter.adoc @@ -114,7 +114,7 @@ However, in the `SourcePollingChannelAdapter`, it is a bit different. The default value for `max-messages-per-poll` is `1`, unless you explicitly set it to a negative value (such as `-1`). This makes sure that the poller can react to lifecycle events (such as start and stop) and prevents it from potentially spinning in an infinite loop if the implementation of the custom method of the `MessageSource` has a potential to never return null and happens to be non-interruptible. -However, if you are sure that your method can return null and you need to poll for as many sources as available per each poll, you should explicitly set `max-messages-per-poll` to a negative value, as the following example shows: +However, if you are sure that your method can return null, and you need to poll for as many sources as available per each poll, you should explicitly set `max-messages-per-poll` to a negative value, as the following example shows: ==== [source,xml] @@ -125,6 +125,9 @@ However, if you are sure that your method can return null and you need to poll f Starting with version 5.5, a `0` value for `max-messages-per-poll` has a special meaning - skip the `MessageSource.receive()` call altogether, which may be considered as pausing for this inbound channel adapter until the `maxMessagesPerPoll` is changed to a non-zero value at a later time, e.g. via a Control Bus. +Starting with version 6.2, the `fixed-delay` and `fixed-rate` can be configured in https://en.wikipedia.org/wiki/ISO_8601#Durations[ISO 8601 Duration] format, e.g. `PT10S`, `P1D` etc. +In addition, an `initial-interval` of the underlying `PeriodicTrigget` is also expose with similar value formats as `fixed-delay` and `fixed-rate`. + Also see <<./endpoint.adoc#global-default-poller,Global Default Poller>> for more information. ===== diff --git a/src/reference/asciidoc/endpoint.adoc b/src/reference/asciidoc/endpoint.adoc index 1120cb481b4..6d43a5a19c1 100644 --- a/src/reference/asciidoc/endpoint.adoc +++ b/src/reference/asciidoc/endpoint.adoc @@ -148,7 +148,7 @@ Starting with version 5.5, a `0` value has a special meaning - skip the `Message The `receiveTimeout` property specifies the amount of time the poller should wait if no messages are available when it invokes the receive operation. For example, consider two options that seem similar on the surface but are actually quite different: The first has an interval trigger of 5 seconds and a receive timeout of 50 milliseconds, while the second has an interval trigger of 50 milliseconds and a receive timeout of 5 seconds. -The first one may receive a message up to 4950 milliseconds later than it arrived on the channel (if that message arrived immediately after one of its poll calls returned). +The first one may receive a message up to 4950 milliseconds later than it accepted on the channel (if that message arrived immediately after one of its poll calls returned). On the other hand, the second configuration never misses a message by more than 50 milliseconds. The difference is that the second option requires a thread to wait. However, as a result, it can respond much more quickly to arriving messages. @@ -167,9 +167,11 @@ consumer.setTaskExecutor(taskExecutor); ==== Furthermore, a `PollingConsumer` has a property called `adviceChain`. -This property lets you to specify a `List` of AOP advices for handling additional cross-cutting concerns including transactions. +This property lets you specify a `List` of AOP advices for handling additional cross-cutting concerns including transactions. These advices are applied around the `doPoll()` method. For more in-depth information, see the sections on AOP advice chains and transaction support under <>. +See also a `@Poller` annotation Javadocs and respective <<./configuration.adoc#annotations, Messaging Annotations Support>> section. +The Java DSL also provides a <<./dsl.adoc#java-dsl-pollers, `.poller()`>> endpoint configuration option with its respective `Pollers` factory. The earlier examples show dependency lookups. However, keep in mind that these consumers are most often configured as Spring bean definitions. @@ -181,7 +183,7 @@ NOTE: Many of the `MessageHandler` implementations can generate reply messages. As mentioned earlier, sending messages is trivial when compared to receiving messages. Nevertheless, when and how many reply messages are sent depends on the handler type. For example, an aggregator waits for a number of messages to arrive and is often configured as a downstream consumer for a splitter, which can generate multiple replies for each message it handles. -When using the namespace configuration, you do not strictly need to know all of the details. +When using the namespace configuration, you do not strictly need to know all the details. However, it still might be worth knowing that several of these components share a common base class, the `AbstractReplyProducingMessageHandler`, and that it provides a `setOutputChannel(..)` method. [[endpoint-namespace]] @@ -192,7 +194,7 @@ Most of these support an `input-channel` attribute and many support an `output-c After being parsed, these endpoint elements produce an instance of either the `PollingConsumer` or the `EventDrivenConsumer`, depending on the type of the `input-channel` that is referenced: `PollableChannel` or `SubscribableChannel`, respectively. When the channel is pollable, the polling behavior is based on the endpoint element's `poller` sub-element and its attributes. -The following listing lists all of the available configuration options for a `poller`: +The following lists all available configuration options for a `poller`: [source,xml] ---- @@ -201,15 +203,16 @@ The following listing lists all of the available configuration options for a `po error-channel="" <3> fixed-delay="" <4> fixed-rate="" <5> - id="" <6> - max-messages-per-poll="" <7> - receive-timeout="" <8> - ref="" <9> - task-executor="" <10> - time-unit="MILLISECONDS" <11> - trigger=""> <12> - <13> - <14> + initial-delay="" <6> + id="" <7> + max-messages-per-poll="" <8> + receive-timeout="" <9> + ref="" <10> + task-executor="" <11> + time-unit="MILLISECONDS" <12> + trigger=""> <13> + <14> + <15> ---- @@ -225,28 +228,30 @@ Optional. To completely suppress exceptions, you can provide a reference to the `nullChannel`. Optional. <4> The fixed delay trigger uses a `PeriodicTrigger` under the covers. -If you do not use the `time-unit` attribute, the specified value is represented in milliseconds. +The numeric value is in `time-unit` or can be as a duration format (starting with version 6.2), e.g. `PT10S`, `P1D`. If this attribute is set, none of the following attributes must be specified: `fixed-rate`, `trigger`, `cron`, and `ref`. <5> The fixed rate trigger uses a `PeriodicTrigger` under the covers. -If you do not use the `time-unit` attribute, the specified value is represented in milliseconds. +The numeric value is in `time-unit` or can be as a duration format (starting with version 6.2), e.g. `PT10S`, `P1D`. If this attribute is set, none of the following attributes must be specified: `fixed-delay`, `trigger`, `cron`, and `ref`. -<6> The ID referring to the poller's underlying bean-definition, which is of type `org.springframework.integration.scheduling.PollerMetadata`. +<6> The initial delay for a `PeriodicTrigger` under the covers(starting with version 6.2). +The numeric value is in `time-unit` or can be as a duration format, e.g. `PT10S`, `P1D`. +<7> The ID referring to the poller's underlying bean-definition, which is of type `org.springframework.integration.scheduling.PollerMetadata`. The `id` attribute is required for a top-level poller element, unless it is the default poller (`default="true"`). -<7> See <<./channel-adapter.adoc#channel-adapter-namespace-inbound,Configuring An Inbound Channel Adapter>> for more information. +<8> See <<./channel-adapter.adoc#channel-adapter-namespace-inbound,Configuring An Inbound Channel Adapter>> for more information. If not specified, the default value depends on the context. If you use a `PollingConsumer`, this attribute defaults to `-1`. However, if you use a `SourcePollingChannelAdapter`, the `max-messages-per-poll` attribute defaults to `1`. Optional. -<8> Value is set on the underlying class `PollerMetadata`. +<9> Value is set on the underlying class `PollerMetadata`. If not specified, it defaults to 1000 (milliseconds). Optional. -<9> Bean reference to another top-level poller. +<10> Bean reference to another top-level poller. The `ref` attribute must not be present on the top-level `poller` element. However, if this attribute is set, none of the following attributes must be specified: `fixed-rate`, `trigger`, `cron`, and `fixed-delay`. -<10> Provides the ability to reference a custom task executor. +<11> Provides the ability to reference a custom task executor. See <> for further information. Optional. -<11> This attribute specifies the `java.util.concurrent.TimeUnit` enum value on the underlying `org.springframework.scheduling.support.PeriodicTrigger`. +<12> This attribute specifies the `java.util.concurrent.TimeUnit` enum value on the underlying `org.springframework.scheduling.support.PeriodicTrigger`. Therefore, this attribute can be used only in combination with the `fixed-delay` or `fixed-rate` attributes. If combined with either `cron` or a `trigger` reference attribute, it causes a failure. The minimal supported granularity for a `PeriodicTrigger` is milliseconds. @@ -254,13 +259,13 @@ Therefore, the only available options are milliseconds and seconds. If this value is not provided, any `fixed-delay` or `fixed-rate` value is interpreted as milliseconds. Basically, this enum provides a convenience for seconds-based interval trigger values. For hourly, daily, and monthly settings, we recommend using a `cron` trigger instead. -<12> Reference to any Spring-configured bean that implements the `org.springframework.scheduling.Trigger` interface. +<13> Reference to any Spring-configured bean that implements the `org.springframework.scheduling.Trigger` interface. However, if this attribute is set, none of the following attributes must be specified: `fixed-delay`, `fixed-rate`, `cron`, and `ref`. Optional. -<13> Allows specifying extra AOP advices to handle additional cross-cutting concerns. +<14> Allows specifying extra AOP advices to handle additional cross-cutting concerns. See <> for further information. Optional. -<14> Pollers can be made transactional. +<15> Pollers can be made transactional. See <> for further information. Optional. diff --git a/src/reference/asciidoc/polling-consumer.adoc b/src/reference/asciidoc/polling-consumer.adoc index a6e0d347f85..f8313b8710d 100644 --- a/src/reference/asciidoc/polling-consumer.adoc +++ b/src/reference/asciidoc/polling-consumer.adoc @@ -20,6 +20,8 @@ They represent a critical cross-cutting concern in many messaging scenarios. In Spring Integration, polling consumers are based on the pattern with the same name, which is described in the book _Enterprise Integration Patterns_, by Gregor Hohpe and Bobby Woolf. You can find a description of the pattern on the https://www.enterpriseintegrationpatterns.com/PollingConsumer.html[book's website]. +For more information polling consumer configuration, see <<./endpoint.adoc#endpoint,Message Endpoints>>. + [[pollable-message-source]] ==== Pollable Message Source diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index 3d152486e5a..69c9a353e1a 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -19,3 +19,6 @@ In general the project has been moved to the latest dependency versions. [[x6.2-general]] === General Changes + +- The XML configuration for `` and `@Poller` annotation now support ISO 8601 duration format for `fixed-delay`, `fixed-rate` and `initial-delay` options. +See <<./endpoint.adoc#endpoint-pollingconsumer, Polling Consumer>> for more information. From e66c92773195c85b6ee70ca91a7fe51674fc258a Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 22 May 2023 11:31:13 -0400 Subject: [PATCH 2/2] Fix typos Co-authored-by: Gary Russell --- .../integration/config/PeriodicTriggerFactoryBean.java | 2 +- src/reference/asciidoc/channel-adapter.adoc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java b/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java index adad9fdd8ee..4b4034a9c48 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/config/PeriodicTriggerFactoryBean.java @@ -28,7 +28,7 @@ /** * The {@link FactoryBean} to produce a {@link PeriodicTrigger} * based on parsing string values for its options. - * This class is mostly driver by the XML configuration requirements for + * This class is mostly driven by the XML configuration requirements for * {@link Duration} value representations for the respective attributes. * * @author Artem Bilan diff --git a/src/reference/asciidoc/channel-adapter.adoc b/src/reference/asciidoc/channel-adapter.adoc index 475e0a637b0..bb9a2d9befd 100644 --- a/src/reference/asciidoc/channel-adapter.adoc +++ b/src/reference/asciidoc/channel-adapter.adoc @@ -126,7 +126,7 @@ However, if you are sure that your method can return null, and you need to poll Starting with version 5.5, a `0` value for `max-messages-per-poll` has a special meaning - skip the `MessageSource.receive()` call altogether, which may be considered as pausing for this inbound channel adapter until the `maxMessagesPerPoll` is changed to a non-zero value at a later time, e.g. via a Control Bus. Starting with version 6.2, the `fixed-delay` and `fixed-rate` can be configured in https://en.wikipedia.org/wiki/ISO_8601#Durations[ISO 8601 Duration] format, e.g. `PT10S`, `P1D` etc. -In addition, an `initial-interval` of the underlying `PeriodicTrigget` is also expose with similar value formats as `fixed-delay` and `fixed-rate`. +In addition, an `initial-interval` of the underlying `PeriodicTrigger` is also exposed with similar value formats as `fixed-delay` and `fixed-rate`. Also see <<./endpoint.adoc#global-default-poller,Global Default Poller>> for more information. =====