001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_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: 6806 $
037     * @author <a href="mailto:stutte@planetek.it">Jens Stutte</a>
038     * @author last edited by: $Author: apoth $
039     *
040     * @version 1.0. $Revision: 6806 $, $Date: 2007-05-04 09:01:16 +0200 (Fr, 04 Mai 2007) $
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 ) throws JNCSException {
091            // Is there an unused instance in the cache?
092            HashSet<ECWFile> files = findNamedFileSet( fileName );
093            ECWFile[] fa = files.toArray( new ECWFile[files.size()] );
094            for ( int i = 0; i < fa.length; i++ ) {
095                if ( fa[i].lockingThread == 0 || fa[i].lockingThread == getThreadId() ) {
096                    fa[i].lockingThread = getThreadId();
097                    fa[i].LastAccess = new java.util.Date();
098                    return fa[i];
099                }
100            }
101            // no: create new one
102            ECWFile f = new ECWFile( fileName );
103            files.add( f );
104            f.lockingThread = getThreadId();
105    
106            return f;
107        }
108    
109        // release file for use
110        private static void unlockFile( ECWFile file ) {
111            if ( file != null ) {
112                file.lockingThread = 0;
113            }
114        }
115    
116        // close (and delete) file access instance
117        private static void closeFile( ECWFile file ) {
118            findNamedFileSet( file.myFile.fileName ).remove( file );
119            ECWAllFiles.remove( file );
120            ECWJNCSFileRef.remove( file.myFile );
121            file.myFile.close( true );
122        }
123    
124        // Find and close expired cache entries.
125        private static void closeExpired() {
126            synchronized ( ECWNamedFileCache ) {
127                ECWFile oldest = null;
128                int numOpen = 0;
129                ECWFile[] fa = ECWAllFiles.toArray( new ECWFile[ECWAllFiles.size()] );
130                java.util.Date now = new java.util.Date();
131                for ( int i = 0; i < fa.length; i++ ) {
132                    if ( fa[i].lockingThread == 0 ) {
133                        numOpen++;
134                        if ( now.getTime() - fa[i].LastAccess.getTime() > EXPIRATION_PERIOD_MS ) {
135                            closeFile( fa[i] );
136                        } else if ( oldest == null
137                                    || 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        /** Claim access to an ECW file.
149         *  <p>
150         *  If there is a non-expired instance in the cache, re-uses and locks this. 
151         *  Otherwise adds a new one to the cache.
152         *  <p>
153         *  CAUTION: This cache DOES NOT HANDLE NESTED LOCKS/UNLOCKS ! This means,
154         *  that after having called claimAcces, you MUST call releaseFile before
155         *  ANY OTHER OPERATION ON THE CACHE within the same thread.
156         *  <p>
157         *  NOTE: There is no periodical cleanup of this cache. The expiration
158         *  method is called only during new calls to claimAccess/releaseFile. 
159         *  So server memory consumption may remain high for a much longer time 
160         *  than the defined ExpirationPeriod.
161         */
162        public static JNCSFile claimAccess( String fileName ) throws JNCSException {
163            synchronized ( ECWNamedFileCache ) {
164                // close old instances
165                closeExpired();
166                ECWFile f = lockFile( fileName );
167                return f.myFile;
168            }
169        }
170    
171        /** Release access to an ECW file.
172         *  <p>
173         *  Unlocks the cache entry.
174         *  Calls also the expiration method for cleanup of
175         *  the instances that exceed MaxNumOpen.
176         */
177        public static void releaseFile( JNCSFile myfile ) {
178            synchronized ( ECWNamedFileCache ) {
179                unlockFile( ECWJNCSFileRef.get( myfile ) );
180                closeExpired();
181            }
182        }
183    
184        /** Set expiration period
185         *  <p>
186         *  Set the time in milliseconds that a cache entry remains valid.
187         *  Calls also the expiration method for cleanup.
188         *  <p>
189         *  Default value: 600000
190         */
191        public static void setExpirationPeriod( long MilliSecs ) {
192            EXPIRATION_PERIOD_MS = MilliSecs;
193            closeExpired();
194        }
195    
196        /** Set maximum number of unused file instances
197         *  <p>
198         *  This parameter describes the maximum number of UNLOCKED entries in the
199         *  cache (locked instances are not counted).
200         *  Calls also the expiration method for cleanup.
201         *  <p>
202         *  Default value: 10
203         */
204        public static void setMaxNumOpen( long MaxOpen ) {
205            MAX_NUM_OPEN = MaxOpen;
206            closeExpired();
207        }
208    }