package io.undertow.server.handlers.resource;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import io.undertow.UndertowLogger;
import io.undertow.server.handlers.cache.DirectBufferCache;
import io.undertow.server.handlers.cache.LRUCache;
public class CachingResourceManager implements ResourceManager {
private final long maxFileSize;
private final ResourceManager underlyingResourceManager;
private final DirectBufferCache dataCache;
private final LRUCache<String, Object> cache;
private final int maxAge;
public CachingResourceManager(final int metadataCacheSize, final long maxFileSize, final DirectBufferCache dataCache, final ResourceManager underlyingResourceManager, final int maxAge) {
this.maxFileSize = maxFileSize;
this.underlyingResourceManager = underlyingResourceManager;
this.dataCache = dataCache;
this.cache = new LRUCache<>(metadataCacheSize, maxAge);
this.maxAge = maxAge;
if(underlyingResourceManager.isResourceChangeListenerSupported()) {
try {
underlyingResourceManager.registerResourceChangeListener(new ResourceChangeListener() {
@Override
public void handleChanges(Collection<ResourceChangeEvent> changes) {
for(ResourceChangeEvent change : changes) {
invalidate(change.getResource());
}
}
});
} catch (Exception e) {
UndertowLogger.ROOT_LOGGER.couldNotRegisterChangeListener(e);
}
}
}
@Override
public CachedResource getResource(final String p) throws IOException {
final String path;
if (p.startsWith("/")) {
path = p.substring(1);
} else {
path = p;
}
Object res = cache.get(path);
if (res instanceof NoResourceMarker) {
NoResourceMarker marker = (NoResourceMarker) res;
long nextCheck = marker.getNextCheckTime();
if(nextCheck > 0) {
long time = System.currentTimeMillis();
if(time > nextCheck) {
marker.setNextCheckTime(time + maxAge);
if(underlyingResourceManager.getResource(path) != null) {
cache.remove(path);
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
} else if (res != null) {
CachedResource resource = (CachedResource) res;
if (resource.checkStillValid()) {
return resource;
} else {
invalidate(path);
}
}
final Resource underlying = underlyingResourceManager.getResource(path);
if (underlying == null) {
cache.add(path, new NoResourceMarker(maxAge > 0 ? System.currentTimeMillis() + maxAge : -1));
return null;
}
final CachedResource resource = new CachedResource(this, underlying, path);
cache.add(path, resource);
return resource;
}
@Override
public boolean isResourceChangeListenerSupported() {
return underlyingResourceManager.isResourceChangeListenerSupported();
}
@Override
public void registerResourceChangeListener(ResourceChangeListener listener) {
underlyingResourceManager.registerResourceChangeListener(listener);
}
@Override
public void removeResourceChangeListener(ResourceChangeListener listener) {
underlyingResourceManager.removeResourceChangeListener(listener);
}
public void invalidate(String path) {
if(path.startsWith("/")) {
path = path.substring(1);
}
Object entry = cache.remove(path);
if (entry instanceof CachedResource) {
((CachedResource) entry).invalidate();
}
}
DirectBufferCache getDataCache() {
return dataCache;
}
public long getMaxFileSize() {
return maxFileSize;
}
public int getMaxAge() {
return maxAge;
}
@Override
public void close() throws IOException {
try {
if(dataCache != null) {
Set<Object> keys = dataCache.getAllKeys();
for(final Object key : keys) {
if(key instanceof CachedResource.CacheKey) {
if(((CachedResource.CacheKey) key).manager == this) {
dataCache.remove(key);
}
}
}
}
} finally {
underlyingResourceManager.close();
}
}
private static final class NoResourceMarker {
volatile long nextCheckTime;
private NoResourceMarker(long nextCheckTime) {
this.nextCheckTime = nextCheckTime;
}
public long getNextCheckTime() {
return nextCheckTime;
}
public void setNextCheckTime(long nextCheckTime) {
this.nextCheckTime = nextCheckTime;
}
}
}