Java: Change detection - java

I have the following design where I need to interact with client's API. And the WatchService (or Change Detection Handler method) had to be in the Testing Class in this caseand not in the FoodOrder class or ClientAPI class.
The ClientAPI class receives food orders from the various food machines via a callback method. All food orders have a unique reference number and are stored in a hashmap. Now when there is an update in instructions for the same food order (identified by the same reference number), a "change" is detected and new instruction is written to a file. The creation / modification of file will be picked up by the WatchService in the Testing class and trigger the proceesInstruction method to read the updated instructions.
I am looking for a better way to detect the change and pass the updated instructions to the Testing class without reading and writing to file, replacing the WatchService entirely, as this is very clumsy?
Class Testing{
public static void main(String[] args){
// connect to API...
ClientAPI clientAPI = new ClientAPI();
clientAPI.connect();
/*** Starting WatchService ***/
WatchService watchService = null;
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
}
scheduler.schedule(new WatchFile(watchService), 5, TimeUnit.SECONDS);
}
private static class WatchFile implements Runnable{
WatchService watchService;
public WatchFile(WatchService watchService) {
this.watchService = watchService;
}
public void run() {
try {
System.out.println("2. WatchService Started Running # " + LocalDateTime.now() + " !!!");
Path path = Paths.get("C:\\Testing\\csv");
path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY,StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.OVERFLOW);
WatchKey key;
while((key = watchService.take()) != null) {
for(WatchEvent<?> event: key.pollEvents()) {
final Path changed = (Path) event.context();
if(changed.endsWith("newInstructions.csv") && event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) {
System.out.println("Instruction File modified!!!");
processInstruction("C:\\Testing\\csv\\newInstructions.csv");
}
}
key.reset();
}
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void proceesInstruction(string filePath){
// read the csv file path and process instruction
}
}
Class FoodOrder{
int machineNumber;
String item;
String instruction;
String referenceNumber;
public FoodOrder(int machineNumber, String item, String instruction, String referenceNumber){
this.machineNumber = machineNumber;
this.item = item;
this.instruction = instruction;
this.referenceNumber = referenceNumber;
}
public void updateFoodOrder(int machineNumber, String item, String instruction, String referenceNumber){
this.machineNumber = machineNumber;
this.item = item;
this.referenceNumber = referenceNumber;
// Detect change in instruction
if(!instruction.equals(this.instruction)){
// my original code writed the new instruction to the
// C:\\Testing\\csv\\newInstructions.csv such that watchservice can detect file has been modified or created
// and process the instructions in the csv
}
this.instruction = instruction;
}
}
Class ClientAPI{
private HashMap<String,FoodOrder> foodOrderHashMap;
// Constructor
public clientAPI(){
foodOrderHashMap = new HashSet<String,FoodOrder>();
}
// API callback function
public void receiveMachineOrder(int machineNumber, String item, String instruction, String referenceNumber){
// my original is to write the instruction
if(foodOrderHashMap.containsKey(referenceNumber)){
foodOrderHashMap.get(referenceNumber).updateFoodOrder(machineNumber,item,instruction,referenceNumber);
} else{
foodOrderHashMap.put(referenceNumber, new FoodOrder(machineNumber, item, instruction, referenceNumber));
}
}
}

Related

java watchservice - running multiple job on cluster

I am using java watchservice to watch a specific folder when a specific file is written from another program.
I am running multiple jobs using this code and each watching each a specific folder that is different from each other.
This seems to work for the first job. But for the subsequent jobs, the watch service doesn't seem to be able get the write event even after the file it is looking for is written. It just kept idle as if the file has not yet been written.
Does anyone has experience with this?
Here is part of the code:
import java.util.*;
import java.nio.file.*;
import java.io.File;
private void run_code() {
WatchEvent.Kind watchType = StandardWatchEventKinds.ENTRY_CREATE;
while (i <= max_loop)
{
// Look out for output from python
String py_java_file = "Py2java_" + Integer.toString(i) + ".txt";
lookOutForFile(work_dir, py_java_file, watchType);
// Write finish file
try {
Files.write(Paths.get(work_dir+"java2Py_" + Integer.toString(i) + ".txt"), "run completed".getBytes());
}
catch (Exception e) {
print("Error writing file. ");
}
// Increment counter
i = i + 1;
}
}
private void lookOutForFile(String watchPath, String watchFile, WatchEvent.Kind watchType){
Path path = Paths.get(watchPath);
try (WatchService watchService = FileSystems.getDefault().newWatchService()) {
WatchKey key = path.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE);
startListening(watchService, watchPath, watchFile, watchType);
print("Found watch file");
} catch (Exception e) {
e.printStackTrace();
}
}
private void startListening (WatchService watchService, String watchPath, String watchFile, WatchEvent.Kind watchType)
throws InterruptedException {
boolean foundEvent = false;
while (!foundEvent) {
WatchKey queuedKey = watchService.take();
for (WatchEvent<?> watchEvent : queuedKey.pollEvents()) {
String sf1=String.format("kind=%s, count=%d, context=%s Context type=%s%n ", watchEvent.kind(), watchEvent.count(), watchEvent.context(), ((Path) watchEvent.context()).getClass());
if (watchEvent.kind() == watchType) {
String fileCreated = String.format("%s", watchEvent.context());
if (fileCreated.equals(watchFile))
{
foundEvent = true;
}
}
if (!queuedKey.reset()) {
break;
}
}
}
}

Why does directory WatchService produce two notification events when I change a file [duplicate]

The javadoc for StandardWatchEventKinds.ENTRY_MODIFY says:
Directory entry modified. When a directory is registered for this
event then the WatchKey is queued when it is observed that an entry in
the directory has been modified. The event count for this event is 1
or greater.
When you edit the content of a file through an editor, it'll modify both date (or other metadata) and content. You therefore get two ENTRY_MODIFY events, but each will have a count of 1 (at least that's what I'm seeing).
I'm trying to monitor a configuration file (servers.cfg previously registered with the WatchService) that is manually updated (ie. through command line vi) with the following code:
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
watchKey.reset();
}
Since you get two ENTRY_MODIFY events, the above would reload the configuration twice when only once is needed. Is there any way to ignore all but one of these, assuming there could be more than one such event?
If the WatchService API has such a utility so much the better. (I kind of don't want to check times between each event. All the handler methods in my code are synchronous.
The same thing occurs if you create (copy/paste) a file from one directory to the watched directory. How can you combine both of those into one event?
WatcherServices reports events twice because the underlying file is updated twice. Once for the content and once for the file modified time. These events happen within a short time span. To solve this, sleep between the poll() or take() calls and the key.pollEvents() call. For example:
#Override
#SuppressWarnings( "SleepWhileInLoop" )
public void run() {
setListening( true );
while( isListening() ) {
try {
final WatchKey key = getWatchService().take();
final Path path = get( key );
// Prevent receiving two separate ENTRY_MODIFY events: file modified
// and timestamp updated. Instead, receive one ENTRY_MODIFY event
// with two counts.
Thread.sleep( 50 );
for( final WatchEvent<?> event : key.pollEvents() ) {
final Path changed = path.resolve( (Path)event.context() );
if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
System.out.println( "Changed: " + changed );
}
}
if( !key.reset() ) {
ignore( path );
}
} catch( IOException | InterruptedException ex ) {
// Stop eavesdropping.
setListening( false );
}
}
}
Calling sleep() helps eliminate the double calls. The delay might have to be as high as three seconds.
I had a similar issue - I am using the WatchService API to keep directories in sync, but observed that in many cases, updates were being performed twice. I seem to have resolved the issue by checking the timestamp on the files - this seems to screen out the second copy operation. (At least in windows 7 - I can't be sure if it will work correctly in other operation systems)
Maybe you could use something similar? Store the timestamp from the file and reload only when the timestamp is updated?
One of my goto solutions for problems like this is to simply queue up the unique event resources and delay processing for an acceptable amount of time. In this case I maintain a Set<String> that contains every file name derived from each event that arrives. Using a Set<> ensures that duplicates don't get added and, therefore, will only be processed once (per delay period).
Each time an interesting event arrives I add the file name to the Set<> and restart my delay timer. When things settle down and the delay period elapses, I proceed to processing the files.
The addFileToProcess() and processFiles() methods are 'synchronized' to ensure that no ConcurrentModificationExceptions are thrown.
This simplified/standalone example is a derivative of Oracle's WatchDir.java:
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 java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class DirectoryWatcherService implements Runnable {
#SuppressWarnings("unchecked")
static <T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
/*
* Wait this long after an event before processing the files.
*/
private final int DELAY = 500;
/*
* Use a SET to prevent duplicates from being added when multiple events on the
* same file arrive in quick succession.
*/
HashSet<String> filesToReload = new HashSet<String>();
/*
* Keep a map that will be used to resolve WatchKeys to the parent directory
* so that we can resolve the full path to an event file.
*/
private final Map<WatchKey,Path> keys;
Timer processDelayTimer = null;
private volatile Thread server;
private boolean trace = false;
private WatchService watcher = null;
public DirectoryWatcherService(Path dir, boolean recursive)
throws IOException {
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<WatchKey,Path>();
if (recursive) {
registerAll(dir);
} else {
register(dir);
}
// enable trace after initial registration
this.trace = true;
}
private synchronized void addFileToProcess(String filename) {
boolean alreadyAdded = filesToReload.add(filename) == false;
System.out.println("Queuing file for processing: "
+ filename + (alreadyAdded?"(already queued)":""));
if (processDelayTimer != null) {
processDelayTimer.cancel();
}
processDelayTimer = new Timer();
processDelayTimer.schedule(new TimerTask() {
#Override
public void run() {
processFiles();
}
}, DELAY);
}
private synchronized void processFiles() {
/*
* Iterate over the set of file to be processed
*/
for (Iterator<String> it = filesToReload.iterator(); it.hasNext();) {
String filename = it.next();
/*
* Sometimes you just have to do what you have to do...
*/
System.out.println("Processing file: " + filename);
/*
* Remove this file from the set.
*/
it.remove();
}
}
/**
* Register the given directory with the WatchService
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (trace) {
Path prev = keys.get(key);
if (prev == null) {
System.out.format("register: %s\n", dir);
} else {
if (!dir.equals(prev)) {
System.out.format("update: %s -> %s\n", prev, dir);
}
}
}
keys.put(key, dir);
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(final Path start) throws IOException {
// register directory and sub-directories
Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException
{
if (dir.getFileName().toString().startsWith(".")) {
return FileVisitResult.SKIP_SUBTREE;
}
register(dir);
return FileVisitResult.CONTINUE;
}
});
}
#SuppressWarnings("unchecked")
#Override
public void run() {
Thread thisThread = Thread.currentThread();
while (server == thisThread) {
try {
// wait for key to be signaled
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
return;
}
Path dir = keys.get(key);
if (dir == null) {
continue;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == OVERFLOW) {
continue;
}
if (kind == ENTRY_MODIFY) {
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path name = ev.context();
Path child = dir.resolve(name);
String filename = child.toAbsolutePath().toString();
addFileToProcess(filename);
}
}
key.reset();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void start() {
server = new Thread(this);
server.setName("Directory Watcher Service");
server.start();
}
public void stop() {
Thread moribund = server;
server = null;
if (moribund != null) {
moribund.interrupt();
}
}
public static void main(String[] args) {
if (args==null || args.length == 0) {
System.err.println("You need to provide a path to watch!");
System.exit(-1);
}
Path p = Paths.get(args[0]);
if (!Files.isDirectory(p)) {
System.err.println(p + " is not a directory!");
System.exit(-1);
}
DirectoryWatcherService watcherService;
try {
watcherService = new DirectoryWatcherService(p, true);
watcherService.start();
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
I modified WatchDir.java to receive only human-made modifications. Comparing .lastModified() of a file.
long lastModi=0; //above for loop
if(kind==ENTRY_CREATE){
System.out.format("%s: %s\n", event.kind().name(), child);
}else if(kind==ENTRY_MODIFY){
if(child.toFile().lastModified() - lastModi > 1000){
System.out.format("%s: %s\n", event.kind().name(), child);
}
}else if(kind==ENTRY_DELETE){
System.out.format("%s: %s\n", event.kind().name(), child);
}
lastModi=child.toFile().lastModified();
Here is a full implementation using timestamps to avoid firing multiple events:
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.*;
public abstract class DirectoryWatcher
{
private WatchService watcher;
private Map<WatchKey, Path> keys;
private Map<Path, Long> fileTimeStamps;
private boolean recursive;
private boolean trace = true;
#SuppressWarnings("unchecked")
private static <T> WatchEvent<T> cast(WatchEvent<?> event)
{
return (WatchEvent<T>) event;
}
/**
* Register the given directory with the WatchService
*/
private void register(Path directory) throws IOException
{
WatchKey watchKey = directory.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);
addFileTimeStamps(directory);
if (trace)
{
Path existingFilePath = keys.get(watchKey);
if (existingFilePath == null)
{
System.out.format("register: %s\n", directory);
} else
{
if (!directory.equals(existingFilePath))
{
System.out.format("update: %s -> %s\n", existingFilePath, directory);
}
}
}
keys.put(watchKey, directory);
}
private void addFileTimeStamps(Path directory)
{
File[] files = directory.toFile().listFiles();
if (files != null)
{
for (File file : files)
{
if (file.isFile())
{
fileTimeStamps.put(file.toPath(), file.lastModified());
}
}
}
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
private void registerAll(Path directory) throws IOException
{
Files.walkFileTree(directory, new SimpleFileVisitor<Path>()
{
#Override
public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs)
throws IOException
{
register(currentDirectory);
return FileVisitResult.CONTINUE;
}
});
}
/**
* Creates a WatchService and registers the given directory
*/
DirectoryWatcher(Path directory, boolean recursive) throws IOException
{
this.watcher = FileSystems.getDefault().newWatchService();
this.keys = new HashMap<>();
fileTimeStamps = new HashMap<>();
this.recursive = recursive;
if (recursive)
{
System.out.format("Scanning %s ...\n", directory);
registerAll(directory);
System.out.println("Done.");
} else
{
register(directory);
}
// enable trace after initial registration
this.trace = true;
}
/**
* Process all events for keys queued to the watcher
*/
void processEvents() throws InterruptedException, IOException
{
while (true)
{
WatchKey key = watcher.take();
Path dir = keys.get(key);
if (dir == null)
{
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> event : key.pollEvents())
{
WatchEvent.Kind watchEventKind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (watchEventKind == OVERFLOW)
{
continue;
}
// Context for directory entry event is the file name of entry
WatchEvent<Path> watchEvent = cast(event);
Path fileName = watchEvent.context();
Path filePath = dir.resolve(fileName);
long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath);
long newFileModifiedTimeStamp = filePath.toFile().lastModified();
if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp)
{
fileTimeStamps.remove(filePath);
onEventOccurred();
fileTimeStamps.put(filePath, filePath.toFile().lastModified());
}
if (recursive && watchEventKind == ENTRY_CREATE)
{
if (Files.isDirectory(filePath, NOFOLLOW_LINKS))
{
registerAll(filePath);
}
}
break;
}
boolean valid = key.reset();
if (!valid)
{
keys.remove(key);
if (keys.isEmpty())
{
break;
}
}
}
}
public abstract void onEventOccurred();
}
Extend the class and implement the onEventOccurred() method.
Are you sure there is problem with jdk7? It gives correct result for me (jdk7u15, windows)
Code
import java.io.IOException;
import java.nio.file.*;
public class WatchTest {
public void watchMyFiles() throws IOException, InterruptedException {
Path path = Paths.get("c:/temp");
WatchService watchService = path.getFileSystem().newWatchService();
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
while (true) {
WatchKey watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: " +
watchEvent.count() + ", event: " + watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch (kind.name()) {
case "ENTRY_MODIFY":
handleModify(watchEvent.context()); // reload configuration class
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
default:
System.out.println("Event not expected " + event.kind().name());
}
}
watchKey.reset();
}
}
private void handleDelete(Path context) {
System.out.println("handleDelete " + context.getFileName());
}
private void handleModify(Path context) {
System.out.println("handleModify " + context.getFileName());
}
public static void main(String[] args) throws IOException, InterruptedException {
new WatchTest().watchMyFiles();
}
}
Output is like below- when file is copied over or edited using notepad.
config.xml, count: 1, event: ENTRY_MODIFY
handleModify config.xml
Vi uses many additional files, and seems to update file attribute multiple times. notepad++ does exactly two times.
If you use RxJava you can use the operator throttleLast. In the example below only the last event in 1000 milliseconds is emitted for each file in the watched directory.
public class FileUtils {
private static final long EVENT_DELAY = 1000L;
public static Observable<FileWatchEvent> watch(Path directory, String glob) {
return Observable.<FileWatchEvent>create(subscriber -> {
final PathMatcher matcher = directory.getFileSystem().getPathMatcher("glob:" + glob);
WatchService watcher = FileSystems.getDefault().newWatchService();
subscriber.setCancellable(watcher::close);
try {
directory.register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY);
} catch (IOException e) {
subscriber.onError(e);
return;
}
while (!subscriber.isDisposed()) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException e) {
if (subscriber.isDisposed())
subscriber.onComplete();
else
subscriber.onError(e);
return;
}
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind != OVERFLOW) {
WatchEvent<Path> ev = (WatchEvent<Path>) event;
Path child = directory.resolve(ev.context());
if (matcher.matches(child.getFileName()))
subscriber.onNext(new FileWatchEvent(kindToType(kind), child));
}
}
if (!key.reset()) {
subscriber.onError(new IOException("Invalid key"));
return;
}
}
}).groupBy(FileWatchEvent::getPath).flatMap(o -> o.throttleLast(EVENT_DELAY, TimeUnit.MILLISECONDS));
}
private static FileWatchEvent.Type kindToType(WatchEvent.Kind kind) {
if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind))
return FileWatchEvent.Type.ADDED;
else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind))
return FileWatchEvent.Type.MODIFIED;
else if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind))
return FileWatchEvent.Type.DELETED;
throw new RuntimeException("Invalid kind: " + kind);
}
public static class FileWatchEvent {
public enum Type {
ADDED, DELETED, MODIFIED
}
private Type type;
private Path path;
public FileWatchEvent(Type type, Path path) {
this.type = type;
this.path = path;
}
public Type getType() {
return type;
}
public Path getPath() {
return path;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileWatchEvent that = (FileWatchEvent) o;
if (type != that.type) return false;
return path != null ? path.equals(that.path) : that.path == null;
}
#Override
public int hashCode() {
int result = type != null ? type.hashCode() : 0;
result = 31 * result + (path != null ? path.hashCode() : 0);
return result;
}
}
}
I solved this problem by defining a global boolean variable named "modifySolver" which be false by default. You can handle this problem as I show bellow:
else if (eventKind.equals (ENTRY_MODIFY))
{
if (event.count() == 2)
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
}
/*capture first modify event*/
else if ((event.count() == 1) && (!modifySolver))
{
getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
modifySolver = true;
}
/*discard the second modify event*/
else if ((event.count() == 1) && (modifySolver))
{
modifySolver = false;
}
}
I compiled Oracle's WatchDir.java and #nilesh's suggestion into an Observable class that will notify it's observers once when the watched file is changed.
I tried to make it as readable and short as possible, but still landed with more than 100 lines. Improvements welcome, of course.
Usage:
FileChangeNotifier fileReloader = new FileChangeNotifier(File file);
fileReloader.addObserver((Observable obj, Object arg) -> {
System.out.println("File changed for the " + arg + " time.");
});
See my solution on GitHub: FileChangeNotifier.java.
I tried this and it's working perfectly :
import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static java.nio.file.StandardWatchEventKinds.*;
public class FileWatcher implements Runnable, AutoCloseable {
private final WatchService service;
private final Map<Path, WatchTarget> watchTargets = new HashMap<>();
private final List<FileListener> fileListeners = new CopyOnWriteArrayList<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock r = lock.readLock();
private final Lock w = lock.writeLock();
private final AtomicBoolean running = new AtomicBoolean(false);
public FileWatcher() throws IOException {
service = FileSystems.getDefault().newWatchService();
}
#Override
public void run() {
if (running.compareAndSet(false, true)) {
while (running.get()) {
WatchKey key;
try {
key = service.take();
} catch (Throwable e) {
break;
}
if (key.isValid()) {
r.lock();
try {
key.pollEvents().stream()
.filter(e -> e.kind() != OVERFLOW)
.forEach(e -> watchTargets.values().stream()
.filter(t -> t.isInterested(e))
.forEach(t -> fireOnEvent(t.path, e.kind())));
} finally {
r.unlock();
}
if (!key.reset()) {
break;
}
}
}
running.set(false);
}
}
public boolean registerPath(Path path, boolean updateIfExists, WatchEvent.Kind... eventKinds) {
w.lock();
try {
WatchTarget target = watchTargets.get(path);
if (!updateIfExists && target != null) {
return false;
}
Path parent = path.getParent();
if (parent != null) {
if (target == null) {
watchTargets.put(path, new WatchTarget(path, eventKinds));
parent.register(service, eventKinds);
} else {
target.setEventKinds(eventKinds);
}
return true;
}
} catch (Throwable e) {
e.printStackTrace();
} finally {
w.unlock();
}
return false;
}
public void addFileListener(FileListener fileListener) {
fileListeners.add(fileListener);
}
public void removeFileListener(FileListener fileListener) {
fileListeners.remove(fileListener);
}
private void fireOnEvent(Path path, WatchEvent.Kind eventKind) {
for (FileListener fileListener : fileListeners) {
fileListener.onEvent(path, eventKind);
}
}
public boolean isRunning() {
return running.get();
}
#Override
public void close() throws IOException {
running.set(false);
w.lock();
try {
service.close();
} finally {
w.unlock();
}
}
private final class WatchTarget {
private final Path path;
private final Path fileName;
private final Set<String> eventNames = new HashSet<>();
private final Event lastEvent = new Event();
private WatchTarget(Path path, WatchEvent.Kind[] eventKinds) {
this.path = path;
this.fileName = path.getFileName();
setEventKinds(eventKinds);
}
private void setEventKinds(WatchEvent.Kind[] eventKinds) {
eventNames.clear();
for (WatchEvent.Kind k : eventKinds) {
eventNames.add(k.name());
}
}
private boolean isInterested(WatchEvent e) {
long now = System.currentTimeMillis();
String name = e.kind().name();
if (e.context().equals(fileName) && eventNames.contains(name)) {
if (lastEvent.name == null || !lastEvent.name.equals(name) || now - lastEvent.when > 100) {
lastEvent.name = name;
lastEvent.when = now;
return true;
}
}
return false;
}
#Override
public int hashCode() {
return path.hashCode();
}
#Override
public boolean equals(Object obj) {
return obj == this || obj != null && obj instanceof WatchTarget && Objects.equals(path, ((WatchTarget) obj).path);
}
}
private final class Event {
private String name;
private long when;
}
public static void main(String[] args) throws IOException, InterruptedException {
FileWatcher watcher = new FileWatcher();
if (watcher.registerPath(Paths.get("filename"), false, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE)) {
watcher.addFileListener((path, eventKind) -> System.out.println(path + " -> " + eventKind.name()));
new Thread(watcher).start();
System.in.read();
}
watcher.close();
System.exit(0);
}
}
FileListener :
import java.nio.file.Path;
import java.nio.file.WatchEvent;
public interface FileListener {
void onEvent(Path path, WatchEvent.Kind eventKind);
}
I had similar problem. I know this is late but it might help someone.
I just needed to eliminate duplicate ENTRY_MODIFY.
Whenever ENTRY_MODIFY is triggered, count() returns either 2 or 1. If it is 1, then there will be another event with count() 1.
So just put a global counter which keeps the count of return values and carry out the operations only when the counter becomes 2. Something like this can do:
WatchEvent event;
int count = 0;
if(event.count() == 2)
count = 2;
if(event.count() == 1)
count++;
if(count == 2){
//your operations here
count = 0;
}
/**
*
*
* in windows os, multiple event will be fired for a file create action
* this method will combine the event on same file
*
* for example:
*
* pathA -> createEvent -> createEvent
* pathA -> createEvent + modifyEvent, .... -> modifyEvent
* pathA -> createEvent + modifyEvent, ...., deleteEvent -> deleteEvent
*
*
*
* 在windows环境下创建一个文件会产生1个创建事件+多个修改事件, 这个方法用于合并重复事件
* 合并优先级为 删除 > 更新 > 创建
*
*
* #param events
* #return
*/
private List<WatchEvent<?>> filterEvent(List<WatchEvent<?>> events) {
// sorted by event create > modify > delete
Comparator<WatchEvent<?>> eventComparator = (eventA, eventB) -> {
HashMap<WatchEvent.Kind, Integer> map = new HashMap<>();
map.put(StandardWatchEventKinds.ENTRY_CREATE, 0);
map.put(StandardWatchEventKinds.ENTRY_MODIFY, 1);
map.put(StandardWatchEventKinds.ENTRY_DELETE, 2);
return map.get(eventA.kind()) - map.get(eventB.kind());
};
events.sort(eventComparator);
HashMap<String, WatchEvent<?>> hashMap = new HashMap<>();
for (WatchEvent<?> event : events) {
// if this is multiple event on same path
// the create event will added first
// then override by modify event
// then override by delete event
hashMap.put(event.context().toString(), event);
}
return new ArrayList<>(hashMap.values());
}
If you are trying the same in Scala using better-files-akka library, I have comeup with this work around based on the solution proposed in the accepted answer.
https://github.com/pathikrit/better-files/issues/313
trait ConfWatcher {
implicit def actorSystem: ActorSystem
private val confPath = "/home/codingkapoor/application.conf"
private val appConfFile = File(confPath)
private var appConfLastModified = appConfFile.lastModifiedTime
val watcher: ActorRef = appConfFile.newWatcher(recursive = false)
watcher ! on(EventType.ENTRY_MODIFY) { file =>
if (appConfLastModified.compareTo(file.lastModifiedTime) < 0) {
// TODO
appConfLastModified = file.lastModifiedTime
}
}
}
The set of multiple events depends on the tool used to create/modify files.
1.Create new file with Vim
MODIFIED, CREATED events are fired
2.Modify file with Vim
DELETED, MODIFIED, CREATED events are fired
3.Use Linux command 'mv' to move file from other folder to watched folder
MODIFIED event is fired
4.Use Linux command 'cp' to copy file from other folder to watched folder
MODIFIED, CREATED are fired if no file with same file name exists
CREATED is fired if file with same file name exists
I used 3 maps to collect CREATED/MODIFIED/DELETED entries in the for loop iterating over WatchEvent. Then, run 3 for loops over those 3 maps to determine the correct event we need to notify (ex: if a file name appears in all three map, then we could say it is a MODIFIED event)
File f = new File(sourceDir);
if (!f.exists() || !f.isDirectory()) {
LOGGER.warn("File " + sourceDir + " does not exist OR is not a directory");
return;
}
WatchService watchService = FileSystems.getDefault().newWatchService();
Path watchedDir = f.toPath();
watchedDir.register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
while (true) {
try {
WatchKey watchKey = watchService.take();
Thread.sleep(checkInterval);
//Events fired by Java NIO file watcher depends on the tool used
//to create/update/delete file. We need those 3 maps to collect
//all entries fired by NIO watcher. Then, make 3 loops at the end
//to determine the correct unique event to notify
final Map<String, Boolean> createdEntries = new HashMap<>();
final Map<String, Boolean> modifiedEntries = new HashMap<>();
final Map<String, Boolean> deletedEntries = new HashMap<>();
List<WatchEvent<?>> events = watchKey.pollEvents();
for (WatchEvent<?> event : events) {
if (event.kind() == OVERFLOW) {
continue;
}
WatchEvent<Path> pathEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = pathEvent.kind();
Path path = pathEvent.context();
String fileName = path.toString();
if (accept(fileName)) {
if (kind == ENTRY_CREATE) {
createdEntries.put(fileName, true);
} else if (kind == ENTRY_MODIFY) {
modifiedEntries.put(fileName, true);
} else if (kind == ENTRY_DELETE) {
deletedEntries.put(fileName, true);
}
}
}
long timeStamp = System.currentTimeMillis();
final Map<String, Boolean> handledEntries = new HashMap<>();
//3 for loops to determine correct event to notify
for (String key : createdEntries.keySet()) {
if (handledEntries.get(key) == null) {
Boolean modified = modifiedEntries.get(key);
Boolean deleted = deletedEntries.get(key);
if (modified != null && deleted != null) {
//A triplet of DELETED/MODIFIED/CREATED means a MODIFIED event
LOGGER.debug("File " + key + " was modified");
notifyFileModified(key, timeStamp);
} else if (modified != null) {
LOGGER.debug("New file " + key + " was created");
notifyFileCreated(key, timeStamp);
} else {
LOGGER.debug("New file " + key + " was created");
notifyFileCreated(key, timeStamp);
}
handledEntries.put(key, true);
}
}
for (String key : modifiedEntries.keySet()) {
if (handledEntries.get(key) == null) {
//Current entry survives from loop on CREATED entries. It is certain
//that we have MODIFIED event
LOGGER.debug("File " + key + " was modified");
notifyFileModified(key, timeStamp);
handledEntries.put(key, true);
}
}
for (String key : deletedEntries.keySet()) {
if (handledEntries.get(key) == null) {
//Current entry survives from two loops on CREATED/MODIFIED entries. It is certain
//that we have DELETE event
LOGGER.debug("File " + key + " was deleted");
notifyFileDeleted(key, timeStamp);
}
}
boolean valid = watchKey.reset();
if (!valid) {
break;
}
} catch (Exception ex) {
LOGGER.warn("Error while handling file events under: " + sourceDir, ex);
}
Thread.sleep(checkInterval);
}
Untested, but perhaps this will work:
AtomicBoolean modifyEventFired = new AtomicBoolean();
modifyEventFired.set(false);
while(true) {
watchKey = watchService.take(); // blocks
for (WatchEvent<?> event : watchKey.pollEvents()) {
WatchEvent<Path> watchEvent = (WatchEvent<Path>) event;
WatchEvent.Kind<Path> kind = watchEvent.kind();
System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
// prints (loop on the while twice)
// servers.cfg, count: 1, event: ENTRY_MODIFY
// servers.cfg, count: 1, event: ENTRY_MODIFY
switch(kind.name()) {
case "ENTRY_MODIFY":
if(!modifyEventFired.get()){
handleModify(watchEvent.context()); // reload configuration class
modifyEventFired.set(true);
}
break;
case "ENTRY_DELETE":
handleDelete(watchEvent.context()); // do something else
break;
}
}
modifyEventFired.set(false);
watchKey.reset();
}

Unit test code with WatchService

Below is a short simple example of using a WatchService to keep data in sync with a file. My question is how to reliably test the code. The test fails occasionally, probably because of a race condition between the os/jvm getting the event into the watch service and the test thread polling the watch service. My desire is to keep the code simple, single threaded, and non blocking but also be testable. I strongly dislike putting sleep calls of arbitrary length into test code. I am hoping there is a better solution.
public class FileWatcher {
private final WatchService watchService;
private final Path path;
private String data;
public FileWatcher(Path path){
this.path = path;
try {
watchService = FileSystems.getDefault().newWatchService();
path.toAbsolutePath().getParent().register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
load();
}
private void load() {
try (BufferedReader br = Files.newBufferedReader(path, Charset.defaultCharset())){
data = br.readLine();
} catch (IOException ex) {
data = "";
}
}
private void update(){
WatchKey key;
while ((key=watchService.poll()) != null) {
for (WatchEvent<?> e : key.pollEvents()) {
WatchEvent<Path> event = (WatchEvent<Path>) e;
if (path.equals(event.context())){
load();
break;
}
}
key.reset();
}
}
public String getData(){
update();
return data;
}
}
And the current test
public class FileWatcherTest {
public FileWatcherTest() {
}
Path path = Paths.get("myFile.txt");
private void write(String s) throws IOException{
try (BufferedWriter bw = Files.newBufferedWriter(path, Charset.defaultCharset())) {
bw.write(s);
}
}
#Test
public void test() throws IOException{
for (int i=0; i<100; i++){
write("hello");
FileWatcher fw = new FileWatcher(path);
Assert.assertEquals("hello", fw.getData());
write("goodbye");
Assert.assertEquals("goodbye", fw.getData());
}
}
}
This timing issue is bound to happen because of the polling happening in the watch service.
This test is not really a unit test because it is testing the actual implementation of the default file system watcher.
If I wanted to make a self-contained unit test for this class, I would first modify the FileWatcher so that it does not rely on the default file system. The way I would do this would be to inject a WatchService into the constructor instead of a FileSystem. For example...
public class FileWatcher {
private final WatchService watchService;
private final Path path;
private String data;
public FileWatcher(WatchService watchService, Path path) {
this.path = path;
try {
this.watchService = watchService;
path.toAbsolutePath().getParent().register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
load();
}
...
Passing in this dependency instead of the class getting hold of a WatchService by itself makes this class a bit more reusable in the future. For example, what if you wanted to use a different FileSystem implementation (such as an in-memory one like https://github.com/google/jimfs)?
You can now test this class by mocking the dependencies, for example...
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 org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.FileSystem;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.spi.FileSystemProvider;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
public class FileWatcherTest {
private FileWatcher fileWatcher;
private WatchService watchService;
private Path path;
#Before
public void setup() throws Exception {
// Set up mock watch service and path
watchService = mock(WatchService.class);
path = mock(Path.class);
// Need to also set up mocks for absolute parent path...
Path absolutePath = mock(Path.class);
Path parentPath = mock(Path.class);
// Mock the path's methods...
when(path.toAbsolutePath()).thenReturn(absolutePath);
when(absolutePath.getParent()).thenReturn(parentPath);
// Mock enough of the path so that it can load the test file.
// On the first load, the loaded data will be "[INITIAL DATA]", any subsequent call it will be "[UPDATED DATA]"
// (this is probably the smellyest bit of this test...)
InputStream initialInputStream = createInputStream("[INITIAL DATA]");
InputStream updatedInputStream = createInputStream("[UPDATED DATA]");
FileSystem fileSystem = mock(FileSystem.class);
FileSystemProvider fileSystemProvider = mock(FileSystemProvider.class);
when(path.getFileSystem()).thenReturn(fileSystem);
when(fileSystem.provider()).thenReturn(fileSystemProvider);
when(fileSystemProvider.newInputStream(path)).thenReturn(initialInputStream, updatedInputStream);
// (end smelly bit)
// Create the watcher - this should load initial data immediately
fileWatcher = new FileWatcher(watchService, path);
// Verify that the watch service was registered with the parent path...
verify(parentPath).register(watchService, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
#Test
public void shouldReturnCurrentStateIfNoChanges() {
// Check to see if the initial data is returned if the watch service returns null on poll...
when(watchService.poll()).thenReturn(null);
assertThat(fileWatcher.getData()).isEqualTo("[INITIAL DATA]");
}
#Test
public void shouldLoadNewStateIfFileChanged() {
// Check that the updated data is loaded when the watch service says the path we are interested in has changed on poll...
WatchKey watchKey = mock(WatchKey.class);
#SuppressWarnings("unchecked")
WatchEvent<Path> pathChangedEvent = mock(WatchEvent.class);
when(pathChangedEvent.context()).thenReturn(path);
when(watchKey.pollEvents()).thenReturn(Arrays.asList(pathChangedEvent));
when(watchService.poll()).thenReturn(watchKey, (WatchKey) null);
assertThat(fileWatcher.getData()).isEqualTo("[UPDATED DATA]");
}
#Test
public void shouldKeepCurrentStateIfADifferentPathChanged() {
// Make sure nothing happens if a different path is updated...
WatchKey watchKey = mock(WatchKey.class);
#SuppressWarnings("unchecked")
WatchEvent<Path> pathChangedEvent = mock(WatchEvent.class);
when(pathChangedEvent.context()).thenReturn(mock(Path.class));
when(watchKey.pollEvents()).thenReturn(Arrays.asList(pathChangedEvent));
when(watchService.poll()).thenReturn(watchKey, (WatchKey) null);
assertThat(fileWatcher.getData()).isEqualTo("[INITIAL DATA]");
}
private InputStream createInputStream(String string) {
return new ByteArrayInputStream(string.getBytes());
}
}
I can see why you might want a "real" test for this that does not use mocks - in which case it would not be a unit test and you might not have much choice but to sleep between checks (the JimFS v1.0 code is hard coded to poll every 5 seconds, have not looked at the poll time on the core Java FileSystem's WatchService)
Hope this helps
I created a wrapper around WatchService to clean up many issues I have with the API. It is now much more testable. I am unsure about some of the concurrency issues in PathWatchService though and I have not done thorough testing of it.
New FileWatcher:
public class FileWatcher {
private final PathWatchService pathWatchService;
private final Path path;
private String data;
public FileWatcher(PathWatchService pathWatchService, Path path) {
this.path = path;
this.pathWatchService = pathWatchService;
try {
this.pathWatchService.register(path.toAbsolutePath().getParent());
} catch (IOException ex) {
throw new RuntimeException(ex);
}
load();
}
private void load() {
try (BufferedReader br = Files.newBufferedReader(path, Charset.defaultCharset())){
data = br.readLine();
} catch (IOException ex) {
data = "";
}
}
public void update(){
PathEvents pe;
while ((pe=pathWatchService.poll()) != null) {
for (WatchEvent we : pe.getEvents()){
if (path.equals(we.context())){
load();
return;
}
}
}
}
public String getData(){
update();
return data;
}
}
Wrapper:
public class PathWatchService implements AutoCloseable {
private final WatchService watchService;
private final BiMap<WatchKey, Path> watchKeyToPath = HashBiMap.create();
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Queue<WatchKey> invalidKeys = new ConcurrentLinkedQueue<>();
/**
* Constructor.
*/
public PathWatchService() {
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* Register the input path with the WatchService for all
* StandardWatchEventKinds. Registering a path which is already being
* watched has no effect.
*
* #param path
* #return
* #throws IOException
*/
public void register(Path path) throws IOException {
register(path, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
}
/**
* Register the input path with the WatchService for the input event kinds.
* Registering a path which is already being watched has no effect.
*
* #param path
* #param kinds
* #return
* #throws IOException
*/
public void register(Path path, WatchEvent.Kind... kinds) throws IOException {
try {
lock.writeLock().lock();
removeInvalidKeys();
WatchKey key = watchKeyToPath.inverse().get(path);
if (key == null) {
key = path.register(watchService, kinds);
watchKeyToPath.put(key, path);
}
} finally {
lock.writeLock().unlock();
}
}
/**
* Close the WatchService.
*
* #throws IOException
*/
#Override
public void close() throws IOException {
try {
lock.writeLock().lock();
watchService.close();
watchKeyToPath.clear();
invalidKeys.clear();
} finally {
lock.writeLock().unlock();
}
}
/**
* Retrieves and removes the next PathEvents object, or returns null if none
* are present.
*
* #return
*/
public PathEvents poll() {
return keyToPathEvents(watchService.poll());
}
/**
* Return a PathEvents object from the input key.
*
* #param key
* #return
*/
private PathEvents keyToPathEvents(WatchKey key) {
if (key == null) {
return null;
}
try {
lock.readLock().lock();
Path watched = watchKeyToPath.get(key);
List<WatchEvent<Path>> events = new ArrayList<>();
for (WatchEvent e : key.pollEvents()) {
events.add((WatchEvent<Path>) e);
}
boolean isValid = key.reset();
if (isValid == false) {
invalidKeys.add(key);
}
return new PathEvents(watched, events, isValid);
} finally {
lock.readLock().unlock();
}
}
/**
* Retrieves and removes the next PathEvents object, waiting if necessary up
* to the specified wait time, returns null if none are present after the
* specified wait time.
*
* #return
*/
public PathEvents poll(long timeout, TimeUnit unit) throws InterruptedException {
return keyToPathEvents(watchService.poll(timeout, unit));
}
/**
* Retrieves and removes the next PathEvents object, waiting if none are yet
* present.
*
* #return
*/
public PathEvents take() throws InterruptedException {
return keyToPathEvents(watchService.take());
}
/**
* Get all paths currently being watched. Any paths which were watched but
* have invalid keys are not returned.
*
* #return
*/
public Set<Path> getWatchedPaths() {
try {
lock.readLock().lock();
Set<Path> paths = new HashSet<>(watchKeyToPath.inverse().keySet());
WatchKey key;
while ((key = invalidKeys.poll()) != null) {
paths.remove(watchKeyToPath.get(key));
}
return paths;
} finally {
lock.readLock().unlock();
}
}
/**
* Cancel watching the specified path. Cancelling a path which is not being
* watched has no effect.
*
* #param path
*/
public void cancel(Path path) {
try {
lock.writeLock().lock();
removeInvalidKeys();
WatchKey key = watchKeyToPath.inverse().remove(path);
if (key != null) {
key.cancel();
}
} finally {
lock.writeLock().unlock();
}
}
/**
* Removes any invalid keys from internal data structures. Note this
* operation is also performed during register and cancel calls.
*/
public void cleanUp() {
try {
lock.writeLock().lock();
removeInvalidKeys();
} finally {
lock.writeLock().unlock();
}
}
/**
* Clean up method to remove invalid keys, must be called from inside an
* acquired write lock.
*/
private void removeInvalidKeys() {
WatchKey key;
while ((key = invalidKeys.poll()) != null) {
watchKeyToPath.remove(key);
}
}
}
Data class:
public class PathEvents {
private final Path watched;
private final ImmutableList<WatchEvent<Path>> events;
private final boolean isValid;
/**
* Constructor.
*
* #param watched
* #param events
* #param isValid
*/
public PathEvents(Path watched, List<WatchEvent<Path>> events, boolean isValid) {
this.watched = watched;
this.events = ImmutableList.copyOf(events);
this.isValid = isValid;
}
/**
* Return an immutable list of WatchEvent's.
* #return
*/
public List<WatchEvent<Path>> getEvents() {
return events;
}
/**
* True if the watched path is valid.
* #return
*/
public boolean isIsValid() {
return isValid;
}
/**
* Return the path being watched in which these events occurred.
*
* #return
*/
public Path getWatched() {
return watched;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PathEvents other = (PathEvents) obj;
if (!Objects.equals(this.watched, other.watched)) {
return false;
}
if (!Objects.equals(this.events, other.events)) {
return false;
}
if (this.isValid != other.isValid) {
return false;
}
return true;
}
#Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + Objects.hashCode(this.watched);
hash = 71 * hash + Objects.hashCode(this.events);
hash = 71 * hash + (this.isValid ? 1 : 0);
return hash;
}
#Override
public String toString() {
return "PathEvents{" + "watched=" + watched + ", events=" + events + ", isValid=" + isValid + '}';
}
}
And finally the test, note this is not a complete unit test but demonstrates the way to write tests for this situation.
public class FileWatcherTest {
public FileWatcherTest() {
}
Path path = Paths.get("myFile.txt");
Path parent = path.toAbsolutePath().getParent();
private void write(String s) throws IOException {
try (BufferedWriter bw = Files.newBufferedWriter(path, Charset.defaultCharset())) {
bw.write(s);
}
}
#Test
public void test() throws IOException, InterruptedException{
write("hello");
PathWatchService real = new PathWatchService();
real.register(parent);
PathWatchService mock = mock(PathWatchService.class);
FileWatcher fileWatcher = new FileWatcher(mock, path);
verify(mock).register(parent);
Assert.assertEquals("hello", fileWatcher.getData());
write("goodbye");
PathEvents pe = real.poll(10, TimeUnit.SECONDS);
if (pe == null){
Assert.fail("Should have an event for writing good bye");
}
when(mock.poll()).thenReturn(pe).thenReturn(null);
Assert.assertEquals("goodbye", fileWatcher.getData());
}
}

issues assigned a value to a variable using a property file [duplicate]

This question already has answers here:
JAVA .properties file
(3 answers)
Closed 10 years ago.
hi all i am having an issue, i have a properties file, this stores all the save locations, i get the data from this file by :
public void loadProp() {
System.out.println("Loading properties");
InputStream in = this.getClass().getClassLoader().getResourceAsStream("config.properties"); //points to a properties file, this will load up destinations instead of having to declare them here
try {
configProp.load(in);
System.out.println(configProp.getProperty("destinationPDF"));
System.out.println(configProp.getProperty("destination"));
System.out.println(configProp.getProperty("fileList"));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("called get username");
username = FacesContext.getCurrentInstance().getExternalContext().getRemoteUser();
System.out.println(username);
}
i then do this to assign the value to destination
public String destination = configProp.getProperty("destination");
but whenever i use destination i get a null value, however if i use configProp.getProperty("destination") i get the full path, what am i doing wrong here as i want the value to be point to destination as other classes depend on it
EDIT :
This class is called on by a command button (web app)
#ViewScoped
#ManagedBean(name = "fileUploadController")
public class FileUploadController {
public boolean isUploadComplete() { //to enable the next button once finished
return uploadComplete;
}
public void setUploadComplete(boolean uploadComplete) {
this.uploadComplete = uploadComplete;
}
public boolean isUploadComplete2() {
//to disable the file upload button, this will stop users uploading multiple files and over writing them as only the last file uploaded will be used
return uploadComplete;
}
public void setUploadComplete2(boolean uploadComplete) {
this.uploadComplete = uploadComplete;
}
/*
public void handleFileUpload(FileUploadEvent event) {
System.out.println("called");
FacesMessage msg = new FacesMessage("Succesful", event.getFile().getFileName() + " is uploaded.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
}
*/
//
//Strings for fileUpload
//oadProp()
//public String fileList = "D:/Documents/NetBeansProjects/printing~subversion/fileupload/web/resources/Directory Files/directoryFiles.txt"; //
private Properties configProp = new Properties();
#PostConstruct
//System.out.println(destinationPDF);
//System.out.println(destination);
// Get the username from the login page, this is used to create a folder for each user
public void loadProp() {
System.out.println("Loading properties");
InputStream in = this.getClass().getClassLoader().getResourceAsStream("config.properties"); //points to a properties file, this will load up destinations instead of having to declare them here
try {
configProp.load(in);
System.out.println(configProp.getProperty("destinationPDF"));
System.out.println(configProp.getProperty("destination"));
System.out.println(configProp.getProperty("fileList"));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("called get username");
username = FacesContext.getCurrentInstance().getExternalContext().getRemoteUser();
System.out.println(username);
}
//String destinationPDF = configProp.getProperty("destinationPDF"); Always makes a null no idea why yet
//private String destinationPDF = configProp.getProperty("destinationPDF");
public String destination = configProp.getProperty("destination");
private String username;
//public static String destination = "D:/Documents/NetBeansProjects/printing~subversion/fileupload/uploaded/"; // main location for uploads//TORNADO ONLY //"D:/My Documents/NetBeansProjects/printing~subversion/fileupload/uploaded/"; // USE ON PREDATOR ONLY
public static String NewDestination;
public static String UploadedfileName;
public static String CompletefileName;
//
//Strings for file copy
//
//private String destinationPDF = "D:/Documents/NetBeansProjects/printing~subversion/fileupload/web/resources/pdf/"; //USE ON TORNADO//"D:/My Documents/NetBeansProjects/printing~subversion/fileupload/web/resources/pdf/";//USE ON PREDATOR
private String NewdestinationPDF;
public static String PdfLocationViewable;
private boolean uploadComplete;
private boolean uploadComplete2;
//
public void File() {
above is the first bit of code for that class
the output in the console is :
INFO: buttonToUploadText invoked
INFO: Loading properties
INFO: D:/Documents/NetBeansProjects/printing~subversion/fileupload/web/resources/pdf/
INFO: D:/Documents/NetBeansProjects/printing~subversion/fileupload/Uploaded/
INFO: D:/Documents/NetBeansProjects/printing~subversion/fileupload/web/resources/Directory Files/directoryFiles.txt
INFO: called get username
INFO: null
INFO: destination is null
Your ordering is off. This is what will happen when your bean is instantiated by the container:
Constructor will be called
All injected fields will be resolved
PostConstruct will be called.
Currently, you are setting the destination value before your properties have been loaded up. A very simple solution to this problem is to simply set the destination value in your #PostConstruct handler.
#PostConstruct
public void loadProp() {
InputStream in = this.getClass().getClassLoader()
.getResourceAsStream("config.properties");
try {
configProp.load(in);
} catch (IOException e) {
e.printStackTrace();
}
destination = configProp.getProperty("destination");
}
One advantage of this method over others is that the destination property will be correctly set every time the loadProp method is called (as opposed to only once).
Replace this line
public String destination = configProp.getProperty("destination");
with
public String destination;
and provide a constructor:
public FileUploadController() {
loadProp();
this.destination = configProp.getProperty("destination");
}
Now, loadProp() will be called by the constructor, and you don't have to do it anymore.

Directory Scanner in Java

Scan a set of directories continuously for a set of file name filters.
For each file name filter arrived, process the file and repeat the steps for all
What can be the recommended design for this in jdk 1.5 , possibly using java.concurrent.Executor and Future
I have done a similar task with the web crawler.Just a few changes had to be made... It is a concurrent implementation with newly found directories getting scanned by the thread pool in the Executor Framework.It uses concurrent collections for Queue and List to hold the indexed files. The indexer picks up the files from the queue and does something with them.
here is the FileFilter implementation
public class ImageFileFilter implements FileFilter
{
private final String[] okFileExtensions =
new String[] {"jpg", "png", "gif"};
public boolean accept(File file)
{
for (String extension : okFileExtensions)
{
if (file.getName().toLowerCase().endsWith(extension))
{
return true;
}
}
return false;
}
}
here is the Class with the main method...
public class FileFilterTest {
public static void main(String[] args) {
File dir = new File("D:\\dev\\css-templates\\cms-admin");
BlockingQueue blockingQueue = new ArrayBlockingQueue(5);
FileCrawler fileCrawler = new FileCrawler(blockingQueue,
new ImageFileFilter(), dir);
new Thread(fileCrawler).start();
FileIndexer indexer = new FileIndexer(blockingQueue);
new Thread(indexer).start();
}
}
Here is the file crawler thread
public class FileCrawler implements Runnable {
private final BlockingQueue fileQueue;
private ConcurrentSkipListSet indexedFiles = new ConcurrentSkipListSet();
private final FileFilter fileFilter;
private final File root;
private final ExecutorService exec = Executors.newCachedThreadPool();
public FileCrawler(BlockingQueue fileQueue,
final FileFilter fileFilter,
File root) {
this.fileQueue = fileQueue;
this.root = root;
this.fileFilter = new FileFilter() {
public boolean accept(File f) {
return f.isDirectory() || fileFilter.accept(f);
}
};
}
public void run() {
submitCrawlTask(root);
}
private void submitCrawlTask(File f) {
CrawlTask crawlTask = new CrawlTask(f);
exec.execute(crawlTask);
}
private class CrawlTask implements Runnable {
private final File file;
CrawlTask(File file ) {
this.file= file;
}
public void run() {
if(Thread.currentThread().isInterrupted())
return;
File[] entries = file.listFiles(fileFilter);
if (entries != null) {
for (File entry : entries)
if (entry.isDirectory())
submitCrawlTask(entry);
else if (entry !=null && !indexedFiles.contains(entry)){
indexedFiles.add(entry);
try {
fileQueue.put(entry);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
}
Here is the file indexer thread
public class FileIndexer implements Runnable {
private final BlockingQueue queue;
public FileIndexer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
try {
while (true) {
indexFile(queue.take());
}
} catch (InterruptedException e) {
System.out.println("Indexer Interrupted");
Thread.currentThread().interrupt();
}
}
public void indexFile(File file) {
// do something with the file...
System.out.println("Indexing File : " + file.getAbsolutePath() + " " + file.getName());
};
}
I guess this is what you are trying to do:
You have a set of dirs:
dir1
dir2
dir3
And you need to place a "watch" on these 3 directories for a specific file name pattern.
Example: If a new file is added with name: watchme_9192.log, then you java logic should kick in and process that file.
So, based on that assumption, you can try: jnotify
JNotify is a java library that allow java application to listen to
file system events, such as:
File created
File modified
File renamed
File deleted
Also, related: best practice for directory polling

Categories

Resources