org.codice.ddf.configuration.admin.ConfigurationFilesPoller.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.configuration.admin.ConfigurationFilesPoller.java

Source

/**
 * Copyright (c) Codice Foundation
 * <p>
 * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
 * General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 * <p>
 * This program 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
 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License
 * is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */

package org.codice.ddf.configuration.admin;

import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.commons.lang.Validate.notNull;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import javax.validation.constraints.NotNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Class that monitors changes to files with a specific extension in a directory.
 */
public class ConfigurationFilesPoller implements Runnable {
    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationFilesPoller.class);

    private final WatchService watchService;

    private final ExecutorService executorService;

    private final Path configurationDirectoryPath;

    private final String fileExtension;

    private ChangeListener changeListener;

    /**
     * Constructor.
     *
     * @param configurationDirectoryPath directory to watch for changes
     * @param fileExtension              extension of of the files to watch
     * @param watchService               watch service to use
     * @param executorService            executor service used to create the watch thread
     */
    public ConfigurationFilesPoller(@NotNull Path configurationDirectoryPath, @NotNull String fileExtension,
            @NotNull WatchService watchService, @NotNull ExecutorService executorService) {
        notNull(configurationDirectoryPath, "configurationDirectoryPath cannot be null");
        notNull(fileExtension, "fileExtension cannot be null");
        notNull(watchService, "watchService cannot be null");
        notNull(executorService, "executorService cannot be null");

        this.configurationDirectoryPath = configurationDirectoryPath;
        this.watchService = watchService;
        this.executorService = executorService;
        this.fileExtension = fileExtension;
    }

    public void init() {
        LOGGER.debug("Starting {}...", this.getClass().getName());
        executorService.execute(this);
    }

    public void register(@NotNull ChangeListener listener) {
        notNull(listener, "ChangeListener cannot be null");
        changeListener = listener;
    }

    @Override
    public void run() {
        try {
            try {
                LOGGER.debug("Registering path [{}] with Watch Service.", configurationDirectoryPath.toString());
                configurationDirectoryPath.register(watchService, ENTRY_CREATE);
            } catch (IOException e) {
                LOGGER.error("Unable to register path [{}] with Watch Service",
                        configurationDirectoryPath.toString(), e);
                return;
            }

            WatchKey key;

            while (!Thread.currentThread().isInterrupted()) {
                key = watchService.take(); // blocking
                LOGGER.debug("Key has been signalled.  Looping over events.");

                for (WatchEvent<?> genericEvent : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = genericEvent.kind();

                    if (kind == OVERFLOW || kind == ENTRY_MODIFY || kind == ENTRY_DELETE) {
                        LOGGER.debug("Skipping event [{}]", kind);
                        continue;
                    }

                    Path filename = (Path) genericEvent.context();

                    if (!filename.toString().endsWith(fileExtension)) {
                        LOGGER.debug("Skipping event for [{}] due to unsupported file extension of [{}].", filename,
                                fileExtension);
                        continue; // just skip to the next event
                    }

                    if (changeListener != null) {
                        // Sleeping before notifying the listener to make sure file is
                        // done writing, otherwise the listener may read the file too soon.
                        TimeUnit.SECONDS.sleep(1);
                        LOGGER.debug("Notifying [{}] of event [{}] for file [{}].",
                                changeListener.getClass().getName(), kind,
                                configurationDirectoryPath.resolve(filename));
                        changeListener.notify(configurationDirectoryPath.resolve(filename));
                    }
                }

                // Reset key, shutdown watcher if directory not able to be observed
                // (possibly deleted)
                if (!key.reset()) {
                    LOGGER.warn("Configurations in [{}] are no longer able to be observed.",
                            configurationDirectoryPath.toString());
                    break;
                }
            }
        } catch (InterruptedException | RuntimeException e) {
            LOGGER.error("The [{}] was interrupted.", this.getClass().getName(), e);
            Thread.currentThread().interrupt();
        }
    }

    public void destroy() {
        try {
            watchService.close();
            executorService.shutdown();

            if (!executorService.awaitTermination(10, SECONDS)) {
                executorService.shutdownNow();

                if (!executorService.awaitTermination(10, SECONDS)) {
                    LOGGER.error("[{}] did not terminate correctly.", getClass().getName());
                }
            }
        } catch (IOException | InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}