
io.github.jhipster.loaded.JHipsterFileSystemWatcher Maven / Gradle / Ivy
The newest version!
package io.github.jhipster.loaded;
import io.github.jhipster.loaded.listener.filewatcher.FileWatcherListener;
import io.github.jhipster.loaded.listener.filewatcher.NewClassLoaderListener;
import com.sun.nio.file.SensitivityWatchEventModifier;
import org.apache.commons.io.filefilter.SuffixFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
/**
* A watcher for the target class folder.
*
* The watcher will monitor all folders and sub-folders to check if a new class
* is created. If so, the new class will be loaded and managed by Spring-Loaded.
*/
public class JHipsterFileSystemWatcher implements FileSystemWatcher, Runnable {
private static Logger log = LoggerFactory.getLogger(JHipsterFileSystemWatcher.class);
private static boolean isStarted;
private final WatchService watcher;
private final Map keys = new HashMap<>();
private final List fileWatcherListeners = new ArrayList<>();
private final List watchFolders;
private final ConfigurableApplicationContext ctx;
private final ClassLoader classLoader;
public JHipsterFileSystemWatcher(List watchFolders, ConfigurableApplicationContext ctx, ClassLoader classLoader) throws Exception {
this.watchFolders = watchFolders;
this.ctx = ctx;
this.classLoader = classLoader;
watcher = FileSystems.getDefault().newWatchService();
// Register all folders
for (String watchFolder : watchFolders) {
final Path classesFolderPath = FileSystems.getDefault().getPath(watchFolder);
watchDirectory(classesFolderPath);
}
registerFileWatcherListeners();
isStarted = true;
}
/**
* Register the classLoader and start a thread that will be used to monitor folders where classes can be created.
*
* @param classLoader the classLoader of the application
* @param ctx the spring application context
*/
public static void register(ClassLoader classLoader, ConfigurableApplicationContext ctx) {
try {
Environment env = ctx.getEnvironment();
// Load from env the list of folders to watch
List watchFolders = getWatchFolders(env);
if (watchFolders.size() == 0) {
log.warn("SpringLoaded - No watched folders have been defined in the application-{profile}.yml. " +
"We will use the default target/classes");
watchFolders.add("target/classes");
}
final Thread thread = new Thread(new JHipsterFileSystemWatcher(watchFolders, ctx, classLoader));
thread.setDaemon(true);
thread.start();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
JHipsterFileSystemWatcher.isStarted = false;
try {
thread.join();
} catch (InterruptedException e) {
log.error("Failed during the JVM shutdown", e);
}
}
});
} catch (Exception e) {
log.error("Failed to start the watcher. New class will not be loaded.", e);
}
}
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public ConfigurableApplicationContext getConfigurableApplicationContext() {
return ctx;
}
@Override
public List getWatchFolders() {
return watchFolders;
}
/**
* Register the given directory, and all its sub-directories, with the
* WatchService.
*/
public void watchDirectory(final Path start) {
// register directory and sub-directories
try {
Files.walkFileTree(start, new SimpleFileVisitor() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
register(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
log.error("Failed to register the directory '{}'", start);
}
}
/**
* Register the given directory with the WatchService.
*/
private void register(Path dir) throws IOException {
WatchKey key = dir.register(watcher, new WatchEvent.Kind[]{ENTRY_CREATE, ENTRY_MODIFY}, SensitivityWatchEventModifier.HIGH);
Path prev = keys.get(key);
if (prev == null) {
log.debug("Directory : '{}' will be monitored for changes", dir);
}
keys.put(key, dir);
}
/**
* Process all events for keys queued to the watcher.
*
* When the event is a ENTRY_CREATE or ENTRY_MODIFY, the folders will be added to the watcher,
* the classes will be loaded by SpringLoaded
*/
public void run() {
while (isStarted) {
// wait for key to be signalled
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();
// Context for directory entry event is the file name of entry
// noinspection unchecked
WatchEvent ev = (WatchEvent) event;
Path name = ev.context();
Path child = dir.resolve(name);
// if directory is created, and watching recursively, then
// register it and its sub-directories
if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
watchDirectory(child);
// load the classes that have been copied
final File[] classes = child.toFile().listFiles((FileFilter) new SuffixFileFilter(".class"));
for (File aFile : classes) {
final String parentFolder = aFile.getParent();
callFileWatcherListerners(parentFolder, aFile.toPath(), kind);
}
} else {
callFileWatcherListerners(dir.toString().replace(File.separator,"/"), child, kind);
}
}
// reset key and remove from set if directory no longer accessible
boolean valid = key.reset();
if (!valid) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty()) {
break;
}
}
}
}
/**
* The definition of the folders to watch must be defined in the application-dev.yml as follow
* hotReload:
* enabled: true
* watchdir:
* - /Users/jhipster/demo-jhipster/target/classes
* - /Users/jhipster/demo-jhipster/target/classes1
* @param env the environment used to retrieve the list of folders
* @return the list of folders
*/
private static List getWatchFolders(Environment env) {
List results = new ArrayList<>();
int i=0;
String folder = env.getProperty("hotReload.watchdir[" + i + "]");
while(folder != null) {
results.add(folder);
i++;
folder = env.getProperty("hotReload.watchdir[" + i + "]");
}
return results;
}
/**
* Register the list of listeners
*/
private void registerFileWatcherListeners() {
fileWatcherListeners.add(new NewClassLoaderListener());
for (FileWatcherListener fileWatcherListener : fileWatcherListeners) {
fileWatcherListener.setFileSystemWatcher(this);
}
}
/**
* Call all listeners on changed file
*/
private void callFileWatcherListerners(String parentFolder, Path child, WatchEvent.Kind kind) {
for (FileWatcherListener fileWatcherListener : fileWatcherListeners) {
if (fileWatcherListener.support(child, kind)) {
fileWatcherListener.onChange(parentFolder, child, kind);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy