View Javadoc

1   package com.atlassian.scheduler.core.util;
2   
3   import javax.annotation.Nullable;
4   import java.util.Random;
5   import java.util.regex.Pattern;
6   
7   /**
8    * Utility class to prevent the use of cron expressions that run more frequently than once per minute.
9    * It is assumed that the cron expression has already been validated or that it is acceptable for the
10   * validation to return the "wrong" message when the expression is validated later on.
11   * <p>
12   * Without this, people could supply cron expressions that run every five seconds or something
13   * equally inappropriate.
14   * </p>
15   *
16   * @since v1.0
17   */
18  public class CronExpressionQuantizer {
19      private static final Pattern REGEX = Pattern.compile("^[0-5]?[0-9] ");
20      private static final Random RANDOM = new Random();
21  
22      /**
23       * Quantize the seconds field using {@link Randomize#AS_NEEDED}.
24       *
25       * @param cronExpression the cron expression to (possibly) quantize.
26       * @return the cron expression with the seconds field forced to a random value between {@code 0} and
27       * {@code 59} (inclusive) if it is not already in that format
28       */
29      public static String quantizeSecondsField(String cronExpression) {
30          return quantizeSecondsField(cronExpression, Randomize.AS_NEEDED);
31      }
32  
33      /**
34       * Quantize the seconds field, randomizing it as requested.
35       *
36       * @param cronExpression the cron expression to (possibly) quantize.
37       * @param randomize      controls the randomization of the seconds fields ({@code null} is assumed to mean
38       *                       {@link Randomize#AS_NEEDED}.
39       * @return the cron expression with the seconds field forced to a value between {@code 0} and
40       * {@code 59} (inclusive) if it is not already in that format or as instructed to by
41       * the {@code randomize} option.
42       */
43      public static String quantizeSecondsField(@Nullable String cronExpression, @Nullable Randomize randomize) {
44          if (!shouldEdit(cronExpression, randomize)) {
45              return cronExpression;
46          }
47  
48          final int pos = cronExpression.indexOf(' ');
49          if (pos <= 0) {
50              return cronExpression;
51          }
52  
53          final StringBuilder sb = new StringBuilder(cronExpression.length());
54          sb.append((randomize != Randomize.NEVER) ? RANDOM.nextInt(60) : 0);
55          return sb.append(cronExpression, pos, cronExpression.length()).toString();
56      }
57  
58      @SuppressWarnings("SimplifiableIfStatement")
59      private static boolean shouldEdit(@Nullable String cronExpression, @Nullable Randomize randomize) {
60          if (cronExpression == null) {
61              return false;
62          }
63          if (randomize == Randomize.ALWAYS) {
64              return true;
65          }
66          return !REGEX.matcher(cronExpression).find();
67      }
68  
69      /**
70       * Controls whether or not randomization is performed when a cron expression is quantized.
71       */
72      public static enum Randomize {
73          /**
74           * Never randomize the seconds field.  If the supplied value does not meet the requirement (a simple
75           * integer from {@code 0} to {@code 59}), then force it to {@code 0}.
76           */
77          NEVER,
78  
79          /**
80           * Randomize the seconds field if quantization is needed; otherwise, leave it alone.  If the supplied
81           * value does not meet the requirement (a simple integer from {@code 0} to {@code 59}), then force it
82           * to a randomized value; otherwise, leave it alone.
83           */
84          AS_NEEDED,
85  
86          /**
87           * Replace the seconds fields with a randomized value in all cases.  The replacement is performed
88           * regardless of whether or not the value was already in the permitted format.
89           */
90          ALWAYS
91      }
92  }