All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.xma.boot.cache.FileCacheStore Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

/*
 * @(#) $Id: FileCacheStore.java 2084 2007-11-27 14:53:31Z s3460 $
 */
package at.spardat.xma.boot.cache;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;

import at.spardat.xma.boot.BootRuntime;
import at.spardat.xma.boot.Statics;
import at.spardat.xma.boot.logger.LogLevel;
import at.spardat.xma.boot.logger.Logger;
import at.spardat.xma.boot.transport.Result;
import at.spardat.xma.boot.util.PropertyFile;
import at.spardat.xma.boot.util.Util;

/**
 * file storage of the file cache
 * @author s2877
 * @since 1.3.0
 */
public class FileCacheStore {

    /** logger */
    private Logger log_;

    /**
     * debug file output
     */
    private Boolean debug;

    /** file lock timeout (milliseconds) */
    private static int lockTimeout  = 30000;
    private static int sleepOnLock  =  150;

    /**
     * The root directory for this file cache.
     * it is defined as //
     */
    private File baseDir_;

    /** used to format if-modified-since correctly */
    private DateFormat formater;


    /**
     * @throws IOException containing the filename
     */
    FileCacheStore(BootRuntime brt) throws IOException {
        log_ = Logger.getLogger( "boot.cache" ); //$NON-NLS-1$
        setBaseDir( brt.getDataDirectory());
        debug = brt.getDebug();
        formater = new SimpleDateFormat( "EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US );            //$NON-NLS-1$
        formater.setTimeZone(new SimpleTimeZone(0, "GMT")); //$NON-NLS-1$
    }


    /** @return the directory for this cache. */
    File getBaseDir() {
        return baseDir_;
    }

    /**
     * sets the root directory
     *
     * @param   fbaseDir   the base directory for the boot runtime.
     * @throws IOException containing the filename
     */
    private void setBaseDir(File fbaseDir) throws IOException {
        if( !fbaseDir.exists())
          throw new IllegalArgumentException( "base directory' "+fbaseDir.getAbsolutePath()+" 'for file-cache does not exist"); //$NON-NLS-1$

       File fCacheDir = new File( fbaseDir, Statics.CACHE_DIRNAME);
       if( !fCacheDir.exists()) {
           boolean success = fCacheDir.mkdirs();
           if(!success) throw new IOException("error creating "+fCacheDir.getAbsolutePath());
       }
       this.baseDir_ = fCacheDir;
    }

    /**
     * Retrieve a resource from the cache.
     * @param resourceFile the File to retrieve
     * @param urlRemote the ULR of the resource on the server
     * @return the found resource or null if it is not allready cached.
     */
    public FCResource getResource(File resourceFile,URL urlRemote) {
        try {
            return new FCResource(urlRemote, resourceFile, false, debug.booleanValue());
        } catch (FileNotFoundException ex) {
            log_.log(LogLevel.FINE, "file not cached: \"{0}\"", resourceFile.toString()); //$NON-NLS-1$
        } catch (IOException ioe) { // if we can't read it, we don't have it
            log_.log(LogLevel.WARNING, "error reading "+resourceFile.getAbsolutePath(),ioe);
        }
        return null;
    }

    /**
     * Put a resource into the cache.
     * @param file to write the content into
     * @param result content of the resource as received from the server
     * @param urlRemote of the content on the server
     * @param temp old expired version of the resource if exists in the cache
     * @param bmode if true the content of the resource file is read into memory imeditately
     * @param bforce indicates if an update of the resource was forced
     * @throws IOException containing the filename
     */
    public FCResource storeResource(File file,Result result,URL urlRemote,IFileCacheResource temp,boolean bmode, boolean bforce) throws IOException {
        FCResource fcr;
        File flock = lock(file);
        try {
            if(result.isModified() && temp!=null && ( temp.isExpired() || bforce == true) ) {
                if( file.delete() == false ) {
                    log_.log(LogLevel.WARNING, "file could not be deleted: \"{0}\"", file.toString() );                 //$NON-NLS-1$
                }
            }
            /* the resource was updated and a modified resource was loaded */
            if( result!=null && result.isModified()) {
                saveContent(file,result.getBuffer());
                createPropertyFile(file,result,urlRemote);
            }

            fcr = new FCResource(urlRemote,file, bmode,debug.booleanValue());

            /* the resource was checked for an updated, but was not modified */
            if(!result.isModified()) {
                fcr.setLastUpdated( System.currentTimeMillis() );
                fcr.setExpiration(result.getExpirationDate());
                fcr.store();
            }
        } finally {
           unlock(flock);
        }
        return fcr;
    }
    /**
     * Put a resource into the cache.
     * @param file to write the content into
     * @param result the resource as received from the server
     * @param content to store into the file
     * @param urlRemote of the content on the server
     * @param bmode if true the content of the resource file is read into memory imeditately
     * @throws IOException containing the filename
     */
    public FCResource storeResource(File file, Result result, byte[] content, URL urlRemote, boolean bmode) throws IOException {
        FCResource fcr;
        File flock = lock(file);
        try {
            saveContent(file,content);
            createPropertyFile(file,result,urlRemote);
            fcr = new FCResource(urlRemote,file, bmode,debug.booleanValue());
        } finally {
            unlock(flock);
        }
        return fcr;
    }

    /**
     * lock a resource by creating a lock file for the resource  fres .
     * 

* this is done by creating a new file with createNewFile. If an existing * lockfile is already found, it checks its validity. that means, the file must not be * older than lockTimeout . Older lockfiles are removed. *

* As other processes will not notify this process about lockfile changes, this process * will poll for changes. It waits sleepOnLock between each retry. *

* After a lockfile is remove by one process, another process may create a new lockfile * in between the delete and our retry for creation. Therefore it only tries and waits for * unlocking 3 times. *

* * @param fres the resource to lock. this is the base resource, without lock extension * @return File the lock-file * @throws IOException containing the filename */ File lock( File fres ) throws IOException { int tryLocking = 0; File lock = new File( fres.getPath() + Statics.LOCKFILE_EXT ); // lockfile if( !lock.exists()) lock.getParentFile().mkdirs(); try { while( tryLocking < 3 && lock.createNewFile() == false ) { // already existing lock-file. wait for unlock long fTime = 0L; long cTime; tryLocking++; do { // check if the lockfile is already too old long ltime = lock.lastModified(); if (ltime == 0L ) break; // 0 if it does not exist anymore if( fTime == 0L ) fTime = ltime; // save the current file-time if( ltime != fTime ) break; // if this new time differs, a new file has been created. cTime = System.currentTimeMillis(); if( (cTime-fTime) >= this.getLockTimeout() ) { // too old; invalid; cleanup lock.delete(); // this may also fail, if meanwhile another process has deleted this file. break; } try { Thread.sleep( getSleepOnLock() ); } catch (InterruptedException e) { } } while( ((cTime-fTime) < this.getLockTimeout()) && lock.exists() ); } // while createNewFile } catch( IOException e ) { IOException ne = new IOException("error creating lock file "+lock.getAbsolutePath()); ne.initCause(e); throw ne; } return lock; } /** * unlock a resource * * @param file the lockfile * @return void */ void unlock( File file ) { if( !file.exists()) { // throw new IllegalArgumentException("lockfile does not exist"); } log_.log(LogLevel.ALL, "locked for: {0} ms; file: {1}", new Object[] { new Long( System.currentTimeMillis()-file.lastModified()), file.getName() }); //$NON-NLS-1$ if( file.delete() == false ) log_.log(LogLevel.INFO, "lockfile delete failed: \"{0}\"", file.getName() ); //$NON-NLS-1$ }// unlock /** @return long lock timeout */ private long getLockTimeout() { return lockTimeout; } /** * returns the sleep time in between file-lock polling * * @return int sleep time */ private static int getSleepOnLock() { return sleepOnLock; } /** * saves transport result to disk. * * @param file file to save information to * @param result transport result * @throws IOException containing the filename */ void saveToDisk(File file, Result result, URL url) throws IOException { saveContent(file,result.getBuffer()); createPropertyFile(file,result,url); } /** * Stores the given data into the given file. If the file * does not exist, it is created together with needed parent directories. * If it exists it is overwritten. * @param file to write * @param data to write into the file * @throws IOException containing the filename if an I/O Error occures */ void saveContent(File file, byte[] data) throws IOException { OutputStream os = null; try { if (!file.exists()) { file.getParentFile().mkdirs(); file.createNewFile(); } os = new FileOutputStream(file); os.write(data); Util.close(os,file.getAbsolutePath()); } catch (IOException exc) { Util.close(os,file.getAbsolutePath()); if (file.exists()) file.delete(); IOException ne = new IOException("saving of '"+file.getAbsolutePath()+"' failed."); ne.initCause(exc); throw ne; } } /** * Creates the .ifo file corresponding to the given file and writes the properties of the * FCResource into it. These are the http-headers contained in 'result', the url and for * .jar files the Hashvalue. * @param file data file * @param result data received from the server * @param url of the corresponding resource on the server * @throws IOException containing the filename */ void createPropertyFile(File file,Result result, URL url) throws IOException { PropertyFile pf = null; try { // make shure, no old file lies around. File ifoFile = new File( file.getPath() + Statics.FILEINFO_EXT); if(ifoFile.exists()) ifoFile.delete(); pf = new PropertyFile( new File( file.getPath() + Statics.FILEINFO_EXT) ,debug.booleanValue()); pf.setPropertyNoflush( Statics.HTTP_EXPIRES, Long.toString(result.getExpirationDate() )); if(debug.booleanValue()) { pf.setPropertyNoflush(Statics.HTTP_EXPIRES + ".STR", formater.format(new Date(result.getExpirationDate())) ); } pf.setPropertyNoflush( Statics.HTTP_LAST_MODIFIED, Long.toString(result.getLastModified() )); if(debug.booleanValue()) { pf.setPropertyNoflush(Statics.HTTP_LAST_MODIFIED + ".STR", formater.format(new Date(result.getLastModified())) ); } pf.setPropertyNoflush(Statics.strContentLength, Long.toString(result.getContentLength() )); long lLastUpdated = System.currentTimeMillis(); pf.setPropertyNoflush(Statics.strLastUpdated, Long.toString(lLastUpdated)); if(debug.booleanValue()) { pf.setPropertyNoflush(Statics.strLastUpdated + ".STR", formater.format(new Date(lLastUpdated)) ); } pf.setPropertyNoflush(Statics.URL_NAME, url.toExternalForm() ); if(result.getEtag() != null ) pf.setPropertyNoflush( Statics.strEtag, result.getEtag()); log_.log( LogLevel.INFO, "cached new resource: \"{0}\"", url.toExternalForm()); //$NON-NLS-1$ if( url.getQuery() == null && file.toString().endsWith( ".jar") ) { JarFile jarfile = new JarFile(file); try { Manifest manifest = jarfile.getManifest(); Attributes attributes = manifest.getMainAttributes(); if(attributes!=null) { String strApplicationDigest = attributes.getValue(Statics.XMA_DIGEST); if( strApplicationDigest!=null) { pf.setPropertyNoflush( Statics.XMA_DIGEST, strApplicationDigest); }else { log_.log(LogLevel.WARNING, "Missing XMA-Digest for jarfile: " + url.toExternalForm() ); } } else { log_.log(LogLevel.WARNING, "Attributes missing for jarfile:" + url.toExternalForm() ); } } finally { try { jarfile.close(); } catch (IOException arg) { log_.log(LogLevel.WARNING,"error closing "+file.getAbsolutePath(),arg); } } } if( url.getQuery()==null && ( file.toString().endsWith( ".jar") || file.toString().endsWith( ".xml")) ) { pf.setPropertyNoflush(Statics.HTTP_MAX_AGE, "0" ); } if(result.getTransformations()!=null) { pf.setPropertyNoflush(Statics.TRANSFORM_HEADER,result.getTransformations()); } pf.store(); /* cleanup all file resources on any error */ } catch( Exception exc ) { if( file.exists()) file.delete(); if( pf!=null) { File f = pf.getStoreFile(); if( f!=null && f.exists()) { f.delete(); } } IOException ne = new IOException("saving of '"+file.getAbsolutePath()+"' failed."); ne.initCause(exc); throw ne; } } /** * deletes both files of the given FCResouce. * @param res to delete * @throws IOException containing the filename if the file locking failed */ void removeResource(FCResource res) throws IOException { remove(res.getResourceFile(),res.getProperties()); } /** * deltes the given file and its corresponding .ifo file. * @param file to delete * @throws IOException containing the filename if the file locking failed * @since 1.3.1 */ void removeResource(File file) throws IOException { remove(file,new File( file.getPath() + Statics.FILEINFO_EXT)); } /** * Removes a file together with its .ifo file. * @param fRes resource file to delete * @param fProp corresponding .ifo file to delete * @throws IOException containing the filename */ private void remove(File fRes,File fProp) throws IOException { File lock = lock(fRes); try { if( fRes.delete() == false) { log_.log(LogLevel.WARNING, "delete of resource failed: {0}", fRes.getAbsolutePath() ); //$NON-NLS-1$ } if( fProp.delete() == false ) { log_.log(LogLevel.WARNING, "delete of property failed: {0}", fProp.getAbsolutePath() ); //$NON-NLS-1$ } } finally { unlock(lock); } } /** * Find the latest previous version of the given file. * This is the file with the same name containing the greatest version number * less than the the version of the given file. If the name of the file does not * contain a valid version number, nothing will be found. * If there are more than one file with the same version number but different hash * values, they will be ignored. * @param file for which to search the previous version * @return the found resource or null. */ public FCResource findPreviousVersion(final File file) { File dir = file.getParentFile(); VersionNumber currentVersion = VersionNumber.parse(file.getName()); if(currentVersion==null) return null; FilenameFilter filter = new FilenameFilter() { final Pattern pattern = VersionNumber.searchPattern(file.getName()); public boolean accept(File dir, String name) { return pattern.matcher(name).matches(); } }; String[] oldNames = dir.list(filter); // find newest version older than current version // if there are more than on file for a version this particual version must be ignored! Set duplicates = new HashSet(); VersionNumber oldVersion = null; int index = -1 ; if(oldNames!=null) { boolean found=false; label: do { oldVersion = null; index = -1; for(int i=oldNames.length-1;i>=0;i--) { VersionNumber version = VersionNumber.parse(oldNames[i]); if(currentVersion.compareTo(version)<=0) continue; // newer or equal current version if(duplicates.contains(version)) continue; // known duplicate -> ignore if(version.equals(oldVersion)) { // new duplicate found duplicates.add(version); // -> add to known dupplicates continue label; // and restart search } if((oldVersion==null || oldVersion.compareTo(version)<0)) { oldVersion=version; index=i; } } found=true; } while(!found); } if(oldVersion!=null) { // previous version found load and return it try { FCResource res = new FCResource(null,new File(dir,oldNames[index]),false,debug.booleanValue()); res.versionNumber=oldVersion; return res; } catch (IOException e) { log_.log(LogLevel.WARNING,"error reading "+dir.getAbsolutePath()+File.separator+oldNames[index]+": ",e); return null; } } else { return null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy