View Javadoc

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 * &gt; foo* &gt; ??? &gt;
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 }