/*
* Copyright 2006-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.core.configuration.support;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.configuration.JobFactory;
import org.springframework.batch.core.configuration.JobRegistry;
import org.springframework.batch.core.configuration.StepRegistry;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.util.Assert;
/**
* Default implementation of {@link JobLoader}. Uses a {@link JobRegistry} to
* manage a population of loaded jobs and clears them up when asked. An optional
* {@link StepRegistry} might also be set to register the step(s) available for
* each registered job.
*
* @author Dave Syer
* @author Stephane Nicoll
*/
public class DefaultJobLoader implements JobLoader, InitializingBean {
private static Log logger = LogFactory.getLog(DefaultJobLoader.class);
private JobRegistry jobRegistry;
private StepRegistry stepRegistry;
private Map<ApplicationContextFactory, ConfigurableApplicationContext> contexts = new ConcurrentHashMap<ApplicationContextFactory, ConfigurableApplicationContext>();
private Map<ConfigurableApplicationContext, Collection<String>> contextToJobNames = new ConcurrentHashMap<ConfigurableApplicationContext, Collection<String>>();
/**
* Default constructor useful for declarative configuration.
*/
public DefaultJobLoader() {
this(null, null);
}
/**
* Creates a job loader with the job registry provided.
*
* @param jobRegistry a {@link JobRegistry}
*/
public DefaultJobLoader(JobRegistry jobRegistry) {
this(jobRegistry, null);
}
/**
* Creates a job loader with the job and step registries provided.
*
* @param jobRegistry a {@link JobRegistry}
* @param stepRegistry a {@link StepRegistry}
*/
public DefaultJobLoader(JobRegistry jobRegistry, StepRegistry stepRegistry) {
this.jobRegistry = jobRegistry;
this.stepRegistry = stepRegistry;
}
/**
* The {@link JobRegistry} to use for jobs created.
*
* @param jobRegistry the job registry
*/
public void setJobRegistry(JobRegistry jobRegistry) {
this.jobRegistry = jobRegistry;
}
/**
* The {@link StepRegistry} to use for the steps of created jobs.
*
* @param stepRegistry the step registry
*/
public void setStepRegistry(StepRegistry stepRegistry) {
this.stepRegistry = stepRegistry;
}
/**
* Unregister all the jobs and close all the contexts created by this
* loader.
*
* @see JobLoader#clear()
*/
@Override
public void clear() {
for (ConfigurableApplicationContext context : contexts.values()) {
if (context.isActive()) {
context.close();
}
}
for (String jobName : jobRegistry.getJobNames()) {
doUnregister(jobName);
}
contexts.clear();
contextToJobNames.clear();
}
@Override
public Collection<Job> reload(ApplicationContextFactory factory) {
// If the same factory is loaded twice the context can be closed
if (contexts.containsKey(factory)) {
ConfigurableApplicationContext context = contexts.get(factory);
for (String name : contextToJobNames.get(context)) {
if (logger.isDebugEnabled()) {
logger.debug("Unregistering job: " + name + " from context: " + context.getDisplayName());
}
doUnregister(name);
}
context.close();
}
try {
return doLoad(factory, true);
}
catch (DuplicateJobException e) {
throw new IllegalStateException("Found duplicate job in reload (it should have been unregistered "
+ "if it was previously registered in this loader)", e);
}
}
@Override
public Collection<Job> load(ApplicationContextFactory factory) throws DuplicateJobException {
return doLoad(factory, false);
}
@SuppressWarnings("resource")
private Collection<Job> doLoad(ApplicationContextFactory factory, boolean unregister) throws DuplicateJobException {
Collection<String> jobNamesBefore = jobRegistry.getJobNames();
ConfigurableApplicationContext context = factory.createApplicationContext();
Collection<String> jobNamesAfter = jobRegistry.getJobNames();
// Try to detect auto-registration (e.g. through a bean post processor)
boolean autoRegistrationDetected = jobNamesAfter.size() > jobNamesBefore.size();
Collection<String> jobsRegistered = new HashSet<String>();
if (autoRegistrationDetected) {
for (String name : jobNamesAfter) {
if (!jobNamesBefore.contains(name)) {
jobsRegistered.add(name);
}
}
}
contexts.put(factory, context);
String[] names = context.getBeanNamesForType(Job.class);
for (String name : names) {
if (!autoRegistrationDetected) {
Job job = (Job) context.getBean(name);
String jobName = job.getName();
// On reload try to unregister first
if (unregister) {
if (logger.isDebugEnabled()) {
logger.debug("Unregistering job: " + jobName + " from context: " + context.getDisplayName());
}
doUnregister(jobName);
}
if (logger.isDebugEnabled()) {
logger.debug("Registering job: " + jobName + " from context: " + context.getDisplayName());
}
doRegister(context, job);
jobsRegistered.add(jobName);
}
}
Collection<Job> result = new ArrayList<Job>();
for (String name : jobsRegistered) {
try {
result.add(jobRegistry.getJob(name));
}
catch (NoSuchJobException e) {
// should not happen;
throw new IllegalStateException("Could not retrieve job that was should have been registered", e);
}
}
contextToJobNames.put(context, jobsRegistered);
return result;
}
/**
* Returns all the {@link Step} instances defined by the specified {@link StepLocator}.
* <br>
* The specified <tt>jobApplicationContext</tt> is used to collect additional steps that
* are not exposed by the step locator
*
* @param stepLocator the given step locator
* @param jobApplicationContext the application context of the job
* @return all the {@link Step} defined by the given step locator and context
* @see StepLocator
*/
private Collection<Step> getSteps(final StepLocator stepLocator, final ApplicationContext jobApplicationContext) {
final Collection<String> stepNames = stepLocator.getStepNames();
final Collection<Step> result = new ArrayList<Step>();
for (String stepName : stepNames) {
result.add(stepLocator.getStep(stepName));
}
// Because some steps are referenced by name, we need to look in the context to see if there
// are more Step instances defined. Right now they are registered as being available in the
// context of the job but we have no idea if they are linked to that Job or not.
final Map<String, Step> allSteps = jobApplicationContext.getBeansOfType(Step.class);
for (Map.Entry<String, Step> entry : allSteps.entrySet()) {
if (!stepNames.contains(entry.getKey())) {
result.add(entry.getValue());
}
}
return result;
}
/**
* Registers the specified {@link Job} defined in the specified {@link ConfigurableApplicationContext}.
* <br>
* Makes sure to update the {@link StepRegistry} if it is available.
*
* @param context the context in which the job is defined
* @param job the job to register
* @throws DuplicateJobException if that job is already registered
*/
private void doRegister(ConfigurableApplicationContext context, Job job) throws DuplicateJobException {
final JobFactory jobFactory = new ReferenceJobFactory(job);
jobRegistry.register(jobFactory);
if (stepRegistry != null) {
if (!(job instanceof StepLocator)) {
throw new UnsupportedOperationException("Cannot locate steps from a Job that is not a StepLocator: job="
+ job.getName() + " does not implement StepLocator");
}
stepRegistry.register(job.getName(), getSteps((StepLocator) job, context));
}
}
/**
* Unregisters the job identified by the specified <tt>jobName</tt>.
*
* @param jobName the name of the job to unregister
*/
private void doUnregister(String jobName) {
jobRegistry.unregister(jobName);
if (stepRegistry != null) {
stepRegistry.unregisterStepsFromJob(jobName);
}
}
@Override
public void afterPropertiesSet() {
Assert.notNull(jobRegistry, "Job registry could not be null.");
}
}