Package org.springframework.batch.core.jsr.launch

Source Code of org.springframework.batch.core.jsr.launch.JsrJobOperator

/*
* Copyright 2013-2014 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.jsr.launch;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

import javax.batch.operations.BatchRuntimeException;
import javax.batch.operations.JobExecutionAlreadyCompleteException;
import javax.batch.operations.JobExecutionIsRunningException;
import javax.batch.operations.JobExecutionNotMostRecentException;
import javax.batch.operations.JobExecutionNotRunningException;
import javax.batch.operations.JobOperator;
import javax.batch.operations.JobRestartException;
import javax.batch.operations.JobSecurityException;
import javax.batch.operations.JobStartException;
import javax.batch.operations.NoSuchJobException;
import javax.batch.operations.NoSuchJobExecutionException;
import javax.batch.operations.NoSuchJobInstanceException;
import javax.batch.runtime.BatchRuntime;
import javax.batch.runtime.JobExecution;
import javax.batch.runtime.JobInstance;
import javax.batch.runtime.StepExecution;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.DuplicateJobException;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.jsr.JsrJobContextFactoryBean;
import org.springframework.batch.core.jsr.JsrJobExecution;
import org.springframework.batch.core.jsr.JsrJobParametersConverter;
import org.springframework.batch.core.jsr.JsrStepExecution;
import org.springframework.batch.core.jsr.configuration.xml.JsrXmlApplicationContext;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.scope.context.StepSynchronizationManager;
import org.springframework.batch.core.step.NoSuchStepException;
import org.springframework.batch.core.step.StepLocator;
import org.springframework.batch.core.step.tasklet.StoppableTasklet;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.util.Assert;

/**
* The entrance for executing batch jobs as defined by JSR-352.  This class provides
* a single base {@link ApplicationContext} that is the equivalent to the following:
*
* <beans>
*   <batch:job-repository id="jobRepository" ... />
*
*    <bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher">
*      ...
*    </bean>
*
*    <bean id="batchJobOperator" class="org.springframework.batch.core.launch.support.SimpleJobOperator">
*      ...
*    </bean>
*
*   <bean id="jobExplorer" class="org.springframework.batch.core.explore.support.JobExplorerFactoryBean">
*     ...
*   </bean>
*
*   <bean id="dataSource"
*     class="org.apache.commons.dbcp.BasicDataSource">
*     ...
*   </bean>
*
*   <bean id="transactionManager"
*     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
*     ...
*   </bean>
*
*   <bean id="jobParametersConverter" class="org.springframework.batch.core.jsr.JsrJobParametersConverter"/>
*
*   <bean id="jobRegistry" class="org.springframework.batch.core.configuration.support.MapJobRegistry"/>
*
*   <bean id="placeholderProperties" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
*     ...
*   </bean>
* </beans>
*
* Calls to {@link JobOperator#start(String, Properties)} will provide a child context to the above context
* using the job definition and batch.xml if provided.
*
* By default, calls to start/restart will result in asynchronous execution of the batch job (via an asynchronous {@link TaskExecutor}.
* For synchronous behavior or customization of thread behavior, a different {@link TaskExecutor} implementation is required to
* be provided.
*
* <em>Note</em>: This class is intended to only be used for JSR-352 configured jobs. Use of
* this {@link JobOperator} to start/stop/restart Spring Batch jobs may result in unexpected behaviors due to
* how job instances are identified differently.
*
* @author Michael Minella
* @author Chris Schaefer
* @since 3.0
*/
public class JsrJobOperator implements JobOperator, InitializingBean {
  private static final String JSR_JOB_CONTEXT_BEAN_NAME = "jsr_jobContext";
  private final Log logger = LogFactory.getLog(getClass());

  private JobExplorer jobExplorer;
  private JobRepository jobRepository;
  private TaskExecutor taskExecutor;
  private JobParametersConverter jobParametersConverter;
  private static ApplicationContext baseContext;
  private static ExecutingJobRegistry jobRegistry = new ExecutingJobRegistry();

  /**
   * Public constructor used by {@link BatchRuntime#getJobOperator()}.  This will bootstrap a
   * singleton ApplicationContext if one has not already been created (and will utilize the existing
   * one if it has) to populate itself.
   */
  public JsrJobOperator() {
    BeanFactoryLocator beanFactoryLocactor = ContextSingletonBeanFactoryLocator.getInstance();
    BeanFactoryReference ref = beanFactoryLocactor.useBeanFactory("baseContext");
    baseContext = (ApplicationContext) ref.getFactory();

    baseContext.getAutowireCapableBeanFactory().autowireBeanProperties(this,
        AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);

    if(taskExecutor == null) {
      taskExecutor = new SimpleAsyncTaskExecutor();
    }
  }

  /**
   * The no-arg constructor is used by the {@link BatchRuntime#getJobOperator()} and so bootstraps
   * an {@link ApplicationContext}.  This constructor does not and is therefore dependency injection
   * friendly.  Also useful for unit testing.
   *
   * @param jobExplorer an instance of Spring Batch's {@link JobExplorer}
   * @param jobRepository an instance of Spring Batch's {@link JobOperator}
   * @param jobParametersConverter an instance of Spring Batch's {@link JobParametersConverter}
   */
  public JsrJobOperator(JobExplorer jobExplorer, JobRepository jobRepository, JobParametersConverter jobParametersConverter) {
    Assert.notNull(jobExplorer, "A JobExplorer is required");
    Assert.notNull(jobRepository, "A JobRepository is required");
    Assert.notNull(jobParametersConverter, "A ParametersConverter is required");

    this.jobExplorer = jobExplorer;
    this.jobRepository = jobRepository;
    this.jobParametersConverter = jobParametersConverter;
  }

  public void setJobExplorer(JobExplorer jobExplorer) {
    Assert.notNull(jobExplorer, "A JobExplorer is required");

    this.jobExplorer = jobExplorer;
  }

  public void setJobRepository(JobRepository jobRepository) {
    Assert.notNull(jobRepository, "A JobRepository is required");

    this.jobRepository = jobRepository;
  }

  public void setTaskExecutor(TaskExecutor taskExecutor) {
    this.taskExecutor = taskExecutor;
  }

  protected TaskExecutor getTaskExecutor() {
    return taskExecutor;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    if (this.taskExecutor == null) {
      this.taskExecutor = new SimpleAsyncTaskExecutor();
    }
  }

  /**
   * Used to convert the {@link Properties} objects used by JSR-352 to the {@link JobParameters}
   * objects used in Spring Batch.  The default implementation used will configure all parameters
   * to be non-identifying (per the JSR).
   *
   * @param converter A {@link Converter} implementation used to convert {@link Properties} to
   * {@link JobParameters}
   */
  public void setJobParametersConverter(JobParametersConverter converter) {
    Assert.notNull(converter, "A Converter is required");

    this.jobParametersConverter = converter;
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#abandon(long)
   */
  @Override
  public void abandon(long jobExecutionId) throws NoSuchJobExecutionException,
  JobExecutionIsRunningException, JobSecurityException {
    org.springframework.batch.core.JobExecution jobExecution = jobExplorer.getJobExecution(jobExecutionId);

    if(jobExecution == null) {
      throw new NoSuchJobExecutionException("Unable to retrieve JobExecution for id " + jobExecutionId);
    }

    if(jobExecution.isRunning()) {
      throw new JobExecutionIsRunningException("Unable to abandon a job that is currently running");
    }

    jobExecution.upgradeStatus(BatchStatus.ABANDONED);
    jobRepository.update(jobExecution);
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobExecution(long)
   */
  @Override
  public JobExecution getJobExecution(long executionId)
      throws NoSuchJobExecutionException, JobSecurityException {
    org.springframework.batch.core.JobExecution jobExecution = jobExplorer.getJobExecution(executionId);

    if(jobExecution == null) {
      throw new NoSuchJobExecutionException("No execution was found for executionId " + executionId);
    }

    return new JsrJobExecution(jobExecution, jobParametersConverter);
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobExecutions(javax.batch.runtime.JobInstance)
   */
  @Override
  public List<JobExecution> getJobExecutions(JobInstance jobInstance)
      throws NoSuchJobInstanceException, JobSecurityException {
    if(jobInstance == null) {
      throw new NoSuchJobInstanceException("A null JobInstance was provided");
    }

    org.springframework.batch.core.JobInstance instance = (org.springframework.batch.core.JobInstance) jobInstance;
    List<org.springframework.batch.core.JobExecution> batchExecutions = jobExplorer.getJobExecutions(instance);

    if(batchExecutions == null || batchExecutions.size() == 0) {
      throw new NoSuchJobInstanceException("Unable to find JobInstance " + jobInstance.getInstanceId());
    }

    List<JobExecution> results = new ArrayList<JobExecution>(batchExecutions.size());
    for (org.springframework.batch.core.JobExecution jobExecution : batchExecutions) {
      results.add(new JsrJobExecution(jobExecution, jobParametersConverter));
    }

    return results;
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobInstance(long)
   */
  @Override
  public JobInstance getJobInstance(long executionId)
      throws NoSuchJobExecutionException, JobSecurityException {
    org.springframework.batch.core.JobExecution execution = jobExplorer.getJobExecution(executionId);

    if(execution == null) {
      throw new NoSuchJobExecutionException("The JobExecution was not found");
    }

    return jobExplorer.getJobInstance(execution.getJobInstance().getId());
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobInstanceCount(java.lang.String)
   */
  @Override
  public int getJobInstanceCount(String jobName) throws NoSuchJobException,
  JobSecurityException {
    try {
      int count = jobExplorer.getJobInstanceCount(jobName);

      if(count <= 0) {
        throw new NoSuchJobException("No job instances were found for job name " + jobName);
      } else {
        return count;
      }
    } catch (org.springframework.batch.core.launch.NoSuchJobException e) {
      throw new NoSuchJobException("No job instances were found for job name " + jobName);
    }
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobInstances(java.lang.String, int, int)
   */
  @Override
  public List<JobInstance> getJobInstances(String jobName, int start, int count)
      throws NoSuchJobException, JobSecurityException {
    List<org.springframework.batch.core.JobInstance> jobInstances = jobExplorer.getJobInstances(jobName, start, count);

    if(jobInstances == null || jobInstances.size() == 0) {
      throw new NoSuchJobException("The job was not found");
    }

    return new ArrayList<JobInstance>(jobInstances);
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getJobNames()
   */
  @Override
  public Set<String> getJobNames() throws JobSecurityException {
    return new HashSet<String>(jobExplorer.getJobNames());
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getParameters(long)
   */
  @Override
  public Properties getParameters(long executionId)
      throws NoSuchJobExecutionException, JobSecurityException {
    org.springframework.batch.core.JobExecution execution = jobExplorer.getJobExecution(executionId);

    if(execution == null) {
      throw new NoSuchJobExecutionException("Unable to find the JobExecution for id " + executionId);
    }

    Properties properties = jobParametersConverter.getProperties(execution.getJobParameters());
    properties.remove(JsrJobParametersConverter.JOB_RUN_ID);

    return properties;
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getRunningExecutions(java.lang.String)
   */
  @Override
  public List<Long> getRunningExecutions(String name)
      throws NoSuchJobException, JobSecurityException {
    Set<org.springframework.batch.core.JobExecution> findRunningJobExecutions = jobExplorer.findRunningJobExecutions(name);

    if(findRunningJobExecutions.isEmpty()) {
      throw new NoSuchJobException("Job name: " + name + " not found.");
    }

    List<Long> results = new ArrayList<Long>(findRunningJobExecutions.size());

    for (org.springframework.batch.core.JobExecution jobExecution : findRunningJobExecutions) {
      results.add(jobExecution.getId());
    }

    return results;
  }

  /* (non-Javadoc)
   * @see javax.batch.operations.JobOperator#getStepExecutions(long)
   */
  @Override
  public List<StepExecution> getStepExecutions(long executionId)
      throws NoSuchJobExecutionException, JobSecurityException {
    org.springframework.batch.core.JobExecution execution = jobExplorer.getJobExecution(executionId);

    if(execution == null) {
      throw new NoSuchJobException("JobExecution with the id " + executionId + " was not found");
    }

    Collection<org.springframework.batch.core.StepExecution> executions = execution.getStepExecutions();

    List<StepExecution> batchExecutions = new ArrayList<StepExecution>();

    if(executions != null) {
      for (org.springframework.batch.core.StepExecution stepExecution : executions) {
        if(!stepExecution.getStepName().contains(":partition")) {
          batchExecutions.add(new JsrStepExecution(jobExplorer.getStepExecution(executionId, stepExecution.getId())));
        }
      }
    }

    return batchExecutions;
  }

  /**
   * Creates a child {@link ApplicationContext} for the job being requested based upon
   * the /META-INF/batch.xml (if exists) and the /META-INF/batch-jobs/&lt;jobName&gt;.xml
   * configuration and restart the job.
   *
   * @param executionId the database id of the job execution to be restarted.
   * @param params any job parameters to be used during the execution of this job.
   * @throws JobExecutionAlreadyCompleteException thrown if the requested job execution has
   * a status of COMPLETE
   * @throws NoSuchJobExecutionException throw if the requested job execution does not exist
   * in the repository
   * @throws JobExecutionNotMostRecentException thrown if the requested job execution is not
   * the most recent attempt for the job instance it's related to.
   * @throws JobRestartException thrown for any general errors during the job restart process
   */
  @Override
  public long restart(long executionId, Properties params)
      throws JobExecutionAlreadyCompleteException,
      NoSuchJobExecutionException, JobExecutionNotMostRecentException,
      JobRestartException, JobSecurityException {
    org.springframework.batch.core.JobExecution previousJobExecution = jobExplorer.getJobExecution(executionId);

    if (previousJobExecution == null) {
      throw new NoSuchJobExecutionException("No JobExecution found for id: [" + executionId + "]");
    } else if(previousJobExecution.getStatus().equals(BatchStatus.COMPLETED)) {
      throw new JobExecutionAlreadyCompleteException("The requested job has already completed");
    }

    List<org.springframework.batch.core.JobExecution> previousExecutions = jobExplorer.getJobExecutions(previousJobExecution.getJobInstance());

    for (org.springframework.batch.core.JobExecution jobExecution : previousExecutions) {
      if(jobExecution.getCreateTime().compareTo(previousJobExecution.getCreateTime()) > 0) {
        throw new JobExecutionNotMostRecentException("The requested JobExecution to restart was not the most recently run");
      }

      if(jobExecution.getStatus().equals(BatchStatus.ABANDONED)) {
        throw new JobRestartException("JobExecution ID: " + jobExecution.getId() + " is abandoned and attempted to be restarted.");
      }
    }

    final String jobName = previousJobExecution.getJobInstance().getJobName();

    Properties jobRestartProperties = getJobRestartProperties(params, previousJobExecution);

    final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(jobRestartProperties);
    batchContext.setValidating(false);

    Resource batchXml = new ClassPathResource("/META-INF/batch.xml");
    Resource jobXml = new ClassPathResource(previousJobExecution.getJobConfigurationName());

    if(batchXml.exists()) {
      batchContext.load(batchXml);
    }

    if(jobXml.exists()) {
      batchContext.load(jobXml);
    }

    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JsrJobContextFactoryBean").getBeanDefinition();
    beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
    batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition);

    batchContext.setParent(baseContext);

    try {
      batchContext.refresh();
    } catch (BeanCreationException e) {
      throw new JobRestartException(e);
    }

    final org.springframework.batch.core.JobExecution jobExecution;

    try {
      JobParameters jobParameters = jobParametersConverter.getJobParameters(jobRestartProperties);
      jobExecution = jobRepository.createJobExecution(previousJobExecution.getJobInstance(), jobParameters, previousJobExecution.getJobConfigurationName());
    } catch (Exception e) {
      throw new JobRestartException(e);
    }

    try {
      final Semaphore semaphore = new Semaphore(1);
      final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>());
      semaphore.acquire();

      taskExecutor.execute(new Runnable() {

        @Override
        public void run() {
          JsrJobContextFactoryBean factoryBean = null;
          try {
            factoryBean = (JsrJobContextFactoryBean) batchContext.getBean("&" + JSR_JOB_CONTEXT_BEAN_NAME);
            factoryBean.setJobExecution(jobExecution);
            final Job job = batchContext.getBean(Job.class);

            if(!job.isRestartable()) {
              throw new JobRestartException("Job " + jobName + " is not restartable");
            }

            semaphore.release();
            // Initialization of the JobExecution for job level dependencies
            jobRegistry.register(job, jobExecution);
            job.execute(jobExecution);
            jobRegistry.remove(jobExecution);
          }
          catch (Exception e) {
            exceptionHolder.add(e);
          } finally {
            if(factoryBean != null) {
              factoryBean.close();
            }

            batchContext.close();

            if(semaphore.availablePermits() == 0) {
              semaphore.release();
            }
          }
        }
      });

      semaphore.acquire();
      if(exceptionHolder.size() > 0) {
        semaphore.release();
        throw new JobRestartException(exceptionHolder.get(0));
      }
    }
    catch (Exception e) {
      jobExecution.upgradeStatus(BatchStatus.FAILED);
      if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) {
        jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e));
      }

      jobRepository.update(jobExecution);

      if(batchContext.isActive()) {
        batchContext.close();
      }

      throw new JobRestartException(e);
    }

    return jobExecution.getId();
  }

  protected Properties getJobRestartProperties(Properties params, org.springframework.batch.core.JobExecution previousJobExecution) {
    Properties jobRestartProperties = new Properties();

    if (previousJobExecution != null) {
      JobParameters previousJobParameters = previousJobExecution.getJobParameters();

      if (previousJobParameters != null && !previousJobParameters.isEmpty()) {
        jobRestartProperties.putAll(previousJobParameters.toProperties());
      }
    }

    if (params != null) {
      Enumeration<?> propertyNames = params.propertyNames();

      while(propertyNames.hasMoreElements()) {
        String curName = (String) propertyNames.nextElement();
        jobRestartProperties.setProperty(curName, params.getProperty(curName));
      }
    }

    return jobRestartProperties;
  }

  /**
   * Creates a child {@link ApplicationContext} for the job being requested based upon
   * the /META-INF/batch.xml (if exists) and the /META-INF/batch-jobs/&lt;jobName&gt;.xml
   * configuration and launches the job.  Per JSR-352, calls to this method will always
   * create a new {@link JobInstance} (and related {@link JobExecution}).
   *
   * @param jobName the name of the job XML file without the .xml that is located within the
   * /META-INF/batch-jobs directory.
   * @param params any job parameters to be used during the execution of this job.
   */
  @Override
  public long start(String jobName, Properties params) throws JobStartException,
  JobSecurityException {
    final JsrXmlApplicationContext batchContext = new JsrXmlApplicationContext(params);
    batchContext.setValidating(false);

    Resource batchXml = new ClassPathResource("/META-INF/batch.xml");
    String jobConfigurationLocation = "/META-INF/batch-jobs/" + jobName + ".xml";
    Resource jobXml = new ClassPathResource(jobConfigurationLocation);

    if(batchXml.exists()) {
      batchContext.load(batchXml);
    }

    if(jobXml.exists()) {
      batchContext.load(jobXml);
    }

    AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition("org.springframework.batch.core.jsr.JsrJobContextFactoryBean").getBeanDefinition();
    beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
    batchContext.registerBeanDefinition(JSR_JOB_CONTEXT_BEAN_NAME, beanDefinition);

    batchContext.setParent(baseContext);

    try {
      batchContext.refresh();
    } catch (BeanCreationException e) {
      throw new JobStartException(e);
    }

    Assert.notNull(jobName, "The job name must not be null.");

    final org.springframework.batch.core.JobExecution jobExecution;

    try {
      JobParameters jobParameters = jobParametersConverter.getJobParameters(params);
      String [] jobNames = batchContext.getBeanNamesForType(Job.class);

      if(jobNames == null || jobNames.length <= 0) {
        throw new BatchRuntimeException("No Job defined in current context");
      }

      org.springframework.batch.core.JobInstance jobInstance = jobRepository.createJobInstance(jobNames[0], jobParameters);
      jobExecution = jobRepository.createJobExecution(jobInstance, jobParameters, jobConfigurationLocation);
    } catch (Exception e) {
      throw new JobStartException(e);
    }

    try {
      final Semaphore semaphore = new Semaphore(1);
      final List<Exception> exceptionHolder = Collections.synchronizedList(new ArrayList<Exception>());
      semaphore.acquire();

      taskExecutor.execute(new Runnable() {

        @Override
        public void run() {
          JsrJobContextFactoryBean factoryBean = null;
          try {
            factoryBean = (JsrJobContextFactoryBean) batchContext.getBean("&" + JSR_JOB_CONTEXT_BEAN_NAME);
            factoryBean.setJobExecution(jobExecution);
            final Job job = batchContext.getBean(Job.class);
            semaphore.release();
            // Initialization of the JobExecution for job level dependencies
            jobRegistry.register(job, jobExecution);
            job.execute(jobExecution);
            jobRegistry.remove(jobExecution);
          }
          catch (Exception e) {
            exceptionHolder.add(e);
          } finally {
            if(factoryBean != null) {
              factoryBean.close();
            }

            batchContext.close();

            if(semaphore.availablePermits() == 0) {
              semaphore.release();
            }
          }
        }
      });

      semaphore.acquire();
      if(exceptionHolder.size() > 0) {
        semaphore.release();
        throw new JobStartException(exceptionHolder.get(0));
      }
    }
    catch (Exception e) {
      if(jobRegistry.exists(jobExecution.getId())) {
        jobRegistry.remove(jobExecution);
      }
      jobExecution.upgradeStatus(BatchStatus.FAILED);
      if (jobExecution.getExitStatus().equals(ExitStatus.UNKNOWN)) {
        jobExecution.setExitStatus(ExitStatus.FAILED.addExitDescription(e));
      }
      jobRepository.update(jobExecution);

      if(batchContext.isActive()) {
        batchContext.close();
      }

      throw new JobStartException(e);
    }
    return jobExecution.getId();
  }

  /**
   * Stops the running job execution if it is currently running.
   *
   * @param executionId the database id for the {@link JobExecution} to be stopped.
   * @throws NoSuchJobExecutionException
   * @throws JobExecutionNotRunningException
   */
  @Override
  public void stop(long executionId) throws NoSuchJobExecutionException,
  JobExecutionNotRunningException, JobSecurityException {
    org.springframework.batch.core.JobExecution jobExecution = jobExplorer.getJobExecution(executionId);
    // Indicate the execution should be stopped by setting it's status to
    // 'STOPPING'. It is assumed that
    // the step implementation will check this status at chunk boundaries.
    BatchStatus status = jobExecution.getStatus();
    if (!(status == BatchStatus.STARTED || status == BatchStatus.STARTING)) {
      throw new JobExecutionNotRunningException("JobExecution must be running so that it can be stopped: "+jobExecution);
    }
    jobExecution.setStatus(BatchStatus.STOPPING);
    jobRepository.update(jobExecution);

    try {
      Job job = jobRegistry.getJob(jobExecution.getId());
      if (job instanceof StepLocator) {//can only process as StepLocator is the only way to get the step object
        //get the current stepExecution
        for (org.springframework.batch.core.StepExecution stepExecution : jobExecution.getStepExecutions()) {
          if (stepExecution.getStatus().isRunning()) {
            try {
              //have the step execution that's running -> need to 'stop' it
              Step step = ((StepLocator)job).getStep(stepExecution.getStepName());
              if (step instanceof TaskletStep) {
                Tasklet tasklet = ((TaskletStep)step).getTasklet();
                if (tasklet instanceof StoppableTasklet) {
                  StepSynchronizationManager.register(stepExecution);
                  ((StoppableTasklet)tasklet).stop();
                  StepSynchronizationManager.release();
                }
              }
            }
            catch (NoSuchStepException e) {
              logger.warn("Step not found",e);
            }
          }
        }
      }
    }
    catch (NoSuchJobException e) {
      logger.warn("Cannot find Job object",e);
    }
  }

  private static class ExecutingJobRegistry {

    private Map<Long, Job> registry = new ConcurrentHashMap<Long, Job>();

    public void register(Job job, org.springframework.batch.core.JobExecution jobExecution) throws DuplicateJobException {

      if(registry.containsKey(jobExecution.getId())) {
        throw new DuplicateJobException("This job execution has already been registered");
      } else {
        registry.put(jobExecution.getId(), job);
      }
    }

    public void remove(org.springframework.batch.core.JobExecution jobExecution) {
      if(!registry.containsKey(jobExecution.getId())) {
        throw new NoSuchJobExecutionException("The job execution " + jobExecution.getId() + " was not found");
      } else {
        registry.remove(jobExecution.getId());
      }
    }

    public boolean exists(long jobExecutionId) {
      return registry.containsKey(jobExecutionId);
    }

    public Job getJob(long jobExecutionId) {
      if(!registry.containsKey(jobExecutionId)) {
        throw new NoSuchJobExecutionException("The job execution " + jobExecutionId + " was not found");
      } else {
        return registry.get(jobExecutionId);
      }
    }
  }
}
TOP

Related Classes of org.springframework.batch.core.jsr.launch.JsrJobOperator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.