Mercurial Hosting > luan
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 } |