Skip to content

Commit

Permalink
Add flexible Duration value parsing in scheduled tasks
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyavy committed Feb 4, 2020
1 parent c2367b3 commit c791d26
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 25 deletions.
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
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

0 comments on commit c791d26

Please sign in to comment.