KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > enterprise > web > connector > grizzly > FileCache


1 /*
2  * The contents of this file are subject to the terms
3  * of the Common Development and Distribution License
4  * (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the license at
8  * https://glassfish.dev.java.net/public/CDDLv1.0.html or
9  * glassfish/bootstrap/legal/CDDLv1.0.txt.
10  * See the License for the specific language governing
11  * permissions and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL
14  * Header Notice in each file and include the License file
15  * at glassfish/bootstrap/legal/CDDLv1.0.txt.
16  * If applicable, add the following below the CDDL Header,
17  * with the fields enclosed by brackets [] replaced by
18  * you own identifying information:
19  * "Portions Copyrighted [year] [name of copyright owner]"
20  *
21  * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
22  */

23
24 package com.sun.enterprise.web.connector.grizzly;
25
26 import java.io.File JavaDoc;
27 import java.io.FileInputStream JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.nio.ByteBuffer JavaDoc;
30 import java.nio.MappedByteBuffer JavaDoc;
31 import java.nio.channels.FileChannel JavaDoc;
32 import java.nio.channels.SocketChannel JavaDoc;
33 import java.util.ArrayList JavaDoc;
34 import java.util.concurrent.ConcurrentHashMap JavaDoc;
35 import java.util.concurrent.ConcurrentLinkedQueue JavaDoc;
36 import java.util.concurrent.Future JavaDoc;
37 import java.util.concurrent.ScheduledThreadPoolExecutor JavaDoc;
38 import java.util.concurrent.TimeUnit JavaDoc;
39 import org.apache.catalina.util.ServerInfo;
40 import org.apache.tomcat.util.http.MimeHeaders;
41
42
43 /**
44  * This class implements a file caching mechanism used to cache static resources.
45  *
46  * @author Jeanfrancois Arcand
47  * @author Scott Oaks
48  */

49 public final class FileCache{
50     
51     public final static String JavaDoc DEFAULT_SERVLET_NAME = "default";
52    
53     
54     /**
55      * A <code>ByteBuffer</code> cache of static pages.
56      */

57     private final ConcurrentHashMap JavaDoc<String JavaDoc,FileCacheEntry> fileCache =
58             new ConcurrentHashMap JavaDoc<String JavaDoc,FileCacheEntry>();
59     
60     
61     /**
62      * A dummy instance of <code>ByteBuffer</code>
63      */

64     private final static ByteBuffer JavaDoc nullByteBuffer =
65                             (ByteBuffer JavaDoc) ByteBuffer.allocate(0);
66    
67   
68     /**
69      * HTTP end line.
70      */

71     private final static String JavaDoc NEWLINE = "\r\n";
72
73
74     /**
75      * HTTP OK header
76      */

77     public final static String JavaDoc OK = "HTTP/1.1 200 OK" + NEWLINE;
78
79     /**
80      * The port associated with this cache.
81      */

82     private int port = 8080;
83     
84     
85     /**
86      * Scheduled Thread that clean the cache every XX seconds.
87      */

88     private ScheduledThreadPoolExecutor JavaDoc cacheResourcesThread
89         = new ScheduledThreadPoolExecutor JavaDoc(1,
90             new GrizzlyThreadFactory("FileCacheThread-" + port,
91                 1,Thread.NORM_PRIORITY));
92     
93     
94     /**
95      * FileCacheEntry cache
96      */

97     private ConcurrentLinkedQueue JavaDoc<FileCacheEntry> cacheManager;
98
99     
100     /**
101      * Timeout before remove the static resource from the cache.
102      */

103     private int secondsMaxAge = -1;
104     
105     
106     /**
107      * The maximum entries in the <code>fileCache</code>
108      */

109     private int maxCacheEntries = 1024;
110     
111  
112     /**
113      * The maximum size of a cached resources.
114      */

115     private long minEntrySize = 2048;
116             
117                
118     /**
119      * The maximum size of a cached resources.
120      */

121     private long maxEntrySize = 537600;
122     
123     
124     /**
125      * The maximum memory mapped bytes
126      */

127     private long maxLargeFileCacheSize = 10485760;
128             
129     
130     /**
131      * The maximum cached bytes
132      */

133     private long maxSmallFileCacheSize = 1048576;
134     
135     
136     /**
137      * The current cache size in bytes
138      */

139     private static long mappedMemorySize = 0;
140     
141     
142     /**
143      * The current cache size in bytes
144      */

145     private static long heapSize = 0;
146     
147             
148     /**
149      * Is the file cache enabled.
150      */

151     private boolean isEnabled = true;
152         
153     
154     /**
155      * Is the large FileCache enabled.
156      */

157     private boolean isLargeFileCacheEnabled = true;
158     
159     
160     /**
161      * Is monitoring enabled.
162      */

163     private static boolean isMonitoringEnabled = false;
164     
165     
166     /**
167      * The number of current open cache entries
168      */

169     private int openCacheEntries = 0;
170    
171        
172     /**
173      * The number of max current open cache entries
174      */

175     private int maxOpenCacheEntries = 0;
176     
177     
178     /**
179      * Max heap space used for cache
180      */

181     private long maxHeapCacheSize = 0;
182     
183     
184     /**
185      * Max mapped memory used for cache
186      */

187     private long maxMappedMemory = 0;
188     
189     
190     /**
191      * Number of cache lookup hits
192      */

193     private int countHits = 0;
194     
195     
196     /**
197      * Number of cache lookup misses
198      */

199     private int countMisses = 0;
200     
201     
202     /**
203      * Number of hits on cached file info
204      */

205     private int countCacheHits;
206     
207     
208     /**
209      * Number of misses on cached file info
210      */

211     private int countCacheMisses;
212         
213     
214     /**
215      * Number of hits on cached file info
216      */

217     private int countMappedHits;
218     
219     
220     /**
221      * Number of misses on cached file info
222      */

223     private int countMappedMisses;
224
225     // ---------------------------------------------------- Methods ----------//
226

227             
228     /**
229      * Add a resource to the cache. Currently, only static resources served
230      * by the DefaultServlet can be cached.
231      */

232     public synchronized void add(String JavaDoc mappedServlet, String JavaDoc baseDir,
233             String JavaDoc requestURI, MimeHeaders headers, boolean xPoweredBy){
234         
235         if ( fileCache.get(requestURI) != null) return;
236         
237         // cache is full.
238
if ( fileCache.size() > maxCacheEntries) {
239             return;
240         }
241         
242         if ( mappedServlet.equals(DEFAULT_SERVLET_NAME) ){
243             File JavaDoc file = new File JavaDoc(baseDir + requestURI);
244             ByteBuffer JavaDoc bb = mapFile(file);
245
246             // Always put the answer into the map. If it's null, then
247
// we know that it doesn't fit into the cache, so there's no
248
// reason to go through this code again.
249
if (bb == null)
250                 bb = nullByteBuffer;
251             
252             FileCacheEntry entry = cacheManager.poll();
253             if ( entry == null){
254                 entry = new FileCacheEntry();
255             }
256             entry.bb = bb;
257             entry.requestURI = requestURI;
258             
259             if ( bb != nullByteBuffer){
260                 entry.lastModified = headers.getHeader("Last-Modified");
261                 entry.contentType = headers.getHeader("content-type");
262                 entry.xPoweredBy = xPoweredBy;
263                 entry.isInHeap = (file.length() < minEntrySize);
264                 entry.date = headers.getHeader("Date");
265                 entry.Etag = headers.getHeader("Etag");
266                 entry.keepAlive = "Keep-Alive".equalsIgnoreCase(
267                         headers.getHeader("Connection"))? true: false;
268                 
269                 configHeaders(entry);
270
271                 if ( isMonitoringEnabled ) {
272                     openCacheEntries++;
273
274                     if ( openCacheEntries > maxOpenCacheEntries){
275                         maxOpenCacheEntries = openCacheEntries;
276                     }
277
278                     if ( heapSize > maxHeapCacheSize){
279                         maxHeapCacheSize = heapSize;
280                     }
281
282                     if ( mappedMemorySize > maxMappedMemory){
283                         maxMappedMemory = mappedMemorySize;
284                     }
285                 }
286
287                 if ( secondsMaxAge > 0 ) {
288                     entry.future = cacheResourcesThread.schedule(entry,
289                                                 secondsMaxAge, TimeUnit.SECONDS);
290                 }
291             }
292             fileCache.put(requestURI,entry);
293         }
294     }
295        
296     
297     /**
298      * Map the file to a <code>ByteBuffer</code>
299      * @return the <code>ByteBuffer</code>
300      */

301     private final ByteBuffer JavaDoc mapFile(File JavaDoc file){
302         FileChannel JavaDoc fileChannel = null;
303         FileInputStream JavaDoc stream = null;
304         try {
305             stream = new FileInputStream JavaDoc(file);
306             fileChannel = stream.getChannel();
307              
308             long size = fileChannel.size();
309             
310             if ( !isLargeFileCacheEnabled ) {
311                 // Large file support are not enabled
312
if ( size > minEntrySize ) {
313                     return null;
314                 }
315             } else if ( size > maxEntrySize){
316                 return null;
317             }
318
319             if ( size > minEntrySize )
320                 mappedMemorySize+= size;
321             else
322                 heapSize+= size;
323  
324             // Cache full
325
if ( mappedMemorySize > maxLargeFileCacheSize ) {
326                 mappedMemorySize-= size;
327                 return null;
328             } else if ( heapSize > maxSmallFileCacheSize ) {
329                 heapSize-= size;
330                 return null;
331             }
332             
333             ByteBuffer JavaDoc bb =
334                     fileChannel.map(FileChannel.MapMode.READ_ONLY,0,size);
335                                  
336             if ( size < minEntrySize) {
337                 ((MappedByteBuffer JavaDoc)bb).load();
338             }
339             return bb;
340         } catch (IOException JavaDoc ioe) {
341             return null;
342         } finally {
343             if (stream != null) {
344                 try {
345                     stream.close();
346                 } catch (IOException JavaDoc ioe) {
347                 }
348             }
349             if (fileChannel != null) {
350                 try {
351                     fileChannel.close();
352                 } catch (IOException JavaDoc ioe) {
353                 }
354             }
355         }
356     }
357         
358     
359     /**
360      * Return <code>true</code> if the file is cached.
361      */

362     private final FileCacheEntry map(byte[] requestBytes,int start, int length){
363         String JavaDoc uri = "";
364         FileCacheEntry entry = null;
365         
366         if ( fileCache.size() != 0 ){
367             uri = new String JavaDoc(requestBytes,start,length);
368             entry = fileCache.get(uri);
369             
370             if ( isMonitoringEnabled) {
371                 if (entry != null && entry.bb != null
372                         && entry.bb != nullByteBuffer){
373                     if ( entry.isInHeap )
374                         countCacheHits++;
375                     else
376                         countMappedHits++;
377
378                     countHits++;
379                 
380                 } else {
381                   countMisses++;
382                 }
383             }
384         }
385         return entry;
386     }
387       
388     
389     /**
390      * Send the cache.
391      */

392     public boolean sendCache(byte[] req, int start, int length,
393             SocketChannel JavaDoc socketChannel, boolean keepAlive){
394
395         try{
396             FileCacheEntry entry = map(req,start,length);
397             if ( entry != null && entry.bb != nullByteBuffer){
398                 sendCache(socketChannel,entry);
399                 return true;
400             }
401         } catch (IOException JavaDoc ex){
402             SelectorThread.logger()
403                 .fine("File Cache: " + ex.getMessage());
404             return true;
405         } catch (Throwable JavaDoc t){
406             // If an unexpected exception occurs, try to serve the page
407
// as if it wasn't in a cache.
408
SelectorThread.logger()
409                 .fine("File Cache thread race: " + t.getMessage());
410         }
411         return false;
412     }
413      
414     
415     /**
416      * Set the cache manager used by this instance.
417      */

418     public void setCacheManager(ConcurrentLinkedQueue JavaDoc cacheManager){
419         this.cacheManager = cacheManager;
420     }
421     
422     
423     // -------------------------------------------------- Static cache -------/
424

425     
426     /**
427      * Send the cached resource.
428      */

429     private void sendCache(SocketChannel JavaDoc socketChannel, FileCacheEntry entry)
430             throws IOException JavaDoc{
431   
432         OutputWriter.flushChannel(socketChannel, entry.headerBuffer.slice());
433         OutputWriter.flushChannel(socketChannel, entry.bb.slice());
434     }
435
436     
437     /**
438      * Return a <code>ByteBuffer</code> contains the server header.
439      */

440     private void configHeaders(FileCacheEntry entry) {
441         if ( entry.headerBuffer == null ) {
442             entry.headerBuffer =
443                     ByteBuffer.allocate(Constants.CHANNEL_BYTE_SIZE);
444         }
445         
446         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
447         sb.append(OK);
448         if ( entry.xPoweredBy){
449             appendHeaderValue(sb,"X-Powered-By", "Servlet/2.5");
450         }
451         appendHeaderValue(sb, "ETag", entry.Etag);
452         appendHeaderValue(sb,"Last-Modified", entry.lastModified);
453         appendHeaderValue(sb,"Content-Type", entry.contentType);
454         appendHeaderValue(sb,"Content-Length", entry.bb.capacity() + "");
455         appendHeaderValue(sb,"Date", entry.date);
456         appendHeaderValue(sb,"Server", ServerInfo.getServerInfo());
457         appendHeaderValue(sb,"Connection",
458                (entry.keepAlive ? "Keep-Alive":"close"));
459         sb.append(NEWLINE);
460         entry.headerBuffer.put(sb.toString().getBytes());
461         entry.headerBuffer.flip();
462     }
463        
464     
465     /**
466      * Utility to add headers to the HTTP response.
467      */

468     private void appendHeaderValue(StringBuffer JavaDoc sb,String JavaDoc name, String JavaDoc value) {
469         sb.append(name);
470         sb.append(": ");
471         sb.append(value);
472         sb.append(NEWLINE);
473     }
474
475     
476     protected final class FileCacheEntry implements Runnable JavaDoc{
477         public String JavaDoc requestURI;
478         public String JavaDoc lastModified;
479         public String JavaDoc contentType;
480         public boolean keepAlive;
481         public ByteBuffer JavaDoc bb;
482         public ByteBuffer JavaDoc headerBuffer;
483         public boolean xPoweredBy;
484         public boolean isInHeap = false;
485         public String JavaDoc date;
486         public String JavaDoc Etag;
487         public Future JavaDoc future;
488              
489         public void run(){
490             fileCache.remove(requestURI);
491
492             if (requestURI == null) return;
493             
494             if (headerBuffer != null) {
495
496                 /**
497                  * If the position !=0, it means the ByteBuffer has a view
498                  * that is still used. If that's the case, wait another 10 seconds
499                  * before marking the ByteBuffer for garbage collection
500                  */

501                 if ( headerBuffer.position() !=0 || bb.position() != 0 ){
502                     future = cacheResourcesThread
503                                 .schedule(this, 10, TimeUnit.SECONDS);
504                     return;
505                 }
506
507                 if ( !isInHeap )
508                     mappedMemorySize -= bb.limit();
509                 else
510                     heapSize -= bb.limit();
511
512                 bb = null;
513                 headerBuffer = null;
514                 openCacheEntries--;
515             }
516             
517             if ( future != null ) {
518                 future.cancel(false);
519                 future = null;
520             }
521             requestURI = null;
522             cacheManager.offer(this);
523         }
524     }
525     
526     
527     // ---------------------------------------------------- Monitoring --------//
528

529     
530     /**
531      * Returns flag indicating whether file cache has been enabled
532      * @return 1 if file cache has been enabled, 0 otherwise
533      */

534     public int getFlagEnabled() {
535         return (isEnabled == true?1:0);
536     }
537     
538     
539     /**
540      * Return the maximum age of a valid cache entry
541      * @return cache entry maximum age
542      */

543     public int getSecondsMaxAge() {
544         return secondsMaxAge;
545     }
546     
547     
548     /**
549      * Return the number of current cache entries.
550      * @return current cache entries
551      */

552     public long getCountEntries() {
553         return fileCache.size();
554     }
555     
556     
557     /**
558      * Return the maximum number of cache entries
559      * @return maximum cache entries
560      */

561     public long getMaxEntries() {
562         return maxCacheEntries;
563     }
564     
565     
566     /**
567      * The number of current open cache entries
568      * @return open cache entries
569      */

570     public long getCountOpenEntries() {
571         return openCacheEntries;
572     }
573     
574     
575     /**
576      * Return the maximum number of open cache entries
577      * @return maximum open cache entries
578      */

579     public long getMaxOpenEntries() {
580        return maxOpenCacheEntries;
581     }
582     
583     
584     /**
585      * Return the heap space used for cache
586      * @return heap size
587      */

588     public long getSizeHeapCache() {
589         return heapSize;
590     }
591     
592     
593     /**
594      * Return the maximum heap space used for cache
595      * @return maximum heap size
596      */

597     public long getMaxHeapCacheSize() {
598         return maxHeapCacheSize;
599     }
600     
601     
602     /**
603      * Return the size of Mapped memory used for caching
604      * @return Mapped memory size
605      */

606     public static long getSizeMmapCache() {
607         return mappedMemorySize;
608     }
609     
610     
611     /**
612      * Return the Maximum Memory Map size to be used for caching
613      * @return maximum Memory Map size
614      */

615     public long getMaxMmapCacheSize() {
616         return maxMappedMemory;
617     }
618     
619     
620     /**
621      * Return the Number of cache lookup hits
622      * @return cache hits
623      */

624     public long getCountHits() {
625         return countHits;
626     }
627     
628     
629     /**
630      * Return the Number of cache lookup misses
631      * @return cache misses
632      */

633     public long getCountMisses() {
634         return countMisses;
635     }
636     
637     
638     /**
639      * The Number of hits on cached file info
640      * @return hits on cached file info
641      */

642     public long getCountInfoHits() {
643         return countCacheHits;
644     }
645     
646     
647     /**
648      * Return the number of misses on cached file info
649      * @return misses on cache file info
650      */

651     public long getCountInfoMisses() {
652         return countCacheMisses;
653     }
654     
655     
656     /**
657      * Return the Number of hits on cached file content
658      * @return hits on cache file content
659      */

660     public long getCountContentHits() {
661         return countMappedHits;
662     }
663     
664     
665     /**
666      * Return the Number of misses on cached file content
667      * @return missed on cached file content
668      */

669     public int getCountContentMisses() {
670         return countMappedMisses;
671     }
672     
673     // ---------------------------------------------------- Properties ----- //
674

675     
676     /**
677      * Turn monitoring on/off
678      */

679     public static void setIsMonitoringEnabled(boolean isMe){
680         isMonitoringEnabled = isMe;
681     }
682     
683      
684     
685     /**
686      * The timeout in seconds before remove a <code>FileCacheEntry</code>
687      * from the <code>fileCache</code>
688      */

689     public void setSecondsMaxAge(int sMaxAges){
690         secondsMaxAge = sMaxAges;
691     }
692     
693     
694     /**
695      * Set the maximum entries this cache can contains.
696      */

697     public void setMaxCacheEntries(int mEntries){
698         maxCacheEntries = mEntries;
699     }
700
701     
702     /**
703      * Return the maximum entries this cache can contains.
704      */

705     public int getMaxCacheEntries(){
706         return maxCacheEntries;
707     }
708     
709     
710     /**
711      * Set the maximum size a <code>FileCacheEntry</code> can have.
712      */

713     public void setMinEntrySize(long mSize){
714         minEntrySize = mSize;
715     }
716     
717     
718     /**
719      * Get the maximum size a <code>FileCacheEntry</code> can have.
720      */

721     public long getMinEntrySize(){
722         return minEntrySize;
723     }
724      
725     
726     /**
727      * Set the maximum size a <code>FileCacheEntry</code> can have.
728      */

729     public void setMaxEntrySize(long mEntrySize){
730         maxEntrySize = mEntrySize;
731     }
732     
733     
734     /**
735      * Get the maximum size a <code>FileCacheEntry</code> can have.
736      */

737     public long getMaxEntrySize(){
738         return maxEntrySize;
739     }
740     
741     
742     /**
743      * Set the maximum cache size
744      */

745     public void setMaxLargeCacheSize(long mCacheSize){
746         maxLargeFileCacheSize = mCacheSize;
747     }
748
749     
750     /**
751      * Get the maximum cache size
752      */

753     public long getMaxLargeCacheSize(){
754         return maxLargeFileCacheSize;
755     }
756     
757     
758     /**
759      * Set the maximum cache size
760      */

761     public void setMaxSmallCacheSize(long mCacheSize){
762         maxSmallFileCacheSize = mCacheSize;
763     }
764     
765     
766     /**
767      * Get the maximum cache size
768      */

769     public long getMaxSmallCacheSize(){
770         return maxSmallFileCacheSize;
771     }
772
773     
774     /**
775      * Is the fileCache enabled.
776      */

777     public boolean isEnabled(){
778         return isEnabled;
779     }
780
781     
782     /**
783      * Is the file caching mechanism enabled.
784      */

785     public void setIsEnabled(boolean isEnabled){
786         this.isEnabled = isEnabled;
787     }
788    
789     
790     /**
791      * Is the large file cache support enabled.
792      */

793     public void setLargeFileCacheEnabled(boolean isLargeEnabled){
794         this.isLargeFileCacheEnabled = isLargeEnabled;
795     }
796    
797     
798     /**
799      * Is the large file cache support enabled.
800      */

801     public boolean getLargeFileCacheEnabled(){
802         return isLargeFileCacheEnabled;
803     }
804     
805     
806     /**
807      * Return the FileCache
808      */

809     public ConcurrentHashMap JavaDoc getCache(){
810         return fileCache;
811     }
812
813 }
814
Popular Tags