/*
* 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.xd.dirt.plugins.job;
import java.util.Collection;
import java.util.List;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.configuration.ListableJobLocator;
import org.springframework.batch.core.job.SimpleJob;
import org.springframework.batch.core.launch.NoSuchJobException;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.SingleColumnRowMapper;
/**
* Implementation of ListableJobLocator used by {@link DistributedJobService}. This class also provides support methods
* to work with batch jobs in a distributed environment.
*
* @author Ilayaperumal Gopinathan
* @author Andrew Eisenberg
*/
public class DistributedJobLocator implements ListableJobLocator {
private static final String GET_ALL_JOB_NAMES = "SELECT JOB_NAME FROM XD_JOB_REGISTRY";
private static final String GET_ALL_RESTARTABLE_JOBS = "SELECT JOB_NAME FROM XD_JOB_REGISTRY WHERE IS_RESTARTABLE='true'";
private static final String JOB_INCREMENTABLE = "SELECT IS_INCREMENTABLE FROM XD_JOB_REGISTRY WHERE JOB_NAME = ?";
private static final String JOB_RESTARTABLE = "SELECT IS_RESTARTABLE FROM XD_JOB_REGISTRY WHERE JOB_NAME = ?";
private static final String GET_STEP_NAMES = "SELECT STEP_NAME FROM XD_JOB_REGISTRY_STEP_NAMES WHERE JOB_NAME = ?";
private static final String ADD_JOB_REGISTRY = "INSERT INTO XD_JOB_REGISTRY(IS_INCREMENTABLE, IS_RESTARTABLE, JOB_NAME) VALUES(?, ?, ?)";
private static final String ADD_STEP_NAME = "INSERT INTO XD_JOB_REGISTRY_STEP_NAMES(JOB_NAME, STEP_NAME) VALUES(?, ?)";
private static final String UPDATE_JOB_REGISTRY = "UPDATE XD_JOB_REGISTRY SET IS_INCREMENTABLE = ?, IS_RESTARTABLE = ? WHERE JOB_NAME = ?";
private static final String DELETE_JOB_REGISTRY = "DELETE FROM XD_JOB_REGISTRY WHERE JOB_NAME = ?";
private static final String DELETE_STEP_NAMES = "DELETE FROM XD_JOB_REGISTRY_STEP_NAMES WHERE JOB_NAME = ?";
private static final String DELETE_ALL_JOB_REGISTRY = "DELETE FROM XD_JOB_REGISTRY";
private static final String DELETE_ALL_STEP_NAMES = "DELETE FROM XD_JOB_REGISTRY_STEP_NAMES";
private JdbcOperations jdbcTemplate;
/**
* Get all the deployed job names.
*/
@Override
public Collection<String> getJobNames() {
return jdbcTemplate.queryForList(GET_ALL_JOB_NAMES, String.class);
}
/**
* Get all the deployed job names that can be restarted.
*
* @return collection of job names.
*/
public Collection<String> getAllRestartableJobs() {
return jdbcTemplate.queryForList(GET_ALL_RESTARTABLE_JOBS, String.class);
}
/**
* Get simple batch job representation for the given job name.
*
* @param name the job name
* @return Job a simple batch job consisting of its step names.
*/
@Override
public Job getJob(final String name) throws NoSuchJobException {
if (!getJobNames().contains(name)) {
throw new NoSuchJobException(name);
}
// Return a simple job that currently supports
// - Get job name
// - Get step names of the given job
SimpleJob job = new SimpleJob(name) {
@Override
public Collection<String> getStepNames() {
return getJobStepNames(name);
}
};
return job;
}
/**
* Get all the steps' names of a given job name.
*
* @param jobName
* @return the list of step names.
*/
public List<String> getJobStepNames(String jobName) {
return jdbcTemplate.query(GET_STEP_NAMES, new SingleColumnRowMapper<String>(String.class), jobName);
}
/**
* Store the job name , job parameterer incrementable and job restartable
* flags for the job when there is a registry update
*
* @param name the job name to add
* @param incrementable flag to specify if the job parameter is incrementable
* @param restartable flag to specify if the job is restartable
*/
protected void addJob(String name, boolean incrementable, boolean restartable) {
Collection<String> jobNames = this.getJobNames();
// XD admin server will prevent any REST client requests create a job definition with an existing name.
// The container could handle the deployment of mutilple job modules with the same name in the following
// scenarios:
// 1) There could be multiple deployments of job modules (of the *same* job definition) inside a single
// or multiple containers.
// 2) The container that had the job module deployed crashes(without graceful shutdown), thereby
// leaving the batch job entry in {@link DistributedJobLocator}. Had the container been shutdown gracefully,
// then the destroy life-cycle method on batch job will take care deleting the job name entry from
// {@link DistributedJobLocator}
// Since, it is the same job with the given name, we can skip the update into {@link DistributedJobLocator}
if (!jobNames.contains(name)) {
addJobName(name, incrementable, restartable);
}
else {
updateJobName(name, incrementable, restartable);
}
}
/**
* Add a new job entry into the XD_JOB_REGISTRY table.
*
* @param name the name of the job
* @param incrementable flag to specify if the job parameter can be incremented
* @param restartable flag to specify if the job can be restarted upon failure/stoppage.
*/
private void addJobName(String name, boolean incrementable, boolean restartable) {
jdbcTemplate.update(ADD_JOB_REGISTRY, incrementable, restartable, name);
}
/**
* Update an existing job entry at the XD_JOB_REGISTRY table.
*
* @param name the name of the job
* @param incrementable flag to specify if the job parameter can be incremented
* @param restartable flag to specify if the job can be restarted upon failure/stoppage.
*/
private void updateJobName(String name, boolean incrementable, boolean restartable) {
jdbcTemplate.update(UPDATE_JOB_REGISTRY, incrementable, restartable, name);
}
/**
* Add the collection of step names into XD_JOB_REGISTRY_STEP_NAMES for a given job.
*
* @param jobName the job name
* @param stepNames the collection of step names associated with this job
*/
protected void addStepNames(String jobName, Collection<String> stepNames) {
for (String stepName : stepNames) {
jdbcTemplate.update(ADD_STEP_NAME, jobName, stepName);
}
}
/**
* Delete the job entry from the registry table.
* This will delete job registry and the associated step names entries
* for the given job name.
*
* @param jobName the job name.
*/
protected void deleteJobRegistry(String jobName) {
jdbcTemplate.update(DELETE_JOB_REGISTRY, jobName);
jdbcTemplate.update(DELETE_STEP_NAMES, jobName);
}
/**
* Delete all the registry and step name entries.
*/
protected void deleteAll() {
jdbcTemplate.update(DELETE_ALL_JOB_REGISTRY);
jdbcTemplate.update(DELETE_ALL_STEP_NAMES);
}
/**
* @param jobName the job name
* @return Boolean flag to specify if the job parameter can be incremented for the given job name.
*/
public Boolean isIncrementable(String jobName) {
return jdbcTemplate.queryForObject(JOB_INCREMENTABLE, Boolean.class, jobName);
}
/**
* @param jobName the job name
* @return Boolean flag to specify if the job can be restarted.
*/
public Boolean isRestartable(String jobName) {
return jdbcTemplate.queryForObject(JOB_RESTARTABLE, Boolean.class, jobName);
}
public JdbcOperations getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcOperations jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}