org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.batch.core.launch.support.JobRegistryBackgroundJobRunner.java

Source

/*
 * 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.launch.support;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.core.Job;
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.support.DefaultJobLoader;
import org.springframework.batch.core.configuration.support.GenericApplicationContextFactory;
import org.springframework.batch.core.configuration.support.JobLoader;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

/**
 * <p>
 * Command line launcher for registering jobs with a {@link JobRegistry}.
 * Normally this will be used in conjunction with an external trigger for the
 * jobs registered, e.g. a JMX MBean wrapper for a {@link JobLauncher}, or a
 * Quartz trigger.
 * </p>
 *
 * <p>
 * With any launch of a batch job within Spring Batch, a Spring context
 * containing the {@link Job} has to be created. Using this launcher, the jobs
 * are all registered with a {@link JobRegistry} defined in a parent application
 * context. The jobs are then set up in child contexts. All dependencies of the
 * runner will then be satisfied by autowiring by type from the parent
 * application context. Default values are provided for all fields except the
 * {@link JobRegistry}. Therefore, if autowiring fails to set it then an
 * exception will be thrown.
 * </p>
 *
 * @author Dave Syer
 *
 */
public class JobRegistryBackgroundJobRunner {

    /**
     * System property key that switches the runner to "embedded" mode
     * (returning immediately from the main method). Useful for testing
     * purposes.
     */
    public static final String EMBEDDED = JobRegistryBackgroundJobRunner.class.getSimpleName() + ".EMBEDDED";

    private static Log logger = LogFactory.getLog(JobRegistryBackgroundJobRunner.class);

    private JobLoader jobLoader;

    private ApplicationContext parentContext = null;

    public static boolean testing = false;

    final private String parentContextPath;

    private JobRegistry jobRegistry;

    private static List<Exception> errors = Collections.synchronizedList(new ArrayList<Exception>());

    /**
     * @param parentContextPath
     */
    public JobRegistryBackgroundJobRunner(String parentContextPath) {
        super();
        this.parentContextPath = parentContextPath;
    }

    /**
     * A loader for the jobs that are going to be registered.
     *
     * @param jobLoader the {@link JobLoader} to set
     */
    public void setJobLoader(JobLoader jobLoader) {
        this.jobLoader = jobLoader;
    }

    /**
     * A job registry that can be used to create a job loader (if none is provided).
     *
     * @param jobRegistry the {@link JobRegistry} to set
     */
    public void setJobRegistry(JobRegistry jobRegistry) {
        this.jobRegistry = jobRegistry;
    }

    /**
     * Public getter for the startup errors encountered during parent context
     * creation.
     * @return the errors
     */
    public static List<Exception> getErrors() {
        synchronized (errors) {
            return new ArrayList<Exception>(errors);
        }
    }

    private void register(String[] paths) throws DuplicateJobException, IOException {

        maybeCreateJobLoader();

        for (int i = 0; i < paths.length; i++) {

            Resource[] resources = parentContext.getResources(paths[i]);

            for (int j = 0; j < resources.length; j++) {

                Resource path = resources[j];
                logger.info("Registering Job definitions from " + Arrays.toString(resources));

                GenericApplicationContextFactory factory = new GenericApplicationContextFactory(path);
                factory.setApplicationContext(parentContext);
                jobLoader.load(factory);
            }

        }

    }

    /**
     * If there is no {@link JobLoader} then try and create one from existing
     * bean definitions.
     */
    private void maybeCreateJobLoader() {

        if (jobLoader != null) {
            return;
        }

        String[] names = parentContext.getBeanNamesForType(JobLoader.class);
        if (names.length == 0) {
            if (parentContext.containsBean("jobLoader")) {
                jobLoader = parentContext.getBean("jobLoader", JobLoader.class);
                return;
            }
            if (jobRegistry != null) {
                jobLoader = new DefaultJobLoader(jobRegistry);
                return;
            }
        }

        jobLoader = parentContext.getBean(names[0], JobLoader.class);
        return;

    }

    /**
     * Supply a list of application context locations, starting with the parent
     * context, and followed by the children. The parent must contain a
     * {@link JobRegistry} and the child contexts are expected to contain
     * {@link Job} definitions, each of which will be registered wit the
     * registry.
     *
     * Example usage:
     *
     * <pre>
     * $ java -classpath ... JobRegistryBackgroundJobRunner job-registry-context.xml job1.xml job2.xml ...
     * </pre>
     *
     * The child contexts are created only when needed though the
     * {@link JobFactory} interface (but the XML is validated on startup by
     * using it to create a {@link BeanFactory} which is then discarded).
     *
     * The parent context is created in a separate thread, and the program will
     * pause for input in an infinite loop until the user hits any key.
     *
     * @param args the context locations to use (first one is for parent)
     * @throws Exception if anything goes wrong with the context creation
     */
    public static void main(String... args) throws Exception {

        Assert.state(args.length >= 1, "At least one argument (the parent context path) must be provided.");

        final JobRegistryBackgroundJobRunner launcher = new JobRegistryBackgroundJobRunner(args[0]);
        errors.clear();

        logger.info("Starting job registry in parent context from XML at: [" + args[0] + "]");

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    launcher.run();
                } catch (RuntimeException e) {
                    errors.add(e);
                    throw e;
                }
            }
        }).start();

        logger.info("Waiting for parent context to start.");
        while (launcher.parentContext == null && errors.isEmpty()) {
            Thread.sleep(100L);
        }

        synchronized (errors) {
            if (!errors.isEmpty()) {
                logger.info(errors.size() + " errors detected on startup of parent context.  Rethrowing.");
                throw errors.get(0);
            }
        }
        errors.clear();

        // Paths to individual job configurations.
        final String[] paths = new String[args.length - 1];
        System.arraycopy(args, 1, paths, 0, paths.length);

        logger.info("Parent context started.  Registering jobs from paths: " + Arrays.asList(paths));
        launcher.register(paths);

        if (System.getProperty(EMBEDDED) != null) {
            launcher.destroy();
            return;
        }

        synchronized (JobRegistryBackgroundJobRunner.class) {
            System.out.println(
                    "Started application.  Interrupt (CTRL-C) or call JobRegistryBackgroundJobRunner.stop() to exit.");
            JobRegistryBackgroundJobRunner.class.wait();
        }
        launcher.destroy();

    }

    /**
     * Unregister all the {@link Job} instances that were registered by this
     * post processor.
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    private void destroy() throws Exception {
        jobLoader.clear();
    }

    private void run() {
        final ApplicationContext parent = new ClassPathXmlApplicationContext(parentContextPath);
        parent.getAutowireCapableBeanFactory().autowireBeanProperties(this,
                AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
        parent.getAutowireCapableBeanFactory().initializeBean(this, getClass().getSimpleName());
        this.parentContext = parent;
    }

    /**
     * If embedded in a JVM, call this method to terminate the main method.
     */
    public static void stop() {
        synchronized (JobRegistryBackgroundJobRunner.class) {
            JobRegistryBackgroundJobRunner.class.notify();
        }
    }

}