au.edu.uq.cmm.paul.queue.AbstractQueueFileManager.java Source code

Java tutorial

Introduction

Here is the source code for au.edu.uq.cmm.paul.queue.AbstractQueueFileManager.java

Source

/*
* Copyright 2013, CMM, University of Queensland.
*
* This file is part of Paul.
*
* Paul is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Paul is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Paul. If not, see <http://www.gnu.org/licenses/>.
*/

package au.edu.uq.cmm.paul.queue;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;

import au.edu.uq.cmm.paul.PaulConfiguration;
import au.edu.uq.cmm.paul.PaulException;

/**
 * Base class for the queue file manager classes.  Note that the vast majority
 * of this behavior <i>needs</i> to be common to cope with the fact that the
 * queue file management strategy can be reconfigured, and therefore the queue
 * could contain any mix of copied or symlinked files.
 * 
 * @author scrawley
 *
 */
public abstract class AbstractQueueFileManager implements QueueFileManager {

    protected static final int RETRY = 10;
    protected final File archiveDirectory;
    protected final File captureDirectory;
    private Logger log;

    public AbstractQueueFileManager(PaulConfiguration config, Logger log) {
        this.log = log;
        this.archiveDirectory = new File(config.getArchiveDirectory());
        checkDirectory(this.archiveDirectory, "archive");
        this.captureDirectory = new File(config.getCaptureDirectory());
        checkDirectory(this.captureDirectory, "capture");
    }

    private void checkDirectory(File dir, String tag) {
        File testFile = new File(dir, "test.txt");
        try (OutputStream os = new FileOutputStream(testFile)) {
            os.write("1 2 3\n".getBytes());
        } catch (IOException ex) {
            throw new PaulException("Problem creating file in " + tag + " directory", ex);
        } finally {
            testFile.delete();
        }
    }

    protected File copyFile(File source, File target, String area) throws QueueFileException {
        // TODO - if the time taken to copy files is a problem, we could 
        // potentially improve this by using NIO or memory mapped files.
        long size = source.length();
        try (FileInputStream is = new FileInputStream(source); FileOutputStream os = new FileOutputStream(target)) {
            byte[] buffer = new byte[(int) Math.min(size, 8192)];
            int nosRead;
            long totalRead = 0;
            while ((nosRead = is.read(buffer, 0, buffer.length)) > 0) {
                os.write(buffer, 0, nosRead);
                totalRead += nosRead;
            }

            // If these happen there is something wrong with our copying, locking
            // and / or file settling heuristics.
            if (totalRead != size) {
                log.error("Copied file size discrepancy - initial file size was " + size + "bytes but we copied "
                        + totalRead + " bytes");
            } else if (size != source.length()) {
                log.error("File size changed during copy - initial file size was " + size
                        + "bytes and current size is " + source.length());
            }
            log.debug("Copied " + totalRead + " bytes from " + source + " to " + target);
            return target;
        } catch (IOException ex) {
            throw new QueueFileException("Problem while copying file to " + area + " area", ex);
        }
    }

    @Override
    public final void enqueueFile(String contents, File target, boolean mayExist) throws QueueFileException {
        if (!mayExist && target.exists()) {
            throw new QueueFileException("File " + target + " already exists");
        }
        try (Writer w = new FileWriter(target)) {
            w.write(contents);
            w.close();
        } catch (IOException ex) {
            throw new QueueFileException("Problem while saving to a queue file", ex);
        }
    }

    @Override
    public final File generateUniqueFile(String suffix, boolean regrabbing) throws QueueFileException {
        String template = regrabbing ? "regrabbed-%d-%d-%d%s" : "file-%d-%d-%d%s";
        long threadId = Thread.currentThread().getId();
        for (int i = 0; i < RETRY; i++) {
            long now = System.currentTimeMillis();
            String name = String.format(template, now, threadId, i, suffix);
            File file = new File(captureDirectory, name);
            if (!file.exists()) {
                return file;
            }
        }
        throw new QueueFileException(RETRY + " attempts to generate a unique filename failed!");
    }

    @Override
    public final File archiveFile(File file) throws QueueFileException {
        switch (getFileStatus(file)) {
        case NON_EXISTENT:
            throw new QueueFileException("File or symlink " + file + " no longer exists");
        case CAPTURED_FILE:
        case CAPTURED_SYMLINK:
            break;
        case BROKEN_CAPTURED_SYMLINK:
            throw new QueueFileException("Symlink target for " + file + " no longer exists");
        default:
            throw new QueueFileException("File or symlink " + file + " is not in the queue");
        }

        File dest = new File(archiveDirectory, file.getName());
        if (dest.exists()) {
            throw new QueueFileException("Archived file " + dest + " already exists");
        }

        if (Files.isSymbolicLink(file.toPath())) {
            dest = copyFile(file, dest, "archive");
            try {
                Files.delete(file.toPath());
            } catch (IOException ex) {
                throw new QueueFileException("Could not remove symlink " + file);
            }
        } else {
            if (!file.renameTo(dest)) {
                throw new QueueFileException("File " + file + " could not be renamed to " + dest);
            }
        }
        log.info("File " + file + " archived as " + dest);
        return dest;
    }

    @Override
    public void removeFile(File file) throws QueueFileException {
        switch (getFileStatus(file)) {
        case NON_EXISTENT:
            throw new QueueFileException("File or symlink " + file + " no longer exists");
        case CAPTURED_FILE:
        case CAPTURED_SYMLINK:
        case BROKEN_CAPTURED_SYMLINK:
            break;
        default:
            throw new QueueFileException("File or symlink " + file + " is not in the queue");
        }

        try {
            Files.delete(file.toPath());
            log.info("File " + file + " deleted from queue area");
        } catch (IOException ex) {
            throw new QueueFileException("File or symlink " + file + " could not be deleted from queue area", ex);
        }
    }

    @Override
    public FileStatus getFileStatus(File file) {
        Path path = file.toPath();
        File parent = path.getParent().toFile();
        if (Files.exists(path)) {
            if (parent.equals(captureDirectory)) {
                if (Files.isSymbolicLink(path)) {
                    return FileStatus.CAPTURED_SYMLINK;
                } else {
                    return FileStatus.CAPTURED_FILE;
                }
            } else if (parent.equals(archiveDirectory)) {
                if (Files.isSymbolicLink(path)) {
                    return FileStatus.ARCHIVED_SYMLINK;
                } else {
                    return FileStatus.ARCHIVED_FILE;
                }
            } else {
                return FileStatus.NOT_OURS;
            }
        } else {
            if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) {
                if (parent.equals(captureDirectory)) {
                    return FileStatus.BROKEN_CAPTURED_SYMLINK;
                } else if (parent.equals(archiveDirectory)) {
                    return FileStatus.BROKEN_ARCHIVED_SYMLINK;
                } else {
                    return FileStatus.NOT_OURS;
                }
            } else {
                return FileStatus.NON_EXISTENT;
            }
        }
    }

    @Override
    public final File renameGrabbedDatafile(File file) throws QueueFileException {
        String extension = FilenameUtils.getExtension(file.toString());
        if (!extension.isEmpty()) {
            extension = "." + extension;
        }
        for (int i = 0; i < RETRY; i++) {
            File newFile = generateUniqueFile(extension, false);
            if (!file.renameTo(newFile)) {
                if (!Files.exists(newFile.toPath(), LinkOption.NOFOLLOW_LINKS)) {
                    throw new QueueFileException("Unable to rename file or symlink " + file + " to " + newFile);
                }
            } else {
                return newFile;
            }
        }
        throw new QueueFileException(RETRY + " attempts to rename file / symlink failed!");
    }

}