@@ -72,22 +72,20 @@ public final class MonitorConfigUtils {
7272 private MonitorConfigUtils () {}
7373
7474 /**
75- * Builds a monitor config from exactly one of a cron, fixed rate or fixed delay .
75+ * Builds a monitor config from exactly one of a cron or a fixed interval .
7676 *
7777 * @param cron 6 field cron (seconds first) or macro, null or empty if unset
7878 * @param zone cron time zone, ignored for intervals
79- * @param fixedRateMillis null if unset
80- * @param fixedDelayMillis null if unset
79+ * @param intervalMillis fixed rate, null if unset
8180 * @return null if the schedule can't be expressed as a Sentry monitor schedule
8281 */
8382 public static @ Nullable MonitorConfig fromSchedule (
8483 final @ Nullable String cron ,
8584 final @ Nullable String zone ,
86- final @ Nullable Long fixedRateMillis ,
87- final @ Nullable Long fixedDelayMillis ) {
85+ final @ Nullable Long intervalMillis ) {
8886 final boolean hasCron = cron != null && !cron .isEmpty ();
8987
90- if (hasCron && cron != null && fixedRateMillis == null && fixedDelayMillis == null ) {
88+ if (hasCron && cron != null && intervalMillis == null ) {
9189 final @ Nullable String crontab = toCrontab (cron );
9290 if (crontab == null ) {
9391 return null ;
@@ -103,9 +101,8 @@ private MonitorConfigUtils() {}
103101 return config ;
104102 }
105103
106- final @ Nullable Long period = fixedRateMillis != null ? fixedRateMillis : fixedDelayMillis ;
107- if (!hasCron && period != null && (fixedRateMillis == null || fixedDelayMillis == null )) {
108- final long millis = period ;
104+ if (!hasCron && intervalMillis != null ) {
105+ final long millis = intervalMillis ;
109106 if (millis < MINUTE_MILLIS || millis % MINUTE_MILLIS != 0 ) {
110107 return null ;
111108 }
@@ -128,30 +125,72 @@ private MonitorConfigUtils() {}
128125
129126 /**
130127 * Builds a monitor config from Spring {@code @Scheduled} values with placeholders resolved. Reads
131- * days of week and a cron without zone the way Spring does.
128+ * the cron and a cron without zone the way Spring does.
132129 *
133130 * @param cron Spring cron, null or empty if unset
134131 * @param zone cron time zone, null or empty for the JVM default zone
135132 * @param fixedRateMillis null if unset
136- * @param fixedDelayMillis null if unset
133+ * @param fixedDelayMillis null if unset; a fixed delay never gets a config
134+ * @param legacyCronParser true for Spring before 5.3, which parses crons with {@code
135+ * CronSequenceGenerator}
137136 * @return null if the schedule can't be expressed as a Sentry monitor schedule
138137 */
139138 public static @ Nullable MonitorConfig fromSpringScheduled (
140139 final @ Nullable String cron ,
141140 final @ Nullable String zone ,
142141 final @ Nullable Long fixedRateMillis ,
143- final @ Nullable Long fixedDelayMillis ) {
144- final @ Nullable String crontabCron = toCrontabDaysOfWeek (cron );
142+ final @ Nullable Long fixedDelayMillis ,
143+ final boolean legacyCronParser ) {
144+ // runs start a delay after the previous run ends, so they drift from any interval
145+ if (fixedDelayMillis != null ) {
146+ return null ;
147+ }
148+ final @ Nullable String crontabCron =
149+ legacyCronParser ? toCrontabLegacy (cron ) : toCrontabDaysOfWeek (cron );
145150 @ Nullable String cronZone = null ;
146151 if (crontabCron != null && !crontabCron .isEmpty ()) {
147152 cronZone = zone == null || zone .isEmpty () ? TimeZone .getDefault ().getID () : zone ;
148153 }
149- return fromSchedule (crontabCron , cronZone , fixedRateMillis , fixedDelayMillis );
154+ return fromSchedule (crontabCron , cronZone , fixedRateMillis );
155+ }
156+
157+ /**
158+ * Spring before 5.3 steps day of month {@code *}/n from 0, so it runs on n, 2n, ... Days of week
159+ * already match crontab. Null if both day fields are set, as it then sometimes runs at midnight.
160+ */
161+ static @ Nullable String toCrontabLegacy (final @ Nullable String cron ) {
162+ if (cron == null ) {
163+ return null ;
164+ }
165+ final @ NotNull String [] fields = cron .trim ().split ("\\ s+" , -1 );
166+ if (fields .length != 6 ) {
167+ return cron ;
168+ }
169+ if (!isAnyDay (fields [3 ]) && !isAnyDay (fields [5 ])) {
170+ return null ;
171+ }
172+ final @ NotNull StringBuilder daysOfMonth = new StringBuilder ();
173+ for (@ NotNull String item : fields [3 ].split ("," , -1 )) {
174+ if (item .startsWith ("*/" )) {
175+ item = item .substring (2 ) + "-31" + item .substring (1 );
176+ }
177+ if (daysOfMonth .length () > 0 ) {
178+ daysOfMonth .append (',' );
179+ }
180+ daysOfMonth .append (item );
181+ }
182+ fields [3 ] = daysOfMonth .toString ();
183+ return join (fields );
184+ }
185+
186+ private static boolean isAnyDay (final @ NotNull String field ) {
187+ return "*" .equals (field ) || "?" .equals (field );
150188 }
151189
152190 /**
153191 * Spring numbers days of week from Monday with 0 or 7 for Sunday, and starts {@code *} on Monday.
154- * Rewrites them so crontab reads them the same way.
192+ * Rewrites them so crontab reads them the same way. Null for {@code #5}, which Spring 5.3 also
193+ * runs in months without a fifth weekday.
155194 */
156195 static @ Nullable String toCrontabDaysOfWeek (final @ Nullable String cron ) {
157196 if (cron == null ) {
@@ -166,6 +205,9 @@ private MonitorConfigUtils() {}
166205 for (int i = 0 ; i < DAY_NAMES .size (); i ++) {
167206 item = item .replace (DAY_NAMES .get (i ), String .valueOf (i == 0 ? 7 : i ));
168207 }
208+ if (item .matches (".*#0*5" )) {
209+ return null ;
210+ }
169211 if (item .startsWith ("*/" )) {
170212 item = "1-7" + item .substring (1 );
171213 } else if (item .startsWith ("7-" )) {
@@ -178,6 +220,10 @@ private MonitorConfigUtils() {}
178220 daysOfWeek .append (item );
179221 }
180222 fields [5 ] = daysOfWeek .toString ();
223+ return join (fields );
224+ }
225+
226+ private static @ NotNull String join (final @ NotNull String [] fields ) {
181227 final @ NotNull StringBuilder result = new StringBuilder ();
182228 for (int i = 0 ; i < fields .length ; i ++) {
183229 if (i > 0 ) {
@@ -370,6 +416,10 @@ private MonitorConfigUtils() {}
370416 return null ;
371417 }
372418 if (items .size () == 1 ) {
419+ // cronsim steps a single value range like 10-10/2 to the field max
420+ if (base .indexOf ('-' ) >= 0 ) {
421+ return null ;
422+ }
373423 return range (items .iterator ().next (), FIELD_MAX [index ], step );
374424 }
375425 final @ NotNull List <Integer > sorted = new ArrayList <>(items );
0 commit comments