/**
* Copyright (C) 2012-2014 the original author or authors.
*
* Licensed 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 ninja;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
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.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
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.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.nio.file.SensitivityWatchEventModifier;
public class WatchAndRestartMachine {
List<String> exludePatterns;
private static Logger logger = LoggerFactory.getLogger(WatchAndRestartMachine.class);
RunClassInSeparateJvmMachine ninjaJettyInsideSeparateJvm;
WatchService watchService;
DelayedRestartTrigger restartAfterSomeTimeAndChanges;
private final Map<WatchKey, Path> mapOfWatchKeysToPaths;
/**
* Creates a WatchService and registers the given
pathectoryToWatchRecursivelyForChangesectory
*/
public WatchAndRestartMachine(
String classNameWithMainToRun,
Path directoryToWatchRecursivelyForChanges,
List<String> classpath,
List<String> excludeRegexPatterns,
String contextPath,
String port) throws IOException {
this.exludePatterns = excludeRegexPatterns;
this.ninjaJettyInsideSeparateJvm = new RunClassInSeparateJvmMachine(
classNameWithMainToRun, classpath, contextPath, port);
this.restartAfterSomeTimeAndChanges
= new DelayedRestartTrigger(
ninjaJettyInsideSeparateJvm);
this.restartAfterSomeTimeAndChanges.start();
this.watchService = FileSystems.getDefault().newWatchService();
this.mapOfWatchKeysToPaths = new HashMap<WatchKey, Path>();
//System.out.format("Scanning: %s ...\n", directoryToWatchRecursivelyForChanges);
registerAll(directoryToWatchRecursivelyForChanges);
}
/**
* Register the given path with the WatchService
*/
private void register(Path path) throws IOException {
////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//// USUALLY THIS IS THE DEFAULT WAY TO REGISTER THE EVENTS:
////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// WatchKey watchKey = path.register(
// watchService,
// ENTRY_CREATE,
// ENTRY_DELETE,
// ENTRY_MODIFY);
////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//// BUT THIS IS DAMN SLOW (at least on a Mac)
//// THEREFORE WE USE EVENTS FROM COM.SUN PACKAGES THAT ARE WAY FASTER
//// THIS MIGHT BREAK COMPATIBILITY WITH OTHER JDKs
//// MORE: http://stackoverflow.com/questions/9588737/is-java-7-watchservice-slow-for-anyone-else
////!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
WatchKey watchKey = path.register(
watchService,
new WatchEvent.Kind[]{
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE
},
SensitivityWatchEventModifier.HIGH);
mapOfWatchKeysToPaths.put(watchKey, path);
}
private void registerAll(final Path path) throws IOException {
// register directory and sub-directories
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path path,
BasicFileAttributes attrs)
throws IOException {
register(path);
return FileVisitResult.CONTINUE;
}
});
}
public void startWatching() {
for (;;) {
WatchKey watchKey;
try {
watchKey = watchService.take();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
return;
}
Path path = mapOfWatchKeysToPaths.get(watchKey);
if (path == null) {
System.err.println("WatchKey not recognized!!");
continue;
}
for (WatchEvent<?> watchEvent : watchKey.pollEvents()) {
WatchEvent.Kind watchEventKind = watchEvent.kind();
// TBD - provide example of how OVERFLOW watchEvent is handled
if (watchEventKind == OVERFLOW) {
continue;
}
// Context for directory entry watchEvent is the file name of entry
WatchEvent<Path> ev = (WatchEvent<Path>) watchEvent;
Path name = ev.context();
Path child = path.resolve(name);
// print out watchEvent
//System.out.format("%s: %s\n", watchEvent.kind().name(), child);
if (watchEventKind == ENTRY_MODIFY) {
// we are not interested in events from parent directories...
if (! child.toFile().isDirectory()) {
if (!checkIfMatchesPattern(exludePatterns, child.toFile().getAbsolutePath())) {
System.out.println(
"Found file modification - triggering reload: "
+ child.toFile().getAbsolutePath());
restartAfterSomeTimeAndChanges.triggerRestart();
}
}
}
// ninjaJettyInsideSeparateJvm.restartNinjaJetty();
// if directory is created, then
// register it and its sub-directories recursively
if (watchEventKind == ENTRY_CREATE) {
restartAfterSomeTimeAndChanges.triggerRestart();
try {
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
registerAll(child);
}
} catch (IOException ioException) {
ioException.printStackTrace();
// ignore to keep sample readable
}
}
}
// reset watchKey and remove from set if directory no longer accessible
boolean valid = watchKey.reset();
if (!valid) {
mapOfWatchKeysToPaths.remove(watchKey);
// all directories are inaccessible
if (mapOfWatchKeysToPaths.isEmpty()) {
break;
}
}
}
}
public static boolean checkIfMatchesPattern(List<String> regexPatterns, String string) {
for (String regex : regexPatterns) {
if (string.matches(regex)) {
return true;
}
}
return false;
}
}