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.configuration.support;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.HashSet;
21  import java.util.Map;
22  import java.util.concurrent.ConcurrentHashMap;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.springframework.batch.core.Job;
27  import org.springframework.batch.core.Step;
28  import org.springframework.batch.core.configuration.DuplicateJobException;
29  import org.springframework.batch.core.configuration.JobFactory;
30  import org.springframework.batch.core.configuration.JobRegistry;
31  import org.springframework.batch.core.configuration.StepRegistry;
32  import org.springframework.batch.core.launch.NoSuchJobException;
33  import org.springframework.batch.core.step.StepLocator;
34  import org.springframework.beans.factory.InitializingBean;
35  import org.springframework.context.ApplicationContext;
36  import org.springframework.context.ConfigurableApplicationContext;
37  import org.springframework.util.Assert;
38  
39  /**
40   * Default implementation of {@link JobLoader}. Uses a {@link JobRegistry} to
41   * manage a population of loaded jobs and clears them up when asked. An optional
42   * {@link StepRegistry} might also be set to register the step(s) available for
43   * each registered job.
44   *
45   * @author Dave Syer
46   * @author Stephane Nicoll
47   */
48  public class DefaultJobLoader implements JobLoader, InitializingBean {
49  
50  	private static Log logger = LogFactory.getLog(DefaultJobLoader.class);
51  
52  	private JobRegistry jobRegistry;
53  	private StepRegistry stepRegistry;
54  
55  	private Map<ApplicationContextFactory, ConfigurableApplicationContext> contexts = new ConcurrentHashMap<ApplicationContextFactory, ConfigurableApplicationContext>();
56  
57  	private Map<ConfigurableApplicationContext, Collection<String>> contextToJobNames = new ConcurrentHashMap<ConfigurableApplicationContext, Collection<String>>();
58  
59  	/**
60  	 * Default constructor useful for declarative configuration.
61  	 */
62  	public DefaultJobLoader() {
63  		this(null, null);
64  	}
65  
66  	/**
67  	 * Creates a job loader with the job registry provided.
68  	 *
69  	 * @param jobRegistry a {@link JobRegistry}
70  	 */
71  	public DefaultJobLoader(JobRegistry jobRegistry) {
72  		this(jobRegistry, null);
73  	}
74  
75  	/**
76  	 * Creates a job loader with the job and step registries provided.
77  	 *
78  	 * @param jobRegistry a {@link JobRegistry}
79  	 * @param stepRegistry a {@link StepRegistry}
80  	 */
81  	public DefaultJobLoader(JobRegistry jobRegistry, StepRegistry stepRegistry) {
82  		this.jobRegistry = jobRegistry;
83  		this.stepRegistry = stepRegistry;
84  	}
85  
86  	/**
87  	 * The {@link JobRegistry} to use for jobs created.
88  	 *
89  	 * @param jobRegistry the job registry
90  	 */
91  	public void setJobRegistry(JobRegistry jobRegistry) {
92  		this.jobRegistry = jobRegistry;
93  	}
94  
95  	/**
96  	 * The {@link StepRegistry} to use for the steps of created jobs.
97  	 *
98  	 * @param stepRegistry the step registry
99  	 */
100 	public void setStepRegistry(StepRegistry stepRegistry) {
101 		this.stepRegistry = stepRegistry;
102 	}
103 
104 	/**
105 	 * Unregister all the jobs and close all the contexts created by this
106 	 * loader.
107 	 *
108 	 * @see JobLoader#clear()
109 	 */
110 	@Override
111 	public void clear() {
112 		for (ConfigurableApplicationContext context : contexts.values()) {
113 			if (context.isActive()) {
114 				context.close();
115 			}
116 		}
117 		for (String jobName : jobRegistry.getJobNames()) {
118 			doUnregister(jobName);
119 		}
120 		contexts.clear();
121 	}
122 
123 	@Override
124 	public Collection<Job> reload(ApplicationContextFactory factory) {
125 
126 		// If the same factory is loaded twice the context can be closed
127 		if (contexts.containsKey(factory)) {
128 			ConfigurableApplicationContext context = contexts.get(factory);
129 			for (String name : contextToJobNames.get(context)) {
130 				logger.debug("Unregistering job: " + name + " from context: " + context.getDisplayName());
131 				doUnregister(name);
132 			}
133 			context.close();
134 		}
135 
136 		try {
137 			return doLoad(factory, true);
138 		}
139 		catch (DuplicateJobException e) {
140 			throw new IllegalStateException("Found duplicate job in reload (it should have been unregistered "
141 					+ "if it was previously registered in this loader)", e);
142 		}
143 	}
144 
145 	@Override
146 	public Collection<Job> load(ApplicationContextFactory factory) throws DuplicateJobException {
147 		return doLoad(factory, false);
148 	}
149 
150 	private Collection<Job> doLoad(ApplicationContextFactory factory, boolean unregister) throws DuplicateJobException {
151 
152 		Collection<String> jobNamesBefore = jobRegistry.getJobNames();
153 		ConfigurableApplicationContext context = factory.createApplicationContext();
154 		Collection<String> jobNamesAfter = jobRegistry.getJobNames();
155 		// Try to detect auto-registration (e.g. through a bean post processor)
156 		boolean autoRegistrationDetected = jobNamesAfter.size() > jobNamesBefore.size();
157 
158 		Collection<String> jobsRegistered = new HashSet<String>();
159 		if (autoRegistrationDetected) {
160 			for (String name : jobNamesAfter) {
161 				if (!jobNamesBefore.contains(name)) {
162 					jobsRegistered.add(name);
163 				}
164 			}
165 		}
166 
167 		contexts.put(factory, context);
168 		String[] names = context.getBeanNamesForType(Job.class);
169 
170 		for (String name : names) {
171 
172 			if (!autoRegistrationDetected) {
173 
174 				Job job = (Job) context.getBean(name);
175 				String jobName = job.getName();
176 
177 				// On reload try to unregister first
178 				if (unregister) {
179 					logger.debug("Unregistering job: " + jobName + " from context: " + context.getDisplayName());
180 					doUnregister(jobName);
181 				}
182 
183 				logger.debug("Registering job: " + jobName + " from context: " + context.getDisplayName());
184 				doRegister(context, job);
185 				jobsRegistered.add(jobName);
186 			}
187 
188 		}
189 
190 		Collection<Job> result = new ArrayList<Job>();
191 		for (String name : jobsRegistered) {
192 			try {
193 				result.add(jobRegistry.getJob(name));
194 			}
195 			catch (NoSuchJobException e) {
196 				// should not happen;
197 				throw new IllegalStateException("Could not retrieve job that was should have been registered", e);
198 			}
199 
200 		}
201 
202 		contextToJobNames.put(context, jobsRegistered);
203 
204 		return result;
205 
206 	}
207 
208 	/**
209 	 * Returns all the {@link Step} instances defined by the specified {@link StepLocator}.
210 	 * <p/>
211 	 * The specified <tt>jobApplicationContext</tt> is used to collect additional steps that
212 	 * are not exposed by the step locator
213 	 *
214 	 * @param stepLocator the given step locator
215 	 * @param jobApplicationContext the application context of the job
216 	 * @return all the {@link Step} defined by the given step locator and context
217 	 * @see StepLocator
218 	 */
219 	private Collection<Step> getSteps(final StepLocator stepLocator, final ApplicationContext jobApplicationContext) {
220 		final Collection<String> stepNames = stepLocator.getStepNames();
221 		final Collection<Step> result = new ArrayList<Step>();
222 		for (String stepName : stepNames) {
223 			result.add(stepLocator.getStep(stepName));
224 		}
225 
226 		// Because some steps are referenced by name, we need to look in the context to see if there
227 		// are more Step instances defined. Right now they are registered as being available in the
228 		// context of the job but we have no idea if they are linked to that Job or not.
229 		@SuppressWarnings("unchecked")
230 		final Map<String, Step> allSteps = jobApplicationContext.getBeansOfType(Step.class);
231 		for (Map.Entry<String, Step> entry : allSteps.entrySet()) {
232 			if (!stepNames.contains(entry.getKey())) {
233 				result.add(entry.getValue());
234 			}
235 		}
236 		return result;
237 	}
238 
239 	/**
240 	 * Registers the specified {@link Job} defined in the specified {@link ConfigurableApplicationContext}.
241 	 * <p/>
242 	 * Makes sure to update the {@link StepRegistry} if it is available.
243 	 *
244 	 * @param context the context in which the job is defined
245 	 * @param job the job to register
246 	 * @throws DuplicateJobException if that job is already registered
247 	 */
248 	private void doRegister(ConfigurableApplicationContext context, Job job) throws DuplicateJobException {
249 		final JobFactory jobFactory = new ReferenceJobFactory(job);
250 		jobRegistry.register(jobFactory);
251 
252 		if (stepRegistry != null) {
253 			if (!(job instanceof StepLocator)) {
254 				throw new UnsupportedOperationException("Cannot locate steps from a Job that is not a StepLocator: job="
255 						+ job.getName() + " does not implement StepLocator");
256 			}
257 			stepRegistry.register(job.getName(), getSteps((StepLocator) job, context));
258 		}
259 	}
260 
261 	/**
262 	 * Unregisters the job identified by the specified <tt>jobName</tt>.
263 	 *
264 	 * @param jobName the name of the job to unregister
265 	 */
266 	private void doUnregister(String jobName)  {
267 		jobRegistry.unregister(jobName);
268 		if (stepRegistry != null) {
269 			stepRegistry.unregisterStepsFromJob(jobName);
270 		}
271 
272 	}
273 
274 	@Override
275 	public void afterPropertiesSet() {
276 		Assert.notNull(jobRegistry, "Job registry could not be null.");
277 	}
278 }