001: /* ResourceCache.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Fri Jun 3 08:59:12 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.util.resource;
020:
021: import org.zkoss.lang.D;
022: import org.zkoss.lang.PotentialDeadLockException;
023: import org.zkoss.lang.SystemException;
024: import org.zkoss.util.CacheMap;
025: import org.zkoss.util.logging.Log;
026: import org.zkoss.util.WaitLock;
027:
028: /**
029: * Used to cache resouces.
030: * To use this class, you have to implement {@link Loader} and then
031: * ResourceCache will use it to check whether a resource is gone,
032: * modified and load the resource.
033: *
034: * <p>Unlike {@link CacheMap}, it is thread-safe.
035: *
036: * <p>The default check period depends on the system propety called
037: * org.zkoss.util.resource.checkPeriod (unit: second).
038: * If not specified, 5 seconds are assumed
039: *
040: * @author tomyeh
041: */
042: public class ResourceCache extends CacheMap {
043: private static final Log log = Log.lookup(ResourceCache.class);
044:
045: /** The loader. */
046: protected final Loader _loader;
047: /** unit=milliseconds. */
048: private int _checkPeriod;
049:
050: /** Constructor.
051: * @param loader the loader to load resource
052: */
053: public ResourceCache(Loader loader) {
054: if (loader == null)
055: throw new NullPointerException();
056: _loader = loader;
057: _checkPeriod = getInitCheckPeriod();
058: }
059:
060: /** Constructor.
061: * @param loader the loader to load resource
062: * @param initsz the initial size of the map
063: */
064: public ResourceCache(Loader loader, int initsz) {
065: super (initsz);
066: if (loader == null)
067: throw new NullPointerException();
068: _loader = loader;
069: _checkPeriod = getInitCheckPeriod();
070: }
071:
072: private static int getInitCheckPeriod() {
073: final String ATTR = "org.zkoss.util.resource.checkPeriod";
074: try {
075: final Integer v = Integer.getInteger(ATTR);
076: if (v != null) {
077: final int i = v.intValue();
078: if (i > 0)
079: return i * 1000;
080: }
081: } catch (Throwable t) {
082: log.warning("Failed to parse " + System.getProperty(ATTR));
083: }
084: return 5000; //5 secs
085: }
086:
087: /** Returns the loader.
088: */
089: public Loader getLoader() {
090: return _loader;
091: }
092:
093: /** Returns how often to check (unit=milliseconds).
094: * <p>Default: 300000
095: */
096: public int getCheckPeriod() {
097: return _checkPeriod;
098: }
099:
100: /** Sets how often to check (unit=milliseconds).
101: * @return this object
102: */
103: public ResourceCache setCheckPeriod(int checkPeriod) {
104: _checkPeriod = checkPeriod;
105: return this ;
106: }
107:
108: //-- Map --//
109: /** Returns the resource, or null if not found.
110: */
111: public Object get(Object src) {
112: WaitLock lock = null;
113: for (;;) {
114: Info ri = null;
115: synchronized (this ) {
116: Object o = super .get(src);
117: if (o instanceof Info) { //was loaded
118: ri = (Info) o;
119: } else if (o instanceof WaitLock) {
120: lock = (WaitLock) o;
121: } else {
122: super .put(src, lock = new WaitLock());
123: break; //then, load it
124: }
125: } //sync(this)
126:
127: //check whether cached is valid
128: if (ri != null) {
129: synchronized (ri) {
130: if (ri.isValid())
131: return ri.getResource(); //reuse cached
132: }
133: //invalid, so remove it (if not updated by others)
134: synchronized (this ) {
135: if (super .get(src) == ri)
136: super .remove(src);
137: }
138: } else if (!lock.waitUntilUnlock(300 * 1000)) { //5 minute
139: final PotentialDeadLockException ex = new PotentialDeadLockException(
140: "Unable to load from " + src
141: + "\nCause: conflict too long.");
142: log.warningBriefly(ex); //very rare, possibly a bug
143: throw ex;
144: }
145: } //for (;;)
146:
147: //load it
148: try {
149: final Info ri = new Info(src);
150: final Object resource = ri.getResource();
151: synchronized (this ) {
152: if (resource != null) {
153: super .put(src, ri);
154: } else {
155: super .remove(src); //remove lock
156: }
157: }
158:
159: return resource;
160: } catch (Throwable ex) {
161: synchronized (this ) {
162: super .remove(src); //remove lock
163: }
164: throw SystemException.Aide.wrap(ex);
165: } finally {
166: lock.unlock();
167: }
168: }
169:
170: /** Don't use it.
171: */
172: public Object put(Object src, Object val) {
173: throw new UnsupportedOperationException("Used only internally");
174: }
175:
176: /** It is OK to remove the resource if you don't want to cache it.
177: * It is thread safe.
178: */
179: public Object remove(Object src) {
180: synchronized (this ) {
181: return super .remove(src);
182: }
183: }
184:
185: /** It is OK to clear up all cached resources if you don't want to cache it.
186: * It is thread safe.
187: */
188: public void clear() {
189: synchronized (this ) {
190: super .clear();
191: }
192: }
193:
194: //-- private --//
195: /** Providing info about a resource. */
196: private class Info {
197: /** The source. */
198: private final Object _src;
199: /** The result resource. */
200: private Object _resource;
201: private long _lastModified;
202: /* When to check lastModified again. */
203: private long _nextCheck;
204:
205: /**
206: * @param src the source
207: */
208: public Info(Object src) throws Exception {
209: //if (D.ON && log.debugable()) log.debug("Loading from "+src);
210: _src = src;
211: load();
212: }
213:
214: /** Returns the result resource.
215: */
216: public final Object getResource() {
217: return _resource;
218: }
219:
220: /** Quick check whether the page is still valid. */
221: public boolean isValid() {
222: final long now = System.currentTimeMillis();
223: if (!_loader.shallCheck(_src, now - _nextCheck))
224: return true;
225:
226: final long lastmod = _loader.getLastModified(_src);
227: if (lastmod == -1) //removed or not support last-modified
228: return false; //reload is required
229:
230: final boolean valid = lastmod == _lastModified;
231: if (!valid)
232: log.info("Source is changed: " + _src);
233: //else if (D.ON && log.finerable())
234: // log.finer("Source not changed: "+_src);
235: return valid;
236: }
237:
238: /** Loads the file. */
239: protected void load() throws Exception {
240: _resource = _loader.load(_src);
241: if (_resource != null) {
242: _lastModified = _loader.getLastModified(_src);
243: _nextCheck = System.currentTimeMillis() + _checkPeriod;
244: }
245: }
246: }
247: }
|