comparison src/org/eclipse/jetty/server/ResourceCache.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children
comparison
equal deleted inserted replaced
801:6a21393191c1 802:3428c60d7cfc
1 //
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.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.server;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.Comparator;
25 import java.util.SortedSet;
26 import java.util.TreeSet;
27 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap;
29 import java.util.concurrent.atomic.AtomicInteger;
30 import java.util.concurrent.atomic.AtomicReference;
31
32 import org.eclipse.jetty.http.HttpContent;
33 import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent;
34 import org.eclipse.jetty.http.HttpFields;
35 import org.eclipse.jetty.http.MimeTypes;
36 import org.eclipse.jetty.io.Buffer;
37 import org.eclipse.jetty.io.ByteArrayBuffer;
38 import org.eclipse.jetty.io.View;
39 import org.eclipse.jetty.io.nio.DirectNIOBuffer;
40 import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
41 import org.eclipse.jetty.util.log.Log;
42 import org.eclipse.jetty.util.log.Logger;
43 import org.eclipse.jetty.util.resource.Resource;
44 import org.eclipse.jetty.util.resource.ResourceFactory;
45
46
47 /* ------------------------------------------------------------ */
48 /**
49 *
50 */
51 public class ResourceCache
52 {
53 private static final Logger LOG = Log.getLogger(ResourceCache.class);
54
55 private final ConcurrentMap<String,Content> _cache;
56 private final AtomicInteger _cachedSize;
57 private final AtomicInteger _cachedFiles;
58 private final ResourceFactory _factory;
59 private final ResourceCache _parent;
60 private final MimeTypes _mimeTypes;
61 private final boolean _etags;
62
63 private boolean _useFileMappedBuffer=true;
64 private int _maxCachedFileSize =4*1024*1024;
65 private int _maxCachedFiles=2048;
66 private int _maxCacheSize =32*1024*1024;
67
68 /* ------------------------------------------------------------ */
69 /** Constructor.
70 * @param mimeTypes Mimetype to use for meta data
71 */
72 public ResourceCache(ResourceCache parent, ResourceFactory factory, MimeTypes mimeTypes,boolean useFileMappedBuffer,boolean etags)
73 {
74 _factory = factory;
75 _cache=new ConcurrentHashMap<String,Content>();
76 _cachedSize=new AtomicInteger();
77 _cachedFiles=new AtomicInteger();
78 _mimeTypes=mimeTypes;
79 _parent=parent;
80 _etags=etags;
81 _useFileMappedBuffer=useFileMappedBuffer;
82 }
83
84 /* ------------------------------------------------------------ */
85 public int getCachedSize()
86 {
87 return _cachedSize.get();
88 }
89
90 /* ------------------------------------------------------------ */
91 public int getCachedFiles()
92 {
93 return _cachedFiles.get();
94 }
95
96 /* ------------------------------------------------------------ */
97 public int getMaxCachedFileSize()
98 {
99 return _maxCachedFileSize;
100 }
101
102 /* ------------------------------------------------------------ */
103 public void setMaxCachedFileSize(int maxCachedFileSize)
104 {
105 _maxCachedFileSize = maxCachedFileSize;
106 shrinkCache();
107 }
108
109 /* ------------------------------------------------------------ */
110 public int getMaxCacheSize()
111 {
112 return _maxCacheSize;
113 }
114
115 /* ------------------------------------------------------------ */
116 public void setMaxCacheSize(int maxCacheSize)
117 {
118 _maxCacheSize = maxCacheSize;
119 shrinkCache();
120 }
121
122 /* ------------------------------------------------------------ */
123 /**
124 * @return Returns the maxCachedFiles.
125 */
126 public int getMaxCachedFiles()
127 {
128 return _maxCachedFiles;
129 }
130
131 /* ------------------------------------------------------------ */
132 /**
133 * @param maxCachedFiles The maxCachedFiles to set.
134 */
135 public void setMaxCachedFiles(int maxCachedFiles)
136 {
137 _maxCachedFiles = maxCachedFiles;
138 shrinkCache();
139 }
140
141 /* ------------------------------------------------------------ */
142 public boolean isUseFileMappedBuffer()
143 {
144 return _useFileMappedBuffer;
145 }
146
147 /* ------------------------------------------------------------ */
148 public void setUseFileMappedBuffer(boolean useFileMappedBuffer)
149 {
150 _useFileMappedBuffer = useFileMappedBuffer;
151 }
152
153 /* ------------------------------------------------------------ */
154 public void flushCache()
155 {
156 if (_cache!=null)
157 {
158 while (_cache.size()>0)
159 {
160 for (String path : _cache.keySet())
161 {
162 Content content = _cache.remove(path);
163 if (content!=null)
164 content.invalidate();
165 }
166 }
167 }
168 }
169
170 /* ------------------------------------------------------------ */
171 /** Get a Entry from the cache.
172 * Get either a valid entry object or create a new one if possible.
173 *
174 * @param pathInContext The key into the cache
175 * @return The entry matching <code>pathInContext</code>, or a new entry
176 * if no matching entry was found. If the content exists but is not cachable,
177 * then a {@link ResourceAsHttpContent} instance is return. If
178 * the resource does not exist, then null is returned.
179 * @throws IOException Problem loading the resource
180 */
181 public HttpContent lookup(String pathInContext)
182 throws IOException
183 {
184 // Is the content in this cache?
185 Content content =_cache.get(pathInContext);
186 if (content!=null && (content).isValid())
187 return content;
188
189 // try loading the content from our factory.
190 Resource resource=_factory.getResource(pathInContext);
191 HttpContent loaded = load(pathInContext,resource);
192 if (loaded!=null)
193 return loaded;
194
195 // Is the content in the parent cache?
196 if (_parent!=null)
197 {
198 HttpContent httpContent=_parent.lookup(pathInContext);
199 if (httpContent!=null)
200 return httpContent;
201 }
202
203 return null;
204 }
205
206 /* ------------------------------------------------------------ */
207 /**
208 * @param resource
209 * @return True if the resource is cacheable. The default implementation tests the cache sizes.
210 */
211 protected boolean isCacheable(Resource resource)
212 {
213 long len = resource.length();
214
215 // Will it fit in the cache?
216 return (len>0 && len<_maxCachedFileSize && len<_maxCacheSize);
217 }
218
219 /* ------------------------------------------------------------ */
220 private HttpContent load(String pathInContext, Resource resource)
221 throws IOException
222 {
223 Content content=null;
224
225 if (resource==null || !resource.exists())
226 return null;
227
228 // Will it fit in the cache?
229 if (!resource.isDirectory() && isCacheable(resource))
230 {
231 // Create the Content (to increment the cache sizes before adding the content
232 content = new Content(pathInContext,resource);
233
234 // reduce the cache to an acceptable size.
235 shrinkCache();
236
237 // Add it to the cache.
238 Content added = _cache.putIfAbsent(pathInContext,content);
239 if (added!=null)
240 {
241 content.invalidate();
242 content=added;
243 }
244
245 return content;
246 }
247
248 return new HttpContent.ResourceAsHttpContent(resource,_mimeTypes.getMimeByExtension(resource.toString()),getMaxCachedFileSize(),_etags);
249
250 }
251
252 /* ------------------------------------------------------------ */
253 private void shrinkCache()
254 {
255 // While we need to shrink
256 while (_cache.size()>0 && (_cachedFiles.get()>_maxCachedFiles || _cachedSize.get()>_maxCacheSize))
257 {
258 // Scan the entire cache and generate an ordered list by last accessed time.
259 SortedSet<Content> sorted= new TreeSet<Content>(
260 new Comparator<Content>()
261 {
262 public int compare(Content c1, Content c2)
263 {
264 if (c1._lastAccessed<c2._lastAccessed)
265 return -1;
266
267 if (c1._lastAccessed>c2._lastAccessed)
268 return 1;
269
270 if (c1._length<c2._length)
271 return -1;
272
273 return c1._key.compareTo(c2._key);
274 }
275 });
276 for (Content content : _cache.values())
277 sorted.add(content);
278
279 // Invalidate least recently used first
280 for (Content content : sorted)
281 {
282 if (_cachedFiles.get()<=_maxCachedFiles && _cachedSize.get()<=_maxCacheSize)
283 break;
284 if (content==_cache.remove(content.getKey()))
285 content.invalidate();
286 }
287 }
288 }
289
290 /* ------------------------------------------------------------ */
291 protected Buffer getIndirectBuffer(Resource resource)
292 {
293 try
294 {
295 int len=(int)resource.length();
296 if (len<0)
297 {
298 LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
299 return null;
300 }
301 Buffer buffer = new IndirectNIOBuffer(len);
302 InputStream is = resource.getInputStream();
303 buffer.readFrom(is,len);
304 is.close();
305 return buffer;
306 }
307 catch(IOException e)
308 {
309 LOG.warn(e);
310 return null;
311 }
312 }
313
314 /* ------------------------------------------------------------ */
315 protected Buffer getDirectBuffer(Resource resource)
316 {
317 try
318 {
319 if (_useFileMappedBuffer && resource.getFile()!=null)
320 return new DirectNIOBuffer(resource.getFile());
321
322 int len=(int)resource.length();
323 if (len<0)
324 {
325 LOG.warn("invalid resource: "+String.valueOf(resource)+" "+len);
326 return null;
327 }
328 Buffer buffer = new DirectNIOBuffer(len);
329 InputStream is = resource.getInputStream();
330 buffer.readFrom(is,len);
331 is.close();
332 return buffer;
333 }
334 catch(IOException e)
335 {
336 LOG.warn(e);
337 return null;
338 }
339 }
340
341 /* ------------------------------------------------------------ */
342 @Override
343 public String toString()
344 {
345 return "ResourceCache["+_parent+","+_factory+"]@"+hashCode();
346 }
347
348 /* ------------------------------------------------------------ */
349 /* ------------------------------------------------------------ */
350 /** MetaData associated with a context Resource.
351 */
352 public class Content implements HttpContent
353 {
354 final Resource _resource;
355 final int _length;
356 final String _key;
357 final long _lastModified;
358 final Buffer _lastModifiedBytes;
359 final Buffer _contentType;
360 final Buffer _etagBuffer;
361
362 volatile long _lastAccessed;
363 AtomicReference<Buffer> _indirectBuffer=new AtomicReference<Buffer>();
364 AtomicReference<Buffer> _directBuffer=new AtomicReference<Buffer>();
365
366 /* ------------------------------------------------------------ */
367 Content(String pathInContext,Resource resource)
368 {
369 _key=pathInContext;
370 _resource=resource;
371
372 _contentType=_mimeTypes.getMimeByExtension(_resource.toString());
373 boolean exists=resource.exists();
374 _lastModified=exists?resource.lastModified():-1;
375 _lastModifiedBytes=_lastModified<0?null:new ByteArrayBuffer(HttpFields.formatDate(_lastModified));
376
377 _length=exists?(int)resource.length():0;
378 _cachedSize.addAndGet(_length);
379 _cachedFiles.incrementAndGet();
380 _lastAccessed=System.currentTimeMillis();
381
382 _etagBuffer=_etags?new ByteArrayBuffer(resource.getWeakETag()):null;
383 }
384
385
386 /* ------------------------------------------------------------ */
387 public String getKey()
388 {
389 return _key;
390 }
391
392 /* ------------------------------------------------------------ */
393 public boolean isCached()
394 {
395 return _key!=null;
396 }
397
398 /* ------------------------------------------------------------ */
399 public boolean isMiss()
400 {
401 return false;
402 }
403
404 /* ------------------------------------------------------------ */
405 public Resource getResource()
406 {
407 return _resource;
408 }
409
410 /* ------------------------------------------------------------ */
411 public Buffer getETag()
412 {
413 return _etagBuffer;
414 }
415
416 /* ------------------------------------------------------------ */
417 boolean isValid()
418 {
419 if (_lastModified==_resource.lastModified() && _length==_resource.length())
420 {
421 _lastAccessed=System.currentTimeMillis();
422 return true;
423 }
424
425 if (this==_cache.remove(_key))
426 invalidate();
427 return false;
428 }
429
430 /* ------------------------------------------------------------ */
431 protected void invalidate()
432 {
433 // Invalidate it
434 _cachedSize.addAndGet(-_length);
435 _cachedFiles.decrementAndGet();
436 _resource.release();
437 }
438
439 /* ------------------------------------------------------------ */
440 public Buffer getLastModified()
441 {
442 return _lastModifiedBytes;
443 }
444
445 /* ------------------------------------------------------------ */
446 public Buffer getContentType()
447 {
448 return _contentType;
449 }
450
451 /* ------------------------------------------------------------ */
452 public void release()
453 {
454 // don't release while cached. Release when invalidated.
455 }
456
457 /* ------------------------------------------------------------ */
458 public Buffer getIndirectBuffer()
459 {
460 Buffer buffer = _indirectBuffer.get();
461 if (buffer==null)
462 {
463 Buffer buffer2=ResourceCache.this.getIndirectBuffer(_resource);
464
465 if (buffer2==null)
466 LOG.warn("Could not load "+this);
467 else if (_indirectBuffer.compareAndSet(null,buffer2))
468 buffer=buffer2;
469 else
470 buffer=_indirectBuffer.get();
471 }
472 if (buffer==null)
473 return null;
474 return new View(buffer);
475 }
476
477
478 /* ------------------------------------------------------------ */
479 public Buffer getDirectBuffer()
480 {
481 Buffer buffer = _directBuffer.get();
482 if (buffer==null)
483 {
484 Buffer buffer2=ResourceCache.this.getDirectBuffer(_resource);
485
486 if (buffer2==null)
487 LOG.warn("Could not load "+this);
488 else if (_directBuffer.compareAndSet(null,buffer2))
489 buffer=buffer2;
490 else
491 buffer=_directBuffer.get();
492 }
493 if (buffer==null)
494 return null;
495
496 return new View(buffer);
497 }
498
499 /* ------------------------------------------------------------ */
500 public long getContentLength()
501 {
502 return _length;
503 }
504
505 /* ------------------------------------------------------------ */
506 public InputStream getInputStream() throws IOException
507 {
508 Buffer indirect = getIndirectBuffer();
509 if (indirect!=null && indirect.array()!=null)
510 return new ByteArrayInputStream(indirect.array(),indirect.getIndex(),indirect.length());
511
512 return _resource.getInputStream();
513 }
514
515 /* ------------------------------------------------------------ */
516 @Override
517 public String toString()
518 {
519 return String.format("%s %s %d %s %s",_resource,_resource.exists(),_resource.lastModified(),_contentType,_lastModifiedBytes);
520 }
521 }
522 }