All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.jef4.status.FileJobStatusStore Maven / Gradle / Ivy

/* 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();
        }
        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(statusDir);
            writer.writeEndElement();
            writer.writeEndElement();
            writer.flush();
            writer.close();
        } catch (XMLStreamException e) {
            throw new IOException("Cannot save as XML.", e);
        }       
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy