Java Source Code: org.apache.jetspeed.cache.disk.JetspeedDiskCacheEntry


   1: /* ====================================================================
   2:  * The Apache Software License, Version 1.1
   3:  *
   4:  * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
   5:  * reserved.
   6:  *
   7:  * Redistribution and use in source and binary forms, with or without
   8:  * modification, are permitted provided that the following conditions
   9:  * are met:
  10:  *
  11:  * 1. Redistributions of source code must retain the above copyright
  12:  *    notice, this list of conditions and the following disclaimer.
  13:  *
  14:  * 2. Redistributions in binary form must reproduce the above copyright
  15:  *    notice, this list of conditions and the following disclaimer in
  16:  *    the documentation and/or other materials provided with the
  17:  *    distribution.
  18:  *
  19:  * 3. The end-user documentation included with the redistribution,
  20:  *    if any, must include the following acknowledgment:
  21:  *       "This product includes software developed by the
  22:  *        Apache Software Foundation (http://www.apache.org/)."
  23:  *    Alternately, this acknowledgment may appear in the software itself,
  24:  *    if and wherever such third-party acknowledgments normally appear.
  25:  *
  26:  * 4. The names "Apache" and "Apache Software Foundation" and
  27:  *     "Apache Jetspeed" must not be used to endorse or promote products
  28:  *    derived from this software without prior written permission. For
  29:  *    written permission, please contact apache@apache.org.
  30:  *
  31:  * 5. Products derived from this software may not be called "Apache" or
  32:  *    "Apache Jetspeed", nor may "Apache" appear in their name, without
  33:  *    prior written permission of the Apache Software Foundation.
  34:  *
  35:  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36:  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38:  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39:  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41:  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42:  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43:  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44:  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45:  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46:  * SUCH DAMAGE.
  47:  * ====================================================================
  48:  *
  49:  * This software consists of voluntary contributions made by many
  50:  * individuals on behalf of the Apache Software Foundation.  For more
  51:  * information on the Apache Software Foundation, please see
  52:  * <http://www.apache.org/>.
  53:  */
  54: 
  55: package org.apache.jetspeed.cache.disk;
  56: 
  57: 
  58: //jetspeed stuff
  59: import org.apache.jetspeed.util.URIEncoder;
  60: import org.apache.jetspeed.services.urlmanager.URLFetcher;
  61: import org.apache.jetspeed.services.resources.JetspeedResources;
  62: 
  63: //turbine stuff
  64: import org.apache.turbine.util.Log;
  65: 
  66: //standard java stuff
  67: import java.io.*;
  68: import java.net.*;
  69: 
  70: /**
  71:  *<p>A cache entry represents a data source that can be stored locally for
  72:  *efficiency.
  73:  *
  74:  *<p>It can deliver a string with its contents, but the preferred 
  75:  *way to access to the entry contents is through a Reader 
  76:  *that will get characters from it.
  77:  *
  78:  *<p>There are two kinds of entries:
  79:  *
  80:  *<ul>
  81:  *  <li>Local: It is not cached.
  82:  *  <li>Remote: It can be cached.
  83:  *</ul>
  84:  *
  85:  *<p>Remote entries can be in the following states:
  86:  *
  87:  *<ul>
  88:  *  <li>Invalid: It has no local reference, and the source is transiently or
  89:  *        permanently delivering errors
  90:  *  <li>Stale: It has no local reference, and it is quiet
  91:  *  <li>Loading: It has no local ref yet, and it is loading
  92:  *  <li>Refreshing: It has a local ref (current or expired),
  93:  *         and it is refreshing it
  94:  *  <li>Expired: It has a local ref, but its content is no longer valid
  95:  *  <li>Current: It has a valid local ref
  96:  *</ul>
  97:  *
  98:  *<p>TODO: Some data sources need to be written (i. e., are writable). For those,
  99:  * a mechanism need to be provided to write back the resource. We currently think
 100:  * about HTTP PUT as a mechanism.
 101:  *
 102:  *@author <a href="mailto:burton@apache.org">Kevin A. Burton</a>
 103:  *@author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
 104:  *@version $Id: JetspeedDiskCacheEntry.java,v 1.30 2002/01/17 11:32:33 sgala Exp $
 105:  **/
 106:	  public class JetspeedDiskCacheEntry implements DiskCacheEntry {
 107:
 108:    /**
 109:     * <p>Expiration interval that will be used it the remote URL does not
 110:     * specify one. The contract here is:
 111:     * <ul>
 112:     * <li>If we have no hits, we will hit our entry every time DiskCacheDaemon is run to revalidate.</li>
 113:     * <li>No matter how many hits we get, we will reach our entry at most once per defaultExpirationInterval.</li>
 114:     * </ul>
 115:     */
 116:    private static long defaultExpirationInterval = 1000 * 
 117:        JetspeedResources.getInt( JetspeedResources.DEFAULT_DCE_EXPIRATION_TIME_KEY, 15 * 60 ); 
 118:
 119:    //Used for Local URL writing
 120:    static String encoding = JetspeedResources.getString(
 121:                JetspeedResources.CONTENT_ENCODING_KEY, "iso-8859-1" );
 122:
 123:    private File    file        = null;
 124:    private String  url         = null;
 125:    private String  sourceURL   = null;
 126:
 127:    /**
 128:     Date (ms since epoch) it was last Modified
 129:     */
 130:    private long  lastModified  = 0;
 131:    /**
 132:     Date (ms since epoch) it expires
 133:     */
 134:    private long  expires  = 0;
 135:    
 136:    /**
 137:     *<p>Build a DiskCacheEntry that is based on a cached filesystem entry
 138:     *
 139:     *<p>This is used to reconstruct the entries from the cache at boot time
 140:     *
 141:     *
 142:     */
 143:	      protected JetspeedDiskCacheEntry( File file ) {
 144:        this.setFile( file );
 145:        this.setURL( this.getSourceURL() );
 146:    }
 147:    
 148:   /**
 149:    *Build a DiskCacheEntry that is based on a remote URL and may be (or not)
 150:    *backed on disk.
 151:    *
 152:    */
 153:	      public JetspeedDiskCacheEntry( String url ) {
 154:
 155:        this.setURL( url );
 156:        this.init();
 157:    }
 158:
 159:    /**
 160:     * Initialize the file variable, if it is null & the url
 161:     * is not local
 162:     *
 163:    */
 164:	      public void init() {
 165:
 166:        URL url = null;
 167:
 168:        // first build the URL object
 169:	          try {
 170:            url = new URL(this.getURL());
 171:        } catch (MalformedURLException e) {
 172:            Log.error( e );
 173:            return;
 174:        }
 175:            
 176:        //if this is a file:/ based URL then build a file from it.
 177:        if ( (this.file == null || this.file.exists() == false ) 
 178:              && "file".equals( url.getProtocol() ) ) 
 179:	          {
 180:	              try {
 181:                File newfile = new File( url.getFile() );
 182:                
 183:                this.setFile( newfile );                
 184:	                  if( newfile.exists() == false ) {
 185:                    JetspeedDiskCache.getInstance().add( this.getURL(), true );
 186:                }
 187:            } catch ( IOException e ) {
 188:                Log.error( e );
 189:                return;
 190:            } 
 191:
 192:        }
 193:
 194:    }
 195:    
 196:    /**
 197:    */
 198:	      public String getURL() {
 199:        return this.url;
 200:    }
 201:    
 202:    /**
 203:    Reconstruct the original URL based on this URL.
 204:    */
 205:	      public String getSourceURL() {
 206:        //if getFile() is null then this isn't cached
 207:	          if ( this.getFile() == null ) {
 208:            return this.getURL();
 209:        } else {
 210:            return URIEncoder.decode( this.getFile().getName() );
 211:        }
 212:    }
 213:
 214:    /**
 215:    Get the File that this URL obtains within the cache.
 216:    */
 217:	      public File getFile() {
 218:        return this.file;
 219:    }
 220:    
 221:    /**
 222:    Set the file.
 223:    */
 224:	      public void setFile( File file ) {
 225:        //now make sure it exists.
 226:	          if ( file.exists() == false ) {
 227:            String message = "The following file does not exist: " + file.getAbsolutePath();
 228:            Log.error( message );
 229:	              try {
 230:                JetspeedDiskCache.getInstance().add( this.url, true );
 231:            } catch (Throwable e) {
 232:            Log.error( e );
 233:                }
 234:        }
 235:        //file should exist after add in the Cache...
 236:        this.file = file;
 237:        this.lastModified = file.lastModified();
 238:        this.expires = System.currentTimeMillis() + 
 239:            defaultExpirationInterval;
 240:    }
 241:    
 242:    /**
 243:    Open this URL and read its data, then return it as a string
 244:    */
 245:	      public String getData() throws IOException {
 246:
 247:      Reader is = this.getReader();
 248:      StringWriter bos = new StringWriter();
 249:      
 250:      //now process the Reader...
 251:      char chars[] = new char[200];
 252:    
 253:      int readCount = 0;
 254:	        while( ( readCount = is.read( chars )) > 0 ) {
 255:      bos.write(chars, 0, readCount);
 256:      }
 257:
 258:      is.close();
 259:
 260:      return bos.toString();
 261:        
 262:    }
 263:
 264:    /**
 265:    Get an input stream  from this entry 
 266:    */
 267:	      public InputStream getInputStream() throws IOException {
 268:        Log.info( "CacheEntry getInputStream() called: " + this.getURL()  );
 269:        if(this.getFile() != null)
 270:	              {
 271:                return new FileInputStream( this.getFile() );
 272:            }
 273:
 274:        if(DiskCacheUtils.isLocal( this.getURL() ) )
 275:	              {
 276:                return new URL( this.getURL() ).openConnection().getInputStream();
 277:            }
 278:
 279:        this.lastModified = 0;
 280:        this.expires = 0;
 281:        URLFetcher.refresh( this.getURL() );
 282:        if(this.getFile() != null)
 283:            return new FileInputStream( this.getFile() );
 284:        throw new IOException( this.getURL() + 
 285:                               ": is not in cache after forcing" );
 286:  }
 287:
 288:    /**
 289:    Get a Reader  from this entry.
 290:        ( Patch for handling character encoding sent by 
 291:          Yoshihiro KANNA  <y-kanna@bl.jp.nec.com> )
 292:      For local entries, we assume that the URL coming
 293:       from the WEB server is allright WRT encoding
 294:    For remote entries, we assume that the cache saved them in the local store
 295:        using UTF8 encoding
 296:    */
 297:	      public Reader getReader() throws IOException {
 298:
 299:        if(DiskCacheUtils.isLocal( this.getURL() ) )
 300:	              {
 301:                URLConnection conn = new URL( this.getURL() ).openConnection();
 302:                // If the URL has a proper encoding, use it
 303:                String encoding = conn.getContentEncoding();
 304:	                  if(encoding == null) {
 305:                    // else take it from configuration
 306:                    encoding = JetspeedResources.getString( JetspeedResources.CONTENT_ENCODING_KEY, 
 307:                                                            "iso-8859-1" );
 308:                }
 309:                //Log.info("Disk Cache Entry: getReader URL -> " +
 310:                //         this.getURL() +
 311:                //         " encoding -> " + 
 312:                //         encoding );
 313:                return new InputStreamReader(conn.getInputStream(),
 314:                                             encoding );
 315:            }
 316:        
 317:        if(this.getFile() != null)
 318:	              {
 319:                InputStreamReader reader = null;
 320:	                  try {
 321:                    //For cache files, we are assuming UTF8
 322:                    // instead of local encoding
 323:                    reader = new InputStreamReader( new FileInputStream( this.getFile() ), "UTF8" );
 324:                } catch (UnsupportedEncodingException e) {
 325:                    Log.error( e );
 326:                    reader = new FileReader( this.getFile() );
 327:                }
 328:                //Log.info("Disk Cache Entry: getReader file -> " + 
 329:                //         this.getURL()  +
 330:                //         " encoding -> " + 
 331:                //         reader.getEncoding() );
 332:                return reader;
 333:            }
 334:
 335:        this.lastModified = 0;
 336:        this.expires = 0;
 337:        URLFetcher.refresh( this.getURL() );
 338:        // If it is in the cache, call recursively...
 339:        if(this.getFile() != null)
 340:            return this.getReader();
 341:        throw new IOException( this.getURL() + 
 342:                               ": is not in cache after forcing" );
 343:
 344:    }
 345:    
 346:    /**
 347:    Get a Writer  to update this entry.
 348:      For local entries, we assume that the URL coming
 349:       from the WEB server allows PUT
 350:    For remote entries, we throws a IOException
 351:
 352:    */
 353:	      public Writer getWriter() throws IOException {
 354:
 355:	          if( DiskCacheUtils.isRemote( this.getURL() ) ) {
 356:            throw new IOException("Cannot write to remote URLs!");
 357:        }
 358:
 359:        if(DiskCacheUtils.isLocal( this.getURL() ) )
 360:	              {
 361:                URL url = new URL( this.getURL() );
 362:
 363:                if (url.getProtocol().equalsIgnoreCase("http"))
 364:	                  {
 365:                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
 366:                    conn.setDoOutput(true);
 367:                    conn.setRequestMethod("PUT");
 368:                    return new HttpURLWriter( conn );
 369:                }
 370:                else
 371:	                  {
 372:                    File file = new File( url.getFile() );
 373:                    file.getParentFile().mkdirs();
 374:                    return new FileURLWriter( file );
 375:                }
 376:
 377:            
 378:            }
 379:
 380:        throw new IOException( this.getURL() + 
 381:                               ": is not local or remote" );
 382:
 383:    }
 384:    
 385:    /**
 386:       Return the last modified date of this entry.
 387:    */
 388:	      public long getLastModified() { 
 389:	          if( isLocal() ) {
 390:	              try {
 391:                String localfile = this.getURL().substring(5); //remove "file:"
 392:                this.lastModified = new File( localfile ).lastModified();
 393:            } catch ( Exception e ) {
 394:                e.printStackTrace();
 395:            }
 396:        }
 397:        return this.lastModified;
 398:   
 399:    }
 400:
 401:    /**
 402:    Set the last modified date of this entry.
 403:    */
 404:	      public void setLastModified(long time) { 
 405:        this.lastModified = time;
 406:        
 407:    }
 408:
 409:    /**
 410:       Set the url on which this is based.
 411:    */
 412:	      public void setURL( String url ) {
 413:
 414:	          if ( DiskCacheUtils.isVirtual( url ) ) {
 415:            url = DiskCacheUtils.getLocalURL( url );
 416:        }
 417:        
 418:        this.url = url;
 419:    }
 420:    
 421:    /**
 422:    Set the expiration  date of this entry.
 423:    */
 424:	      public long getExpirationTime() { 
 425:        return this.expires;
 426:    }
 427:
 428:    /**
 429:    Set the expiration  date of this entry.
 430:    */
 431:	      public void setExpirationTime(long time) { 
 432:        this.expires = time;
 433:        if(this.expires < System.currentTimeMillis())
 434:	              {
 435:                this.expires = System.currentTimeMillis() +
 436:                    defaultExpirationInterval;
 437:            }
 438:        
 439:    }
 440:
 441:    /**
 442:    */
 443:	      public boolean hasExpired() { 
 444:        return this.expires <= 0 || 
 445:            this.expires < System.currentTimeMillis();
 446:    }
 447:
 448:    /**
 449:    */
 450:	      public boolean isLocal() { 
 451:
 452:        return DiskCacheUtils.isLocal(this.getSourceURL());
 453:    }
 454:
 455:    class HttpURLWriter extends OutputStreamWriter
 456:	      {
 457:        private HttpURLConnection conn;
 458:
 459:        public HttpURLWriter( HttpURLConnection conn )
 460:            throws UnsupportedEncodingException, IOException
 461:	          {
 462:            super( conn.getOutputStream(), encoding );
 463:            this.conn = conn;
 464:            Log.info("HttpURLWriter encoding -> " + 
 465:                     encoding + " method -> " + this.conn.getRequestMethod() );
 466:        }
 467:
 468:        public void close() throws IOException
 469:	          {
 470:            //We close the stream
 471:            super.close();
 472:            //Required to get the real connection sending PUT data
 473:            this.conn.getResponseCode();
 474:            Log.info("HttpURLWriter close encoding -> " + 
 475:                     encoding + " method -> " + this.conn.getRequestMethod() +
 476:                     " Status -> " + this.conn.getResponseCode() );
 477:            
 478:        }
 479:    }
 480:
 481:    class FileURLWriter extends FileWriter
 482:	      {
 483:        private String filename;
 484:
 485:        public FileURLWriter( File file )
 486:            throws UnsupportedEncodingException, IOException
 487:	          {
 488:            super( file );
 489:            this.filename = file.getPath();
 490:            Log.info("FileURLWriter opening file -> " + filename );
 491:        }
 492:
 493:        public void close() throws IOException
 494:	          {
 495:            //We close the stream
 496:            super.close();
 497:            Log.info("FileURLWriter closing file -> " + filename );
 498:
 499:        }
 500:    }
 501:}