/***************************************************************************
* Copyright (c) 2012-2014 VMware, Inc. All Rights Reserved.
* 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 com.vmware.bdd.service.job;
import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.job.JobParametersExtractor;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.util.Assert;
/**
* @author Jarred Li
* @version 0.9
* @since 0.9
*
*/
public class SubJobStep extends AbstractStep {
private static final Logger logger = Logger.getLogger(SubJobStep.class);
private static String subJobParametersKey = "subJobParameters";
private Job job;
private JobLauncher jobLauncher;
private JobParametersExtractor jobParametersExtractor =
new SubJobParametersExtractor();
private JobExecutionStatusHolder jobExecutionStatusHolder;
private JobExecutionStatusHolder mainJobExecutionStatusHolder;
@Override
public void afterPropertiesSet() throws Exception {
super.afterPropertiesSet();
Assert.state(jobLauncher != null, "A JobLauncher must be provided");
Assert.state(job != null, "A Job must be provided");
}
/**
* The {@link Job} to delegate to in this step.
*
* @param job
* a {@link Job}
*/
public void setJob(Job job) {
this.job = job;
}
/**
* A {@link JobLauncher} is required to be able to run the enclosed
* {@link Job}.
*
* @param jobLauncher
* the {@link JobLauncher} to set
*/
public void setJobLauncher(JobLauncher jobLauncher) {
this.jobLauncher = jobLauncher;
}
/**
* The {@link JobParametersExtractor} is used to extract
* {@link JobParametersExtractor} from the {@link StepExecution} to run the
* {@link Job}. By default an instance will be provided that simply copies
* the {@link JobParameters} from the parent job.
*
* @param jobParametersExtractor
* the {@link JobParametersExtractor} to set
*/
public void setJobParametersExtractor(
JobParametersExtractor jobParametersExtractor) {
this.jobParametersExtractor = jobParametersExtractor;
}
/**
* @return the jobExecutionStatusHolder
*/
public JobExecutionStatusHolder getJobExecutionStatusHolder() {
return jobExecutionStatusHolder;
}
/**
* @param jobExecutionStatusHolder
* the jobExecutionStatusHolder to set
*/
public void setJobExecutionStatusHolder(
JobExecutionStatusHolder jobExecutionStatusHolder) {
this.jobExecutionStatusHolder = jobExecutionStatusHolder;
}
/**
* @return the mainJobExecutionStatusHolder
*/
public JobExecutionStatusHolder getMainJobExecutionStatusHolder() {
return mainJobExecutionStatusHolder;
}
/**
* @param mainJobExecutionStatusHolder
* the mainJobExecutionStatusHolder to set
*/
public void setMainJobExecutionStatusHolder(
JobExecutionStatusHolder mainJobExecutionStatusHolder) {
this.mainJobExecutionStatusHolder = mainJobExecutionStatusHolder;
}
/**
* Execute the job provided by delegating to the {@link JobLauncher} to
* prevent duplicate executions. The job parameters will be generated by the
* {@link JobParametersExtractor} provided (if any), otherwise empty. On a
* restart, the job parameters will be the same as the last (failed)
* execution.
*
* @see AbstractStep#doExecute(StepExecution)
*/
@Override
protected void doExecute(StepExecution stepExecution) throws Exception {
ExecutionContext executionContext = stepExecution.getExecutionContext();
JobParameters jobParameters;
if (executionContext.containsKey(subJobParametersKey)) {
jobParameters =
(JobParameters) executionContext.get(subJobParametersKey);
} else {
jobParameters =
jobParametersExtractor.getJobParameters(job, stepExecution);
executionContext.put(subJobParametersKey, jobParameters);
}
JobExecution subJobExecution = jobLauncher.run(job, jobParameters);
//Wait for job completion
while (true) {
if (subJobExecution.getStatus().isRunning()) {
double subJobProgress =
jobExecutionStatusHolder.getCurrentProgress(subJobExecution
.getId());
mainJobExecutionStatusHolder.setCurrentStepProgress(stepExecution
.getJobExecution().getId(), subJobProgress);
Thread.sleep(3000);
} else {
break;
}
}
String nodeName = null;
if (subJobExecution.getJobInstance().getJobParameters().getParameters()
.containsKey(JobConstants.SUB_JOB_NODE_NAME)) {
nodeName =
subJobExecution.getJobInstance().getJobParameters()
.getString(JobConstants.SUB_JOB_NODE_NAME);
} else {
String stepName = stepExecution.getStepName();
nodeName = stepName.substring(stepName.lastIndexOf("-") + 1);
}
ExecutionContext mainJobExecutionContext =
stepExecution.getJobExecution().getExecutionContext();
updateExecutionStatus(subJobExecution, nodeName, mainJobExecutionContext);
}
/**
* update sub job status into main job's execution context for reporting
*
* @param subJobExecution
* sub job execution
* @param nodeName
* node name of sub job
* @param mainJobExecutionContext
* main job execution context
*/
private void updateExecutionStatus(JobExecution subJobExecution,
String nodeName, ExecutionContext mainJobExecutionContext) {
String rollbackStr =
(String) subJobExecution.getExecutionContext().get(
JobConstants.SUB_JOB_FAIL_FLAG);
boolean rollback = false;
if (rollbackStr != null) {
rollback = Boolean.parseBoolean(rollbackStr);
}
if (subJobExecution.getStatus().isUnsuccessful() || rollback) {
Object errorMessageO =
subJobExecution.getExecutionContext().get(
JobConstants.CURRENT_ERROR_MESSAGE);
String errorMessage = null;
if (errorMessageO != null) {
errorMessage = (String)errorMessageO;
}
Object failedObj =
mainJobExecutionContext.get(JobConstants.SUB_JOB_NODES_FAIL);
List<NodeOperationStatus> failedNodes = null;
if (failedObj == null) {
failedNodes = new ArrayList<NodeOperationStatus>();
} else {
failedNodes = (ArrayList<NodeOperationStatus>) failedObj;
}
NodeOperationStatus failedSubJob =
new NodeOperationStatus(nodeName, false, errorMessage);
failedNodes.add(failedSubJob);
mainJobExecutionContext.put(JobConstants.SUB_JOB_NODES_FAIL,
failedNodes);
} else {
Object succeededObj =
mainJobExecutionContext.get(JobConstants.SUB_JOB_NODES_SUCCEED);
List<NodeOperationStatus> succeededNodes = null;
if (succeededObj == null) {
succeededNodes = new ArrayList<NodeOperationStatus>();
} else {
succeededNodes = (ArrayList<NodeOperationStatus>) succeededObj;
}
NodeOperationStatus succeededSubJob = new NodeOperationStatus(nodeName);
succeededNodes.add(succeededSubJob);
mainJobExecutionContext.put(JobConstants.SUB_JOB_NODES_SUCCEED,
succeededNodes);
}
}
}