Skip to content

Instantly share code, notes, and snippets.

@tuner
Created August 26, 2016 06:50
Show Gist options
  • Save tuner/1924b56675157a42a259827e97457ce6 to your computer and use it in GitHub Desktop.
Save tuner/1924b56675157a42a259827e97457ce6 to your computer and use it in GitHub Desktop.
A FaceletCache that expires facelets by using a FileWatcher
package fi.bdb.cms.utils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.faces.view.facelets.Facelet;
import javax.faces.view.facelets.FaceletCache;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
/**
* @author Kari Lavikka
*/
@ApplicationScoped
public class CmsFaceletCache extends FaceletCache<Facelet> {
final Logger logger = LoggerFactory.getLogger(CmsFaceletCache.class);
private LoadingCache<URL, Facelet> faceletCache;
private LoadingCache<URL, Facelet> viewMetadataCache;
private ConcurrentMap<URL, Facelet> faceletCacheMap;
private ConcurrentMap<URL, Facelet> viewMetadataCacheMap;
private final WatchService watcher;
private final Map<Path, WatchKey> watchedDirs = new HashMap<>();
public CmsFaceletCache() {
WatchService watchService;
try {
watchService = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
watchService = null;
logger.warn("Can't create WatchService");
}
this.watcher = watchService;
faceletCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<URL, Facelet>() {
public Facelet load(URL url) throws IOException {
logger.debug("Loading to faceletCache: {}", url);
Facelet facelet = getMemberFactory().newInstance(url);
registerToWatcher(url);
return facelet;
}
});
faceletCacheMap = faceletCache.asMap();
viewMetadataCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build(
new CacheLoader<URL, Facelet>() {
public Facelet load(URL url) throws IOException {
logger.debug("Loading to viewMetadataCache: {}", url);
return getMetadataMemberFactory().newInstance(url);
}
});
viewMetadataCacheMap = viewMetadataCache.asMap();
}
private Thread watcherThread = new Thread() {
@Override
public void run() {
logger.info("Watcher thread started");
while (!isInterrupted()) {
WatchKey key;
try {
key = watcher.take();
} catch (InterruptedException x) {
break;
}
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
if (kind == StandardWatchEventKinds.OVERFLOW) {
continue;
}
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path filename = ev.context();
// Verify that the new
// file is a text file.
try {
// Resolve the filename against the directory.
// If the filename is "test" and the directory is "foo",
// the resolved name is "test/foo".
Path child = ((Path)key.watchable()).resolve(filename);
invalidate(child.toUri().toURL());
} catch (IOException e) {
logger.warn("Exception", e);
}
}
// Reset the key -- this step is critical if you want to
// receive further watch events. If the key is no longer valid,
// the directory is inaccessible so exit the loop.
boolean valid = key.reset();
/*
if (!valid) {
break;
}
*/
}
logger.info("Watcher thread stopped");
}
};
private void registerToWatcher(URL url) {
if (watcher == null || !"file".equals(url.getProtocol())) {
return;
}
Path path;
try {
path = Paths.get(url.toURI()).getParent();
} catch (URISyntaxException e) {
logger.warn("Can't register to watcher", e);
return;
}
try {
synchronized (watchedDirs) {
if (!watchedDirs.containsKey(path)) {
WatchKey watchKey = path.register(watcher, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
watchedDirs.put(path, watchKey);
logger.info("Registered {} to watcher", path);
}
}
} catch (Exception e) {
logger.warn("Can't register path {} to watcher!", path, e);
}
}
@PostConstruct
private void init() {
logger.info("init()");
watcherThread.start();
}
@PreDestroy
private void destroy() {
logger.info("destroy()");
watcherThread.interrupt();
if (watcher != null) {
try {
watcher.close();
} catch (IOException e) {
logger.warn("Can't close watcher!", e);
}
}
}
public void invalidate(URL url) {
logger.info("Invalidating: {}", url);
faceletCache.invalidate(url);
viewMetadataCache.invalidate(url);
}
@Override
public Facelet getFacelet(URL url) throws IOException {
logger.trace("getFacelet({})", url);
try {
return faceletCache.get(url);
} catch (ExecutionException e) {
throw (IOException)e.getCause();
}
}
@Override
public boolean isFaceletCached(URL url) {
return faceletCacheMap.containsKey(url);
}
@Override
public Facelet getViewMetadataFacelet(URL url) throws IOException {
logger.trace("getViewMetadataFacelet({})", url);
try {
return viewMetadataCache.get(url);
} catch (ExecutionException e) {
throw (IOException)e.getCause();
}
}
@Override
public boolean isViewMetadataFaceletCached(URL url) {
return viewMetadataCacheMap.containsKey(url);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment