Coverage Report - org.crosswire.jsword.book.sword.state.OpenFileStateManager
 
Classes in this File Line Coverage Branch Coverage Complexity
OpenFileStateManager
0%
0/91
0%
0/30
2.625
OpenFileStateManager$1
0%
0/10
0%
0/6
2.625
OpenFileStateManager$2
0%
0/4
N/A
2.625
 
 1  
 /**
 2  
  * Distribution License:
 3  
  * JSword is free software; you can redistribute it and/or modify it under
 4  
  * the terms of the GNU Lesser General Public License, version 2.1 or later
 5  
  * as published by the Free Software Foundation. This program is distributed
 6  
  * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 7  
  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 8  
  * See the GNU Lesser General Public License for more details.
 9  
  *
 10  
  * The License is available on the internet at:
 11  
  *      http://www.gnu.org/copyleft/lgpl.html
 12  
  * or by writing to:
 13  
  *      Free Software Foundation, Inc.
 14  
  *      59 Temple Place - Suite 330
 15  
  *      Boston, MA 02111-1307, USA
 16  
  *
 17  
  * © CrossWire Bible Society, 2013 - 2016
 18  
  *
 19  
  */
 20  
 package org.crosswire.jsword.book.sword.state;
 21  
 
 22  
 import java.util.HashMap;
 23  
 import java.util.Iterator;
 24  
 import java.util.Map;
 25  
 import java.util.Queue;
 26  
 import java.util.concurrent.ConcurrentLinkedQueue;
 27  
 import java.util.concurrent.Executors;
 28  
 import java.util.concurrent.ScheduledFuture;
 29  
 import java.util.concurrent.ThreadFactory;
 30  
 import java.util.concurrent.TimeUnit;
 31  
 
 32  
 import org.crosswire.jsword.book.BookException;
 33  
 import org.crosswire.jsword.book.BookMetaData;
 34  
 import org.crosswire.jsword.book.sword.BlockType;
 35  
 import org.slf4j.Logger;
 36  
 import org.slf4j.LoggerFactory;
 37  
 
 38  
 /**
 39  
  * Manages the creation and re-distribution of open file states. This increases
 40  
  * performance as more often than not, the same file state may be used. For
 41  
  * example we may be carrying out a contains() operation followed by a read to
 42  
  * disk for a particular key
 43  
  * 
 44  
  * Each {@link BookMetaData} has a corresponding a file state which is
 45  
  * different to another. Furthermore, concurrent accesses cannot share this file
 46  
  * state as the {@link OpenFileState} records where in the file it is, for
 47  
  * reading several verses together for example. As a result, we want to key a
 48  
  * lookup by {@link BookMetaData}, which then gives us a pool of available
 49  
  * file states... We create some more if none are available.
 50  
  * 
 51  
  * In order to prevent memory leaks (OpenFileStates might be quite heavy as they do some internal caching of file data..
 52  
  * In order to avoid many file references piling up in memory, we implement a background cleaning thread which will clean
 53  
  * up redundant keys every so often.
 54  
  *
 55  
  *
 56  
  * 
 57  
  * @see gnu.lgpl.License The GNU Lesser General Public License for details.
 58  
  * 
 59  
  * @author DM Smith
 60  
  * @author Chris Burrell
 61  
  */
 62  0
 public final class OpenFileStateManager {
 63  
     /**
 64  
      * prevent instantiation
 65  
      */
 66  0
     private OpenFileStateManager(final int cleanupIntervalSeconds, final int maxExpiry) {
 67  
         // no op
 68  0
         this.monitoringThread = Executors.newScheduledThreadPool(1, new ThreadFactory() {
 69  
             public Thread newThread(Runnable r) {
 70  0
                 Thread t = new Thread(r);
 71  0
                 t.setDaemon(true);
 72  0
                 return t;
 73  
             }
 74  
 
 75  0
         }).scheduleWithFixedDelay(new Runnable() {
 76  
             public void run() {
 77  
                 // check the state of the maps and queues... The queues may have too much in them and that will in turn max out
 78  
                 // the heap.
 79  0
                 long currentTime = System.currentTimeMillis();
 80  
 
 81  0
                 for (Queue<OpenFileState> e : OpenFileStateManager.this.metaToStates.values()) {
 82  0
                     for (Iterator<OpenFileState> iterator = e.iterator(); iterator.hasNext(); ) {
 83  0
                         final OpenFileState state = iterator.next();
 84  0
                         if (state.getLastAccess() + maxExpiry * 1000 < currentTime) {
 85  
                             //release resources
 86  0
                             state.releaseResources();
 87  
 
 88  
                             //remove from the queues
 89  0
                             iterator.remove();
 90  
                         }
 91  0
                     }
 92  
                 }
 93  0
             }
 94  
         }, 0, cleanupIntervalSeconds, TimeUnit.SECONDS);
 95  0
     }
 96  
 
 97  
     /**
 98  
      * Allow the caller to initialize with their own settings. Should the OpenFileStateManager already be initialized
 99  
      * a no-op will occur. No need for double-checked locking here
 100  
      * 
 101  
      * @param cleanupIntervalSeconds seconds before cleanup
 102  
      * @param maxExpiry 
 103  
      */
 104  
     public static synchronized void init(final int cleanupIntervalSeconds, final int maxExpiry) {
 105  0
         if (manager == null) {
 106  0
             manager = new OpenFileStateManager(cleanupIntervalSeconds, maxExpiry);
 107  
         } else {
 108  
             // already initialized
 109  0
             LOGGER.warn("The OpenFileStateManager has already been initialised, potentially with its default settings. The following values were ignored: cleanUpInterval [{}], maxExpiry=[{}]", Integer.toString(cleanupIntervalSeconds), Integer.toString(maxExpiry));
 110  
         }
 111  
 
 112  0
     }
 113  
 
 114  
     /**
 115  
      * Singleton instance method to return the one and only Open File State Manager
 116  
      * @return the singleton
 117  
      */
 118  
     public static OpenFileStateManager instance() {
 119  0
         if (manager == null) {
 120  0
             synchronized (OpenFileStateManager.class) {
 121  0
                 init(60, 60);
 122  0
             }
 123  
         }
 124  0
         return manager;
 125  
     }
 126  
 
 127  
     public RawBackendState getRawBackendState(BookMetaData metadata) throws BookException {
 128  0
         ensureNotShuttingDown();
 129  
 
 130  0
         RawBackendState state = getInstance(metadata);
 131  0
         if (state == null) {
 132  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 133  0
             return new RawBackendState(metadata);
 134  
         }
 135  
 
 136  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 137  0
         return state;
 138  
     }
 139  
 
 140  
     public RawFileBackendState getRawFileBackendState(BookMetaData metadata) throws BookException {
 141  0
         ensureNotShuttingDown();
 142  
 
 143  0
         RawFileBackendState state = getInstance(metadata);
 144  0
         if (state == null) {
 145  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 146  0
             return new RawFileBackendState(metadata);
 147  
         }
 148  
 
 149  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 150  0
         return state;
 151  
     }
 152  
 
 153  
     public GenBookBackendState getGenBookBackendState(BookMetaData metadata) throws BookException {
 154  0
         ensureNotShuttingDown();
 155  
 
 156  0
         GenBookBackendState state = getInstance(metadata);
 157  0
         if (state == null) {
 158  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 159  0
             return new GenBookBackendState(metadata);
 160  
         }
 161  
 
 162  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 163  0
         return state;
 164  
     }
 165  
 
 166  
     public RawLDBackendState getRawLDBackendState(BookMetaData metadata) throws BookException {
 167  0
         ensureNotShuttingDown();
 168  
 
 169  0
         RawLDBackendState state = getInstance(metadata);
 170  0
         if (state == null) {
 171  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 172  0
             return new RawLDBackendState(metadata);
 173  
         }
 174  
 
 175  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 176  0
         return state;
 177  
     }
 178  
 
 179  
     public ZLDBackendState getZLDBackendState(BookMetaData metadata) throws BookException {
 180  0
         ensureNotShuttingDown();
 181  
 
 182  0
         ZLDBackendState state = getInstance(metadata);
 183  0
         if (state == null) {
 184  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 185  0
             return new ZLDBackendState(metadata);
 186  
         }
 187  
 
 188  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 189  0
         return state;
 190  
     }
 191  
 
 192  
     public ZVerseBackendState getZVerseBackendState(BookMetaData metadata, BlockType blockType) throws BookException {
 193  0
         ensureNotShuttingDown();
 194  
 
 195  0
         ZVerseBackendState state = getInstance(metadata);
 196  0
         if (state == null) {
 197  0
             LOGGER.trace("Initializing: {}", metadata.getInitials());
 198  0
             return new ZVerseBackendState(metadata, blockType);
 199  
         }
 200  
 
 201  0
         LOGGER.trace("Reusing: {}", metadata.getInitials());
 202  0
         return state;
 203  
     }
 204  
 
 205  
     @SuppressWarnings("unchecked")
 206  
     private <T extends OpenFileState> T getInstance(BookMetaData metadata) {
 207  0
         Queue<OpenFileState> availableStates = getQueueForMeta(metadata);
 208  0
         final T state = (T) availableStates.poll();
 209  
 
 210  
         //while not strictly necessary, the documentation suggests that iterating through the collection
 211  
         //gives you a snapshot at some point in time, though not necessarily consistent, so just in case this remains
 212  
         //in access of the iterator() functionality, we update the last access date to avoid it being destroyed while we
 213  
         //use it
 214  0
         if (state != null) {
 215  0
             state.setLastAccess(System.currentTimeMillis());
 216  
         }
 217  0
         return state;
 218  
     }
 219  
 
 220  
     private Queue<OpenFileState> getQueueForMeta(BookMetaData metadata) {
 221  0
         Queue<OpenFileState> availableStates = metaToStates.get(metadata);
 222  0
         if (availableStates == null) {
 223  0
             synchronized (OpenFileState.class) {
 224  0
                 availableStates = new ConcurrentLinkedQueue<OpenFileState>();
 225  0
                 metaToStates.put(metadata, availableStates);
 226  0
             }
 227  
         }
 228  0
         return availableStates;
 229  
     }
 230  
 
 231  
     public void release(OpenFileState fileState) {
 232  0
         if (fileState == null) {
 233  
             // can't release anything. JSword has failed to open a file state,
 234  
             // and a finally block is trying to close this
 235  0
             return;
 236  
         }
 237  
 
 238  0
         fileState.setLastAccess(System.currentTimeMillis());
 239  
 
 240  
         // instead of releasing, we add to our queue
 241  0
         BookMetaData bmd = fileState.getBookMetaData();
 242  0
         Queue<OpenFileState> queueForMeta = getQueueForMeta(bmd);
 243  0
         LOGGER.trace("Offering to releasing: {}", bmd.getInitials());
 244  0
         boolean offered = queueForMeta.offer(fileState);
 245  
 
 246  
         // ignore if we couldn't offer to the queue
 247  0
         if (!offered) {
 248  0
             LOGGER.trace("Released: {}", bmd.getInitials());
 249  0
             fileState.releaseResources();
 250  
         }
 251  0
     }
 252  
 
 253  
     /**
 254  
      * Shuts down all open files
 255  
      */
 256  
     public void shutDown() {
 257  0
         shuttingDown = true;
 258  0
         this.monitoringThread.cancel(true);
 259  0
         for (Queue<OpenFileState> e : metaToStates.values()) {
 260  0
             OpenFileState state = null;
 261  0
             while ((state = e.poll()) != null) {
 262  0
                 state.releaseResources();
 263  
             }
 264  0
         }
 265  0
     }
 266  
 
 267  
     private void ensureNotShuttingDown() throws BookException {
 268  0
         if (shuttingDown) {
 269  0
             throw new BookException("Unable to read book, application is shutting down.");
 270  
         }
 271  0
     }
 272  
 
 273  
     private final ScheduledFuture<?> monitoringThread;
 274  0
     private final Map<BookMetaData, Queue<OpenFileState>> metaToStates = new HashMap<BookMetaData, Queue<OpenFileState>>();
 275  
     private volatile boolean shuttingDown;
 276  
 
 277  
     private static volatile OpenFileStateManager manager;
 278  0
     private static final Logger LOGGER = LoggerFactory.getLogger(OpenFileStateManager.class);
 279  
 }