Skip to content
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

Add flexible Duration value parsing in scheduled tasks #24069

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@

import java.lang.reflect.Method;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.List;
Expand All @@ -30,6 +32,8 @@
import java.util.TimeZone;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -115,6 +119,20 @@ public class ScheduledAnnotationBeanPostProcessor
*/
public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";

private static Pattern DURATION_IN_TEXT_FORMAT = Pattern.compile("^([\\+\\-]?\\d+)([a-zA-Z]{1,2})$");

private static final Map<String, ChronoUnit> UNITS;

static {
Map<String, ChronoUnit> units = new HashMap<>();
units.put("ns", ChronoUnit.NANOS);
units.put("ms", ChronoUnit.MILLIS);
units.put("s", ChronoUnit.SECONDS);
units.put("m", ChronoUnit.MINUTES);
units.put("h", ChronoUnit.HOURS);
units.put("d", ChronoUnit.DAYS);
UNITS = Collections.unmodifiableMap(units);
}

protected final Log logger = LogFactory.getLog(getClass());

Expand Down Expand Up @@ -394,13 +412,9 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);
}
if (StringUtils.hasLength(initialDelayString)) {
try {
initialDelay = parseDelayAsLong(initialDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
}
initialDelay = parseDelayAsLong(initialDelayString,
"Invalid initialDelayString value \"" +
initialDelayString + "\" - cannot parse into long");
}
}

Expand Down Expand Up @@ -448,13 +462,9 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
if (StringUtils.hasLength(fixedDelayString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedDelay = parseDelayAsLong(fixedDelayString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");
}
fixedDelay = parseDelayAsLong(fixedDelayString,
"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into long");

tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));
}
}
Expand All @@ -474,13 +484,9 @@ protected void processScheduled(Scheduled scheduled, Method method, Object bean)
if (StringUtils.hasLength(fixedRateString)) {
Assert.isTrue(!processedSchedule, errorMessage);
processedSchedule = true;
try {
fixedRate = parseDelayAsLong(fixedRateString);
}
catch (RuntimeException ex) {
throw new IllegalArgumentException(
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
}
fixedRate = parseDelayAsLong(fixedRateString,
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");

tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
}
}
Expand Down Expand Up @@ -515,11 +521,24 @@ protected Runnable createRunnable(Object target, Method method) {
return new ScheduledMethodRunnable(target, invocableMethod);
}

private static long parseDelayAsLong(String value) throws RuntimeException {
if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {
return Duration.parse(value).toMillis();
private static long parseDelayAsLong(String value, String exceptionMessage) throws IllegalArgumentException {
try {
if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {
return Duration.parse(value).toMillis();
}
return Long.parseLong(value);

}
catch (RuntimeException ex) {
Matcher matcher = DURATION_IN_TEXT_FORMAT.matcher(value);

Assert.isTrue(matcher.matches(), exceptionMessage);
long amount = Long.parseLong(matcher.group(1));
ChronoUnit unit = UNITS.get(matcher.group(2).toLowerCase());
Assert.notNull(unit, exceptionMessage);

return Duration.of(amount, unit).toMillis();
}
return Long.parseLong(value);
}

private static boolean isP(char ch) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

/**
* @author Mark Fisher
Expand Down Expand Up @@ -107,6 +108,34 @@ public void fixedDelayTask() {
assertThat(task.getInterval()).isEqualTo(5000L);
}

@Test
public void fixedDelayTaskInSimpleReadableForm() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedDelayInSimpleReadableFormTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();

ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);

Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedDelayTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedDelayTasks");
assertThat(fixedDelayTasks.size()).isEqualTo(1);
IntervalTask task = fixedDelayTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedDelay");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(5000L);
}

@Test
public void fixedRateTask() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
Expand Down Expand Up @@ -135,6 +164,44 @@ public void fixedRateTask() {
assertThat(task.getInterval()).isEqualTo(3000L);
}

@Test
public void fixedRateTaskInSimpleReadableForm() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateInSimpleReadableFormTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
context.refresh();

ScheduledTaskHolder postProcessor = context.getBean("postProcessor", ScheduledTaskHolder.class);
assertThat(postProcessor.getScheduledTasks().size()).isEqualTo(1);

Object target = context.getBean("target");
ScheduledTaskRegistrar registrar = (ScheduledTaskRegistrar)
new DirectFieldAccessor(postProcessor).getPropertyValue("registrar");
@SuppressWarnings("unchecked")
List<IntervalTask> fixedRateTasks = (List<IntervalTask>)
new DirectFieldAccessor(registrar).getPropertyValue("fixedRateTasks");
assertThat(fixedRateTasks.size()).isEqualTo(1);
IntervalTask task = fixedRateTasks.get(0);
ScheduledMethodRunnable runnable = (ScheduledMethodRunnable) task.getRunnable();
Object targetObject = runnable.getTarget();
Method targetMethod = runnable.getMethod();
assertThat(targetObject).isEqualTo(target);
assertThat(targetMethod.getName()).isEqualTo("fixedRate");
assertThat(task.getInitialDelay()).isEqualTo(0L);
assertThat(task.getInterval()).isEqualTo(3000L);
}

@Test
public void fixedRateTaskIncorrectSimpleReadableForm() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
BeanDefinition targetDefinition = new RootBeanDefinition(FixedRateIncorrectSimpleReadableFormTestBean.class);
context.registerBeanDefinition("postProcessor", processorDefinition);
context.registerBeanDefinition("target", targetDefinition);
assertThatThrownBy(context::refresh).isInstanceOf(BeanCreationException.class)
.hasCauseInstanceOf(IllegalStateException.class);
}

@Test
public void fixedRateTaskWithInitialDelay() {
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
Expand Down Expand Up @@ -702,6 +769,13 @@ public void fixedDelay() {
}
}

static class FixedDelayInSimpleReadableFormTestBean {

@Scheduled(fixedDelayString = "5s")
public void fixedDelay() {
}
}


static class FixedRateTestBean {

Expand All @@ -710,6 +784,20 @@ public void fixedRate() {
}
}

static class FixedRateInSimpleReadableFormTestBean {

@Scheduled(fixedRateString = "3000ms")
public void fixedRate() {
}
}

static class FixedRateIncorrectSimpleReadableFormTestBean {

@Scheduled(fixedRateString = "5az")
public void fixedRate() {
}
}


static class FixedRateWithInitialDelayTestBean {

Expand Down