1 | |
package org.jbehave.core.embedder; |
2 | |
|
3 | |
import static java.util.Arrays.asList; |
4 | |
import static java.util.regex.Pattern.compile; |
5 | |
|
6 | |
import java.util.ArrayList; |
7 | |
import java.util.HashMap; |
8 | |
import java.util.List; |
9 | |
import java.util.Map; |
10 | |
import java.util.regex.Matcher; |
11 | |
import java.util.regex.Pattern; |
12 | |
import java.util.regex.PatternSyntaxException; |
13 | |
|
14 | |
import org.apache.commons.lang.builder.ToStringBuilder; |
15 | |
import org.apache.commons.lang.builder.ToStringStyle; |
16 | |
import org.codehaus.plexus.util.StringUtils; |
17 | |
import org.jbehave.core.model.Story; |
18 | |
|
19 | |
public class StoryTimeouts { |
20 | |
|
21 | |
private static final String COMMA = ","; |
22 | |
private EmbedderControls embedderControls; |
23 | |
private EmbedderMonitor embedderMonitor; |
24 | 40 | private List<TimeoutParser> parsers = new ArrayList<TimeoutParser>(); |
25 | |
|
26 | |
public StoryTimeouts(EmbedderControls embedderControls, |
27 | 40 | EmbedderMonitor embedderMonitor) { |
28 | 40 | this.embedderControls = embedderControls; |
29 | 40 | this.embedderMonitor = embedderMonitor; |
30 | 40 | configureDefaultParsers(); |
31 | 40 | } |
32 | |
|
33 | |
private void configureDefaultParsers() { |
34 | 40 | this.parsers.addAll(asList(new SimpleTimeoutParser(), new DigitTimeoutParser())); |
35 | 40 | } |
36 | |
|
37 | |
public StoryTimeouts withParsers(TimeoutParser...parsers){ |
38 | 26 | this.parsers.addAll(0, asList(parsers)); |
39 | 26 | return this; |
40 | |
} |
41 | |
|
42 | |
public long getTimeoutInSecs(Story story) { |
43 | 86 | Map<String, StoryTimeout> timeouts = asMap(embedderControls |
44 | 43 | .storyTimeouts()); |
45 | |
|
46 | |
|
47 | 43 | for (StoryTimeout timeout : timeouts.values()) { |
48 | 56 | if (timeout.allowedByPath(story.getPath())) { |
49 | 22 | long timeoutInSecs = timeout.getTimeoutInSecs(); |
50 | 20 | embedderMonitor.usingTimeout(story.getName(), timeoutInSecs); |
51 | 20 | return timeoutInSecs; |
52 | |
} |
53 | 34 | } |
54 | |
|
55 | |
|
56 | 21 | for (StoryTimeout timeout : timeouts.values()) { |
57 | 22 | if (timeout.isDefault()) { |
58 | 17 | long timeoutInSecs = timeout.getTimeoutInSecs(); |
59 | 17 | embedderMonitor.usingTimeout(story.getName(), timeoutInSecs); |
60 | 17 | return timeoutInSecs; |
61 | |
} |
62 | 5 | } |
63 | |
|
64 | |
|
65 | 4 | long timeoutInSecs = 300; |
66 | 4 | embedderMonitor.usingTimeout(story.getName(), timeoutInSecs); |
67 | 4 | return timeoutInSecs; |
68 | |
} |
69 | |
|
70 | |
private Map<String, StoryTimeout> asMap(String timeoutsAsString) { |
71 | 43 | Map<String, StoryTimeout> timeouts = new HashMap<String, StoryTimeout>(); |
72 | 43 | if (StringUtils.isBlank(timeoutsAsString)) { |
73 | 1 | return timeouts; |
74 | |
} |
75 | 106 | for (String timeoutAsString : timeoutsAsString.split(COMMA)) { |
76 | 64 | StoryTimeout timeout = new StoryTimeout(timeoutAsString, parsers); |
77 | 64 | timeouts.put(timeout.getPathPattern(), timeout); |
78 | |
} |
79 | 42 | return timeouts; |
80 | |
} |
81 | |
|
82 | |
public static class StoryTimeout { |
83 | |
private static final String COLON = ":"; |
84 | |
private boolean isDefault; |
85 | 64 | private String pathPattern = ""; |
86 | 64 | private String timeout = "0"; |
87 | |
private String timeoutAsString; |
88 | |
private List<TimeoutParser> parsers; |
89 | |
|
90 | 64 | public StoryTimeout(String timeoutAsString, List<TimeoutParser> parsers) { |
91 | 64 | this.timeoutAsString = timeoutAsString; |
92 | 64 | this.parsers = parsers; |
93 | 64 | if (timeoutAsString.contains(COLON)) { |
94 | 44 | String[] timeoutByPath = timeoutAsString.split(COLON); |
95 | 44 | pathPattern = timeoutByPath[0]; |
96 | 44 | timeout = timeoutByPath[1]; |
97 | 44 | } else { |
98 | 20 | isDefault = true; |
99 | 20 | timeout = timeoutAsString; |
100 | |
} |
101 | 64 | } |
102 | |
|
103 | |
public boolean allowedByPath(String path) { |
104 | 56 | if (path != null) { |
105 | 50 | return path.matches(regexOf(pathPattern)); |
106 | |
} |
107 | 6 | return false; |
108 | |
} |
109 | |
|
110 | |
public boolean isDefault() { |
111 | 22 | return isDefault; |
112 | |
} |
113 | |
|
114 | |
public String getPathPattern() { |
115 | 64 | return pathPattern; |
116 | |
} |
117 | |
|
118 | |
public long getTimeoutInSecs() { |
119 | 39 | for (TimeoutParser parser : parsers) { |
120 | 72 | if (parser.isValid(timeout)) { |
121 | 37 | return parser.asSeconds(timeout); |
122 | |
} |
123 | 35 | } |
124 | 2 | throw new TimeoutFormatException("No format found for timeout: "+timeout); |
125 | |
} |
126 | |
|
127 | |
public String getTimeoutAsString() { |
128 | 0 | return timeoutAsString; |
129 | |
} |
130 | |
|
131 | |
private String regexOf(String pattern) { |
132 | |
try { |
133 | |
|
134 | 50 | Pattern.compile(pattern); |
135 | 19 | return pattern; |
136 | 31 | } catch (PatternSyntaxException e) { |
137 | |
|
138 | 31 | return pattern.replace("*", ".*"); |
139 | |
} |
140 | |
} |
141 | |
|
142 | |
@Override |
143 | |
public String toString() { |
144 | 0 | return ToStringBuilder.reflectionToString(this, |
145 | |
ToStringStyle.SHORT_PREFIX_STYLE); |
146 | |
} |
147 | |
} |
148 | |
|
149 | |
public static interface TimeoutParser { |
150 | |
|
151 | |
boolean isValid(String timeout); |
152 | |
|
153 | |
long asSeconds(String timeout); |
154 | |
|
155 | |
} |
156 | |
|
157 | |
|
158 | |
|
159 | |
|
160 | |
public static class SimpleTimeoutParser implements TimeoutParser { |
161 | |
|
162 | |
private static final String UNIT_PATTERN = "[a-zA-Z]+"; |
163 | 1 | private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)\\s*(" |
164 | |
+ UNIT_PATTERN + ")"); |
165 | 40 | private Map<String, Long> units = new HashMap<String, Long>(); |
166 | |
|
167 | 40 | public SimpleTimeoutParser() { |
168 | 40 | addUnit("d", 24 * 3600).addUnit("h", 3600).addUnit("m", 60) |
169 | 40 | .addUnit("s", 1); |
170 | 40 | } |
171 | |
|
172 | |
private SimpleTimeoutParser addUnit(String unit, long value) { |
173 | 160 | if (!unit.matches(UNIT_PATTERN)) { |
174 | 0 | throw new TimeoutFormatException("Unit '" + unit |
175 | |
+ "' must be a non-numeric word"); |
176 | |
} |
177 | 160 | if (value < 0) { |
178 | 0 | throw new TimeoutFormatException("Unit value '" + value |
179 | |
+ "' cannot be negative"); |
180 | |
} |
181 | 160 | units.put(unit, Long.valueOf(value)); |
182 | 160 | return this; |
183 | |
} |
184 | |
|
185 | |
public boolean isValid(String timeout) { |
186 | 38 | return TIMEOUT_PATTERN.matcher(timeout).find(); |
187 | |
} |
188 | |
|
189 | |
public long asSeconds(String timeout) { |
190 | 5 | long total = 0; |
191 | 5 | Matcher matcher = TIMEOUT_PATTERN.matcher(timeout); |
192 | 16 | while (matcher.find()) { |
193 | 11 | long value = Long.parseLong(matcher.group(1)); |
194 | 11 | String unit = matcher.group(2); |
195 | 11 | if (!units.containsKey(unit)) { |
196 | 0 | throw new TimeoutFormatException("Unrecognized unit: " |
197 | |
+ unit); |
198 | |
} |
199 | 11 | total += units.get(unit).longValue() * value; |
200 | 11 | } |
201 | 5 | return total; |
202 | |
} |
203 | |
|
204 | |
} |
205 | |
|
206 | |
|
207 | |
|
208 | |
|
209 | 40 | public static class DigitTimeoutParser implements TimeoutParser { |
210 | |
|
211 | 1 | private static final Pattern TIMEOUT_PATTERN = compile("(\\d+)"); |
212 | |
|
213 | |
public boolean isValid(String timeout) { |
214 | 33 | return TIMEOUT_PATTERN.matcher(timeout).find(); |
215 | |
} |
216 | |
|
217 | |
public long asSeconds(String timeout) { |
218 | 31 | return Long.parseLong(timeout); |
219 | |
} |
220 | |
|
221 | |
} |
222 | |
|
223 | |
@SuppressWarnings("serial") |
224 | |
public static class TimeoutFormatException extends IllegalArgumentException { |
225 | |
|
226 | |
public TimeoutFormatException(String message) { |
227 | 2 | super(message); |
228 | 2 | } |
229 | |
|
230 | |
} |
231 | |
} |