/** * Copyright 2011 meltmedia * * 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. */ package org.xchain.framework.strategy; import java.net.URL; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xchain.framework.net.DependencyTracker; import org.xchain.framework.net.UrlFactory; import org.xchain.framework.net.UrlUtil; import org.xchain.framework.util.LruCacheMap; import org.xchain.framework.lifecycle.Lifecycle; /** * A LoadStrategy implementation that performs caching. * * @param <T> The class being cached. * @param <S> The source type for the objects. * * @author Devon Tackett * @author Christian Trimble * @author Mike Moulton * @author Josh Kennedy */ public class CachingLoadStrategy<T, S> implements LoadStrategy<T, S> { public static Logger log = LoggerFactory.getLogger( CachingLoadStrategy.class ); private LruCacheMap<String, CachedObject<T>> objectCache; /** * @param size The maximum size for the LruMap. */ public CachingLoadStrategy(int size) { super(); this.objectCache = new LruCacheMap<String, CachedObject<T>>(size); } public T getObject(String systemId, SourceStrategy<S> sourceStrategy, ConsumerStrategy<T, S> consumerStrategy) throws Exception { CachedObject<T> cachedObject = null; // Synchronize on the objectCache to only allow one lookup at a time. synchronized(objectCache) { cachedObject = objectCache.get(systemId); if (cachedObject == null) { // The request object was not found in the cache. // Create a new entry for the cache. cachedObject = new CachedObject<T>(); cachedObject.setSystemId(systemId); objectCache.put(systemId, cachedObject); } } // Synchronize on the cachedObject to only allow one loading of the requested object. synchronized(cachedObject) { if (cachedObject.getObject() == null || isStale(cachedObject)) { // Either the object could not be found or it should be reloaded loadObject(cachedObject, sourceStrategy, consumerStrategy); } } // Return the loaded object. return cachedObject.getObject(); } /** * Check if the given CachedObject should be reloaded. * * @param cachedObject The cachedObject entry to check. * * @return Whether the object needs to be reloaded. */ protected boolean isStale(CachedObject<T> cachedObject) throws Exception { if ( Lifecycle.getLifecycleContext().getConfigContext().isMonitored() ) { // get an instance of the url util. UrlUtil urlUtil = UrlUtil.getInstance(); // get the url for the system id. URL url = UrlFactory.getInstance().newUrl(cachedObject.getSystemId()); return urlUtil.lastModifiedAfter( url, cachedObject.getLastModified() ) || urlUtil.lastModifiedAfter( cachedObject.getDependencySet(), cachedObject.getLastModified() ); } else { // Changes are not being monitored, always return false. return false; } } /** * Load the object from the using the given source strategy and consumer strategy. * * @param cachedObject The cached object representation to be loaded. * @param sourceStrategy The strategy to determine the source of the object. * @param consumerStrategy The strategy to transform the source data into a proper object. */ private void loadObject(CachedObject<T> cachedObject, SourceStrategy<S> sourceStrategy, ConsumerStrategy<T, S> consumerStrategy) throws Exception { DependencyTracker tracker = DependencyTracker.getInstance(); long lastModified = System.currentTimeMillis(); // start tracking. Set<URL> dependencySet = null; T object = null; tracker.startTracking(); try { object = consumerStrategy.consume(cachedObject.getSystemId(), sourceStrategy, tracker); if( log.isDebugEnabled() ) { log.debug("The catalog for system id '" + cachedObject.getSystemId() + "' is "+(object==null?"null":"not null")+"."); } } finally { dependencySet = tracker.stopTracking(); } cachedObject.setObject(object); cachedObject.setDependencySet(dependencySet); cachedObject.setLastModified(lastModified); } /** * This keeps track of when the object was last modified, the system id of the object and the dependency set * for the object. * * @param <OBJ> The type of object to cache. */ private class CachedObject<OBJ> { // Timestamp of when the object was last modified. private long lastModified = 0; // The system identifier for the object. private String systemId = null; // A set of dependencies for the object. private Set<URL> dependencySet = null; // The object itself. private OBJ object; public long getLastModified() { return lastModified; } public void setLastModified(long lastModified) { this.lastModified = lastModified; } public String getSystemId() { return systemId; } public void setSystemId(String systemId) { this.systemId = systemId; } public Set<URL> getDependencySet() { return dependencySet; } public void setDependencySet(Set<URL> dependencySet) { this.dependencySet = dependencySet; } public OBJ getObject() { return object; } public void setObject(OBJ object) { this.object = object; } } }