Created
August 26, 2016 06:50
-
-
Save tuner/1924b56675157a42a259827e97457ce6 to your computer and use it in GitHub Desktop.
A FaceletCache that expires facelets by using a FileWatcher
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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