/*
* Copyright (c) 2016 Red Hat, Inc. and/or its affiliates.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.jberet.operations;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.Set;
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.BatchStatus;
import javax.batch.runtime.JobExecution;
import javax.batch.runtime.JobInstance;
import javax.batch.runtime.StepExecution;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.InvalidTransactionException;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.jberet._private.BatchLogger;
import org.jberet.creation.ArchiveXmlLoader;
import org.jberet.creation.ArtifactFactoryWrapper;
import org.jberet.job.model.Job;
import org.jberet.repository.ApplicationAndJobName;
import org.jberet.repository.JobRepository;
import org.jberet.runtime.JobExecutionImpl;
import org.jberet.runtime.JobInstanceImpl;
import org.jberet.runtime.context.JobContextImpl;
import org.jberet.runtime.runner.JobExecutionRunner;
import org.jberet.spi.BatchEnvironment;
import org.jberet.spi.PropertyKey;
import static org.jberet._private.BatchMessages.MESSAGES;
/**
* An abstract implementation of a {@link JobOperator}. Subclasses should generally delegate to the super methods of
* this abstraction.
*
* @author Cheng Fang - Initial API and implementation
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
public abstract class AbstractJobOperator implements JobOperator {
/**
* Returns the batch environment that should be used for this JobOperator. Implementations should never return
* {@code null}.
*
* @return the batch environment
*/
protected abstract BatchEnvironment getBatchEnvironment();
/**
* This is equivalent to {@link BatchEnvironment#getJobRepository() getBatchEnvironment().getJobRepository()}.
*
* @return the job repository that belongs to the batch environment
*/
protected JobRepository getJobRepository() {
return getBatchEnvironment().getJobRepository();
}
@Override
public long start(final String jobXMLName, final Properties jobParameters) throws JobStartException, JobSecurityException {
return start(jobXMLName, jobParameters, null);
}
/**
* Creates a new job instance and starts the first execution of that instance.
*
* @param jobXMLName specifies the name of the job XML describing the job
* @param jobParameters specifies the keyword/value pairs for attribute substitution in the Job XML
* @param user the user to associate with the job execution
*
* @return executionId for the job execution.
*
* @throws JobStartException if the job failed to start
* @throws JobSecurityException if there is a security issue with starting the job
*
* @see #start(String, Properties)
*/
public long start(final String jobXMLName, final Properties jobParameters, final String user) throws JobStartException, JobSecurityException {
final BatchEnvironment batchEnvironment = getBatchEnvironment();
final Job jobDefined = ArchiveXmlLoader.loadJobXml(jobXMLName, batchEnvironment.getClassLoader(),
new ArrayList<Job>(), batchEnvironment.getJobXmlResolver());
return start(jobDefined, jobParameters, user);
}
/**
* Starts a pre-configured {@link Job} instance, with job parameters.
*
* @param jobDefined a pre-configured job
* @param jobParameters job parameters for the current job execution
*
* @return job execution id as a long number
*
* @throws JobStartException if failed to start the job
* @throws JobSecurityException if failed to start the job due to security permission
* @see org.jberet.job.model.JobBuilder
* @since 1.2.0
*/
public long start(final Job jobDefined, final Properties jobParameters) throws JobStartException, JobSecurityException {
return start(jobDefined, jobParameters, null);
}
/**
* Starts a pre-configured {@link Job} instance, with job parameters and sets the user on the execution
*
* @param jobDefined a pre-configured job
* @param jobParameters job parameters for the current job execution
* @param user the user to associate with the job execution
*
* @return job execution id as a long number
*
* @throws JobStartException if failed to start the job
* @throws JobSecurityException if failed to start the job due to security permission
* @see org.jberet.job.model.JobBuilder
* @since 1.2.2
*/
public long start(final Job jobDefined, final Properties jobParameters, final String user) throws JobStartException, JobSecurityException {
final BatchEnvironment batchEnvironment = getBatchEnvironment();
final ClassLoader classLoader = batchEnvironment.getClassLoader();
final String applicationName = getApplicationName();
getJobRepository().addJob(new ApplicationAndJobName(applicationName, jobDefined.getId()), jobDefined);
try {
return invokeTransaction(new TransactionInvocation<Long>() {
@Override
public Long invoke() throws JobStartException, JobSecurityException {
final JobInstanceImpl jobInstance = getJobRepository().createJobInstance(jobDefined, applicationName, classLoader);
return startJobExecution(jobInstance, jobParameters, null, user);
}
});
} catch (InvalidTransactionException | SystemException e) {
throw new JobStartException(e);
}
}
@Override
public void stop(final long executionId) throws NoSuchJobExecutionException,
JobExecutionNotRunningException, JobSecurityException {
final JobExecutionImpl jobExecution = getJobExecutionImpl(executionId);
final BatchStatus s = jobExecution.getBatchStatus();
if (s == BatchStatus.STOPPED || s == BatchStatus.FAILED || s == BatchStatus.ABANDONED ||
s == BatchStatus.COMPLETED) {
throw MESSAGES.jobExecutionNotRunningException(executionId, s);
} else if (s == BatchStatus.STOPPING) {
//in process of stopping, do nothing
} else {
jobExecution.setBatchStatus(BatchStatus.STOPPING);
jobExecution.stop();
}
}
@Override
public Set<String> getJobNames() throws JobSecurityException {
return getJobRepository().getJobNames();
}
@Override
public int getJobInstanceCount(final String jobName) throws NoSuchJobException, JobSecurityException {
if (jobName == null) {
throw MESSAGES.noSuchJobException(null);
}
final JobRepository repository = getJobRepository();
final int count = repository.getJobInstanceCount(jobName);
if (count == 0 && !repository.jobExists(jobName)) {
throw MESSAGES.noSuchJobException(jobName);
}
return count;
}
@Override
public List<JobInstance> getJobInstances(final String jobName, final int start, final int count) throws NoSuchJobException, JobSecurityException {
if (jobName == null) {
throw MESSAGES.noSuchJobException(null);
}
final JobRepository repository = getJobRepository();
final List<JobInstance> instances = repository.getJobInstances(jobName);
final int size = instances.size();
if (size == 0 && !repository.jobExists(jobName)) {
throw MESSAGES.noSuchJobException(jobName);
}
return instances.subList(Math.min(start, size), Math.min(start + count, size));
}
@Override
public List<Long> getRunningExecutions(final String jobName) throws NoSuchJobException, JobSecurityException {
if (jobName == null) {
throw MESSAGES.noSuchJobException(null);
}
final JobRepository repository = getJobRepository();
final List<Long> result = repository.getRunningExecutions(jobName);
if (result.size() == 0 && !repository.jobExists(jobName)) {
throw MESSAGES.noSuchJobException(jobName);
}
return result;
}
@Override
public Properties getParameters(final long executionId) throws NoSuchJobExecutionException, JobSecurityException {
return getJobExecutionImpl(executionId).getJobParameters();
}
@Override
public long restart(final long executionId, final Properties restartParameters) throws JobExecutionAlreadyCompleteException,
NoSuchJobExecutionException, JobExecutionNotMostRecentException, JobRestartException, JobSecurityException {
return restart(executionId, restartParameters, null);
}
/**
* Restarts a stopped or failed job with the specified user.
*
* @param executionId the execution id used for the restart, this must be the most recent execution
* @param restartParameters the new properties used for the restart
* @param user the user to associate with the job execution
*
* @return the new execution id
*
* @throws JobExecutionAlreadyCompleteException if the job was already completed
* @throws NoSuchJobExecutionException the job does not exist
* @throws JobExecutionNotMostRecentException if the execution id is not the most recent execution
* @throws JobRestartException of the job failed to restart
* @throws JobSecurityException if failed to start the job due to security permission
*/
public long restart(final long executionId, final Properties restartParameters, final String user) throws JobExecutionAlreadyCompleteException,
NoSuchJobExecutionException, JobExecutionNotMostRecentException, JobRestartException, JobSecurityException {
final JobExecutionImpl originalToRestart = getJobExecutionImpl(executionId);
if (Job.UNRESTARTABLE.equals(originalToRestart.getRestartPosition())) {
throw MESSAGES.unrestartableJob(originalToRestart.getJobName(), executionId);
}
final BatchStatus previousStatus = originalToRestart.getBatchStatus();
if (previousStatus == BatchStatus.FAILED || previousStatus == BatchStatus.STOPPED) {
return restartFailedOrStopped(executionId, originalToRestart, restartParameters, user);
}
if (previousStatus == BatchStatus.COMPLETED) {
throw MESSAGES.jobExecutionAlreadyCompleteException(executionId);
}
if (previousStatus == BatchStatus.ABANDONED) {
throw MESSAGES.jobRestartException(executionId, previousStatus);
}
//previousStatus is now one of STARTING, STARTED, or STOPPING
final String restartMode = restartParameters != null ? restartParameters.getProperty(PropertyKey.RESTART_MODE) : null;
if (PropertyKey.RESTART_MODE_STRICT.equalsIgnoreCase(restartMode)) {
throw MESSAGES.jobRestartException(executionId, previousStatus);
} else if (restartMode == null || restartMode.equalsIgnoreCase(PropertyKey.RESTART_MODE_DETECT)) {
//to detect if originalToRestart had crashed or not
if (originalToRestart.getJobInstance().getUnsubstitutedJob() != null) {
throw MESSAGES.restartRunningExecution(executionId, originalToRestart.getJobName(), previousStatus, restartMode);
}
} else if (!restartMode.equalsIgnoreCase(PropertyKey.RESTART_MODE_FORCE)) {
throw MESSAGES.invalidRestartMode(executionId, originalToRestart.getJobName(), previousStatus, restartMode,
Arrays.asList(PropertyKey.RESTART_MODE_DETECT, PropertyKey.RESTART_MODE_FORCE, PropertyKey.RESTART_MODE_STRICT));
}
//update batch status in originalToRestart to FAILED, for previousStatus STARTING, STARTED, or STOPPING
BatchLogger.LOGGER.markAsFailed(executionId, originalToRestart.getJobName(), previousStatus, restartMode);
originalToRestart.setBatchStatus(BatchStatus.FAILED);
getJobRepository().updateJobExecution(originalToRestart, false, false);
return restartFailedOrStopped(executionId, originalToRestart, restartParameters, user);
}
@Override
public void abandon(final long executionId) throws
NoSuchJobExecutionException, JobExecutionIsRunningException, JobSecurityException {
final JobExecutionImpl jobExecution = getJobExecutionImpl(executionId);
final BatchStatus batchStatus = jobExecution.getBatchStatus();
if (batchStatus == BatchStatus.COMPLETED ||
batchStatus == BatchStatus.FAILED ||
batchStatus == BatchStatus.STOPPED ||
batchStatus == BatchStatus.ABANDONED) {
jobExecution.setBatchStatus(BatchStatus.ABANDONED);
getJobRepository().updateJobExecution(jobExecution, false, false);
final JobInstanceImpl jobInstance = jobExecution.getJobInstance();
if (jobInstance != null) {
jobInstance.setUnsubstitutedJob(null);
}
} else {
throw MESSAGES.jobExecutionIsRunningException(executionId);
}
}
@Override
public JobInstance getJobInstance(final long executionId) throws NoSuchJobExecutionException, JobSecurityException {
final JobExecutionImpl jobExecution = getJobExecutionImpl(executionId);
return jobExecution.getJobInstance();
}
@Override
public List<JobExecution> getJobExecutions(final JobInstance instance) throws
NoSuchJobInstanceException, JobSecurityException {
return getJobRepository().getJobExecutions(instance);
}
@Override
public JobExecution getJobExecution(final long executionId) throws NoSuchJobExecutionException, JobSecurityException {
return getJobExecutionImpl(executionId);
}
@Override
public List<StepExecution> getStepExecutions(final long jobExecutionId) throws
NoSuchJobExecutionException, JobSecurityException {
final List<StepExecution> stepExecutions = getJobRepository().getStepExecutions(jobExecutionId, getBatchEnvironment().getClassLoader());
if (stepExecutions.isEmpty()) {
//check if the jobExecutionId passed in points to a valid JobExecution
//since no step executions under this jobExecutionId was found, it's likely this job execution may not exist
//getJobExecutionImpl() call will throw NoSuchJobExecutionException for non-exist jobExecutionId.
getJobExecutionImpl(jobExecutionId);
}
return stepExecutions;
}
/**
* Returns the job execution implementation found in the job repository.
*
* @param executionId the execution id
*
* @return the job execution implementation
*
* @throws NoSuchJobExecutionException if the job was not found in the repository
*/
@SuppressWarnings("WeakerAccess")
protected JobExecutionImpl getJobExecutionImpl(final long executionId) throws NoSuchJobExecutionException {
final JobExecutionImpl jobExecution = (JobExecutionImpl) getJobRepository().getJobExecution(executionId);
if (jobExecution == null) {
throw MESSAGES.noSuchJobExecution(executionId);
}
return jobExecution;
}
/**
* Restarts a FAILED or STOPPED job execution.
*
* @param executionId the old job execution id to restart
* @param originalToRestart the old job execution
* @param restartParameters restart job parameters
* @param user the user to associate with the job execution
*
* @return the new job execution id
*
* @throws JobExecutionNotMostRecentException
* @throws JobRestartException
*/
private long restartFailedOrStopped(final long executionId, final JobExecutionImpl originalToRestart, final Properties restartParameters, final String user)
throws JobExecutionNotMostRecentException, JobRestartException {
final JobInstanceImpl jobInstance = originalToRestart.getJobInstance();
if (jobInstance == null) {
throw MESSAGES.noSuchJobInstance(null);
}
final List<JobExecution> executions = getJobExecutions(jobInstance);
final JobExecution mostRecentExecution = executions.get(executions.size() - 1);
if (executionId != mostRecentExecution.getExecutionId()) {
throw MESSAGES.jobExecutionNotMostRecentException(executionId, jobInstance.getInstanceId());
}
final BatchEnvironment batchEnvironment = getBatchEnvironment();
final JobRepository repository = getJobRepository();
// the job may not have been loaded, e.g., when the restart is performed in a new JVM
final String jobName = originalToRestart.getJobName();
Properties oldJobParameters = originalToRestart.getJobParameters();
Job jobDefined = jobInstance.getUnsubstitutedJob();
if (jobDefined == null) {
final ApplicationAndJobName applicationAndJobName = new ApplicationAndJobName(jobInstance.getApplicationName(), jobName);
jobDefined = repository.getJob(applicationAndJobName);
if (jobDefined == null) {
String jobXmlName = null;
if (oldJobParameters != null) {
jobXmlName = oldJobParameters.getProperty(Job.JOB_XML_NAME);
}
if (jobXmlName == null) {
jobXmlName = jobName;
} else {
oldJobParameters.remove(Job.JOB_XML_NAME);
if (!oldJobParameters.propertyNames().hasMoreElements()) {
oldJobParameters = null;
}
}
jobDefined = ArchiveXmlLoader.loadJobXml(jobXmlName, batchEnvironment.getClassLoader(), new ArrayList<Job>(), batchEnvironment.getJobXmlResolver());
repository.addJob(applicationAndJobName, jobDefined);
}
jobInstance.setUnsubstitutedJob(jobDefined);
}
try {
final Properties combinedProperties;
if (oldJobParameters != null) {
if (restartParameters == null) {
combinedProperties = oldJobParameters;
} else {
combinedProperties = new Properties(oldJobParameters);
for (final String k : restartParameters.stringPropertyNames()) {
combinedProperties.setProperty(k, restartParameters.getProperty(k));
}
}
} else {
combinedProperties = restartParameters;
}
return invokeTransaction(new TransactionInvocation<Long>() {
@Override
public Long invoke() throws JobStartException, JobSecurityException {
return startJobExecution(jobInstance, combinedProperties, originalToRestart, user);
}
});
} catch (final Exception e) {
throw new JobRestartException(e);
}
}
private long startJobExecution(final JobInstanceImpl jobInstance, final Properties jobParameters, final JobExecutionImpl originalToRestart, final String user) throws JobStartException, JobSecurityException {
final BatchEnvironment batchEnvironment = getBatchEnvironment();
final JobRepository repository = getJobRepository();
final JobExecutionImpl jobExecution = repository.createJobExecution(jobInstance, jobParameters);
jobExecution.setUser(user);
final JobContextImpl jobContext = new JobContextImpl(jobExecution, originalToRestart, new ArtifactFactoryWrapper(batchEnvironment.getArtifactFactory()), repository, batchEnvironment);
final JobExecutionRunner jobExecutionRunner = new JobExecutionRunner(jobContext);
jobContext.getBatchEnvironment().submitTask(jobExecutionRunner);
return jobExecution.getExecutionId();
}
private String getApplicationName() {
try {
return InitialContext.doLookup("java:app/AppName");
} catch (NamingException e) {
return null;
}
}
private <T> T invokeTransaction(final TransactionInvocation<T> transactionInvocation) throws SystemException, InvalidTransactionException {
final TransactionManager tm = getBatchEnvironment().getTransactionManager();
final Transaction tx = tm.suspend();
if (tx != null) {
try {
return transactionInvocation.invoke();
} finally {
tm.resume(tx);
}
}
// No transaction in process
return transactionInvocation.invoke();
}
private interface TransactionInvocation<T> {
T invoke() throws JobStartException, JobSecurityException;
}
}