Skip to content

GH-8625: Add Duration support for <poller> #8627

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ configure(javaProjects) { subproject ->

compileJava {
options.release = 17
options.compilerArgs << '-parameters'
}

compileTestJava {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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 "";
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PeriodicTrigger> {

@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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assert that timeUnit is not provided in this case?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having time-unit does not cause any ambiguity, so ignoring it as it is also done in the ScheduledAnnotationBeanPostProcessor should be totally OK.

}
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());
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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;

Expand All @@ -45,12 +45,15 @@
*/
public class PollerParser extends AbstractBeanDefinitionParser {

private static final String MULTIPLE_TRIGGER_DEFINITIONS = "A <poller> cannot specify more than one trigger configuration.";
private static final String MULTIPLE_TRIGGER_DEFINITIONS =
"A <poller> cannot specify more than one trigger configuration.";

private static final String NO_TRIGGER_DEFINITIONS = "A <poller> 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)) {
Expand Down Expand Up @@ -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<String> triggerBeanNames = new ArrayList<String>();
List<String> 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);
Expand All @@ -142,33 +146,20 @@ private void trigger(Element pollerElement, ParserContext parserContext, String
List<String> 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<String> 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<String> triggerBeanNames) {
private void period(ParserContext parserContext, String fixedDelayAttribute, String fixedRateAttribute,
String initialDelayAttribute, String timeUnit, List<String> 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);
Expand All @@ -187,4 +178,5 @@ private void cron(Element pollerElement, ParserContext parserContext, String cro
builder.getBeanDefinition(), parserContext.getRegistry());
triggerBeanNames.add(triggerBeanName);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1983,7 +1983,7 @@
</xsd:sequence>
<xsd:attribute name="fixed-delay" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Fixed delay trigger (in milliseconds).</xsd:documentation>
<xsd:documentation>Fixed delay trigger (decimal for time unit or Duration string).</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="optional">
Expand All @@ -1997,7 +1997,14 @@
</xsd:attribute>
<xsd:attribute name="fixed-rate" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Fixed rate trigger (in milliseconds).</xsd:documentation>
<xsd:documentation>Fixed rate trigger (decimal for time unit or Duration string).</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="initial-delay" type="xsd:string">
<xsd:annotation>
<xsd:documentation>
Periodic trigger initial delay (decimal for time unit or Duration string).
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="time-unit">
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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();
}

Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
<beans:prop key="seconds">SECONDS</beans:prop>
</util:properties>

<poller id="poller" receive-timeout="1234" fixed-rate="5" time-unit="${seconds}"/>
<poller id="poller" receive-timeout="1234" fixed-rate="5" time-unit="${seconds}" initial-delay="PT45S"/>

</beans:beans>
Original file line number Diff line number Diff line change
@@ -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
Loading