View Javadoc

1   /*
2    * Copyright 2000-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.jetspeed.cache.disk;
18  
19  //jetspeed
20  import org.apache.jetspeed.util.URIEncoder;
21  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
22  import org.apache.jetspeed.services.logging.JetspeedLogger;
23  import org.apache.jetspeed.services.threadpool.ThreadPool;
24  import org.apache.jetspeed.services.urlmanager.URLManager;
25  import org.apache.jetspeed.services.urlmanager.URLFetcher;
26  import org.apache.jetspeed.services.urlmanager.URLFetcherDownloader;
27  import org.apache.jetspeed.services.resources.JetspeedResources;
28  
29  //standard java stuff
30  import java.io.File;
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.io.Reader;
34  import java.io.OutputStreamWriter;
35  import java.net.MalformedURLException;
36  import java.util.Enumeration;
37  import java.util.Hashtable;
38  import java.util.Vector;
39  import javax.servlet.ServletContext;
40  
41  //turbine
42  import org.apache.turbine.services.servlet.TurbineServlet;
43  
44  
45  /***
46     <p>Sample Implementation of the Disk Cache interface.</p>
47     <p>Entries are updated when DiskCacheDaemon runs</p>
48  
49  @see DiskCache
50  @see org.apache.jetspeed.daemon.impl.DiskCacheDaemon
51  @author <A HREF="mailto:burton@apache.org">Kevin A. Burton</A>
52  @author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
53  @version $Id: JetspeedDiskCache.java,v 1.50 2004/02/23 02:45:29 jford Exp $
54  */
55  public class JetspeedDiskCache implements DiskCache {
56  
57  
58      /***
59      The default cache folder
60      */
61      public static String DEFAULT_CACHE_DIRECTORY = 
62          JetspeedResources.getString( JetspeedResources.CACHE_DIRECTORY_KEY, "WEB-INF/cache" );
63     
64      /***
65      Stores instances of JetspeedDiskCaches
66      */
67      private static Hashtable instances = new Hashtable();
68  
69      /***
70      This is the directory used to cache the documents.
71      */
72      private String directory;
73  
74      /***
75      This is a hashtable with all the entries in this cache.
76      */
77      private Hashtable entries = new Hashtable();
78  
79      /***
80       * Static initialization of the logger for this class
81       */
82      private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedDiskCache.class.getName());
83      
84      /***
85      Create an instance of the document cache.
86      */
87      private JetspeedDiskCache(String directory) {
88          this.directory = directory;
89  
90  	if ( DEFAULT_CACHE_DIRECTORY.equals("use-servlet-temp") ) {
91              String tempdir = new String("WEB-INF/cache");
92              try {
93                  ServletContext sc = TurbineServlet.getServletContext();
94                  tempdir = sc.getAttribute("javax.servlet.context.tempdir").toString() + "/jetspeed/cache";
95                  if ( logger.isDebugEnabled() )
96                  {
97                      logger.debug("DISK CACHE: will create cache in servlet temp directory " + tempdir);
98                  }
99              } catch (Exception e) {
100                 logger.error("DISK CACHE: problems creating cache in servlet temp directory "
101                            + " falling back to WEB-INF/cache : " + e);
102             }
103 	    this.directory = tempdir;    
104 	} else {
105                 if ( logger.isDebugEnabled() )
106                 {
107                     logger.debug("DISK CACHE: will use cache in user configured directory " + directory);
108                 }
109 	}
110     }
111     
112 
113     /***
114 
115    Create entries in the hashtable corresponding to the cached files...
116     
117     @see DiskCache#getEntries
118     */
119     private void initEntries() {
120         
121         logger.info("Disk Cache init Entries...");
122         //HACK: we need the URLManager started now to avoid locks...
123         //init() acts as a barrier, making our thread wait until
124         //URLManager is done initialization.
125         //This is the long time sought sporadic race condition/lock :-)
126         //To whomever rewrite this code: please change API so the cache is initialized
127         //through the URLManager service *after* the Manager is initialized, and possibly
128         //accessed through URLManager.getReader( url ) or something similar.
129         try {
130             org.apache.turbine.services.TurbineServices
131                 .getInstance()
132                 .getService( org.apache.jetspeed.services.urlmanager.URLManagerService.SERVICE_NAME ).init();
133         } 
134         catch (Throwable t) 
135         {
136             logger.error( "initEntries: Unable to start URLManagerService", t );
137         }
138 
139         File temp = new File( directory );
140         
141         String files[] = temp.list();
142 
143 
144         if (files == null)
145         {
146             logger.error("DiskCache.initEntries: Error!!! - The cache directory cannot be found: " + directory);
147         }
148 
149         for ( int i = 0; i < files.length; ++i ) {
150 
151             if ( files[i].indexOf("http_") == 0 )  {
152                 logger.info("Initializing cache entry: " + files[i]);
153                 JetspeedDiskCacheEntry ent = new JetspeedDiskCacheEntry( new File( getRoot(), files[i] ) );
154                 logger.info("Adding cache entry for " + ent.getSourceURL());
155 
156                 String interned = ent.getSourceURL().intern();
157                 entries.put( interned, ent);
158                 URLManager.register( interned,
159                                      URLManager.STATUS_OK,
160                                      "Recovered from cache" );
161             }
162             
163         }
164         logger.info("Disk Cache init Entries DONE.");
165         
166     }
167     
168     /***
169     Get a list of all the documents within the cache...
170 
171     Modified to create the entries in the hashtable...
172     
173     @see DiskCache#getEntries
174     */
175     public DiskCacheEntry[] getEntries() {
176         
177         Vector diskEntries = new Vector();
178         
179         Enumeration cacheEntries = entries.elements();
180         logger.info("Calling JetspeedDiskCache getEntries");
181         while(cacheEntries.hasMoreElements())
182             {
183                 diskEntries.addElement(cacheEntries.nextElement());
184             }
185         DiskCacheEntry[] found = new DiskCacheEntry[diskEntries.size()];
186         diskEntries.copyInto(found);
187         return found;
188         
189     }
190     
191     /***
192     Return the root of this DiskCache
193     
194     @see DiskCache#getRoot
195     */
196     public String getRoot() {
197         new File( this.directory ).mkdirs();
198         return this.directory;
199     }
200 
201     /***
202     @see DiskCache#getEntry( String url )
203     */
204     public DiskCacheEntry getEntry( String url ) throws IOException {
205         return getEntry( url, false );
206     }
207     
208     /***
209     Force this URL to update
210     */
211     public DiskCacheEntry getEntry( String url, 
212                                     boolean force ) throws IOException 
213     {
214         
215         if ( url == null ) {
216             throw new IllegalArgumentException("You must specify a URL to obtain an entry from the cache");
217         }
218 
219         //return right away if the entry exists in the cache...
220         String interned = url.intern();
221         JetspeedDiskCacheEntry entry = (JetspeedDiskCacheEntry)entries.get(interned);
222         if( entry != null)
223             {
224 
225                 //Log.info("Returning local URL because it is cached: " + interned );
226                 if(force)
227                     {
228                         logger.info("Refreshing local URL!!!" + interned);
229 
230                         URLFetcher.refresh(interned);
231                     }
232                 return entry;
233             }
234 
235 
236         //attempt to see if the user didn't specify a URL if they didn't then 
237         //assume it is localhost with the servlet port
238         logger.warn( "Cache getEntry Called with " + url );
239         if ( DiskCacheUtils.isLocal( url ) ) { 
240 
241             String local = DiskCacheUtils.getLocalURL( url ).intern();
242             JetspeedDiskCacheEntry dce = (JetspeedDiskCacheEntry)entries.get( local );
243             if(dce == null )
244                 {
245 
246                     logger.info("Adding Local to cache list: " + local);
247                     dce = new JetspeedDiskCacheEntry( local );
248                     entries.put(local, dce);
249                     URLManager.register( local,
250                                          URLManager.STATUS_OK,
251                                          "Local added" );
252                 }
253             logger.info("Returning local cached URL");
254 
255             return dce;
256         }
257         
258                
259         //only return for URLs that are cacheable and that are based on URLs 
260         //that are remote
261         if ( DiskCacheUtils.isCacheable( url ) ) {        
262         
263             if ( ( DiskCacheUtils.isCached( this, url ) == false ) || force )  { 
264 
265                 //Log.info( "DiskCache: MISS - fectching document: " + url );
266                 
267                 //if it doesn't exist then pull it down from a URL and save it to a file
268                 // SGP We can arrive here either because force was true or
269                 // because force is false and the url is not cached.
270                 // We must force load in both cases
271                 this.add( url, true );
272 
273             } 
274             return this.getEntry(url, force);
275             //return new JetspeedDiskCacheEntry( DiskCacheUtils.getFile( this, url ) );
276             
277         } else {
278 
279             //else it is a remote URL and can not be cached.
280             logger.info( "DiskCache: this URL can't be stored in cache... providing it directly." + url );
281             return new JetspeedDiskCacheEntry( url );
282 
283         }
284 
285     }
286 
287     /***
288      Get an Entry given a Reader and the URL from which it has been fetched.
289     @see DiskCache#getEntry( String url, Reader is )
290     */
291     public DiskCacheEntry getEntry( String url, 
292                                     Reader is ) throws IOException { 
293 
294         String uri = URIEncoder.encode( url );
295         String oldfilename = this.getRoot() + "/old." + uri;
296         String filename = DiskCacheUtils.getFile( this, url ).getAbsolutePath();
297         String newfilename = this.getRoot() + "/new." + uri;
298         File file = new File( DiskCacheUtils.getFile( this, url ).getAbsolutePath() );
299         File newfile = new File( newfilename);
300 
301         OutputStreamWriter os = new OutputStreamWriter (new FileOutputStream( newfile ), "utf-8" );
302 
303         //now process the InputStream...
304         char chars[] = new char[200];
305 
306         int readCount = 0;
307         while( ( readCount = is.read( chars )) > 0 ) {
308             os.write(chars, 0, readCount);
309         }
310 
311         is.close();
312         os.close();
313         
314         File oldfile = new File( oldfilename);
315         if(oldfile.exists())
316             oldfile.delete();
317         if(newfile.exists() && newfile.length() > 0) {
318             file = new File( filename );
319             file.renameTo(oldfile);
320             newfile.renameTo(file);
321         }
322         try {
323             if( oldfile.exists() )
324                 oldfile.delete();
325         } catch (Exception e) {
326             logger.info("Exception " + 
327                      e.getMessage() + 
328                      " while deleting " + oldfilename, e);
329         }
330         JetspeedDiskCacheEntry dce = (JetspeedDiskCacheEntry) entries.get(url.intern());
331         if (dce != null )
332             {
333                 dce.setFile( file );
334                 return dce;
335             } else {
336                 return this.getEntry(url, false);
337             }
338         
339     }
340     
341     
342     /***
343     @see DiskCache#remove( String url )
344     */
345     public void remove( String url ) throws IOException {
346         String uri = URIEncoder.encode( url );
347         if( DiskCacheUtils.isCached( this, url ) ) {
348             entries.remove(url.intern());
349             URLManager.unregister( url.intern() );
350             File file = DiskCacheUtils.getFile( this, url );
351             if(file.exists()) {
352                 file.delete();
353             }
354         }
355         String oldfilename = this.getRoot() + "/old." + uri;
356         File file = new File(oldfilename);
357         if(file.exists()) {
358             file.delete();
359         }
360         String newfilename = this.getRoot() + "/new." + uri;
361         file = new File(newfilename);
362         if(file.exists()) {
363             file.delete();
364         }
365         
366     }
367 
368     /***
369     @see DiskCache#add( String url )
370     */
371     public void add( String url ) throws IOException {
372         add( url, false );
373     }
374     
375     /***
376     @see DiskCache#add( String url )
377     */
378     public void add( String url, boolean force ) throws IOException {
379         String interned = url.intern();
380         this.fetch( url, 
381                     DiskCacheUtils.getFile( this, url ).getAbsolutePath(),
382                     force );
383         if(entries.get(interned) != null ) return;
384         entries.put(interned, new JetspeedDiskCacheEntry(interned)); 
385         URLManager.register( interned,
386                              URLManager.STATUS_OK,
387                              "Added by Program" );
388 
389     }
390     
391     /***
392 
393     @see DiskCache#fetch( String url, String cache )
394     @param url the url to retrieve
395     @param cache what file to store it in.
396     */
397     public String fetch( String url, 
398                          String cache ) throws IOException {
399         return fetch( url, cache, false );
400     }
401 
402     /***
403     Pulls in the remote URL from the net and saves it to disk
404 
405     @see DiskCache#fetch( String url, String cache )
406     @param url the url to retrieve
407     @param cache what file to store it in.
408     */
409     public String fetch( String url, 
410                          String cache,
411                          boolean force ) throws IOException {
412 
413         if (url == null) {
414             throw new IllegalArgumentException("url cannot be null");
415         }
416         
417         if (cache == null) {
418             throw new IllegalArgumentException("cache cannot be null");
419         }
420 
421         try {
422 
423             //The URL fecther will try to get the URL or it will throw 
424             //an Exception here.
425             Reader is = URLFetcher.fetch( url, force );
426             
427             OutputStreamWriter os = new OutputStreamWriter( new FileOutputStream( cache ),
428                                                            "utf-8" );
429 
430             //now process the InputStream...
431             char chars[] = new char[200];
432     
433             int readCount = 0;
434             while( ( readCount = is.read( chars )) > 0 ) {
435                 os.write(chars, 0, readCount);
436             }
437     
438             is.close();
439             os.close();
440     
441         } catch (MalformedURLException e) {
442             logger.error("Error in URL", e );
443         }
444     
445         return cache; 
446     
447     }
448 
449     /***
450     @see DiskCache#refresh
451     */
452     public void refresh( String url ) {
453         ThreadPool.process( new URLFetcherDownloader( url ) );        
454     }
455     
456     /***
457     Return the default instance of the JetspeedDiskCache cache.
458     */
459     public static JetspeedDiskCache getInstance() {
460 
461         return JetspeedDiskCache.getInstance( DEFAULT_CACHE_DIRECTORY );
462     }
463 
464     /***
465     Return the default instance of the JetspeedDiskCache cache.
466     
467     @param location A directory to store the cache at.
468     */
469     public static JetspeedDiskCache getInstance( String directory ) {
470 
471         synchronized(JetspeedDiskCache.instances) {
472 		
473             JetspeedDiskCache cache = (JetspeedDiskCache)JetspeedDiskCache.instances.get(directory);
474             
475             if (cache == null) {
476                 cache = new JetspeedDiskCache(directory);
477                 JetspeedDiskCache.instances.put( directory, cache );
478                 logger.info("DISK CACHE: Initing cache for " + directory);
479                 cache.initEntries();
480                 logger.info("DISK CACHE: Inited cache:" + directory);
481             }
482             return cache;
483         }
484     }
485 
486     /***
487     */
488     public boolean isCached(String url)
489     {
490         return entries.containsKey(url.intern());
491     }
492     
493 }
494 
495