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

uk.gov.nationalarchives.droid.submitter.FileWalker Maven / Gradle / Ivy

There is a newer version: 6.8.1
Show newest version
/**
 * Copyright (c) 2016, The National Archives 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 *  * Neither the name of the The National Archives nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package uk.gov.nationalarchives.droid.submitter;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import uk.gov.nationalarchives.droid.core.interfaces.ResourceId;

/**
 * A file walker which supports resume.
 * @author rflitcroft
 *
 */
@XmlAccessorType(XmlAccessType.NONE)
public class FileWalker {

    private static final String FILE_SYSTEM_UNAVAILABLE = "File system appears to be unavailable for file: [%s]";

    private Log log = LogFactory.getLog(this.getClass());
    

    private URI root;

    @XmlAttribute(name = "Recursive")
    private boolean recursive;
    
    @XmlElementWrapper(name = "Progress")
    @XmlElement(name = "ProgressEntry")
    private Deque progress;

    private String topLevelAbsolutePath;
    private FileWalkerHandler fileHandler;
    private FileWalkerHandler directoryHandler;
    private FileWalkerHandler restrictedDirectoryHandler;
    private boolean fastForward;
    private List recoveryRoad;
    
    /**
     * Default Constructor.
     */
    FileWalker() { }
    
    /**
     * @param root the root of the walk
     * @param recursive if the Filewalker should operate recursively
     */
    public FileWalker(URI root, boolean recursive) {
        this.recursive = recursive;
        setRootUri(root);
    }

    /**
     *
     * @return the current root Uri.
     */
    @XmlElement(name = "RootUri")
    public URI getRootUri() {
        return root;
    }

    /**
     *
     * @param rootUri the Uri to set.
     */
    public void setRootUri(URI rootUri) {
        this.root = rootUri;
        this.topLevelAbsolutePath = new File(root).getAbsolutePath();
    }

    /**
     * (Re)starts the file walker.
     * @throws IOException if an IO exception occured
     */
    public void walk() throws IOException {
        if (progress != null) {
            // fast-forward to the recovery point
            fastForward = true;
            recoveryRoad = reverseProgress(progress);
        } else {
            // initialise an empty progress queue and start from scratch
            progress = new ArrayDeque();
        }
        
        walk(new File(root), 0);
    }

    private static List reverseProgress(Deque progress) {
        List reversed = new ArrayList();
        for (Iterator it = progress.descendingIterator(); it.hasNext();) {
            ProgressEntry entry = it.next();
            reversed.add(entry);
        }
        return reversed;
    }

    private void walk(File directory, int depth) throws IOException {
        if (handleDirectory(directory, depth)) {
            final File[] children = directory.listFiles();
            if (children != null) {
                handleDirectoryStart(directory, depth, children);
                if (recursive || depth == 0) {
                    final int childDepth = depth + 1;
                    for (final File child : children) {
                        if (child.isDirectory()) {
                            walk(child, childDepth);
                        } else {
                            handleFile(child, childDepth);
                        }
                    }
                }
                handleDirectoryEnd(directory, depth);
            } else { // can't access children - restricted directory.
                handleRestrictedDirectory(directory, depth);
            }
        }
    }

    /**
     *
     * @param dir file.
     * @param depth depth to which to check
     * @return false if directory yet to be processed, otherwise true
     * @throws IOException An error occurs in accessing the resource
     */
    protected boolean handleDirectory(File dir, int depth) throws IOException {
        boolean processDir = true;

        if (!SubmitterUtils.isFileSystemAvailable(dir, topLevelAbsolutePath)) {
            log.error(String.format(FILE_SYSTEM_UNAVAILABLE, dir.getAbsolutePath()));
            throw new IOException(dir.getAbsolutePath());
        }

        if (fastForward) {
            if (!(depth < recoveryRoad.size() && recoveryRoad.get(depth).getFile().equals(dir))) {
                // This directory is NOT on our road to recovery.
                if (recoveryRoad.get(depth - 1).containsChild(dir)) {
                    // This directory is yet to be processed
                    fastForward = false;
                } else {
                    // Not interested - skip it.
                    processDir = false;
                }
            }
        }
        return processDir;
    }

    /**
     *
     * @param directory directory to handle.
     * @param depth depth to which to check
     * @param children array of files
     * @throws IOException An error occurs in accessing the resource
     */
    protected void handleDirectoryStart(File directory, int depth, File[] children)
        throws IOException {
        
        // if we are fast forwarding, then just keep going...
        if (fastForward) {
            return; 
        }
        
        ProgressEntry parent = progress.peek();
        ResourceId directoryId = directoryHandler.handle(directory, depth, parent);
        progress.push(new ProgressEntry(directory, directoryId, children));
    }

    /**
     *
     * @param file file to hanndle.
     * @param depth level to whhich to check
     * @throws IOException  An error occurs in accessing the resource
     */
    protected void handleFile(File file, int depth)
        throws IOException {

        if (!SubmitterUtils.isFileSystemAvailable(file, topLevelAbsolutePath)) {
            log.error(String.format(FILE_SYSTEM_UNAVAILABLE, file.getAbsolutePath()));
            throw new IOException(file.getAbsolutePath());
        }

        if (fastForward) {
            if (recoveryRoad.get(depth - 1).containsChild(file)) {
                // FOUND IT!!
                fastForward = false;
            } else {
                return;
            }
        }

        ProgressEntry progressEntry = progress.peek();
        if (file.isFile()) {
            fileHandler.handle(file, depth, progressEntry);
        }
        progressEntry.removeChild(file);
    }

    /**
     *
     * @param directory directory.
     * @param depth depth to which to check.
     */
    protected void handleDirectoryEnd(File directory, int depth) {
        
        if (fastForward) {
            // Indicates a failure to recover from an expected directory, so we
            // should resume here instead.
            fastForward = false;
            // trim the progress until this directory is current
            while (progress.size() - 1 > depth) {
                progress.pop();
            }
        }
        
        progress.pop();
        if (!progress.isEmpty()) {
            progress.peek().removeChild(directory);
        }
    }


    private void handleRestrictedDirectory(File directory, int depth) throws IOException {
        // if we are fast forwarding, then just keep going...
        if (fastForward) {
            return;
        }

        ProgressEntry parent = progress.peek();
        restrictedDirectoryHandler.handle(directory, depth, parent);
        if (!progress.isEmpty()) {
            progress.peek().removeChild(directory);
        }
    }
    
    /**
     * @param fileHandler the fileHandler to set
     */
    public void setFileHandler(FileWalkerHandler fileHandler) {
        this.fileHandler = fileHandler;
    }
    
    /**
     * @param directoryHandler the directoryHandler to set
     */
    public void setDirectoryHandler(FileWalkerHandler directoryHandler) {
        this.directoryHandler = directoryHandler;
    }
    
    /**
     * @param restrictedDirectoryHandler the restrictedDirectoryHandler to set
     */
    public void setRestrictedDirectoryHandler(FileWalkerHandler restrictedDirectoryHandler) {
        this.restrictedDirectoryHandler = restrictedDirectoryHandler;
    }
    
    /**
     * @return the progress
     */
    Deque progress() {
        return progress;
    }
    
    /**
     * @param progress the progress to set
     */
    void setProgress(Deque progress) {
        this.progress = progress;
    }


    /**
     * A progress entry.
     * @author rflitcroft
     *
     */
    @XmlAccessorType(XmlAccessType.NONE)
    public static final class ProgressEntry {
        
        @XmlAttribute(name = "Id")
        private long id;
        
        @XmlAttribute(name = "Prefix")
        private String prefix;
        
        private File directory;


        private File[] children;

        /**
         * Default constructor.
         */
        ProgressEntry() { }
        
        /**
         * @param directory the File of the directory
         * @param id the ID of the directory
         * @param prefix the prefix of the directory
         * @param children the directory's children
         */
        ProgressEntry(File directory, long id, String prefix, File[] children) {
            this.directory = directory;
            this.id = id;
            this.prefix = prefix;
            this.children = children;
        }


        
        /**
         * @param directory the File of the directory
         * @param resourceId the ResourceId of the directory
         * @param children the directory's children
         */
        ProgressEntry(File directory, ResourceId resourceId, File[] children) {
            if (resourceId == null) {
                throw new IllegalArgumentException("Cannot construct a ProgressEntry with a null ResourceId");
            }
            this.directory = directory;
            this.id = resourceId.getId();
            this.prefix = resourceId.getPath();
            this.children = children;
        }

        /**
         *
         * @return the current directory as a URI.
         */
        @XmlElement(name = "Uri")
        public URI getUri() {
            return this.directory.toURI();
        }

        /**
         * Sets the current directory as a URI.
         * @param theDirectory a URI representing the directory to set.
         */
        public void setUri(URI theDirectory) {
            this.directory = new File(theDirectory);
        }

        /**
         * Gets a list of child URIs.
         * @return   A list of child URIs
         */
        @XmlElementWrapper(name = "Children")
        @XmlElement(name = "ChildUri")
        public List getChildUri() {
            List result = new ArrayList();
            if (children != null) {
                for (File child : children) {
                    if (child != null) {
                        result.add(child.toURI());
                    }
                }
            }
            return result;
        }

        /**
         * Set child URLs of the current one in the tree.
         * @param childURIs List of chile URLs
         */
        public void setChildUri(List childURIs) {
            if (childURIs != null) {
                this.children = new File[childURIs.size()];
                int index = 0;
                for (URI childURI : childURIs) {
                    children[index++] = new File(childURI);
                }
            }
        }

        /**
         * @return the id of the entry
         */
        public long getId() {
            return id;
        }
        
        /**
         * 
         * @return The prefix of the entry.
         */
        public String getPrefix() {
            return prefix;
        }
        
        /**
         * 
         * @return A resource id for the progress entry.
         */
        public ResourceId getResourceId() {
            return new ResourceId(id, prefix);
        }
        
        /**
         * @return the File of the directory.
         */
        public File getFile() {
            return directory;
        }
        
        /**
         * @param child the child uri to remove
         */
        private void removeChild(File child) {
            if (children != null) {
                for (int i = 0; i < children.length; i++) {
                    if (child.equals(children[i])) {
                        children[i] = null;
                        break;
                    }
                }
            }
        }
        
        /**
         * 
         * @param child the child file to check
         * @return true if the progress entry contains the child specified.
         */
        public boolean containsChild(File child) {
            if (children != null) {
                for (int i = 0; i < children.length; i++) {
                    if (child.equals(children[i])) {
                        return true;
                    }
                }
            }
            return false;
        }

    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy