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  
20  //jetspeed stuff
21  import org.apache.jetspeed.util.URIEncoder;
22  import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
23  import org.apache.jetspeed.services.logging.JetspeedLogger;
24  import org.apache.jetspeed.services.urlmanager.URLFetcher;
25  import org.apache.jetspeed.services.resources.JetspeedResources;
26  
27  //standard java stuff
28  import java.io.*;
29  import java.net.*;
30  
31  /***
32   *<p>A cache entry represents a data source that can be stored locally for
33   *efficiency.
34   *
35   *<p>It can deliver a string with its contents, but the preferred 
36   *way to access to the entry contents is through a Reader 
37   *that will get characters from it.
38   *
39   *<p>There are two kinds of entries:
40   *
41   *<ul>
42   *  <li>Local: It is not cached.
43   *  <li>Remote: It can be cached.
44   *</ul>
45   *
46   *<p>Remote entries can be in the following states:
47   *
48   *<ul>
49   *  <li>Invalid: It has no local reference, and the source is transiently or
50   *        permanently delivering errors
51   *  <li>Stale: It has no local reference, and it is quiet
52   *  <li>Loading: It has no local ref yet, and it is loading
53   *  <li>Refreshing: It has a local ref (current or expired),
54   *         and it is refreshing it
55   *  <li>Expired: It has a local ref, but its content is no longer valid
56   *  <li>Current: It has a valid local ref
57   *</ul>
58   *
59   *<p>TODO: Some data sources need to be written (i. e., are writable). For those,
60   * a mechanism need to be provided to write back the resource. We currently think
61   * about HTTP PUT as a mechanism.
62   *
63   *@author <a href="mailto:burton@apache.org">Kevin A. Burton</a>
64   *@author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
65   *@version $Id: JetspeedDiskCacheEntry.java,v 1.33 2004/02/23 02:45:29 jford Exp $
66   **/
67  public class JetspeedDiskCacheEntry implements DiskCacheEntry {
68  
69      /***
70       * <p>Expiration interval that will be used it the remote URL does not
71       * specify one. The contract here is:
72       * <ul>
73       * <li>If we have no hits, we will hit our entry every time DiskCacheDaemon is run to revalidate.</li>
74       * <li>No matter how many hits we get, we will reach our entry at most once per defaultExpirationInterval.</li>
75       * </ul>
76       */
77      private static long defaultExpirationInterval = 1000 * 
78          JetspeedResources.getInt( JetspeedResources.DEFAULT_DCE_EXPIRATION_TIME_KEY, 15 * 60 ); 
79  
80      //Used for Local URL writing
81      static String encoding = JetspeedResources.getString(
82                  JetspeedResources.CONTENT_ENCODING_KEY, "iso-8859-1" );
83  
84      private File    file        = null;
85      private String  url         = null;
86      private String  sourceURL   = null;
87  
88      /***
89       Date (ms since epoch) it was last Modified
90       */
91      private long  lastModified  = 0;
92      /***
93       Date (ms since epoch) it expires
94       */
95      private long  expires  = 0;
96      
97      /***
98       * Static initialization of the logger for this class
99       */
100     private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedDiskCacheEntry.class.getName());
101     
102     /***
103      *<p>Build a DiskCacheEntry that is based on a cached filesystem entry
104      *
105      *<p>This is used to reconstruct the entries from the cache at boot time
106      *
107      *
108      */
109     protected JetspeedDiskCacheEntry( File file ) {
110         this.setFile( file );
111         this.setURL( this.getSourceURL() );
112     }
113     
114    /***
115     *Build a DiskCacheEntry that is based on a remote URL and may be (or not)
116     *backed on disk.
117     *
118     */
119     public JetspeedDiskCacheEntry( String url ) {
120 
121         this.setURL( url );
122         this.init();
123     }
124 
125     /***
126      * Initialize the file variable, if it is null & the url
127      * is not local
128      *
129     */
130     public void init() {
131 
132         URL url = null;
133 
134         // first build the URL object
135         try {
136             url = new URL(this.getURL());
137         } catch (MalformedURLException e) {
138             logger.error("Error in URL", e);
139             return;
140         }
141             
142         //if this is a file:/ based URL then build a file from it.
143         if ( (this.file == null || this.file.exists() == false ) 
144               && "file".equals( url.getProtocol() ) ) 
145         {
146             try {
147                 File newfile = new File( url.getFile() );
148                 
149                 this.setFile( newfile );                
150                 if( newfile.exists() == false ) {
151                     JetspeedDiskCache.getInstance().add( this.getURL(), true );
152                 }
153             } catch ( IOException e ) {
154                 logger.error("Error building from file", e);
155                 return;
156             } 
157 
158         }
159 
160     }
161     
162     /***
163     */
164     public String getURL() {
165         return this.url;
166     }
167     
168     /***
169     Reconstruct the original URL based on this URL.
170     */
171     public String getSourceURL() {
172         //if getFile() is null then this isn't cached
173         if ( this.getFile() == null ) {
174             return this.getURL();
175         } else {
176             return URIEncoder.decode( this.getFile().getName() );
177         }
178     }
179 
180     /***
181     Get the File that this URL obtains within the cache.
182     */
183     public File getFile() {
184         return this.file;
185     }
186     
187     /***
188     Set the file.
189     */
190     public void setFile( File file ) {
191         //now make sure it exists.
192         if ( file.exists() == false ) {
193             String message = "The following file does not exist: " + file.getAbsolutePath();
194             logger.error( message );
195             try {
196                 JetspeedDiskCache.getInstance().add( this.url, true );
197             } catch (Throwable e) {
198             logger.error("Error setting file", e );
199                 }
200         }
201         //file should exist after add in the Cache...
202         this.file = file;
203         this.lastModified = file.lastModified();
204         this.expires = System.currentTimeMillis() + 
205             defaultExpirationInterval;
206     }
207     
208     /***
209     Open this URL and read its data, then return it as a string
210     */
211     public String getData() throws IOException {
212 
213       Reader is = this.getReader();
214       StringWriter bos = new StringWriter();
215       
216       //now process the Reader...
217       char chars[] = new char[200];
218     
219       int readCount = 0;
220       while( ( readCount = is.read( chars )) > 0 ) {
221       bos.write(chars, 0, readCount);
222       }
223 
224       is.close();
225 
226       return bos.toString();
227         
228     }
229 
230     /***
231     Get an input stream  from this entry 
232     */
233     public InputStream getInputStream() throws IOException {
234         logger.info( "CacheEntry getInputStream() called: " + this.getURL()  );
235         if(this.getFile() != null)
236             {
237                 return new FileInputStream( this.getFile() );
238             }
239 
240         if(DiskCacheUtils.isLocal( this.getURL() ) )
241             {
242                 return new URL( this.getURL() ).openConnection().getInputStream();
243             }
244 
245         this.lastModified = 0;
246         this.expires = 0;
247         URLFetcher.refresh( this.getURL() );
248         if(this.getFile() != null)
249             return new FileInputStream( this.getFile() );
250         throw new IOException( this.getURL() + 
251                                ": is not in cache after forcing" );
252   }
253 
254     /***
255     Get a Reader  from this entry.
256         ( Patch for handling character encoding sent by 
257           Yoshihiro KANNA  <y-kanna@bl.jp.nec.com> )
258       For local entries, we assume that the URL coming
259        from the WEB server is allright WRT encoding
260     For remote entries, we assume that the cache saved them in the local store
261         using UTF8 encoding
262     */
263     public Reader getReader() throws IOException {
264 
265         if(DiskCacheUtils.isLocal( this.getURL() ) )
266             {
267                 URLConnection conn = new URL( this.getURL() ).openConnection();
268                 // If the URL has a proper encoding, use it
269                 String encoding = conn.getContentEncoding();
270                 if(encoding == null) {
271                     // else take it from configuration
272                     encoding = JetspeedResources.getString( JetspeedResources.CONTENT_ENCODING_KEY, 
273                                                             "iso-8859-1" );
274                 }
275                 //Log.info("Disk Cache Entry: getReader URL -> " +
276                 //         this.getURL() +
277                 //         " encoding -> " + 
278                 //         encoding );
279                 return new InputStreamReader(conn.getInputStream(),
280                                              encoding );
281             }
282         
283         if(this.getFile() != null)
284             {
285                 InputStreamReader reader = null;
286                 try {
287                     //For cache files, we are assuming UTF8
288                     // instead of local encoding
289                     reader = new InputStreamReader( new FileInputStream( this.getFile() ), "UTF8" );
290                 } catch (UnsupportedEncodingException e) {
291                     logger.error("Encoding error", e);
292                     reader = new FileReader( this.getFile() );
293                 }
294                 //Log.info("Disk Cache Entry: getReader file -> " + 
295                 //         this.getURL()  +
296                 //         " encoding -> " + 
297                 //         reader.getEncoding() );
298                 return reader;
299             }
300 
301         this.lastModified = 0;
302         this.expires = 0;
303         URLFetcher.refresh( this.getURL() );
304         // If it is in the cache, call recursively...
305         if(this.getFile() != null)
306             return this.getReader();
307         throw new IOException( this.getURL() + 
308                                ": is not in cache after forcing" );
309 
310     }
311     
312     /***
313     Get a Writer  to update this entry.
314       For local entries, we assume that the URL coming
315        from the WEB server allows PUT
316     For remote entries, we throws a IOException
317 
318     */
319     public Writer getWriter() throws IOException {
320 
321         if( DiskCacheUtils.isRemote( this.getURL() ) ) {
322             throw new IOException("Cannot write to remote URLs!");
323         }
324 
325         if(DiskCacheUtils.isLocal( this.getURL() ) )
326             {
327                 URL url = new URL( this.getURL() );
328 
329                 if (url.getProtocol().equalsIgnoreCase("http"))
330                 {
331                     HttpURLConnection conn = (HttpURLConnection) url.openConnection();
332                     conn.setDoOutput(true);
333                     conn.setRequestMethod("PUT");
334                     return new HttpURLWriter( conn );
335                 }
336                 else
337                 {
338                     File file = new File( url.getFile() );
339                     file.getParentFile().mkdirs();
340                     return new FileURLWriter( file );
341                 }
342 
343             
344             }
345 
346         throw new IOException( this.getURL() + 
347                                ": is not local or remote" );
348 
349     }
350     
351     /***
352        Return the last modified date of this entry.
353     */
354     public long getLastModified() { 
355         if( isLocal() ) {
356             try {
357                 String localfile = this.getURL().substring(5); //remove "file:"
358                 this.lastModified = new File( localfile ).lastModified();
359             } catch ( Exception e ) {
360                 if( logger.isDebugEnabled() ) {
361                     logger.debug("Error getLastModified ", e);
362                 }
363                 e.printStackTrace();
364             }
365         }
366         return this.lastModified;
367    
368     }
369 
370     /***
371     Set the last modified date of this entry.
372     */
373     public void setLastModified(long time) { 
374         this.lastModified = time;
375         
376     }
377 
378     /***
379        Set the url on which this is based.
380     */
381     public void setURL( String url ) {
382 
383         if ( DiskCacheUtils.isVirtual( url ) ) {
384             url = DiskCacheUtils.getLocalURL( url );
385         }
386         
387         this.url = url;
388     }
389     
390     /***
391     Set the expiration  date of this entry.
392     */
393     public long getExpirationTime() { 
394         return this.expires;
395     }
396 
397     /***
398     Set the expiration  date of this entry.
399     */
400     public void setExpirationTime(long time) { 
401         this.expires = time;
402         if(this.expires < System.currentTimeMillis())
403             {
404                 this.expires = System.currentTimeMillis() +
405                     defaultExpirationInterval;
406             }
407         
408     }
409 
410     /***
411     */
412     public boolean hasExpired() { 
413         return this.expires <= 0 || 
414             this.expires < System.currentTimeMillis();
415     }
416 
417     /***
418     */
419     public boolean isLocal() { 
420 
421         return DiskCacheUtils.isLocal(this.getSourceURL());
422     }
423 
424     class HttpURLWriter extends OutputStreamWriter
425     {
426         private HttpURLConnection conn;
427 
428         public HttpURLWriter( HttpURLConnection conn )
429             throws UnsupportedEncodingException, IOException
430         {
431             super( conn.getOutputStream(), encoding );
432             this.conn = conn;
433             logger.info("HttpURLWriter encoding -> " + 
434                      encoding + " method -> " + this.conn.getRequestMethod() );
435         }
436 
437         public void close() throws IOException
438         {
439             //We close the stream
440             super.close();
441             //Required to get the real connection sending PUT data
442             this.conn.getResponseCode();
443             logger.info("HttpURLWriter close encoding -> " + 
444                      encoding + " method -> " + this.conn.getRequestMethod() +
445                      " Status -> " + this.conn.getResponseCode() );
446             
447         }
448     }
449 
450     class FileURLWriter extends FileWriter
451     {
452         private String filename;
453 
454         public FileURLWriter( File file )
455             throws UnsupportedEncodingException, IOException
456         {
457             super( file );
458             this.filename = file.getPath();
459             logger.info("FileURLWriter opening file -> " + filename );
460         }
461 
462         public void close() throws IOException
463         {
464             //We close the stream
465             super.close();
466             logger.info("FileURLWriter closing file -> " + filename );
467 
468         }
469     }
470 }