View Javadoc

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.scope.context;
17  
18  import java.util.ArrayList;
19  import java.util.Collections;
20  import java.util.HashMap;
21  import java.util.HashSet;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Properties;
25  import java.util.Set;
26  import java.util.Map.Entry;
27  
28  import org.springframework.batch.core.JobInstance;
29  import org.springframework.batch.core.JobParameter;
30  import org.springframework.batch.core.JobParameters;
31  import org.springframework.batch.core.StepExecution;
32  import org.springframework.batch.core.UnexpectedJobExecutionException;
33  import org.springframework.batch.core.scope.StepScope;
34  import org.springframework.batch.item.ExecutionContext;
35  import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
36  import org.springframework.util.Assert;
37  
38  /**
39   * A context object that can be used to interrogate the current
40   * {@link StepExecution} and some of its associated properties using expressions
41   * based on bean paths. Has public getters for the step execution and
42   * convenience methods for accessing commonly used properties like the
43   * {@link ExecutionContext} associated with the step or its enclosing job
44   * execution.
45   * 
46   * @author Dave Syer
47   * 
48   */
49  public class StepContext extends SynchronizedAttributeAccessor {
50  
51  	private StepExecution stepExecution;
52  
53  	private Map<String, Set<Runnable>> callbacks = new HashMap<String, Set<Runnable>>();
54  
55  	/**
56  	 * Create a new instance of {@link StepContext} for this
57  	 * {@link StepExecution}.
58  	 * 
59  	 * @param stepExecution a step execution
60  	 */
61  	public StepContext(StepExecution stepExecution) {
62  		super();
63  		Assert.notNull(stepExecution, "A StepContext must have a non-null StepExecution");
64  		this.stepExecution = stepExecution;
65  	}
66  
67  	/**
68  	 * Convenient accessor for current step name identifier. Usually this is the
69  	 * same as the bean name of the step that is executing (but might not be
70  	 * e.g. in a partition).
71  	 * 
72  	 * @return the step name identifier of the current {@link StepExecution}
73  	 */
74  	public String getStepName() {
75  		return stepExecution.getStepName();
76  	}
77  
78  	/**
79  	 * Convenient accessor for current job name identifier.
80  	 * 
81  	 * @return the job name identifier of the enclosing {@link JobInstance}
82  	 * associated with the current {@link StepExecution}
83  	 */
84  	public String getJobName() {
85  		Assert.state(stepExecution.getJobExecution() != null, "StepExecution does not have a JobExecution");
86  		Assert.state(stepExecution.getJobExecution().getJobInstance() != null,
87  				"StepExecution does not have a JobInstance");
88  		return stepExecution.getJobExecution().getJobInstance().getJobName();
89  	}
90  
91  	/**
92  	 * Convenient accessor for System properties to make it easy to access them
93  	 * from placeholder expressions.
94  	 * 
95  	 * @return the current System properties
96  	 */
97  	public Properties getSystemProperties() {
98  		return System.getProperties();
99  	}
100 
101 	/**
102 	 * @return a map containing the items from the step {@link ExecutionContext}
103 	 */
104 	public Map<String, Object> getStepExecutionContext() {
105 		Map<String, Object> result = new HashMap<String, Object>();
106 		for (Entry<String, Object> entry : stepExecution.getExecutionContext().entrySet()) {
107 			result.put(entry.getKey(), entry.getValue());
108 		}
109 		return Collections.unmodifiableMap(result);
110 	}
111 
112 	/**
113 	 * @return a map containing the items from the job {@link ExecutionContext}
114 	 */
115 	public Map<String, Object> getJobExecutionContext() {
116 		Map<String, Object> result = new HashMap<String, Object>();
117 		for (Entry<String, Object> entry : stepExecution.getJobExecution().getExecutionContext().entrySet()) {
118 			result.put(entry.getKey(), entry.getValue());
119 		}
120 		return Collections.unmodifiableMap(result);
121 	}
122 
123 	/**
124 	 * @return a map containing the items from the {@link JobParameters}
125 	 */
126 	public Map<String, Object> getJobParameters() {
127 		Map<String, Object> result = new HashMap<String, Object>();
128 		for (Entry<String, JobParameter> entry : stepExecution.getJobParameters().getParameters().entrySet()) {
129 			result.put(entry.getKey(), entry.getValue().getValue());
130 		}
131 		return Collections.unmodifiableMap(result);
132 	}
133 
134 	/**
135 	 * Allow clients to register callbacks for clean up on close.
136 	 * 
137 	 * @param name the callback id (unique attribute key in this context)
138 	 * @param callback a callback to execute on close
139 	 */
140 	public void registerDestructionCallback(String name, Runnable callback) {
141 		synchronized (callbacks) {
142 			Set<Runnable> set = callbacks.get(name);
143 			if (set == null) {
144 				set = new HashSet<Runnable>();
145 				callbacks.put(name, set);
146 			}
147 			set.add(callback);
148 		}
149 	}
150 
151 	private void unregisterDestructionCallbacks(String name) {
152 		synchronized (callbacks) {
153 			callbacks.remove(name);
154 		}
155 	}
156 
157 	/**
158 	 * Override base class behaviour to ensure destruction callbacks are
159 	 * unregistered as well as the default behaviour.
160 	 * 
161 	 * @see SynchronizedAttributeAccessor#removeAttribute(String)
162 	 */
163 	@Override
164 	public Object removeAttribute(String name) {
165 		unregisterDestructionCallbacks(name);
166 		return super.removeAttribute(name);
167 	}
168 
169 	/**
170 	 * Clean up the context at the end of a step execution. Must be called once
171 	 * at the end of a step execution to honour the destruction callback
172 	 * contract from the {@link StepScope}.
173 	 */
174 	public void close() {
175 
176 		List<Exception> errors = new ArrayList<Exception>();
177 
178 		Map<String, Set<Runnable>> copy = Collections.unmodifiableMap(callbacks);
179 
180 		for (Entry<String, Set<Runnable>> entry : copy.entrySet()) {
181 			Set<Runnable> set = entry.getValue();
182 			for (Runnable callback : set) {
183 				if (callback != null) {
184 					/*
185 					 * The documentation of the interface says that these
186 					 * callbacks must not throw exceptions, but we don't trust
187 					 * them necessarily...
188 					 */
189 					try {
190 						callback.run();
191 					}
192 					catch (RuntimeException t) {
193 						errors.add(t);
194 					}
195 				}
196 			}
197 		}
198 
199 		if (errors.isEmpty()) {
200 			return;
201 		}
202 
203 		Exception error = errors.get(0);
204 		if (error instanceof RuntimeException) {
205 			throw (RuntimeException) error;
206 		}
207 		else {
208 			throw new UnexpectedJobExecutionException("Could not close step context, rethrowing first of "
209 					+ errors.size() + " exceptions.", error);
210 		}
211 	}
212 
213 	/**
214 	 * The current {@link StepExecution} that is active in this context.
215 	 * 
216 	 * @return the current {@link StepExecution}
217 	 */
218 	public StepExecution getStepExecution() {
219 		return stepExecution;
220 	}
221 
222 	/**
223 	 * @return unique identifier for this context based on the step execution
224 	 */
225 	public String getId() {
226 		Assert.state(stepExecution.getId() != null, "StepExecution has no id.  "
227 				+ "It must be saved before it can be used in step scope.");
228 		return "execution#" + stepExecution.getId();
229 	}
230 
231 	/**
232 	 * Extend the base class method to include the step execution itself as a
233 	 * key (i.e. two contexts are only equal if their step executions are the
234 	 * same).
235 	 * 
236 	 * @see SynchronizedAttributeAccessor#equals(Object)
237 	 */
238 	@Override
239 	public boolean equals(Object other) {
240 		if (!(other instanceof StepContext))
241 			return false;
242 		if (other == this)
243 			return true;
244 		StepContext context = (StepContext) other;
245 		if (context.stepExecution == stepExecution) {
246 			return true;
247 		}
248 		return stepExecution.equals(context.stepExecution);
249 	}
250 
251 	/**
252 	 * Overrides the default behaviour to provide a hash code based only on the
253 	 * step execution.
254 	 * 
255 	 * @see SynchronizedAttributeAccessor#hashCode()
256 	 */
257 	@Override
258 	public int hashCode() {
259 		return stepExecution.hashCode();
260 	}
261 
262 	@Override
263 	public String toString() {
264 		return super.toString() + ", stepExecutionContext=" + getStepExecutionContext() + ", jobExecutionContext="
265 				+ getJobExecutionContext() + ", jobParameters=" + getJobParameters();
266 	}
267 
268 }