001: // ========================================================================
002: // Copyright 2000-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014:
015: package org.mortbay.jetty;
016:
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.io.Serializable;
020: import java.util.HashMap;
021: import java.util.Map;
022:
023: import org.mortbay.component.AbstractLifeCycle;
024: import org.mortbay.io.Buffer;
025: import org.mortbay.io.ByteArrayBuffer;
026: import org.mortbay.io.View;
027: import org.mortbay.log.Log;
028: import org.mortbay.resource.Resource;
029: import org.mortbay.resource.ResourceFactory;
030:
031: /* ------------------------------------------------------------ */
032: /**
033: * @author Greg Wilkins
034: */
035: public class ResourceCache extends AbstractLifeCycle implements
036: Serializable {
037: private int _maxCachedFileSize = 1024 * 1024;
038: private int _maxCachedFiles = 2048;
039: private int _maxCacheSize = 16 * 1024 * 1024;
040: private MimeTypes _mimeTypes;
041:
042: protected transient Map _cache;
043: protected transient int _cachedSize;
044: protected transient int _cachedFiles;
045: protected transient Content _mostRecentlyUsed;
046: protected transient Content _leastRecentlyUsed;
047:
048: /* ------------------------------------------------------------ */
049: /** Constructor.
050: */
051: public ResourceCache(MimeTypes mimeTypes) {
052: _mimeTypes = mimeTypes;
053: }
054:
055: /* ------------------------------------------------------------ */
056: public int getCachedSize() {
057: return _cachedSize;
058: }
059:
060: /* ------------------------------------------------------------ */
061: public int getCachedFiles() {
062: return _cachedFiles;
063: }
064:
065: /* ------------------------------------------------------------ */
066: public int getMaxCachedFileSize() {
067: return _maxCachedFileSize;
068: }
069:
070: /* ------------------------------------------------------------ */
071: public void setMaxCachedFileSize(int maxCachedFileSize) {
072: _maxCachedFileSize = maxCachedFileSize;
073: flushCache();
074: }
075:
076: /* ------------------------------------------------------------ */
077: public int getMaxCacheSize() {
078: return _maxCacheSize;
079: }
080:
081: /* ------------------------------------------------------------ */
082: public void setMaxCacheSize(int maxCacheSize) {
083: _maxCacheSize = maxCacheSize;
084: flushCache();
085: }
086:
087: /* ------------------------------------------------------------ */
088: /**
089: * @return Returns the maxCachedFiles.
090: */
091: public int getMaxCachedFiles() {
092: return _maxCachedFiles;
093: }
094:
095: /* ------------------------------------------------------------ */
096: /**
097: * @param maxCachedFiles The maxCachedFiles to set.
098: */
099: public void setMaxCachedFiles(int maxCachedFiles) {
100: _maxCachedFiles = maxCachedFiles;
101: }
102:
103: /* ------------------------------------------------------------ */
104: public void flushCache() {
105: if (_cache != null) {
106: synchronized (this ) {
107: _cache.clear();
108: _cachedSize = 0;
109: _cachedFiles = 0;
110: _mostRecentlyUsed = null;
111: _leastRecentlyUsed = null;
112: }
113: }
114: System.gc();
115: }
116:
117: /* ------------------------------------------------------------ */
118: /** Get a Entry from the cache.
119: * Get either a valid entry object or create a new one if possible.
120: *
121: * @param pathInContext The key into the cache
122: * @param factory If no matching entry is found, this {@link ResourceFactory} will be used to create the {@link Resource}
123: * for the new enry that is created.
124: * @return The entry matching <code>pathInContext</code>, or a new entry if no matching entry was found
125: */
126: public Content lookup(String pathInContext, ResourceFactory factory)
127: throws IOException {
128: if (Log.isDebugEnabled())
129: Log.debug("lookup {}", pathInContext);
130:
131: Content content = null;
132:
133: // Look up cache operations
134: synchronized (_cache) {
135: // Look for it in the cache
136: content = (Content) _cache.get(pathInContext);
137:
138: if (content != null && content.isValid()) {
139: if (Log.isDebugEnabled())
140: Log.debug("CACHE HIT: {}", pathInContext);
141: return content;
142: }
143: }
144:
145: Resource resource = factory.getResource(pathInContext);
146: if (resource != null && resource.exists()
147: && !resource.isDirectory()) {
148: long len = resource.length();
149: if (len > 0 && len < _maxCachedFileSize
150: && len < _maxCacheSize) {
151: if (Log.isDebugEnabled())
152: Log.debug("CACHE MISS: {}", pathInContext);
153:
154: content = new Content(resource);
155: fill(content);
156:
157: synchronized (_cache) {
158: // check that somebody else did not fill this spot.
159: Content content2 = (Content) _cache
160: .get(pathInContext);
161: if (content2 != null) {
162: if (Log.isDebugEnabled())
163: Log.debug("FALSE HIT: {}", pathInContext);
164: content.release();
165: return content2;
166: }
167:
168: if (Log.isDebugEnabled())
169: Log.debug("CACHE FILL: {}", pathInContext);
170:
171: int must_be_smaller_than = _maxCacheSize
172: - (int) len;
173: while (_cachedSize > must_be_smaller_than
174: || (_maxCachedFiles > 0 && _cachedFiles >= _maxCachedFiles))
175: _leastRecentlyUsed.invalidate();
176: content.cache(pathInContext);
177:
178: return content;
179: }
180: }
181: }
182:
183: if (Log.isDebugEnabled())
184: Log.debug("CACHE MISS: {}", pathInContext);
185: return null;
186: }
187:
188: /* ------------------------------------------------------------ */
189: public synchronized void doStart() throws Exception {
190: _cache = new HashMap();
191: _cachedSize = 0;
192: _cachedFiles = 0;
193: }
194:
195: /* ------------------------------------------------------------ */
196: /** Stop the context.
197: */
198: public void doStop() throws InterruptedException {
199: flushCache();
200: _cache = null;
201: }
202:
203: /* ------------------------------------------------------------ */
204: protected void fill(Content content) throws IOException {
205: try {
206: InputStream in = content.getResource().getInputStream();
207: int len = (int) content.getResource().length();
208: Buffer buffer = new ByteArrayBuffer(len);
209: buffer.readFrom(in, len);
210: in.close();
211: content.setBuffer(buffer);
212: } finally {
213: content.getResource().release();
214: }
215: }
216:
217: /* ------------------------------------------------------------ */
218: /* ------------------------------------------------------------ */
219: /** MetaData associated with a context Resource.
220: */
221: public class Content implements HttpContent {
222: String _key;
223: Resource _resource;
224: long _lastModified;
225: Content _prev;
226: Content _next;
227:
228: Buffer _lastModifiedBytes;
229: Buffer _contentType;
230: Buffer _buffer;
231:
232: /* ------------------------------------------------------------ */
233: Content(Resource resource) {
234: _resource = resource;
235:
236: _next = this ;
237: _prev = this ;
238: _contentType = _mimeTypes.getMimeByExtension(_resource
239: .toString());
240:
241: _lastModified = resource.lastModified();
242: }
243:
244: /* ------------------------------------------------------------ */
245: void cache(String pathInContext) {
246: _key = pathInContext;
247: _next = _mostRecentlyUsed;
248: _mostRecentlyUsed = this ;
249: if (_next != null)
250: _next._prev = this ;
251: _prev = null;
252: if (_leastRecentlyUsed == null)
253: _leastRecentlyUsed = this ;
254:
255: _cache.put(_key, this );
256: _cachedSize += _buffer.length();
257: _cachedFiles++;
258: if (_lastModified != -1)
259: _lastModifiedBytes = new ByteArrayBuffer(HttpFields
260: .formatDate(_lastModified, false));
261: }
262:
263: /* ------------------------------------------------------------ */
264: public String getKey() {
265: return _key;
266: }
267:
268: /* ------------------------------------------------------------ */
269: public boolean isCached() {
270: return _key != null;
271: }
272:
273: /* ------------------------------------------------------------ */
274: public Resource getResource() {
275: return _resource;
276: }
277:
278: /* ------------------------------------------------------------ */
279: boolean isValid() {
280: if (_lastModified == _resource.lastModified()) {
281: if (_mostRecentlyUsed != this ) {
282: Content tp = _prev;
283: Content tn = _next;
284:
285: _next = _mostRecentlyUsed;
286: _mostRecentlyUsed = this ;
287: if (_next != null)
288: _next._prev = this ;
289: _prev = null;
290:
291: if (tp != null)
292: tp._next = tn;
293: if (tn != null)
294: tn._prev = tp;
295:
296: if (_leastRecentlyUsed == this && tp != null)
297: _leastRecentlyUsed = tp;
298: }
299: return true;
300: }
301:
302: invalidate();
303: return false;
304: }
305:
306: /* ------------------------------------------------------------ */
307: public void invalidate() {
308: synchronized (this ) {
309: // Invalidate it
310: _cache.remove(_key);
311: _key = null;
312: _cachedSize = _cachedSize - (int) _buffer.length();
313: _cachedFiles--;
314:
315: if (_mostRecentlyUsed == this )
316: _mostRecentlyUsed = _next;
317: else
318: _prev._next = _next;
319:
320: if (_leastRecentlyUsed == this )
321: _leastRecentlyUsed = _prev;
322: else
323: _next._prev = _prev;
324:
325: _prev = null;
326: _next = null;
327: _resource = null;
328:
329: }
330: }
331:
332: /* ------------------------------------------------------------ */
333: public Buffer getLastModified() {
334: return _lastModifiedBytes;
335: }
336:
337: /* ------------------------------------------------------------ */
338: public Buffer getContentType() {
339: return _contentType;
340: }
341:
342: /* ------------------------------------------------------------ */
343: public void setContentType(Buffer type) {
344: _contentType = type;
345: }
346:
347: /* ------------------------------------------------------------ */
348: public void release() {
349: }
350:
351: /* ------------------------------------------------------------ */
352: public Buffer getBuffer() {
353: if (_buffer == null)
354: return null;
355: return new View(_buffer);
356: }
357:
358: /* ------------------------------------------------------------ */
359: public void setBuffer(Buffer buffer) {
360: _buffer = buffer;
361: }
362:
363: /* ------------------------------------------------------------ */
364: public long getContentLength() {
365: if (_buffer == null)
366: return -1;
367: return _buffer.length();
368: }
369:
370: /* ------------------------------------------------------------ */
371: public InputStream getInputStream() throws IOException {
372: return _resource.getInputStream();
373: }
374:
375: }
376:
377: }
|