- package org.jbehave.core.embedder;
- import static java.util.Arrays.asList;
- import static java.util.regex.Pattern.compile;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- import java.util.regex.PatternSyntaxException;
- import org.apache.commons.lang3.builder.ToStringBuilder;
- import org.apache.commons.lang3.builder.ToStringStyle;
- import org.codehaus.plexus.util.StringUtils;
- import org.jbehave.core.model.Story;
- public class StoryTimeouts {
- private static final String COMMA = ",";
- private EmbedderControls embedderControls;
- private EmbedderMonitor embedderMonitor;
- private List<TimeoutParser> parsers = new ArrayList<>();
- public StoryTimeouts(EmbedderControls embedderControls,
- EmbedderMonitor embedderMonitor) {
- this.embedderControls = embedderControls;
- this.embedderMonitor = embedderMonitor;
- configureDefaultParsers();
- }
- private void configureDefaultParsers() {
- this.parsers.addAll(asList(new SimpleTimeoutParser(), new DigitTimeoutParser()));
- }
- public StoryTimeouts withParsers(TimeoutParser...parsers) {
- this.parsers.addAll(0, asList(parsers));
- return this;
- }
- public long getTimeoutInSecs(Story story) {
- Map<String, StoryTimeout> timeouts = asMap(embedderControls
- .storyTimeouts());
- // look for timeout by path
- for (StoryTimeout timeout : timeouts.values()) {
- if (timeout.allowedByPath(story.getPath())) {
- long timeoutInSecs = timeout.getTimeoutInSecs();
- embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
- return timeoutInSecs;
- }
- }
- // look for default timeout
- for (StoryTimeout timeout : timeouts.values()) {
- if (timeout.isDefault()) {
- long timeoutInSecs = timeout.getTimeoutInSecs();
- embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
- return timeoutInSecs;
- }
- }
- // default to 300 for backward compatibility
- long timeoutInSecs = 300;
- embedderMonitor.usingTimeout(story.getName(), timeoutInSecs);
- return timeoutInSecs;
- }
- private Map<String, StoryTimeout> asMap(String timeoutsAsString) {
- Map<String, StoryTimeout> timeouts = new HashMap<>();
- if (StringUtils.isBlank(timeoutsAsString)) {
- return timeouts;
- }
- for (String timeoutAsString : timeoutsAsString.split(COMMA)) {
- StoryTimeout timeout = new StoryTimeout(timeoutAsString, parsers);
- timeouts.put(timeout.getPathPattern(), timeout);
- }
- return timeouts;
- }
- public static class StoryTimeout {
- private static final String COLON = ":";
- private boolean isDefault;
- private String pathPattern = "";
- private String timeout = "0";
- private String timeoutAsString;
- private List<TimeoutParser> parsers;
- public StoryTimeout(String timeoutAsString, List<TimeoutParser> parsers) {
- this.timeoutAsString = timeoutAsString;
- this.parsers = parsers;
- if (timeoutAsString.contains(COLON)) {
- String[] timeoutByPath = timeoutAsString.split(COLON);
- pathPattern = timeoutByPath[0];
- timeout = timeoutByPath[1];
- } else {
- isDefault = true;
- timeout = timeoutAsString;
- }
- }
- public boolean allowedByPath(String path) {
- if (path != null) {
- return path.matches(regexOf(pathPattern));
- }
- return false;
- }
- public boolean isDefault() {
- return isDefault;
- }
- public String getPathPattern() {
- return pathPattern;
- }
- public long getTimeoutInSecs() {
- for (TimeoutParser parser : parsers) {
- if (parser.isValid(timeout)) {
- return parser.asSeconds(timeout);
- }
- }
- throw new TimeoutFormatException("No format found for timeout: " + timeout);
- }
- public String getTimeoutAsString() {
- return timeoutAsString;
- }
- private String regexOf(String pattern) {
- try {
- // check if pattern is already a valid regex
- Pattern.compile(pattern);
- return pattern;
- } catch (PatternSyntaxException e) {
- // assume Ant-style pattern: **/path/*.story
- return pattern.replace("*", ".*");
- }
- }
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this,
- ToStringStyle.SHORT_PREFIX_STYLE);
- }
- }
- public static interface TimeoutParser {
- boolean isValid(String timeout);
- long asSeconds(String timeout);
- }
- /**
- * A simple parser for timeouts of format: 1d 2h 30m 15s.
- */
- public static class SimpleTimeoutParser implements TimeoutParser {
- private static final String UNIT_PATTERN = "[a-zA-Z]+";
- private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)\\s*("
- + UNIT_PATTERN + ")");
- private Map<String, Long> units = new HashMap<>();
- public SimpleTimeoutParser() {
- addUnit("d", 24 * 3600).addUnit("h", 3600).addUnit("m", 60)
- .addUnit("s", 1);
- }
- private SimpleTimeoutParser addUnit(String unit, long value) {
- if (!unit.matches(UNIT_PATTERN)) {
- throw new TimeoutFormatException("Unit '" + unit
- + "' must be a non-numeric word");
- }
- if (value < 0) {
- throw new TimeoutFormatException("Unit value '" + value
- + "' cannot be negative");
- }
- units.put(unit, Long.valueOf(value));
- return this;
- }
- @Override
- public boolean isValid(String timeout) {
- return TIMEOUT_PATTERN.matcher(timeout).find();
- }
- @Override
- public long asSeconds(String timeout) {
- long total = 0;
- Matcher matcher = TIMEOUT_PATTERN.matcher(timeout);
- while (matcher.find()) {
- long value = Long.parseLong(matcher.group(1));
- String unit = matcher.group(2);
- if (!units.containsKey(unit)) {
- throw new TimeoutFormatException("Unrecognized unit: "
- + unit);
- }
- total += units.get(unit).longValue() * value;
- }
- return total;
- }
- }
- /**
- * A digit parser for timeouts
- */
- public static class DigitTimeoutParser implements TimeoutParser {
- private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)");
- @Override
- public boolean isValid(String timeout) {
- return TIMEOUT_PATTERN.matcher(timeout).find();
- }
- @Override
- public long asSeconds(String timeout) {
- return Long.parseLong(timeout);
- }
- }
- @SuppressWarnings("serial")
- public static class TimeoutFormatException extends IllegalArgumentException {
- public TimeoutFormatException(String message) {
- super(message);
- }
- }
- }