/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.zeppelin.scheduler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Skeletal implementation of the Job concept.
* - designed for inheritance
* - should be run on a separate thread
* - maintains internal state: it's status
* - supports listeners who are updated on status change
*
* Job class is serialized/deserialized and used server<->client communication
* and saving/loading jobs from disk.
* Changing/adding/deleting non transitive field name need consideration of that.
*/
public abstract class Job {
/**
* Job status.
*
* READY - Job is not running, ready to run.
* PENDING - Job is submitted to scheduler. but not running yet
* RUNNING - Job is running.
* FINISHED - Job finished run. with success
* ERROR - Job finished run. with error
* ABORT - Job finished by abort
*/
public static enum Status {
READY, PENDING, RUNNING, FINISHED, ERROR, ABORT;
public boolean isReady() {
return this == READY;
}
public boolean isRunning() {
return this == RUNNING;
}
public boolean isPending() {
return this == PENDING;
}
}
private String jobName;
String id;
Date dateCreated;
Date dateStarted;
Date dateFinished;
Status status;
static Logger LOGGER = LoggerFactory.getLogger(Job.class);
transient boolean aborted = false;
private String errorMessage;
private transient Throwable exception;
private transient JobListener listener;
private long progressUpdateIntervalMs;
public Job(String jobName, JobListener listener, long progressUpdateIntervalMs) {
this.jobName = jobName;
this.listener = listener;
this.progressUpdateIntervalMs = progressUpdateIntervalMs;
dateCreated = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss");
id = dateFormat.format(dateCreated) + "_" + super.hashCode();
setStatus(Status.READY);
}
public Job(String jobName, JobListener listener) {
this(jobName, listener, JobProgressPoller.DEFAULT_INTERVAL_MSEC);
}
public Job(String jobId, String jobName, JobListener listener) {
this(jobId, jobName, listener, JobProgressPoller.DEFAULT_INTERVAL_MSEC);
}
public Job(String jobId, String jobName, JobListener listener, long progressUpdateIntervalMs) {
this.jobName = jobName;
this.listener = listener;
this.progressUpdateIntervalMs = progressUpdateIntervalMs;
dateCreated = new Date();
id = jobId;
setStatus(Status.READY);
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object o) {
return ((Job) o).hashCode() == hashCode();
}
public Status getStatus() {
return status;
}
/**
* just set status without notifying to listeners for spell.
*/
public void setStatusWithoutNotification(Status status) {
this.status = status;
}
public void setStatus(Status status) {
if (this.status == status) {
return;
}
Status before = this.status;
Status after = status;
if (listener != null) {
listener.beforeStatusChange(this, before, after);
}
this.status = status;
if (listener != null) {
listener.afterStatusChange(this, before, after);
}
}
public void setListener(JobListener listener) {
this.listener = listener;
}
public JobListener getListener() {
return listener;
}
public boolean isTerminated() {
return !this.status.isReady() && !this.status.isRunning() && !this.status.isPending();
}
public boolean isRunning() {
return this.status.isRunning();
}
public void run() {
JobProgressPoller progressUpdator = null;
dateStarted = new Date();
try {
progressUpdator = new JobProgressPoller(this, progressUpdateIntervalMs);
progressUpdator.start();
completeWithSuccess(jobRun());
} catch (Throwable e) {
LOGGER.error("Job failed", e);
completeWithError(e);
} finally {
if (progressUpdator != null) {
progressUpdator.interrupt();
}
//aborted = false;
}
}
private synchronized void completeWithSuccess(Object result) {
setResult(result);
exception = null;
errorMessage = null;
dateFinished = new Date();
}
private synchronized void completeWithError(Throwable error) {
setResult(error.getMessage());
setException(error);
dateFinished = new Date();
}
public static String getStack(Throwable e) {
if (e == null) {
return "";
}
Throwable cause = ExceptionUtils.getRootCause(e);
if (cause != null) {
return ExceptionUtils.getFullStackTrace(cause);
} else {
return ExceptionUtils.getFullStackTrace(e);
}
}
public synchronized Throwable getException() {
return exception;
}
protected synchronized void setException(Throwable t) {
exception = t;
errorMessage = getStack(t);
}
public abstract Object getReturn();
public String getJobName() {
return jobName;
}
public void setJobName(String jobName) {
this.jobName = jobName;
}
public abstract int progress();
public abstract Map<String, Object> info();
protected abstract Object jobRun() throws Throwable;
protected abstract boolean jobAbort();
public void abort() {
aborted = jobAbort();
}
public boolean isAborted() {
return aborted;
}
public Date getDateCreated() {
return dateCreated;
}
public Date getDateStarted() {
return dateStarted;
}
public synchronized Date getDateFinished() {
return dateFinished;
}
public abstract void setResult(Object results);
public synchronized String getErrorMessage() {
return errorMessage;
}
public synchronized void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}