org.elasticsearch.watcher.FileWatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you 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 org.elasticsearch.watcher;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
/**
* File resources watcher
*
* The file watcher checks directory and all its subdirectories for file changes and notifies its listeners accordingly
*/
public class FileWatcher extends AbstractResourceWatcher {
private FileObserver rootFileObserver;
private Path file;
private static final ESLogger logger = Loggers.getLogger(FileWatcher.class);
/**
* Creates new file watcher on the given directory
*/
public FileWatcher(Path file) {
this.file = file;
rootFileObserver = new FileObserver(file);
}
/**
* Clears any state with the FileWatcher, making all files show up as new
*/
public void clearState() {
rootFileObserver = new FileObserver(file);
try {
rootFileObserver.init(false);
} catch (IOException e) {
// ignore IOException
}
}
@Override
protected void doInit() throws IOException {
rootFileObserver.init(true);
}
@Override
protected void doCheckAndNotify() throws IOException {
rootFileObserver.checkAndNotify();
}
private static FileObserver[] EMPTY_DIRECTORY = new FileObserver[0];
private class FileObserver {
private Path file;
private boolean exists;
private long length;
private long lastModified;
private boolean isDirectory;
private FileObserver[] children;
public FileObserver(Path file) {
this.file = file;
}
public void checkAndNotify() throws IOException {
boolean prevExists = exists;
boolean prevIsDirectory = isDirectory;
long prevLength = length;
long prevLastModified = lastModified;
exists = Files.exists(file);
// TODO we might use the new NIO2 API to get real notification?
if (exists) {
BasicFileAttributes attributes = Files.readAttributes(file, BasicFileAttributes.class);
isDirectory = attributes.isDirectory();
if (isDirectory) {
length = 0;
lastModified = 0;
} else {
length = attributes.size();
lastModified = attributes.lastModifiedTime().toMillis();
}
} else {
isDirectory = false;
length = 0;
lastModified = 0;
}
// Perform notifications and update children for the current file
if (prevExists) {
if (exists) {
if (isDirectory) {
if (prevIsDirectory) {
// Remained a directory
updateChildren();
} else {
// File replaced by directory
onFileDeleted();
onDirectoryCreated(false);
}
} else {
if (prevIsDirectory) {
// Directory replaced by file
onDirectoryDeleted();
onFileCreated(false);
} else {
// Remained file
if (prevLastModified != lastModified || prevLength != length) {
onFileChanged();
}
}
}
} else {
// Deleted
if (prevIsDirectory) {
onDirectoryDeleted();
} else {
onFileDeleted();
}
}
} else {
// Created
if (exists) {
if (isDirectory) {
onDirectoryCreated(false);
} else {
onFileCreated(false);
}
}
}
}
private void init(boolean initial) throws IOException {
exists = Files.exists(file);
if (exists) {
BasicFileAttributes attributes = Files.readAttributes(file, BasicFileAttributes.class);
isDirectory = attributes.isDirectory();
if (isDirectory) {
onDirectoryCreated(initial);
} else {
length = attributes.size();
lastModified = attributes.lastModifiedTime().toMillis();
onFileCreated(initial);
}
}
}
private FileObserver createChild(Path file, boolean initial) throws IOException {
FileObserver child = new FileObserver(file);
child.init(initial);
return child;
}
private Path[] listFiles() throws IOException {
final Path[] files = FileSystemUtils.files(file);
Arrays.sort(files);
return files;
}
private FileObserver[] listChildren(boolean initial) throws IOException {
Path[] files = listFiles();
if (files != null && files.length > 0) {
FileObserver[] children = new FileObserver[files.length];
for (int i = 0; i < files.length; i++) {
children[i] = createChild(files[i], initial);
}
return children;
} else {
return EMPTY_DIRECTORY;
}
}
private void updateChildren() throws IOException {
Path[] files = listFiles();
if (files != null && files.length > 0) {
FileObserver[] newChildren = new FileObserver[files.length];
int child = 0;
int file = 0;
while (file < files.length || child < children.length ) {
int compare;
if (file >= files.length) {
compare = -1;
} else if (child >= children.length) {
compare = 1;
} else {
compare = children[child].file.compareTo(files[file]);
}
if (compare == 0) {
// Same file copy it and update
children[child].checkAndNotify();
newChildren[file] = children[child];
file++;
child++;
} else {
if (compare > 0) {
// This child doesn't appear in the old list - init it
newChildren[file] = createChild(files[file], false);
file++;
} else {
// The child from the old list is missing in the new list
// Delete it
deleteChild(child);
child++;
}
}
}
children = newChildren;
} else {
// No files - delete all children
for (int child = 0; child < children.length; child++) {
deleteChild(child);
}
children = EMPTY_DIRECTORY;
}
}
private void deleteChild(int child) {
if (children[child].exists) {
if (children[child].isDirectory) {
children[child].onDirectoryDeleted();
} else {
children[child].onFileDeleted();
}
}
}
private void onFileCreated(boolean initial) {
for (FileChangesListener listener : listeners()) {
try {
if (initial) {
listener.onFileInit(file);
} else {
listener.onFileCreated(file);
}
} catch (Throwable t) {
logger.warn("cannot notify file changes listener", t);
}
}
}
private void onFileDeleted() {
for (FileChangesListener listener : listeners()) {
try {
listener.onFileDeleted(file);
} catch (Throwable t) {
logger.warn("cannot notify file changes listener", t);
}
}
}
private void onFileChanged() {
for (FileChangesListener listener : listeners()) {
try {
listener.onFileChanged(file);
} catch (Throwable t) {
logger.warn("cannot notify file changes listener", t);
}
}
}
private void onDirectoryCreated(boolean initial) throws IOException {
for (FileChangesListener listener : listeners()) {
try {
if (initial) {
listener.onDirectoryInit(file);
} else {
listener.onDirectoryCreated(file);
}
} catch (Throwable t) {
logger.warn("cannot notify file changes listener", t);
}
}
children = listChildren(initial);
}
private void onDirectoryDeleted() {
// First delete all children
for (int child = 0; child < children.length; child++) {
deleteChild(child);
}
for (FileChangesListener listener : listeners()) {
try {
listener.onDirectoryDeleted(file);
} catch (Throwable t) {
logger.warn("cannot notify file changes listener", t);
}
}
}
}
}