2 // ========================================================================
3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server;
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.ReadableByteChannel;
26 import java.util.Comparator;
27 import java.util.SortedSet;
28 import java.util.TreeSet;
29 import java.util.concurrent.ConcurrentHashMap;
30 import java.util.concurrent.ConcurrentMap;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.concurrent.atomic.AtomicReference;
34 import org.eclipse.jetty.http.DateGenerator;
35 import org.eclipse.jetty.http.HttpContent;
36 import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
37 import org.eclipse.jetty.http.MimeTypes;
38 import org.eclipse.jetty.util.BufferUtil;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.util.log.Logger;
41 import org.eclipse.jetty.util.resource.Resource;
42 import org.eclipse.jetty.util.resource.ResourceFactory;
45 /* ------------------------------------------------------------ */
49 public class ResourceCache
51 private static final Logger LOG = Log.getLogger(ResourceCache.class);
53 private final ConcurrentMap<String,Content> _cache;
54 private final AtomicInteger _cachedSize;
55 private final AtomicInteger _cachedFiles;
56 private final ResourceFactory _factory;
57 private final ResourceCache _parent;
58 private final MimeTypes _mimeTypes;
59 private final boolean _etagSupported;
60 private final boolean _useFileMappedBuffer;
62 private int _maxCachedFileSize =4*1024*1024;
63 private int _maxCachedFiles=2048;
64 private int _maxCacheSize =32*1024*1024;
66 /* ------------------------------------------------------------ */
68 * @param mimeTypes Mimetype to use for meta data
70 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
73 _cache=new ConcurrentHashMap<String,Content>();
74 _cachedSize=new AtomicInteger();
75 _cachedFiles=new AtomicInteger();
78 _useFileMappedBuffer=useFileMappedBuffer;
82 /* ------------------------------------------------------------ */
83 public int getCachedSize()
85 return _cachedSize.get();
88 /* ------------------------------------------------------------ */
89 public int getCachedFiles()
91 return _cachedFiles.get();
94 /* ------------------------------------------------------------ */
95 public int getMaxCachedFileSize()
97 return _maxCachedFileSize;
100 /* ------------------------------------------------------------ */
101 public void setMaxCachedFileSize(int maxCachedFileSize)
103 _maxCachedFileSize = maxCachedFileSize;
107 /* ------------------------------------------------------------ */
108 public int getMaxCacheSize()
110 return _maxCacheSize;
113 /* ------------------------------------------------------------ */
114 public void setMaxCacheSize(int maxCacheSize)
116 _maxCacheSize = maxCacheSize;
120 /* ------------------------------------------------------------ */
122 * @return Returns the maxCachedFiles.
124 public int getMaxCachedFiles()
126 return _maxCachedFiles;
129 /* ------------------------------------------------------------ */
131 * @param maxCachedFiles The maxCachedFiles to set.
133 public void setMaxCachedFiles(int maxCachedFiles)
135 _maxCachedFiles = maxCachedFiles;
139 /* ------------------------------------------------------------ */
140 public boolean isUseFileMappedBuffer()
142 return _useFileMappedBuffer;
145 /* ------------------------------------------------------------ */
146 public void flushCache()
150 while (_cache.size()>0)
152 for (String path : _cache.keySet())
154 Content content = _cache.remove(path);
156 content.invalidate();
162 /* ------------------------------------------------------------ */
163 /** Get a Entry from the cache.
164 * Get either a valid entry object or create a new one if possible.
166 * @param pathInContext The key into the cache
167 * @return The entry matching <code>pathInContext</code>, or a new entry
168 * if no matching entry was found. If the content exists but is not cachable,
169 * then a {@link ResourceAsHttpContent} instance is return. If
170 * the resource does not exist, then null is returned.
171 * @throws IOException Problem loading the resource
173 public HttpContent lookup(String pathInContext)
176 // Is the content in this cache?
177 Content content =_cache.get(pathInContext);
178 if (content!=null && (content).isValid())
181 // try loading the content from our factory.
182 Resource resource=_factory.getResource(pathInContext);
183 HttpContent loaded = load(pathInContext,resource);
187 // Is the content in the parent cache?
190 HttpContent httpContent=_parent.lookup(pathInContext);
191 if (httpContent!=null)
198 /* ------------------------------------------------------------ */
201 * @return True if the resource is cacheable. The default implementation tests the cache sizes.
203 protected boolean isCacheable(Resource resource)
205 long len = resource.length();
207 // Will it fit in the cache?
208 return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
211 /* ------------------------------------------------------------ */
212 private HttpContent load(String pathInContext, Resource resource)
215 Content content=null;
217 if (resource==null || !resource.exists())
220 // Will it fit in the cache?
221 if (!resource.isDirectory() && isCacheable(resource))
223 // Create the Content (to increment the cache sizes before adding the content
224 content = new Content(pathInContext,resource);
226 // reduce the cache to an acceptable size.
229 // Add it to the cache.
230 Content added = _cache.putIfAbsent(pathInContext,content);
233 content.invalidate();
240 return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etagSupported);
244 /* ------------------------------------------------------------ */
245 private void shrinkCache()
247 // While we need to shrink
248 while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
250 // Scan the entire cache and generate an ordered list by last accessed time.
251 SortedSet<Content> sorted= new TreeSet<Content>(
252 new Comparator<Content>()
254 public int compare(Content c1, Content c2)
256 if (c1._lastAccessed<c2._lastAccessed)
259 if (c1._lastAccessed>c2._lastAccessed)
262 if (c1._length<c2._length)
265 return c1._key.compareTo(c2._key);
268 for (Content content : _cache.values())
271 // Invalidate least recently used first
272 for (Content content : sorted)
274 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
276 if (content==_cache.remove(content.getKey()))
277 content.invalidate();
282 /* ------------------------------------------------------------ */
283 protected ByteBuffer getIndirectBuffer(Resource resource)
287 return BufferUtil.toBuffer(resource,true);
289 catch(IOException|IllegalArgumentException e)
296 /* ------------------------------------------------------------ */
297 protected ByteBuffer getDirectBuffer(Resource resource)
301 if (_useFileMappedBuffer && resource.getFile()!=null)
302 return BufferUtil.toMappedBuffer(resource.getFile());
304 return BufferUtil.toBuffer(resource,true);
306 catch(IOException|IllegalArgumentException e)
313 /* ------------------------------------------------------------ */
315 public String toString()
317 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
320 /* ------------------------------------------------------------ */
321 /* ------------------------------------------------------------ */
322 /** MetaData associated with a context Resource.
324 public class Content implements HttpContent
326 final Resource _resource;
329 final long _lastModified;
330 final ByteBuffer _lastModifiedBytes;
331 final ByteBuffer _contentType;
334 volatile long _lastAccessed;
335 AtomicReference<ByteBuffer> _indirectBuffer=new AtomicReference<ByteBuffer>();
336 AtomicReference<ByteBuffer> _directBuffer=new AtomicReference<ByteBuffer>();
338 /* ------------------------------------------------------------ */
339 Content(String pathInContext,Resource resource)
344 String mimeType = _mimeTypes.getMimeByExtension(_resource.toString());
345 _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType));
346 boolean exists=resource.exists();
347 _lastModified=exists?resource.lastModified():-1;
348 _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified));
350 _length=exists?(int)resource.length():0;
351 _cachedSize.addAndGet(_length);
352 _cachedFiles.incrementAndGet();
353 _lastAccessed=System.currentTimeMillis();
355 _etag=ResourceCache.this._etagSupported?resource.getWeakETag():null;
359 /* ------------------------------------------------------------ */
360 public String getKey()
365 /* ------------------------------------------------------------ */
366 public boolean isCached()
371 /* ------------------------------------------------------------ */
372 public boolean isMiss()
377 /* ------------------------------------------------------------ */
379 public Resource getResource()
384 /* ------------------------------------------------------------ */
386 public String getETag()
391 /* ------------------------------------------------------------ */
394 if (_lastModified==_resource.lastModified() && _length==_resource.length())
396 _lastAccessed=System.currentTimeMillis();
400 if (this==_cache.remove(_key))
405 /* ------------------------------------------------------------ */
406 protected void invalidate()
409 _cachedSize.addAndGet(-_length);
410 _cachedFiles.decrementAndGet();
414 /* ------------------------------------------------------------ */
416 public String getLastModified()
418 return BufferUtil.toString(_lastModifiedBytes);
421 /* ------------------------------------------------------------ */
423 public String getContentType()
425 return BufferUtil.toString(_contentType);
428 /* ------------------------------------------------------------ */
430 public void release()
432 // don't release while cached. Release when invalidated.
435 /* ------------------------------------------------------------ */
437 public ByteBuffer getIndirectBuffer()
439 ByteBuffer buffer = _indirectBuffer.get();
442 ByteBuffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
445 LOG.warn("Could not load "+this);
446 else if (_indirectBuffer.compareAndSet(null,buffer2))
449 buffer=_indirectBuffer.get();
453 return buffer.slice();
457 /* ------------------------------------------------------------ */
459 public ByteBuffer getDirectBuffer()
461 ByteBuffer buffer = _directBuffer.get();
464 ByteBuffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
467 LOG.warn("Could not load "+this);
468 else if (_directBuffer.compareAndSet(null,buffer2))
471 buffer=_directBuffer.get();
475 return buffer.asReadOnlyBuffer();
478 /* ------------------------------------------------------------ */
480 public long getContentLength()
485 /* ------------------------------------------------------------ */
487 public InputStream getInputStream() throws IOException
489 ByteBuffer indirect = getIndirectBuffer();
490 if (indirect!=null && indirect.hasArray())
491 return new ByteArrayInputStream(indirect.array(),indirect.arrayOffset()+indirect.position(),indirect.remaining());
493 return _resource.getInputStream();
496 /* ------------------------------------------------------------ */
498 public ReadableByteChannel getReadableByteChannel() throws IOException
500 return _resource.getReadableByteChannel();
504 /* ------------------------------------------------------------ */
506 public String toString()
508 return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);