RegularProcess.java
/*
* Copyright 2018 Gregory Graham.
*
* Commercial licenses are available, please contact info@gregs.co.nz for details.
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/4.0/
* or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
*
* You are free to:
* Share - copy and redistribute the material in any medium or format
* Adapt - remix, transform, and build upon the material
*
* The licensor cannot revoke these freedoms as long as you follow the license terms.
* Under the following terms:
*
* Attribution -
* You must give appropriate credit, provide a link to the license, and indicate if changes were made.
* You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
* NonCommercial -
* You may not use the material for commercial purposes.
* ShareAlike -
* If you remix, transform, or build upon the material,
* you must distribute your contributions under the same license as the original.
* No additional restrictions -
* You may not apply legal terms or technological measures that legally restrict others from doing anything the
* license permits.
*
* Check the Creative Commons website for any details, legalese, and updates.
*/
package nz.co.gregs.dbvolution.utility;
import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import nz.co.gregs.dbvolution.databases.DBDatabase;
import nz.co.gregs.dbvolution.databases.DBDatabaseImplementation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Regular Processes provide a standard means of performing regular database
* related tasks.
*
* <p>
* Examples of regular processes are database backups, and clearing old records
* from tables.</p>
*
* <p>
* Use
* {@link DBDatabaseImplementation#addRegularProcess(nz.co.gregs.dbvolution.utility.RegularProcess)}
* to add a regular process.</p>
*
* <p>
* Regular processes can access the database they are registered with using
* {@link #getDatabase() }. Accessing more than one database is not supported
* automatically.</p>
*
* <p>
* The only method you need to implement is {@link #process() } but you can
* overload {@link #preprocess() }
* to perform checks before processing and {@link #postprocess() } to clean up
* any resources.</p>
*
* @author gregorygraham
*/
public abstract class RegularProcess implements Serializable {
public static final long serialVersionUID = 1l;
final Log LOG = LogFactory.getLog(RegularProcess.class);
private Instant nextRun = Instant.now();
ChronoUnit timeField = ChronoUnit.MINUTES;
private int timeOffset = 5;
private DBDatabase dbDatabase;
private String lastResult = "Not Processed Yet";
private Instant lastRunTime = Instant.now();
private String simpleName = null;
private boolean stopped = false;
/**
* Method that does all the processing that needs to be regularly performed.
*
* <p>
* If {@link #preprocess() } returns true, process() is called to perform the
* actual processing.
*
* <p>
* {@link #postprocess() } will be called to clean up any resources after
* processing and
* {@link #handleExceptionDuringProcessing(java.lang.Exception)} can be
* overloaded if exceptions during processing need to be handled.
*
* @return the output from processing
* @throws Exception any exception can thrown
*/
public abstract String process() throws Exception;
public final boolean isDueToRun() {
return hasExceededTimeLimit();
}
public final boolean hasExceededTimeLimit() {
return nextRun.isBefore(Instant.now());
}
/**
* Sets the time field and offset value to use when generating the next run
* time.
*
* <p>
* Note that {@link DBDatabase} regular processes are checked once a minute.
*
* @param calendarTimeField the time unit to offset the regular process by
* @param offset the number time units to offset by
*/
public final void setTimeOffset(ChronoUnit calendarTimeField, int offset) {
timeField = calendarTimeField;
timeOffset = offset;
}
/**
* A method that is called before {@link #process() } and will stop processing
* if FALSE is returned.
*
* <p>
* By default this method returns true always.
*
* @return TRUE if processing should continue, FALSE otherwise.
*/
public boolean preprocess() {
return true;
}
/**
* A method that is always called after {@link #preprocess() } and {@link #process()
* }.
*
* <p>
* By default this method does nothing.
*/
public void postprocess() {
}
/**
* Provides a way to intercept exceptions thrown during processing.
*
* <p>
* By default, this method logs the exception as a warning.
*
* @param ex the exception that has occurred
*/
public void handleExceptionDuringProcessing(Exception ex) {
LOG.warn(this, ex);
}
/**
* Used to generate the next run time for this process
*
*/
public final void offsetTime() {
lastRunTime = Instant.now();
var duration = Duration.ZERO.plus(timeOffset, timeField);
Instant instant = Instant.now().plus(duration);
nextRun = instant;
}
/**
* Returns the (last) database that this process has been added to using {@link DBDatabaseImplementation#addRegularProcess(nz.co.gregs.dbvolution.utility.RegularProcess)
* }.
*
* @return the database that this process should work upon.
*/
protected final DBDatabase getDatabase() {
return dbDatabase;
}
/**
* Used by {@link DBDatabaseImplementation#addRegularProcess(nz.co.gregs.dbvolution.utility.RegularProcess) }
* to set the database.
*
* <p>
* You probably don't need this method.
*
* @param db the database this process interacts with
*/
public final void setDatabase(DBDatabase db) {
try {
this.dbDatabase = db.clone();
} catch (CloneNotSupportedException ex) {
Logger.getLogger(RegularProcess.class.getName()).log(Level.SEVERE, null, ex);
}
}
public final void stop() {
this.dbDatabase = null;
this.stopped = true;
}
public final boolean canRun() {
boolean canRun = false;
if (stopped) {
LOG.warn("This database is stopped and " + this.getClass().getSimpleName() + " can not proceed.");
} else if (this.dbDatabase == null) {
LOG.warn(this.getClass().getSimpleName() + " has not had setDatabase(DBDatabase) called and can not proceed.");
} else {
canRun = true;
}
return canRun;
}
public String getLastResult() {
return lastResult;
}
public Instant getLastRuntime() {
return lastRunTime;
}
public Instant getNextRuntime() {
return nextRun;
}
public void setLastResult(String process) {
this.lastResult = process;
}
public String getSimpleName() {
if (simpleName == null || simpleName.isEmpty()) {
return this.getClass().getSimpleName();
} else {
return simpleName;
}
}
public void setSimpleName(String simpleName) {
this.simpleName = simpleName;
}
public void clearSimpleName() {
this.simpleName = null;
}
}