/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.util.file.monitor;
import java.io.IOException;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Allows the user to configure a {@link java.nio.file.Path Path} to watch for modifications and periodically poll to check if the file has been modified
*/
public class SynchronousFileWatcher {
private final Path path;
private final long checkUpdateMillis;
private final UpdateMonitor monitor;
private final AtomicReference<StateWrapper> lastState;
private final Lock resourceLock = new ReentrantLock();
public SynchronousFileWatcher(final Path path, final UpdateMonitor monitor) {
this(path, monitor, 0L);
}
public SynchronousFileWatcher(final Path path, final UpdateMonitor monitor, final long checkMillis) {
if (checkMillis < 0) {
throw new IllegalArgumentException();
}
this.path = path;
checkUpdateMillis = checkMillis;
this.monitor = monitor;
Object currentState;
try {
currentState = monitor.getCurrentState(path);
} catch (final IOException e) {
currentState = null;
}
this.lastState = new AtomicReference<>(new StateWrapper(currentState));
}
/**
* Checks if the file has been updated according to the configured {@link UpdateMonitor} and resets the state
*
* @return true if updated; false otherwise
* @throws IOException if failure occurs checking for changes
*/
public boolean checkAndReset() throws IOException {
if (checkUpdateMillis <= 0) { // if checkUpdateMillis <= 0, always check
return checkForUpdate();
} else {
final StateWrapper stateWrapper = lastState.get();
if (stateWrapper.getTimestamp() < System.currentTimeMillis() - checkUpdateMillis) {
return checkForUpdate();
}
return false;
}
}
private boolean checkForUpdate() throws IOException {
if (resourceLock.tryLock()) {
try {
final StateWrapper wrapper = lastState.get();
final Object newState = monitor.getCurrentState(path);
if (newState == null && wrapper.getState() == null) {
return false;
}
if (newState == null || wrapper.getState() == null) {
lastState.set(new StateWrapper(newState));
return true;
}
final boolean unmodified = newState.equals(wrapper.getState());
if (!unmodified) {
lastState.set(new StateWrapper(newState));
}
return !unmodified;
} finally {
resourceLock.unlock();
}
} else {
return false;
}
}
private static class StateWrapper {
private final Object state;
private final long timestamp;
public StateWrapper(final Object state) {
this.state = state;
this.timestamp = System.currentTimeMillis();
}
public Object getState() {
return state;
}
public long getTimestamp() {
return timestamp;
}
}
}