org.eclipse.jetty.http.content.ValidatingCachingHttpContentFactory Maven / Gradle / Ivy
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.http.content;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.thread.Scheduler;
/**
*
* {@link HttpContent.Factory} implementation of {@link CachingHttpContentFactory} which evicts invalid entries from the cache.
* Uses a validationTime parameter to check files when they are accessed and/or sweepDelay parameter for a periodic sweep
* for invalid entries in the cache.
*
*
* {@link HttpContent} validation checks are configured through the {@code validationTime} parameter in the constructor.
* If an {@link HttpContent} is found to be invalid it will be removed from the cache.
* A value of -1 means that the cached {@link HttpContent} will never be checked if it is still valid,
* a value of 0 means it is checked on every request,
* a positive value indicates the number of milliseconds of the minimum time between validation checks.
*
*
* This also remember a missed entry for the time set by {@code validationTime}ms. After this has
* elapsed the entry will be invalid and will be evicted from the cache at the next access.
*
*/
public class ValidatingCachingHttpContentFactory extends CachingHttpContentFactory implements Runnable
{
private final Scheduler _scheduler;
private final long _sweepDelay;
private final long _validationTime;
private final long _maxCacheIdleTime;
/**
* Construct a {@link ValidatingCachingHttpContentFactory} which validates entries upon use to check if they
* are still valid.
*
* @param authority the wrapped {@link HttpContent.Factory} to use.
* @param validationPeriod time between filesystem checks in ms to see if an {@link HttpContent} is still valid (-1 never validate, 0 always validate).
* @param bufferPool the {@link org.eclipse.jetty.io.ByteBufferPool} to use.
*/
public ValidatingCachingHttpContentFactory(@Name("authority") HttpContent.Factory authority,
@Name("validationPeriod") long validationPeriod,
@Name("bufferPool") ByteBufferPool bufferPool)
{
this(authority, validationPeriod, bufferPool, null, -1, -1);
}
/**
* Construct a {@link ValidatingCachingHttpContentFactory} which validates entries upon use to check if they
* are still valid and an optional period sweeper of the cache to find invalid and old entries to evict.
*
* @param authority the wrapped {@link HttpContent.Factory} to use.
* @param validationPeriod time between filesystem checks in ms to see if an {@link HttpContent} is still valid (-1 never validate, 0 always validate).
* @param bufferPool the {@link org.eclipse.jetty.io.ByteBufferPool} to use.
* @param scheduler scheduler to use for the sweeper, can be null to not use sweeper.
* @param sweepPeriod time between runs of the sweeper in ms (if 0 never sweep for invalid entries).
* @param idleTimeout amount of time in ms an entry can be unused before evicted by the sweeper (if 0 never evict unused entries).
*/
public ValidatingCachingHttpContentFactory(@Name("authority") HttpContent.Factory authority,
@Name("validationPeriod") long validationPeriod,
@Name("byteBufferPool") ByteBufferPool bufferPool,
@Name("scheduler") Scheduler scheduler,
@Name("sweepPeriod") long sweepPeriod,
@Name("idleTimeout") long idleTimeout)
{
super(authority, bufferPool);
_validationTime = validationPeriod;
_scheduler = scheduler;
_sweepDelay = sweepPeriod;
_maxCacheIdleTime = idleTimeout;
if (scheduler != null && sweepPeriod > 0)
schedule();
}
@Override
protected boolean isCacheable(HttpContent httpContent)
{
if (httpContent == null)
return (_validationTime != 0);
return super.isCacheable(httpContent);
}
private void schedule()
{
_scheduler.schedule(this, _sweepDelay, TimeUnit.MILLISECONDS);
}
@Override
public void run()
{
try
{
ConcurrentMap cache = getCache();
for (Map.Entry entry : cache.entrySet())
{
CachingHttpContent value = entry.getValue();
if (_maxCacheIdleTime > 0 && NanoTime.since(value.getLastAccessedNanos()) > TimeUnit.MILLISECONDS.toNanos(_maxCacheIdleTime))
removeFromCache(value);
else if (!value.isValid())
removeFromCache(value);
}
}
finally
{
schedule();
}
}
@Override
protected CachingHttpContent newCachedContent(String p, HttpContent httpContent)
{
return new ValidatingCachedContent(p, httpContent, _validationTime);
}
@Override
protected CachingHttpContent newNotFoundContent(String p)
{
return new ValidatingNotFoundContent(p, _validationTime);
}
protected class ValidatingCachedContent extends CachedHttpContent
{
private final long _validationTime;
private final AtomicLong _lastValidated = new AtomicLong();
public ValidatingCachedContent(String key, HttpContent httpContent, long validationTime)
{
super(key, httpContent);
_lastValidated.set(NanoTime.now());
_validationTime = validationTime;
}
@Override
public boolean isValid()
{
if (_validationTime < 0)
{
return true;
}
else if (_validationTime > 0)
{
long now = NanoTime.now();
if (_lastValidated.updateAndGet(lastChecked ->
(NanoTime.elapsed(lastChecked, now) > TimeUnit.MILLISECONDS.toNanos(_validationTime)) ? now : lastChecked) != now)
return true;
}
return Objects.equals(getLastModifiedInstant(), getWrapped().getLastModifiedInstant());
}
}
protected static class ValidatingNotFoundContent extends NotFoundHttpContent
{
private final long _validationTime;
private final AtomicLong _lastValidated = new AtomicLong();
public ValidatingNotFoundContent(String key, long validationTime)
{
super(key);
_validationTime = validationTime;
_lastValidated.set(NanoTime.now());
}
@Override
public boolean isValid()
{
if (_validationTime < 0)
return true;
if (_validationTime > 0)
return NanoTime.since(_lastValidated.get()) < TimeUnit.MILLISECONDS.toNanos(_validationTime);
return false;
}
}
}