/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.dbpersistence.service;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
/**
* A Step-Behind Cache is always "one step behind"
* the ground truth - when a read is requested, it returns
* the last version of the value it read immediately,
* then asynchronously looks up the next version of the
* value.
*
* This is useful when:
* - You want up-to-date values to be recognized at
* some point, but not within any fixed time period.
* - You must get a useful return value quickly, if
* possible.
* - You do not want to spend time polling in the
* background.
*
* In the special case where there is no prior
* lookup to fall back on, the Step-Behind Cache
* will trigger an explicit lookup (which may
* take a while)
*
* @author vwoeltje
*
*/
public class StepBehindCache<T> {
private static final int THREAD_POOL_SIZE = 4;
private AtomicReference<T> cache =
new AtomicReference<T>(null);
private static final ExecutorService THREAD_POOL =
Executors.newFixedThreadPool(THREAD_POOL_SIZE);
private Lookup<T> lookup;
private long lastLookup = Long.MIN_VALUE;
private long period = 1000L;
/**
* Create a new step-behind cache wrapping the
* specified lookup procedure, not to be executed
* more often than the specified interval.
* @param lookup the actual lookup procedure
* @param delay the delay, in milliseconds, between lookups
*/
public StepBehindCache (Lookup<T> lookup, long delay) {
this.lookup = lookup;
this.period = delay;
}
/**
* Create a new step-behind cache wrapping the
* specified lookup procedure.
* @param lookup the actual lookup procedure
*/
public StepBehindCache (Lookup<T> lookup) {
this.lookup = lookup;
}
/**
* Get the currently cached value, and trigger a lookup
* for the next value on a background thread, assuming
* the current value is sufficiently out-dated (default
* delay between lookups is one second, but this can be
* overriden in the constructor.)
*
* The first call to this method will invoke the actual
* lookup immediately, so it is not quite always expected
* to return quickly.
*
* @return the last value looked up
*/
public T get() {
T cached = cache.get();
if (cached == null) {
cached = lookup.lookup();
cache.set(cached);
} else {
long now = System.currentTimeMillis();
if (lastLookup <= now - period) {
lastLookup = now;
backgroundLookup();
}
}
return cached;
}
private void backgroundLookup() {
THREAD_POOL.submit(new Runnable() {
public void run() {
cache.set(lookup.lookup());
}
});
}
/**
* Refresh the cache. This clears the cache and
* initiates a new lookup in the background. If
* this lookup has not completed by the time the
* next get() is called, that get() will issue a
* new lookup and block until it has completed.
*/
public void refresh() {
cache.set(null);
backgroundLookup();
}
/**
* Describes a method for looking up a value. Typically
* this lookup is not timely to perform (some delay
* before the response is anticipated.)
*
* @param <S> the type of value to be looked up
*/
public static interface Lookup<S> {
public S lookup();
}
}