/** * Reads latest config from {@link ConfigService} for the given {@link Task} and caches it in that * {@link Task}. If config is found but can't be parsed, the {@link Task}'s {@code configured*} * fields will include the bad {@link Trigger} expression and a {@link BadConfigTrigger}. If * config simply can't be read at all, e.g. network outage, that will just be logged and the * {@link Task}'s config will be unchanged. * * <p>This also handles initialization of the {@link Task's} default {@link Trigger} <em>every * time</em>. Which means you can technically change the default and it will be picked up. * * @param task * @return * @throws BadTriggerConfigException if the default configuration for the given {@link Task} is * broken. Exceptions during read of latest config from {@link ConfigService} are caught and * handled. */ protected void mergeLatestTriggerConfig(Task task) throws BadTriggerConfigException { // Null will mean "have no idea what the latest config would be or // if it even exists." Everything else will mean "either found good // config, use it, or found bad config and it's up to you what to do // with it." Pair<String, Trigger> newTriggerConfig = null; try { newTriggerConfig = readNewTriggerConfig(task.triggerExpressionConfigName); } catch (BadTriggerConfigException e) { LOGGER.warn( "Unable to parse task [{}] trigger config named [{}].", new Object[] {task.id, task.triggerExpressionConfigName, e}); newTriggerConfig = new Pair<String, Trigger>(null, new BadConfigTrigger(e.getConfig(), e)); } catch (RuntimeException e) { // Probably db connection or other systemic issue issue. Will end up // leaving this task alone. LOGGER.error( "Unable to read trigger config named [{}]. This" + " was probably a transient and/or systemic issue " + " rather than a parse issue.", task.triggerExpressionConfigName, e); } if (newTriggerConfig != null && newTriggerConfig.getSecond() == null) { // Really a programmer error but let's just quietly normalize to // simplify boolean expressions below. newTriggerConfig = null; } if (newTriggerConfig == null) { task.configuredTriggerExpression = null; task.configuredTrigger = null; } else { task.configuredTriggerExpression = newTriggerConfig.getFirst(); task.configuredTrigger = newTriggerConfig.getSecond(); } // Do this every time to avoid weird stuff from mis-use where the // expression and trigger don't match. task.defaultTrigger = parseTriggerConfig(task.defaultTriggerExpression); }
protected Trigger parseTriggerConfig(String configValue) throws BadTriggerConfigException { BadTriggerConfigException badPeriodicTiggerException = null; BadTriggerConfigException badCronTiggerException = null; try { return tryExpressionAsPeriodicTrigger(configValue); } catch (BadTriggerConfigException e) { badPeriodicTiggerException = e; } try { return tryExpressionAsCronTrigger(configValue); } catch (BadTriggerConfigException e) { badCronTiggerException = e; } throw new BadTriggerConfigException( "All trigger config parsing attempts failed. First reason: [" + badPeriodicTiggerException.getMessage() + "]. Second reason: [" + badCronTiggerException.getMessage() + "]", configValue); }
protected Trigger tryExpressionAsPeriodicTrigger(String configValue) throws BadTriggerConfigException { BadTriggerConfigException longParseException = null; try { final long period = Long.parseLong(configValue); if (period < 0) { return new DisabledTrigger(); } // millis since that's what @Scheduled methods were historically // configured in return new PeriodicTrigger(period, TimeUnit.MILLISECONDS); } catch (NumberFormatException e) { longParseException = new BadTriggerConfigException( "Config [" + configValue + "] did not parse to a long.", configValue, e); } catch (IllegalArgumentException e) { longParseException = new BadTriggerConfigException( "Config [" + configValue + "] could not be used to initialize a PeriodicTrigger", configValue, e); } final Matcher matcher = PERIODIC_TRIGGER_WITH_INITIAL_DELAY_PATTERN.matcher(configValue); if (!(matcher.matches())) { throw new BadTriggerConfigException( "Trigger expression could not be parsed as either a" + " simple period or as a period with an initial " + "offset. Original parse failure: [" + longParseException.getMessage() + "]. To be considered a period with an offset, " + "the expression must match this regexp" + "(without brackets): [" + PERIODIC_TRIGGER_WITH_INITIAL_DELAY_PATTERN + "]", configValue); } try { final String periodStr = matcher.group(1); final long periodLong = Long.parseLong(periodStr); if (periodLong < 0) { return new DisabledTrigger(); } final String offsetStr = matcher.group(2); final long offsetLong = Long.parseLong(offsetStr); final PeriodicTrigger trigger = new PeriodicTrigger(periodLong, TimeUnit.MILLISECONDS); trigger.setInitialDelay(offsetLong); return trigger; } catch (NumberFormatException e) { throw new BadTriggerConfigException( "Trigger expression could not be parsed as either a" + " simple period or as a period with an initial " + "offset. Original parse failure: [" + longParseException.getMessage() + "]. To be considered a period with an initial " + "delay, the expression must match this regexp" + "(without brackets): [" + PERIODIC_TRIGGER_WITH_INITIAL_DELAY_PATTERN + "]", configValue, e); } catch (IllegalArgumentException e) { throw new BadTriggerConfigException( "Trigger expression parsed as a period [{}] with an " + "initial delay [{}] but could not be used to " + "initialize a PeriodicTrigger", configValue, e); } }