com.norconex.jef4.status.FileJobStatusStore.java Source code

Java tutorial

Introduction

Here is the source code for com.norconex.jef4.status.FileJobStatusStore.java

Source

/* 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);
        }
    }

}