    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/io/ecwapi/ECWFileCache.java $
    002    /*----------------    FILE HEADER  ------------------------------------------
    003
    004     This file is part of deegree.
    005     Copyright (C) 2007 by:
    006     Planetek Italia s.r.l, Bari, Italia
    007     http://www.planetek.it
    008
    009     This library is free software; you can redistribute it and/or
    010     modify it under the terms of the GNU Lesser General Public
    011     License as published by the Free Software Foundation; either
    012     version 2.1 of the License, or (at your option) any later version.
    013
    014     This library is distributed in the hope that it will be useful,
    015     but WITHOUT ANY WARRANTY; without even the implied warranty of
    016     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    017     Lesser General Public License for more details.
    018
    019     You should have received a copy of the GNU Lesser General Public
    020     License along with this library; if not, write to the Free Software
    021     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    022
    023     ---------------------------------------------------------------------------*/
    024    package org.deegree.io.ecwapi;
    025
    026    import java.util.HashSet;
    027    import java.util.Hashtable;
    028
    029    import com.ermapper.ecw.JNCSException;
    030    import com.ermapper.ecw.JNCSFile;
    031
    032    /**
    033     *
    034     *
    035     *
    036     * @version $Revision: 10660 $
    037     * @author <a href="mailto:stutte@planetek.it">Jens Stutte</a>
    038     * @author last edited by: $Author: apoth $
    039     *
    040     * @version 1.0. $Revision: 10660 $, $Date: 2008-03-24 22:39:54 +0100 (Mo, 24. Mär 2008) $
    041     *
    042     * @since 2.0
    043     */
    044    public class ECWFileCache {
    045
    046        private static Hashtable<String, HashSet<ECWFile>> ECWNamedFileCache = new Hashtable<String, HashSet<ECWFile>>();
    047
    048        private static HashSet<ECWFile> ECWAllFiles = new HashSet<ECWFile>();
    049
    050        private static Hashtable<JNCSFile, ECWFile> ECWJNCSFileRef = new Hashtable<JNCSFile, ECWFile>();
    051
    052        public static long EXPIRATION_PERIOD_MS = 600000;
    053
    054        public static long MAX_NUM_OPEN = 10;
    055
    056        // A private inner class that describes a cache entry
    057        private static class ECWFile {
    058            java.util.Date LastAccess;
    059
    060            JNCSFile myFile;
    061
    062            long lockingThread;
    063
    064            ECWFile( String fileName ) throws JNCSException {
    065                myFile = new JNCSFile( fileName, false );
    066                LastAccess = new java.util.Date();
    067                ECWAllFiles.add( this );
    068                ECWJNCSFileRef.put( myFile, this );
    069                lockingThread = 0;
    070            }
    071        }
    072
    073        // Retrieve the thread id.
    074        private static long getThreadId() {
    075            return Thread.currentThread().getId();
    076        }
    077
    078        // Find the opened instances for a file name
    079        private static HashSet<ECWFile> findNamedFileSet( String fileName ) {
    080            HashSet<ECWFile> files = ECWNamedFileCache.get( fileName );
    081            if ( files == null ) {
    082                files = new HashSet<ECWFile>();
    083                ECWNamedFileCache.put( fileName, files );
    084            }
    085            return files;
    086        }
    087
    088        // lock the file for the current thread, opens new instance
    089        // if nothing present in cache
    090        private static ECWFile lockFile( String fileName )
    091                                throws JNCSException {
    092            // Is there an unused instance in the cache?
    093            HashSet<ECWFile> files = findNamedFileSet( fileName );
    094            ECWFile[] fa = files.toArray( new ECWFile[files.size()] );
    095            for ( int i = 0; i < fa.length; i++ ) {
    096                if ( fa[i].lockingThread == 0 || fa[i].lockingThread == getThreadId() ) {
    097                    fa[i].lockingThread = getThreadId();
    098                    fa[i].LastAccess = new java.util.Date();
    099                    return fa[i];
    100                }
    101            }
    102            // no: create new one
    103            ECWFile f = new ECWFile( fileName );
    104            files.add( f );
    105            f.lockingThread = getThreadId();
    106
    107            return f;
    108        }
    109
    110        // release file for use
    111        private static void unlockFile( ECWFile file ) {
    112            if ( file != null ) {
    113                file.lockingThread = 0;
    114            }
    115        }
    116
    117        // close (and delete) file access instance
    118        private static void closeFile( ECWFile file ) {
    119            findNamedFileSet( file.myFile.fileName ).remove( file );
    120            ECWAllFiles.remove( file );
    121            ECWJNCSFileRef.remove( file.myFile );
    122            file.myFile.close( true );
    123        }
    124
    125        // Find and close expired cache entries.
    126        private static void closeExpired() {
    127            synchronized ( ECWNamedFileCache ) {
    128                ECWFile oldest = null;
    129                int numOpen = 0;
    130                ECWFile[] fa = ECWAllFiles.toArray( new ECWFile[ECWAllFiles.size()] );
    131                java.util.Date now = new java.util.Date();
    132                for ( int i = 0; i < fa.length; i++ ) {
    133                    if ( fa[i].lockingThread == 0 ) {
    134                        numOpen++;
    135                        if ( now.getTime() - fa[i].LastAccess.getTime() > EXPIRATION_PERIOD_MS ) {
    136                            closeFile( fa[i] );
    137                        } else if ( oldest == null || oldest.LastAccess.getTime() > fa[i].LastAccess.getTime() ) {
    138                            oldest = fa[i];
    139                        }
    140                    }
    141                }
    142                if ( oldest != null && numOpen > MAX_NUM_OPEN ) {
    143                    closeFile( oldest );
    144                }
    145            }
    146        }
    147
    148        /**
    149         * Claim access to an ECW file.
    150         * <p>
    151         * If there is a non-expired instance in the cache, re-uses and locks this. Otherwise adds a new
    152         * one to the cache.
    153         * <p>
    154         * CAUTION: This cache DOES NOT HANDLE NESTED LOCKS/UNLOCKS ! This means, that after having
    155         * called claimAcces, you MUST call releaseFile before ANY OTHER OPERATION ON THE CACHE within
    156         * the same thread.
    157         * <p>
    158         * NOTE: There is no periodical cleanup of this cache. The expiration method is called only
    159         * during new calls to claimAccess/releaseFile. So server memory consumption may remain high for
    160         * a much longer time than the defined ExpirationPeriod.
    161         */
    162        public static JNCSFile claimAccess( String fileName )
    163                                throws JNCSException {
    164            synchronized ( ECWNamedFileCache ) {
    165                // close old instances
    166                closeExpired();
    167                ECWFile f = lockFile( fileName );
    168                return f.myFile;
    169            }
    170        }
    171
    172        /**
    173         * Release access to an ECW file.
    174         * <p>
    175         * Unlocks the cache entry. Calls also the expiration method for cleanup of the instances that
    176         * exceed MaxNumOpen.
    177         */
    178        public static void releaseFile( JNCSFile myfile ) {
    179            synchronized ( ECWNamedFileCache ) {
    180                unlockFile( ECWJNCSFileRef.get( myfile ) );
    181                closeExpired();
    182            }
    183        }
    184
    185        /**
    186         * Set expiration period
    187         * <p>
    188         * Set the time in milliseconds that a cache entry remains valid. Calls also the expiration
    189         * method for cleanup.
    190         * <p>
    191         * Default value: 600000
    192         */
    193        public static void setExpirationPeriod( long MilliSecs ) {
    194            EXPIRATION_PERIOD_MS = MilliSecs;
    195            closeExpired();
    196        }
    197
    198        /**
    199         * Set maximum number of unused file instances
    200         * <p>
    201         * This parameter describes the maximum number of UNLOCKED entries in the cache (locked
    202         * instances are not counted). Calls also the expiration method for cleanup.
    203         * <p>
    204         * Default value: 10
    205         */
    206        public static void setMaxNumOpen( long MaxOpen ) {
    207            MAX_NUM_OPEN = MaxOpen;
    208            closeExpired();
    209        }
    210    }