1 | /* |
2 | * Copyright 2006-2007 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 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 | * Create a new state {@link StateTransition} specification with a wildcard |
69 | * pattern that matches all outcomes. |
70 | * |
71 | * @param state the {@link State} used to generate the outcome for this |
72 | * transition |
73 | * @param next the name of the next {@link State} to execute |
74 | */ |
75 | public static StateTransition createStateTransition(State state, String next) { |
76 | return createStateTransition(state, null, next); |
77 | } |
78 | |
79 | /** |
80 | * Create a new {@link StateTransition} specification from one {@link State} |
81 | * to another (by name). |
82 | * |
83 | * @param state the {@link State} used to generate the outcome for this |
84 | * transition |
85 | * @param pattern the pattern to match in the exit status of the |
86 | * {@link State} |
87 | * @param next the name of the next {@link State} to execute |
88 | */ |
89 | public static StateTransition createStateTransition(State state, String pattern, String next) { |
90 | return new StateTransition(state, pattern, next); |
91 | } |
92 | |
93 | private StateTransition(State state, String pattern, String next) { |
94 | super(); |
95 | if (!StringUtils.hasText(pattern)) { |
96 | this.pattern = "*"; |
97 | } |
98 | else { |
99 | this.pattern = pattern; |
100 | } |
101 | |
102 | Assert.notNull(state, "A state is required for a StateTransition"); |
103 | if (state.isEndState() && StringUtils.hasText(next)) { |
104 | throw new IllegalStateException("End state cannot have next: "+state); |
105 | } |
106 | |
107 | this.next = next; |
108 | this.state = state; |
109 | } |
110 | |
111 | /** |
112 | * Public getter for the State. |
113 | * @return the State |
114 | */ |
115 | public State getState() { |
116 | return state; |
117 | } |
118 | |
119 | /** |
120 | * Public getter for the next State name. |
121 | * @return the next |
122 | */ |
123 | public String getNext() { |
124 | return next; |
125 | } |
126 | |
127 | /** |
128 | * Check if the provided status matches the pattern, signalling that the |
129 | * next State should be executed. |
130 | * |
131 | * @param status the status to compare |
132 | * @return true if the pattern matches this status |
133 | */ |
134 | public boolean matches(String status) { |
135 | return PatternMatcher.match(pattern, status); |
136 | } |
137 | |
138 | /** |
139 | * Check for a special next State signalling the end of a job. |
140 | * |
141 | * @return true if this transition goes nowhere (there is no next) |
142 | */ |
143 | public boolean isEnd() { |
144 | return next == null; |
145 | } |
146 | |
147 | /** |
148 | * Sorts by decreasing specificity of pattern, based on just counting |
149 | * wildcards (with * taking precedence over ?). If wildcard counts are equal |
150 | * then falls back to alphabetic comparison. Hence * > foo* > ??? > |
151 | * fo? > foo. |
152 | * @see Comparable#compareTo(Object) |
153 | */ |
154 | public int compareTo(StateTransition other) { |
155 | String value = other.pattern; |
156 | if (pattern.equals(value)) { |
157 | return 0; |
158 | } |
159 | int patternCount = StringUtils.countOccurrencesOf(pattern, "*"); |
160 | int valueCount = StringUtils.countOccurrencesOf(value, "*"); |
161 | if (patternCount > valueCount) { |
162 | return 1; |
163 | } |
164 | if (patternCount < valueCount) { |
165 | return -1; |
166 | } |
167 | patternCount = StringUtils.countOccurrencesOf(pattern, "?"); |
168 | valueCount = StringUtils.countOccurrencesOf(value, "?"); |
169 | if (patternCount > valueCount) { |
170 | return 1; |
171 | } |
172 | if (patternCount < valueCount) { |
173 | return -1; |
174 | } |
175 | return pattern.compareTo(value); |
176 | } |
177 | |
178 | /* |
179 | * (non-Javadoc) |
180 | * |
181 | * @see java.lang.Object#toString() |
182 | */ |
183 | @Override |
184 | public String toString() { |
185 | return String.format("StateTransition: [state=%s, pattern=%s, next=%s]", state == null ? null : state.getName(), |
186 | pattern, next); |
187 | } |
188 | |
189 | } |