de.codecentric.batch.jsr352.CustomJsrJobOperator.java Source code

Java tutorial

Introduction

Here is the source code for de.codecentric.batch.jsr352.CustomJsrJobOperator.java

Source

/*
 * Copyright 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 de.codecentric.batch.jsr352;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

import javax.batch.operations.BatchRuntimeException;
import javax.batch.operations.JobSecurityException;
import javax.batch.operations.JobStartException;
import javax.batch.operations.NoSuchJobExecutionException;

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.configuration.DuplicateJobException;
import org.springframework.batch.core.converter.JobParametersConverter;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.job.AbstractJob;
import org.springframework.batch.core.jsr.JsrJobContextFactoryBean;
import org.springframework.batch.core.jsr.configuration.xml.JsrXmlApplicationContext;
import org.springframework.batch.core.jsr.launch.JsrJobOperator;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
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.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.Assert;

import de.codecentric.batch.listener.AddListenerToJobService;

/**
 * We cannot use Spring Batch's JsrJobOperator out of two reasons:
 *
 * <p>In the current implementation it's not possible to use an existing ApplicationContext
 * as base context for the batch job contexts.<br>
 * Second reason is that we want to add listeners automatically to the job for having features
 * like log file separation and standard batch protocols.<br>
 *
 * That's why I patched it to add the functionality we need.
 *
 * @author Tobias Flohre
 */
public class CustomJsrJobOperator extends JsrJobOperator {
    private static final String JSR_JOB_CONTEXT_BEAN_NAME = "jsr_jobContext";

    private ApplicationContext parentContext;
    private JobRepository jobRepository;
    private TaskExecutor taskExecutor;
    private JobParametersConverter jobParametersConverter;
    private static ExecutingJobRegistry jobRegistry = new ExecutingJobRegistry();
    private AddListenerToJobService addListenerToJobService;

    public CustomJsrJobOperator(JobExplorer jobExplorer, JobRepository jobRepository,
            JobParametersConverter jobParametersConverter, AddListenerToJobService addListenerToJobService,
            PlatformTransactionManager transactionManager) {
        super(jobExplorer, jobRepository, jobParametersConverter, transactionManager);
        this.jobRepository = jobRepository;
        this.jobParametersConverter = jobParametersConverter;
        this.addListenerToJobService = addListenerToJobService;
    }

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

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.parentContext = applicationContext;
    }

    @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(parentContext);

        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 AbstractJob job = batchContext.getBean(AbstractJob.class);
                        addListenerToJobService.addListenerToJob(job);
                        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();
    }

    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);
        }

    }

}