1 | /* |
2 | * Copyright 2006-2013 the original author or authors. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.springframework.batch.core.job.flow.support; |
17 | |
18 | import org.springframework.batch.core.ExitStatus; |
19 | import org.springframework.batch.core.job.flow.State; |
20 | import org.springframework.batch.support.PatternMatcher; |
21 | import org.springframework.util.Assert; |
22 | import org.springframework.util.StringUtils; |
23 | |
24 | /** |
25 | * Value object representing a potential transition from one {@link State} to |
26 | * another. The originating State name and the next {@link State} to execute are |
27 | * linked by a pattern for the {@link ExitStatus#getExitCode() exit code} of an |
28 | * execution of the originating State. |
29 | * |
30 | * @author Dave Syer |
31 | * @since 2.0 |
32 | */ |
33 | public final class StateTransition implements Comparable<StateTransition> { |
34 | |
35 | private final State state; |
36 | |
37 | private final String pattern; |
38 | |
39 | private final String next; |
40 | |
41 | /** |
42 | * Create a new end state {@link StateTransition} specification. This |
43 | * transition explicitly goes unconditionally to an end state (i.e. no more |
44 | * executions). |
45 | * |
46 | * @param state the {@link State} used to generate the outcome for this |
47 | * transition |
48 | */ |
49 | public static StateTransition createEndStateTransition(State state) { |
50 | return createStateTransition(state, null, null); |
51 | } |
52 | |
53 | /** |
54 | * Create a new end state {@link StateTransition} specification. This |
55 | * transition explicitly goes to an end state (i.e. no more processing) if |
56 | * the outcome matches the pattern. |
57 | * |
58 | * @param state the {@link State} used to generate the outcome for this |
59 | * transition |
60 | * @param pattern the pattern to match in the exit status of the |
61 | * {@link State} |
62 | */ |
63 | public static StateTransition createEndStateTransition(State state, String pattern) { |
64 | return createStateTransition(state, pattern, null); |
65 | } |
66 | |
67 | /** |
68 | * Convenience method to switch the origin and destination of a transition, |
69 | * creating a new instance. |
70 | * |
71 | * @param stateTransition an existing state transition |
72 | * @param state the new state for the origin |
73 | * @param next the new name for the destination |
74 | * |
75 | * @return a {@link StateTransition} |
76 | */ |
77 | public static StateTransition switchOriginAndDestination(StateTransition stateTransition, State state, String next) { |
78 | return createStateTransition(state, stateTransition.pattern, next); |
79 | } |
80 | |
81 | /** |
82 | * Create a new state {@link StateTransition} specification with a wildcard |
83 | * pattern that matches all outcomes. |
84 | * |
85 | * @param state the {@link State} used to generate the outcome for this |
86 | * transition |
87 | * @param next the name of the next {@link State} to execute |
88 | */ |
89 | public static StateTransition createStateTransition(State state, String next) { |
90 | return createStateTransition(state, null, next); |
91 | } |
92 | |
93 | /** |
94 | * Create a new {@link StateTransition} specification from one {@link State} |
95 | * to another (by name). |
96 | * |
97 | * @param state the {@link State} used to generate the outcome for this |
98 | * transition |
99 | * @param pattern the pattern to match in the exit status of the |
100 | * {@link State} |
101 | * @param next the name of the next {@link State} to execute |
102 | */ |
103 | public static StateTransition createStateTransition(State state, String pattern, String next) { |
104 | return new StateTransition(state, pattern, next); |
105 | } |
106 | |
107 | private StateTransition(State state, String pattern, String next) { |
108 | super(); |
109 | if (!StringUtils.hasText(pattern)) { |
110 | this.pattern = "*"; |
111 | } |
112 | else { |
113 | this.pattern = pattern; |
114 | } |
115 | |
116 | Assert.notNull(state, "A state is required for a StateTransition"); |
117 | if (state.isEndState() && StringUtils.hasText(next)) { |
118 | throw new IllegalStateException("End state cannot have next: " + state); |
119 | } |
120 | |
121 | this.next = next; |
122 | this.state = state; |
123 | } |
124 | |
125 | /** |
126 | * Public getter for the State. |
127 | * @return the State |
128 | */ |
129 | public State getState() { |
130 | return state; |
131 | } |
132 | |
133 | /** |
134 | * Public getter for the next State name. |
135 | * @return the next |
136 | */ |
137 | public String getNext() { |
138 | return next; |
139 | } |
140 | |
141 | /** |
142 | * Check if the provided status matches the pattern, signalling that the |
143 | * next State should be executed. |
144 | * |
145 | * @param status the status to compare |
146 | * @return true if the pattern matches this status |
147 | */ |
148 | public boolean matches(String status) { |
149 | return PatternMatcher.match(pattern, status); |
150 | } |
151 | |
152 | /** |
153 | * Check for a special next State signalling the end of a job. |
154 | * |
155 | * @return true if this transition goes nowhere (there is no next) |
156 | */ |
157 | public boolean isEnd() { |
158 | return next == null; |
159 | } |
160 | |
161 | /** |
162 | * Sorts by decreasing specificity of pattern, based on just counting |
163 | * wildcards (with * taking precedence over ?). If wildcard counts are equal |
164 | * then falls back to alphabetic comparison. Hence * > foo* > ??? > |
165 | * fo? > foo. |
166 | * @see Comparable#compareTo(Object) |
167 | */ |
168 | @Override |
169 | public int compareTo(StateTransition other) { |
170 | String value = other.pattern; |
171 | if (pattern.equals(value)) { |
172 | return 0; |
173 | } |
174 | int patternCount = StringUtils.countOccurrencesOf(pattern, "*"); |
175 | int valueCount = StringUtils.countOccurrencesOf(value, "*"); |
176 | if (patternCount > valueCount) { |
177 | return 1; |
178 | } |
179 | if (patternCount < valueCount) { |
180 | return -1; |
181 | } |
182 | patternCount = StringUtils.countOccurrencesOf(pattern, "?"); |
183 | valueCount = StringUtils.countOccurrencesOf(value, "?"); |
184 | if (patternCount > valueCount) { |
185 | return 1; |
186 | } |
187 | if (patternCount < valueCount) { |
188 | return -1; |
189 | } |
190 | return pattern.compareTo(value); |
191 | } |
192 | |
193 | /* |
194 | * (non-Javadoc) |
195 | * |
196 | * @see java.lang.Object#toString() |
197 | */ |
198 | @Override |
199 | public String toString() { |
200 | return String.format("StateTransition: [state=%s, pattern=%s, next=%s]", |
201 | state == null ? null : state.getName(), pattern, next); |
202 | } |
203 | |
204 | } |