com.ibm.jbatch.container.impl.JobControllerImpl Maven / Gradle / Ivy
/*
* Copyright 2012 International Business Machines Corp.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. 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.ibm.jbatch.container.impl;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.batch.runtime.BatchStatus;
import javax.batch.runtime.StepExecution;
import com.ibm.jbatch.container.AbortedBeforeStartException;
import com.ibm.jbatch.container.IController;
import com.ibm.jbatch.container.IExecutionElementController;
import com.ibm.jbatch.container.artifact.proxy.InjectionReferences;
import com.ibm.jbatch.container.artifact.proxy.JobListenerProxy;
import com.ibm.jbatch.container.artifact.proxy.ListenerFactory;
import com.ibm.jbatch.container.context.impl.JobContextImpl;
import com.ibm.jbatch.container.context.impl.StepContextImpl;
import com.ibm.jbatch.container.exception.BatchContainerRuntimeException;
import com.ibm.jbatch.container.jobinstance.JobExecutionHelper;
import com.ibm.jbatch.container.jobinstance.RuntimeJobContextJobExecutionBridge;
import com.ibm.jbatch.container.jsl.ExecutionElement;
import com.ibm.jbatch.container.jsl.Navigator;
import com.ibm.jbatch.container.jsl.Transition;
import com.ibm.jbatch.container.jsl.TransitionElement;
import com.ibm.jbatch.container.services.IJobStatusManagerService;
import com.ibm.jbatch.container.services.IPersistenceManagerService;
import com.ibm.jbatch.container.services.impl.JDBCPersistenceManagerImpl;
import com.ibm.jbatch.container.servicesmanager.ServicesManagerImpl;
import com.ibm.jbatch.container.status.InternalExecutionElementStatus;
import com.ibm.jbatch.container.util.BatchWorkUnit;
import com.ibm.jbatch.container.util.PartitionDataWrapper;
import com.ibm.jbatch.jsl.model.Decision;
import com.ibm.jbatch.jsl.model.End;
import com.ibm.jbatch.jsl.model.Fail;
import com.ibm.jbatch.jsl.model.Flow;
import com.ibm.jbatch.jsl.model.JSLJob;
import com.ibm.jbatch.jsl.model.JSLProperties;
import com.ibm.jbatch.jsl.model.Property;
import com.ibm.jbatch.jsl.model.Split;
import com.ibm.jbatch.jsl.model.Step;
import com.ibm.jbatch.jsl.model.Stop;
public class JobControllerImpl implements IController {
private final static String CLASSNAME = JobControllerImpl.class.getName();
private final static Logger logger = Logger.getLogger(CLASSNAME);
private IJobStatusManagerService jobStatusService = null;
private IPersistenceManagerService persistenceService = null;
private RuntimeJobContextJobExecutionBridge jobExecution = null;
private final JobContextImpl jobContext;
private final Navigator jobNavigator;
private BlockingQueue analyzerQueue;
private ListenerFactory listenerFactory = null;
private final long jobInstanceId;
private RuntimeJobContextJobExecutionBridge rootJobExecution = null;
//
// The currently executing controller, this will only be set to the
// local variable reference when we are ready to accept stop events for
// this execution.
private volatile IExecutionElementController currentStoppableElementController = null;
public JobControllerImpl(RuntimeJobContextJobExecutionBridge jobExecution, RuntimeJobContextJobExecutionBridge rootJobExecution) {
this.jobExecution = jobExecution;
this.jobContext = jobExecution.getJobContext();
this.rootJobExecution = rootJobExecution;
jobNavigator = jobExecution.getJobNavigator();
jobInstanceId = jobExecution.getJobInstance().getInstanceId();
jobStatusService = ServicesManagerImpl.getInstance().getJobStatusManagerService();
persistenceService = ServicesManagerImpl.getInstance().getPersistenceManagerService();
setContextProperties();
setupListeners();
}
private void setContextProperties() {
JSLJob jobModel = jobExecution.getJobNavigator().getJSL();
JSLProperties jslProps = jobModel.getProperties();
if (jslProps != null) {
Properties contextProps = jobContext.getProperties();
for (Property property : jslProps.getPropertyList()) {
contextProps.setProperty(property.getName(), property.getValue());
}
}
}
private void setupListeners() {
JSLJob jobModel = jobExecution.getJobNavigator().getJSL();
InjectionReferences injectionRef = new InjectionReferences(jobContext, null, null);
listenerFactory = new ListenerFactory(jobModel, injectionRef);
jobExecution.setListenerFactory(listenerFactory);
}
public void executeJob() {
final String methodName = "executeJob";
if (logger.isLoggable(Level.FINE)) {
logger.entering(CLASSNAME, methodName);
}
try {
// Periodic check for stopping job
if (BatchStatus.STOPPING.equals(jobContext.getBatchStatus())) {
updateJobBatchStatus(BatchStatus.STOPPED);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as job has been stopped");
}
return;
}
updateJobBatchStatus(BatchStatus.STARTING);
// Periodic check for stopping job
if (jobContext.getBatchStatus().equals(BatchStatus.STOPPING)) {
updateJobBatchStatus(BatchStatus.STOPPED);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as job has been stopped");
}
return;
}
updateJobBatchStatus(BatchStatus.STARTED);
List jobListeners = listenerFactory.getJobListeners();
// Call @BeforeJob on all the job listeners
for (JobListenerProxy listenerProxy : jobListeners) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Invoking @BeforeJob on jobListener: " + listenerProxy.getDelegate() + " of type: " + listenerProxy.getDelegate().getClass());
}
listenerProxy.beforeJob();
}
// Periodic check for stopping job
if (jobContext.getBatchStatus().equals(BatchStatus.STOPPING)) {
updateJobBatchStatus(BatchStatus.STOPPED);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as job has been stopped");
}
return;
}
// --------------------
//
// The BIG loop!!!
//
// --------------------
doExecutionLoop(jobNavigator);
// TODO - Before or after afterJob()?
BatchStatus currentStatus = jobContext.getBatchStatus();
if (currentStatus == null) {
throw new IllegalStateException("Job BatchStatus should have been set by now");
}
BatchStatus curStatus = currentStatus;
// BatchStatus may already have been set by a JSL or
// decision directive.
if (!(curStatus.equals(BatchStatus.FAILED) || curStatus.equals(BatchStatus.STOPPED))) {
updateJobBatchStatus(BatchStatus.COMPLETED);
}
// TODO - this can't be the exact order... but I'm assuming
// @AfterJob wants to react to the exit status.
if (jobContext.getExitStatus() == null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("No job-level exitStatus set, defaulting to job batch Status = " + jobContext.getBatchStatus());
}
jobContext.setExitStatus(jobContext.getBatchStatus().name());
}
// TODO - These job listener afterJob() still gets called on
// stop/end/fail, right?
// Call @AfterJob on all the job listeners
for (JobListenerProxy listenerProxy : jobListeners) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Invoking @AfterJob on jobListener: " + listenerProxy.getDelegate() + " of type: " + listenerProxy.getDelegate().getClass());
}
listenerProxy.afterJob();
}
} catch (Throwable t) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
logger.severe(CLASSNAME + ": caught exception/error: " + t.getMessage() + " : Stack trace: " + sw.toString());
updateJobBatchStatus(BatchStatus.FAILED);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Job failed with exception/error: " + t.getMessage());
}
if (jobContext.getExitStatus() == null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("No job-level exitStatus set, defaulting to job batch Status = " + jobContext.getBatchStatus());
}
jobContext.setExitStatus(jobContext.getBatchStatus().name());
}
throw new BatchContainerRuntimeException(t);
} finally {
// Persist exit status, setting default if not set
// TODO - should we override the empty string or just null?
if (logger.isLoggable(Level.FINE)) {
logger.fine("Job complete for job id=" + jobExecution.getJobInstance().getJobName() + ", executionId="
+ jobExecution.getExecutionId() + ", batchStatus=" + jobContext.getBatchStatus() + ", exitStatus="
+ jobContext.getExitStatus());
}
try {
jobStatusService.updateJobExecutionStatus(jobExecution.getInstanceId(),
jobContext.getBatchStatus(),
jobContext.getExitStatus());
//set update time onto the runtime JobExecution Obj - should I also update the status string here too?
long time = System.currentTimeMillis();
Timestamp updateTS = new Timestamp(time);
jobExecution.setLastUpdateTime(updateTS);
jobExecution.setEndTime(updateTS);
if (persistenceService instanceof JDBCPersistenceManagerImpl){
persistenceService.jobExecutionStatusStringUpdate(jobExecution.getExecutionId(), JDBCPersistenceManagerImpl.EXIT_STATUS, jobContext.getExitStatus(), updateTS);
persistenceService.jobExecutionTimestampUpdate(jobExecution.getExecutionId(), JDBCPersistenceManagerImpl.END_TIME, updateTS);
}
} catch (Throwable t) {
if (logger.isLoggable(Level.WARNING)) {
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
logger.warning("Caught Throwable on updating execution status: " + sw.toString());
}
}
if (logger.isLoggable(Level.FINE)) {
logger.exiting(CLASSNAME, methodName);
}
}
}
private void doExecutionLoop(Navigator jobNavigator) throws Exception {
final String methodName = "doExecutionLoop";
JobContextImpl jobContext = jobExecution.getJobContext();
ExecutionElement currentExecutionElement = null;
try {
currentExecutionElement = jobNavigator.getFirstExecutionElement(jobExecution.getRestartOn());
} catch (Exception e) {
throw new IllegalArgumentException("Job doesn't contain a step.", e);
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("First execution element = " + currentExecutionElement.getId());
}
StepContextImpl stepContext = null;
ExecutionElement previousExecutionElement = null;
IExecutionElementController previousElementController = null;
while (true) {
if (!(currentExecutionElement instanceof Step) && !(currentExecutionElement instanceof Decision)
&& !(currentExecutionElement instanceof Flow) && !(currentExecutionElement instanceof Split)) {
throw new IllegalStateException("Found unknown currentExecutionElement type = " + currentExecutionElement.getClass().getName());
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Next execution element = " + currentExecutionElement.getId());
}
IExecutionElementController elementController =
ExecutionElementControllerFactory.getExecutionElementController(jobExecution, currentExecutionElement);
//If this is a sub job it may have a analyzer queue we need to pass along
elementController.setAnalyzerQueue(this.analyzerQueue);
// Depending on the execution element new up the associated context
// and add it to the controller
if (currentExecutionElement instanceof Decision) {
if (previousExecutionElement == null) {
// only job context is available to the decider since it is
// the first execution element in the job
} else if (previousExecutionElement instanceof Decision) {
throw new BatchContainerRuntimeException("A decision cannot precede another decision.");
} else if (previousExecutionElement instanceof Step) {
// the
// context
// from the
// previous
// execution
// element
StepExecution lastStepExecution = getLastStepExecution((Step) previousExecutionElement);
((DecisionControllerImpl)elementController).setStepExecution((Step)previousExecutionElement, lastStepExecution);
} else if (previousExecutionElement instanceof Split) {
List stepExecutions = getSplitStepExecutions(previousElementController);
((DecisionControllerImpl)elementController).setStepExecutions((Split)previousExecutionElement, stepExecutions);
} else if (previousExecutionElement instanceof Flow) {
// get last step in flow
Step last = getLastStepInTheFlow(previousExecutionElement);
// get last step StepExecution
StepExecution lastStepExecution = getLastStepExecution(last);
((DecisionControllerImpl)elementController).setStepExecution((Flow)previousExecutionElement, lastStepExecution);
}
} else if (currentExecutionElement instanceof Step) {
String stepId = ((Step) currentExecutionElement).getId();
stepContext = new StepContextImpl(stepId);
elementController.setStepContext(stepContext);
} else if (currentExecutionElement instanceof Flow) {
String flowId = ((Flow) currentExecutionElement).getId();
} else if (currentExecutionElement instanceof Split) {
String splitId = ((Split) currentExecutionElement).getId();
}
// check for stop before every executing each execution element
if (jobContext.getBatchStatus().equals(BatchStatus.STOPPING)) {
updateJobBatchStatus(BatchStatus.STOPPED);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as job has been stopped");
}
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Start executing element = " + currentExecutionElement.getId());
}
/*
* NOTE:
* One approach would be to call: jobStatusService.updateJobCurrentStep()
* now. However for something like a flow the element controller (flow controller) will
* have a better view of what the "current step" is, so let's delegate to it instead.
*/
this.currentStoppableElementController = elementController;
InternalExecutionElementStatus executionElementStatus = null;
try {
//////////////////////////////////////////////////////////////////////////
// This is it ... the point where we actually execute the next element !
//////////////////////////////////////////////////////////////////////////
executionElementStatus = elementController.execute(this.rootJobExecution);
} catch (AbortedBeforeStartException e) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Execution failed before even getting to execute execution element = " + currentExecutionElement.getId());
}
logger.warning("Execution failed, InstanceId: " + this.jobInstanceId + ", executionId = " + this.jobExecution.getExecutionId());
throw new BatchContainerRuntimeException("Execution failed before even getting to execute execution element = " +
currentExecutionElement.getId() + "; breaking out of execution loop.");
}
// Throw an exception on fail
if (executionElementStatus.getBatchStatus().equals(BatchStatus.FAILED)) {
logger.warning("Sub-execution returned its own BatchStatus of FAILED. Deal with this by throwing exception to the next layer.");
throw new BatchContainerRuntimeException("Sub-execution returned its own BatchStatus of FAILED. Deal with this by throwing exception to the next layer.");
}
// JSL Stop
if (executionElementStatus.getBatchStatus().equals(BatchStatus.STOPPED)) {
String restartOn = executionElementStatus.getRestartOn();
jslStop(executionElementStatus, executionElementStatus.getExitStatus(), restartOn);
return;
}
// set the execution element controller to null so we don't try to
// call stop on it after the element has finished executing
this.currentStoppableElementController = null;
previousElementController = elementController;
if (logger.isLoggable(Level.FINE)) {
logger.fine("Done executing element=" + currentExecutionElement.getId() + ", exitStatus=" + executionElementStatus);
}
// If we are currently in STOPPING state, then we can now move transition to STOPPED state.
if (jobContext.getBatchStatus().equals(BatchStatus.STOPPING)) {
updateJobBatchStatus(BatchStatus.STOPPED);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as job has been stopped");
}
return;
}
Transition nextTransition = jobNavigator.getNextTransition(currentExecutionElement, executionElementStatus.getExitStatus());
// TODO
if (nextTransition == null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " TODO: is this an expected state or not? ");
}
return;
}
if (nextTransition.getNextExecutionElement() != null) {
// hold on to the previous execution element for the decider
// we need it because we need to inject the context of the
// previous execution element
// into the decider
previousExecutionElement = currentExecutionElement;
currentExecutionElement = nextTransition.getNextExecutionElement();
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , Looping through to next execution element=" + currentExecutionElement.getId());
}
} else if (nextTransition.getTransitionElement() != null) {
// TODO - update job status mgr
TransitionElement transitionElement = nextTransition.getTransitionElement();
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , Looping through to next control element=" + transitionElement);
}
if (transitionElement instanceof Stop) {
String restartOn = ((Stop) transitionElement).getRestart();
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , next control element is a : " + transitionElement + " with restartOn=" + restartOn);
}
String newExitStatus = ((Stop) transitionElement).getExitStatus();
if (newExitStatus != null && !newExitStatus.isEmpty()) { // overrides with exit status in JSL @exit-status
jobContext.setExitStatus(newExitStatus);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , on stop, setting new JSL-specified exit status to: " + newExitStatus);
}
}
InternalExecutionElementStatus internalStopStatus = new InternalExecutionElementStatus(BatchStatus.STOPPED, newExitStatus);
jslStop(internalStopStatus, newExitStatus, restartOn);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting stopped job");
}
return;
} else if (transitionElement instanceof End) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , next control element is an : " + transitionElement);
}
updateJobBatchStatus(BatchStatus.COMPLETED);
String newExitStatus = ((End) transitionElement).getExitStatus();
if (newExitStatus != null && !newExitStatus.isEmpty()) { // overrides with exit status in JSL @exit-status
jobContext.setExitStatus(newExitStatus);
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , on end, setting new JSL-specified exit status to: " + newExitStatus);
}
}
} else if (transitionElement instanceof Fail) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , next control element is a : " + transitionElement);
}
updateJobBatchStatus(BatchStatus.FAILED);
String newExitStatus = ((Fail) transitionElement).getExitStatus();
if (newExitStatus != null && !newExitStatus.isEmpty()) { // overrides
// with
jobContext.setExitStatus(newExitStatus); // exit status
// in
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " , on fail, setting new JSL-specified exit status to: " + newExitStatus);
}
} // @exit-status
} else {
throw new IllegalStateException("Not sure how we'd get here but better than looping.");
}
return;
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Exiting as there are no more execution elements= ");
}
return;
}
}
}
private List getSplitStepExecutions(
IExecutionElementController previousElementController) {
List stepExecutions = new ArrayList();
if(previousElementController != null) {
SplitControllerImpl controller = (SplitControllerImpl)previousElementController;
for (BatchWorkUnit batchWorkUnit : controller.getParallelJobExecs()) {
StepExecution lastStepExecution = null;
List stepExecs = persistenceService.getStepExecutionIDListQueryByJobID(batchWorkUnit.getJobExecutionImpl().getExecutionId());
for (StepExecution stepExecution : stepExecs) {
lastStepExecution = stepExecution;
}
stepExecutions.add(lastStepExecution);
}
}
return stepExecutions;
}
private StepExecution getLastStepExecution(Step last) {
StepExecution lastStepExecution = null;
List stepExecs = persistenceService.getStepExecutionIDListQueryByJobID(jobExecution.getExecutionId());
for (StepExecution stepExecution : stepExecs) {
if(last.getId().equals(stepExecution.getStepName())) {
lastStepExecution = stepExecution;
}
}
return lastStepExecution;
}
private Step getLastStepInTheFlow(ExecutionElement previousExecutionElement) {
Flow flow = (Flow)previousExecutionElement;
Step last = null;
for (ExecutionElement elem : flow.getExecutionElements()) {
if(elem instanceof Step) {
last = (Step) elem;
}
}
return last;
}
@Override
public void stop() {
if (jobContext.getBatchStatus().equals(BatchStatus.STARTING) ||
jobContext.getBatchStatus().equals(BatchStatus.STARTED)) {
updateJobBatchStatus(BatchStatus.STOPPING);
if (this.currentStoppableElementController != null) {
this.currentStoppableElementController.stop();
}
} else {
//TODO do we need to throw an error if the batchlet is already stopping/stopped
//a stop gets issued twice
}
}
private void updateJobBatchStatus(BatchStatus batchStatus) {
String methodName = "updateJobBatchStatus";
if (logger.isLoggable(Level.FINE)) {
logger.fine(methodName + " Setting job batch status to: " + batchStatus);
}
jobContext.setBatchStatus(batchStatus);
jobStatusService.updateJobBatchStatus(jobInstanceId, batchStatus);
//set update time onto the runtime JobExecution Obj - should I also update the status string here too?
long time = System.currentTimeMillis();
Timestamp timestamp = new Timestamp(time);
jobExecution.setLastUpdateTime(timestamp);
switch (batchStatus) {
case STARTING:
//perisistence call to update batch status and update time
JobExecutionHelper.updateBatchStatusUPDATEonly(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
case STARTED:
//perisistence call to update batch status and update time and start time
// Timestamp startTS = new Timestamp(time);
JobExecutionHelper.updateBatchStatusSTART(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
case STOPPING:
//perisistence call to update batch status and update time
JobExecutionHelper.updateBatchStatusUPDATEonly(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
case STOPPED:
//perisistence call to update batch status and update time and stop time
// Timestamp stopTS = new Timestamp(time);
JobExecutionHelper.updateBatchStatusSTOP(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
case COMPLETED:
//perisistence call to update batch status and update time and end time
// Timestamp stopTS = new Timestamp(time);
JobExecutionHelper.updateBatchStatusCOMPLETED(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
case FAILED:
//perisistence call to update batch status and update time and end time
// Timestamp endTS = new Timestamp(time);
JobExecutionHelper.updateBatchStatusFAILED(jobExecution.getExecutionId(), batchStatus.name(), timestamp);
break;
default:
//?
}
// update job execution instance information keyed by execution id
//if (persistenceService instanceof JDBCPersistenceManagerImpl){
// persistenceService.jobExecutionStatusStringUpdate(jobExecution.getExecutionId(), JDBCPersistenceManagerImpl.BATCH_STATUS, ExecutionStatus.getStringValue(batchStatus), updateTS);
//}
}
public void setAnalyzerQueue(BlockingQueue analyzerQueue) {
this.analyzerQueue = analyzerQueue;
}
private void jslStop(InternalExecutionElementStatus status, String exitStatus, String restartOn) {
logger.fine("Logging JSL stop(): status = " + status + ", exitStatus = " + exitStatus + ", restartOn = " +restartOn );
updateJobBatchStatus(BatchStatus.STOPPED);
jobStatusService.updateJobStatusFromJSLStop(jobInstanceId, restartOn);
jobContext.setExitStatus(exitStatus);
return;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy