com.norconex.jef4.status.FileJobStatusStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of norconex-jef Show documentation
Show all versions of norconex-jef Show documentation
JEF is a Java API library meant to facilitate the lives of developers and integrators who have to build any kind of maintenance tasks on a server.
/* Copyright 2010-2014 Norconex Inc.
*
* 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.norconex.jef4.status;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.norconex.commons.lang.config.ConfigurationUtil;
import com.norconex.commons.lang.file.FileUtil;
import com.norconex.commons.lang.map.Properties;
import com.norconex.jef4.JEFException;
import com.norconex.jef4.JEFUtil;
import com.norconex.jef4.job.JobException;
/**
* Serializer using a file to store job status information. The created
* file name matches the job id, plus the ".job" extension. The path
* where to locate the file depends on the constructor invoked.
*
* @author Pascal Essiembre
*/
@SuppressWarnings("nls")
public class FileJobStatusStore implements IJobStatusStore {
private static final Logger LOG =
LogManager.getLogger(FileJobStatusStore.class);
private String jobdirLatest;
private String jobdirBackupBase;
private String statusDir;
public FileJobStatusStore() {
super();
}
/**
* Creates a file-based job status serializer storing files in the given
* job directory.
* @param statusDir the base directory where to serialize the job status
*/
public FileJobStatusStore(final String statusDir) {
this.statusDir = statusDir;
resolveDirs();
}
public String getStatusDirectory() {
return statusDir;
}
public void setStatusDirectory(String statusDirectory) {
this.statusDir = statusDirectory;
resolveDirs();
}
private void resolveDirs() {
String path = statusDir;
if (StringUtils.isBlank(statusDir)) {
LOG.info("No status directory specified.");
path = JEFUtil.FALLBACK_WORKDIR.getAbsolutePath();
} else {
path = new File(path).getAbsolutePath();
}
LOG.debug("Status serialization directory: " + path);
jobdirLatest = path + File.separatorChar
+ "latest" + File.separatorChar + "status";
jobdirBackupBase = path + "/backup";
File dir = new File(jobdirLatest);
if (!dir.exists()) {
try {
FileUtils.forceMkdir(dir);
} catch (IOException e) {
throw new JEFException("Cannot create status directory: "
+ dir, e);
}
}
}
@Override
public final void write(
String suiteName, final IJobStatus jobStatus)
throws IOException {
File file = getStatusFile(suiteName, jobStatus.getJobId());
if (!file.exists()) {
if (!file.createNewFile()) {
throw new IOException("Cannot create status file: " + file);
}
}
if (LOG.isDebugEnabled()) {
LOG.debug("Writing status file: " + file);
}
Properties config = new Properties();
config.setString("jobId", jobStatus.getJobId());
config.setDouble("progress", jobStatus.getProgress());
if (jobStatus.getNote() != null) {
config.setString("note", jobStatus.getNote());
}
JobDuration duration = jobStatus.getDuration();
if (jobStatus.getResumeAttempts() > 0) {
config.setInt("resumeAttempts", jobStatus.getResumeAttempts());
config.setDate("resumedStartTime", duration.getResumedStartTime());
config.setDate("resumedLastActivity",
duration.getResumedLastActivity());
}
if (duration.getStartTime() != null) {
config.setDate("startTime", duration.getStartTime());
}
if (duration.getEndTime() != null) {
config.setDate("endTime", duration.getEndTime());
}
if (jobStatus.isStopping() || jobStatus.isStopped()) {
config.setBoolean("stopped", true);
}
Properties props = jobStatus.getProperties();
for (String key : props.keySet()) {
config.put("prop." + key, props.get(key));
}
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
config.store(os, "Status for job: " + jobStatus.getJobId());
os.close();
}
@Override
public final IJobStatus read(
String suiteName, final String jobId)
throws IOException {
MutableJobStatus jobStatus = new MutableJobStatus(jobId);
File file = getStatusFile(suiteName, jobId);
if (LOG.isDebugEnabled()) {
LOG.debug("Reading status file: " + file);
}
if (!file.exists()) {
return jobStatus;
}
Properties config = new Properties();
InputStream is = null;
try {
is = new FileInputStream(file);
config.load(is);
if (LOG.isDebugEnabled()) {
LOG.debug(jobId + " last active time: "
+ new Date(file.lastModified()));
}
jobStatus.setLastActivity(new Date(file.lastModified()));
jobStatus.setProgress(config.getDouble("progress", 0d));
jobStatus.setNote(config.getString("note", null));
jobStatus.setResumeAttempts(config.getInt("resumeAttempts", 0));
JobDuration duration = new JobDuration();
duration.setResumedStartTime(
config.getDate("resumedStartTime", null));
duration.setResumedLastActivity(
config.getDate("resumedLastActivity", null));
duration.setStartTime(config.getDate("startTime", null));
duration.setEndTime(config.getDate("endTime", null));
jobStatus.setDuration(duration);
jobStatus.setStopRequested(config.getBoolean("stopped", false));
Properties props = jobStatus.getProperties();
for (String key : config.keySet()) {
if (key.startsWith("prop.")) {
props.put(StringUtils.removeStart(
"prop.", key), props.get(key));
}
}
} finally {
IOUtils.closeQuietly(is);
}
return jobStatus;
}
@Override
public final void remove(final String suiteName, final String jobId)
throws IOException {
File file = getStatusFile(suiteName, jobId);
FileUtil.delete(file);
}
@Override
public final void backup(
final String suiteName, final String jobId, final Date backupDate)
throws IOException {
File progressFile = getStatusFile(suiteName, jobId);
File backupFile = getBackupFile(suiteName, jobId, backupDate);
if (progressFile.exists()) {
FileUtil.moveFile(progressFile, backupFile);
}
}
@Override
public long touch(String suiteName, String jobId) throws IOException {
File file = getStatusFile(suiteName, jobId);
FileUtils.touch(file);
return file.lastModified();
}
/**
* Gets the file used to store the job progress.
* @param suiteName name space given to the job progress
* @param jobId the job unique name
* @return file used to store the job process
*/
private File getStatusFile(final String suiteName, final String jobId) {
return new File(jobdirLatest
+ "/" + FileUtil.toSafeFileName(suiteName)
+ "__" + FileUtil.toSafeFileName(jobId) + ".job");
}
/**
* Gets the file used to store the job progress backup.
* @param suiteName name space given to the job progress
* @param jobId the id of the job
* @param backupDate date used to timestamp to backup
* @return file used to store the job process
*/
private File getBackupFile(final String suiteName, final String jobId,
final Date backupDate) {
String date = new SimpleDateFormat(
"yyyyMMddHHmmssSSSS").format(backupDate);
File backupDir;
try {
backupDir = FileUtil.createDateDirs(
new File(jobdirBackupBase), backupDate);
} catch (IOException e) {
throw new JobException("Could not create backup directory for "
+ "job \"" + jobId + "\".");
}
backupDir = new File(backupDir, "status");
if (!backupDir.exists()) {
try {
FileUtils.forceMkdir(backupDir);
} catch (IOException e) {
throw new JEFException("Cannot create backup directory: "
+ backupDir, e);
}
}
return new File(backupDir + "/" + date + "__"
+ FileUtil.toSafeFileName(suiteName)
+ "__" + FileUtil.toSafeFileName(jobId) + ".job");
}
@Override
public void loadFromXML(Reader in) throws IOException {
XMLConfiguration xml = ConfigurationUtil.newXMLConfiguration(in);
setStatusDirectory(xml.getString("statusDir", statusDir));
}
@Override
public void saveToXML(Writer out) throws IOException {
XMLOutputFactory factory = XMLOutputFactory.newInstance();
try {
XMLStreamWriter writer = factory.createXMLStreamWriter(out);
writer.writeStartElement("statusStore");
writer.writeAttribute("class", getClass().getCanonicalName());
writer.writeStartElement("statusDir");
writer.writeCharacters(new File(statusDir).getAbsolutePath());
writer.writeEndElement();
writer.writeEndElement();
writer.flush();
writer.close();
} catch (XMLStreamException e) {
throw new IOException("Cannot save as XML.", e);
}
}
}