org.duracloud.sync.mgmt.ChangedList.java Source code

Java tutorial

Introduction

Here is the source code for org.duracloud.sync.mgmt.ChangedList.java

Source

/*
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 *     http://duracloud.org/license/
 */
package org.duracloud.sync.mgmt;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.event.EventListenerSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The list of files which have been changed.
 *
 * @author: Bill Branan
 * Date: Mar 15, 2010
 */
public class ChangedList implements Serializable {

    private static final Logger log = LoggerFactory.getLogger(ChangedList.class);
    private LinkedHashMap<String, ChangedFile> fileList;
    private LinkedHashMap<String, ChangedFile> reservedFiles;
    private ExecutorService executorService;
    private long listVersion;
    private boolean shutdown = false;

    private static ChangedList instance;

    private FileExclusionManager fileExclusionManager;
    private EventListenerSupport<ChangedListListener> listeners;

    public static synchronized ChangedList getInstance() {
        if (instance == null) {
            instance = new ChangedList();
        }
        return instance;
    }

    private ChangedList() {
        fileList = new LinkedHashMap<String, ChangedFile>();
        reservedFiles = new LinkedHashMap<String, ChangedFile>();
        this.fileExclusionManager = new FileExclusionManager();
        listVersion = 0;
        listeners = new EventListenerSupport<ChangedListListener>(ChangedListListener.class);
        this.executorService = Executors.newSingleThreadExecutor();
    }

    public void setFileExclusionManager(FileExclusionManager fileExclusionManager) {
        if (fileExclusionManager == null) {
            throw new IllegalArgumentException("fileExclusionManager must not be null");
        }

        this.fileExclusionManager = fileExclusionManager;
    }

    /**
     * Adds a changed file to the list of items to be processed. If the file
     * happens to match exclusion rules it will not be added to the list (and
     * the method will return false). Note that only the most current update to
     * any given file is provided to the change processor.
     *
     * @param changedFile a file which has changed on the file system
     * @return false if the changedFile is null or matches at least one
     * exclusion rule.
     */
    public boolean addChangedFile(final File changedFile) {
        if (null != changedFile) {
            return addChangedFile(new ChangedFile(changedFile));
        } else {
            log.warn("The changedFile parameter was unexpectedly null. Ignored.");
            return false;
        }
    }

    /**
     * Gets the current size of the changed list
     *
     * @return the size of the list
     */
    public int getListSize() {
        return fileList.size();
    }

    /**
     * Gets the current size of the changed list included the files that have been reserved
     *
     * @return the size of the list
     */
    public int getListSizeIncludingReservedFiles() {
        return fileList.size() + reservedFiles.size();
    }

    synchronized boolean addChangedFile(ChangedFile changedFile) {
        File file = changedFile.getFile();
        if (fileExclusionManager.isExcluded(file)) {
            return false;
        }
        fileList.put(file.getAbsolutePath(), changedFile);
        incrementVersion();
        fireChangedEvent();
        return true;
    }

    protected void fireChangedEvent() {
        listeners.fire().listChanged(this);
    }

    protected void fireChangedEventAsync() {
        this.executorService.execute(new Runnable() {
            @Override
            public void run() {
                fireChangedEvent();
            }
        });
    }

    public void addListener(ChangedListListener listener) {
        this.listeners.addListener(listener);
    }

    public void removeListener(ChangedListListener listener) {
        this.listeners.removeListener(listener);
    }

    /**
     * Removes all files from the changed list.
     */
    public synchronized void clear() {
        fileList.clear();
        reservedFiles.clear();
        fireChangedEvent();
    }

    /**
     * Retrieves a changed file for processing and removes it from the list of unreserved files.
     * Returns null if there are no changed files in the list.
     *
     * @return a file which has changed on the file system
     */
    public synchronized ChangedFile reserve() {
        if (fileList.isEmpty() || shutdown) {
            return null;
        }

        String key = fileList.keySet().iterator().next();
        ChangedFile changedFile = fileList.remove(key);
        reservedFiles.put(key, changedFile);
        incrementVersion();
        fireChangedEventAsync();
        return changedFile;
    }

    private void incrementVersion() {
        if (listVersion < Long.MAX_VALUE) {
            listVersion++;
        } else {
            listVersion = 0;
        }
    }

    public long getVersion() {
        return listVersion;
    }

    /**
     * Writes out the current state of the ChangeList to the given file.
     *
     * @param persistFile file to write state to
     * @return the version ID of the ChangedList which was persisted
     */
    public long persist(File persistFile) {
        try {
            FileOutputStream fileStream = new FileOutputStream(persistFile);
            ObjectOutputStream oStream = new ObjectOutputStream((fileStream));

            long persistVersion;
            Map<String, ChangedFile> fileListCopy;
            synchronized (this) {
                fileListCopy = (Map<String, ChangedFile>) fileList.clone();
                fileListCopy.putAll(reservedFiles);
                persistVersion = listVersion;
            }

            oStream.writeObject(fileListCopy);
            oStream.close();
            return persistVersion;
        } catch (IOException e) {
            throw new RuntimeException("Unable to persist File Changed List:" + e.getMessage(), e);
        }
    }

    /**
     * Restores the state of the ChangedList using the given backup file
     *
     * @param persistFile file containing previous state
     * @param contentDirs content directories currently configured.
     */
    public synchronized void restore(File persistFile, List<File> contentDirs) {
        try {
            FileInputStream fileStream = new FileInputStream(persistFile);
            ObjectInputStream oStream = new ObjectInputStream(fileStream);
            log.info("Restoring changed list from backup: {}", persistFile.getAbsolutePath());
            synchronized (this) {
                LinkedHashMap<String, ChangedFile> fileListFromDisk = (LinkedHashMap<String, ChangedFile>) oStream
                        .readObject();

                //remove files in change list that are not in the content dir list.
                if (contentDirs != null && !contentDirs.isEmpty()) {

                    Iterator<Entry<String, ChangedFile>> entries = fileListFromDisk.entrySet().iterator();
                    while (entries.hasNext()) {
                        Entry<String, ChangedFile> entry = entries.next();
                        ChangedFile file = entry.getValue();
                        boolean watched = false;
                        for (File contentDir : contentDirs) {
                            if (file.getFile().getAbsolutePath().startsWith(contentDir.getAbsolutePath())
                                    && !this.fileExclusionManager.isExcluded(file.getFile())) {
                                watched = true;
                                break;
                            }
                        }

                        if (!watched) {
                            entries.remove();
                        }
                    }
                }

                this.fileList = fileListFromDisk;
            }
            oStream.close();
        } catch (Exception e) {
            throw new RuntimeException("Unable to restore File Changed List:" + e.getMessage(), e);
        }
    }

    public synchronized List<File> peek(int maxFiles) {
        List<File> files = new LinkedList<File>();
        Iterator<Entry<String, ChangedFile>> it = this.fileList.entrySet().iterator();
        int count = 0;
        while (it.hasNext() && count < maxFiles) {
            files.add(it.next().getValue().getFile());
            count++;
        }
        return files;
    }

    /**
     * Removes a previously reserved ChangedFile from the list of
     * reserved files, effectively removing it from the ChangedList.
     * However if this instance of the ChangedFile or a new
     * ChangedFile with an identical file path is re-added to the ChangedList
     * before the reserved file is removed,  calling remove will only remove
     * the changed file from the reserved list.
     *
     * @param changedFile
     */
    synchronized void remove(ChangedFile changedFile) {
        this.reservedFiles.remove(getKey(changedFile));
    }

    /**
     * Releases the reservation on the file (if still reserved) and returns
     * it to the list.
     *
     * @param changedFile
     */
    synchronized void unreserve(ChangedFile changedFile) {
        ChangedFile removedFile = this.reservedFiles.remove(getKey(changedFile));
        if (removedFile != null && !this.fileList.containsKey(getKey(removedFile))) {
            addChangedFile(removedFile);
        }
    }

    private String getKey(ChangedFile changedFile) {
        return changedFile.getFile().getAbsolutePath();
    }

    public void shutdown() {
        executorService.shutdown();
        shutdown = true;
        ChangedList.instance = null;
    }

}