001: /*
002: * Created on 16-Mar-2006
003: */
004: package uk.org.ponder.springutil;
005:
006: import java.io.ByteArrayInputStream;
007: import java.io.File;
008: import java.io.InputStream;
009: import java.util.HashMap;
010: import java.util.Map;
011:
012: import org.springframework.core.io.ClassPathResource;
013: import org.springframework.core.io.Resource;
014: import org.springframework.core.io.ResourceLoader;
015:
016: import uk.org.ponder.fileutil.StalenessEntry;
017: import uk.org.ponder.streamutil.StreamResolver;
018: import uk.org.ponder.util.UniversalRuntimeException;
019:
020: /**
021: * A "caching" source of InputStreams that will only poll the filesystem for
022: * changes after a specified lag. Currently any non-filesystem resources are
023: * assumed to be ALWAYS STALE, that is, they will always have their streams
024: * returned rather than the marker.
025: *
026: * @author Antranig Basman (antranig@caret.cam.ac.uk)
027: */
028:
029: public class CachingInputStreamSource implements StreamResolver {
030: /**
031: * If the stream is considered up to date, either through actually being up to
032: * date or through having been polled within the last
033: * <code>cacheSeconds</code>, this marker value is returned from
034: * getInputStream. Do not attempt to use any methods of this object!
035: */
036: public static final InputStream UP_TO_DATE = new ByteArrayInputStream(
037: new byte[0]);
038:
039: private int cachesecs;
040:
041: public static final int ALWAYS_STALE = 0;
042:
043: public static final long NEVER_STALE_MODTIME = Long.MAX_VALUE;
044:
045: /**
046: * Sets the lag after which the filesystem will be checked again for change of
047: * datestamp. At a value of ALWAYS_STALE (0) the resource will always be
048: * reloaded.
049: */
050:
051: public void setCacheSeconds(int cachesecs) {
052: this .cachesecs = cachesecs;
053: }
054:
055: private ResourceLoader resourceloader;
056:
057: private StreamResolver baseresolver;
058:
059: private Map stalenesses = new HashMap();
060:
061: // The first argument here is typically the ApplicationContext - note that
062: // it will stubbornly interpret ALL paths as relative to the ServletContext,
063: // whether they begin with slash or no - @see ServletContextResource
064: public CachingInputStreamSource(ResourceLoader resourceloader,
065: int cachesecs) {
066: this .resourceloader = resourceloader;
067: this .cachesecs = cachesecs;
068: init();
069: }
070:
071: public void init() {
072: baseresolver = new SpringStreamResolver(resourceloader);
073: }
074:
075: public StreamResolver getNonCachingResolver() {
076: return baseresolver;
077: }
078:
079: public InputStream openStream(String fullpath) {
080: StalenessEntry staleness = (StalenessEntry) stalenesses
081: .get(fullpath);
082: boolean isnew = false;
083: if (staleness == null) {
084: isnew = true;
085: staleness = new StalenessEntry();
086: }
087: long now = System.currentTimeMillis();
088: boolean isstale = false;
089:
090: Resource res = null;
091: try {
092: if (staleness.modtime != NEVER_STALE_MODTIME
093: && now > staleness.lastchecked + cachesecs * 1000) {
094: res = resourceloader.getResource(fullpath);
095: if (res == null || !res.exists())
096: return null;
097: try {
098: if (res instanceof ClassPathResource) {
099: staleness.modtime = NEVER_STALE_MODTIME;
100: } else {
101: // Logger.log.debug("Trying to load from path " + fullpath);
102: File f = res.getFile(); // throws IOException
103: long modtime = f.lastModified();
104: if (modtime > staleness.modtime) {
105: staleness.modtime = modtime;
106: isstale = true;
107: }
108: }
109: if (isnew) {
110: stalenesses.put(fullpath, staleness);
111: }
112: } catch (Exception e) {
113: // If it's not a file, it's always stale.
114: return res.getInputStream();
115: }
116: }
117: staleness.lastchecked = now;
118:
119: return isstale ? res.getInputStream() : UP_TO_DATE;
120: } catch (Exception e) {
121: throw UniversalRuntimeException.accumulate(e,
122: "Error opening stream for resource " + fullpath);
123: }
124: }
125:
126: }
|