001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }