Java tutorial
package org.apache.jcs.auxiliary.disk.block; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jcs.auxiliary.disk.LRUMapJCS; import org.apache.jcs.utils.timing.ElapsedTimer; import EDU.oswego.cs.dl.util.concurrent.ClockDaemon; import EDU.oswego.cs.dl.util.concurrent.ThreadFactory; /** * This is responsible for storing the keys. * <p> * @author Aaron Smuts */ public class BlockDiskKeyStore { /** The logger */ private static final Log log = LogFactory.getLog(BlockDiskKeyStore.class); /** Attributes governing the behavior of the block disk cache. */ private BlockDiskCacheAttributes blockDiskCacheAttributes; /** The key to block map */ private Map keyHash; /** The file where we persist the keys */ private File keyFile; /** The name to prefix log messages with. */ private final String logCacheName; /** Name of the file where we persist the keys */ private String fileName; /** The maximum number of keys to store in memory */ private int maxKeySize; /** we need this so we can communicate free blocks to the data store when keys fall off the LRU */ private BlockDiskCache blockDiskCache; /** The root directory in which the keyFile lives */ private File rootDirectory; /** * The background key persister, one for all regions. */ private static ClockDaemon persistenceDaemon; /** * Set the configuration options. * <p> * @param cacheAttributes * @param blockDiskCache used for freeing * @throws Exception */ public BlockDiskKeyStore(BlockDiskCacheAttributes cacheAttributes, BlockDiskCache blockDiskCache) throws Exception { this.blockDiskCacheAttributes = cacheAttributes; this.logCacheName = "Region [" + this.blockDiskCacheAttributes.getCacheName() + "] "; this.fileName = this.blockDiskCacheAttributes.getCacheName(); this.maxKeySize = cacheAttributes.getMaxKeySize(); this.blockDiskCache = blockDiskCache; String rootDirName = cacheAttributes.getDiskPath(); this.rootDirectory = new File(rootDirName); this.rootDirectory.mkdirs(); if (log.isInfoEnabled()) { log.info(logCacheName + "Cache file root directory [" + rootDirName + "]"); } this.keyFile = new File(rootDirectory, fileName + ".key"); if (log.isInfoEnabled()) { log.info(logCacheName + "Key File [" + this.keyFile.getAbsolutePath() + "]"); } if (keyFile.length() > 0) { loadKeys(); // TODO verify somehow } else { initKeyMap(); } // add this region to the persistence thread. // TODO we might need to stagger this a bit. if (this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() > 0) { if (persistenceDaemon == null) { persistenceDaemon = new ClockDaemon(); persistenceDaemon.setThreadFactory(new MyThreadFactory()); } persistenceDaemon.executePeriodically( this.blockDiskCacheAttributes.getKeyPersistenceIntervalSeconds() * 1000, new Runnable() { public void run() { saveKeys(); } }, false); } } /** * Saves key file to disk. This gets the LRUMap entry set and write the entries out one by one * after putting them in a wrapper. */ protected void saveKeys() { try { ElapsedTimer timer = new ElapsedTimer(); int numKeys = keyHash.size(); if (log.isInfoEnabled()) { log.info(logCacheName + "Saving keys to [" + this.keyFile.getAbsolutePath() + "], key count [" + numKeys + "]"); } keyFile.delete(); keyFile = new File(rootDirectory, fileName + ".key"); FileOutputStream fos = new FileOutputStream(keyFile); BufferedOutputStream bos = new BufferedOutputStream(fos, 1024); ObjectOutputStream oos = new ObjectOutputStream(bos); try { // don't need to synchronize, since the underlying collection makes a copy Iterator keyIt = keyHash.entrySet().iterator(); while (keyIt.hasNext()) { Map.Entry entry = (Map.Entry) keyIt.next(); BlockDiskElementDescriptor descriptor = new BlockDiskElementDescriptor(); descriptor.setKey((Serializable) entry.getKey()); descriptor.setBlocks((int[]) entry.getValue()); // stream these out in the loop. oos.writeObject(descriptor); } } finally { oos.flush(); oos.close(); } if (log.isInfoEnabled()) { log.info(logCacheName + "Finished saving keys. It took " + timer.getElapsedTimeString() + " to store " + numKeys + " keys. Key file length [" + keyFile.length() + "]"); } } catch (Exception e) { log.error(logCacheName + "Problem storing keys.", e); } } /** * Resets the file and creates a new key map. */ protected void reset() { File keyFileTemp = new File(this.rootDirectory, fileName + ".key"); keyFileTemp.delete(); keyFile = new File(this.rootDirectory, fileName + ".key"); initKeyMap(); } /** * This is mainly used for testing. It leave the disk in tact, and just clears memory. */ protected void clearMemoryMap() { this.keyHash.clear(); } /** * Create the map for keys that contain the index position on disk. */ private void initKeyMap() { keyHash = null; if (maxKeySize >= 0) { keyHash = new LRUMap(maxKeySize); if (log.isInfoEnabled()) { log.info(logCacheName + "Set maxKeySize to: '" + maxKeySize + "'"); } } else { // If no max size, use a plain map for memory and processing efficiency. keyHash = new HashMap(); // keyHash = Collections.synchronizedMap( new HashMap() ); if (log.isInfoEnabled()) { log.info(logCacheName + "Set maxKeySize to unlimited'"); } } } /** * Loads the keys from the .key file. The keys are stored individually on disk. They are added * one by one to an LRUMap.. * <p> * @throws InterruptedException */ protected void loadKeys() throws InterruptedException { if (log.isInfoEnabled()) { log.info(logCacheName + "Loading keys for " + keyFile.toString()); } try { // create a key map to use. initKeyMap(); HashMap keys = new HashMap(); FileInputStream fis = new FileInputStream(keyFile); BufferedInputStream bis = new BufferedInputStream(fis); ObjectInputStream ois = new ObjectInputStream(bis); try { while (true) { BlockDiskElementDescriptor descriptor = (BlockDiskElementDescriptor) ois.readObject(); if (descriptor != null) { keys.put(descriptor.getKey(), descriptor.getBlocks()); } } } catch (EOFException eof) { // nothing } finally { ois.close(); } if (!keys.isEmpty()) { if (log.isDebugEnabled()) { log.debug(logCacheName + "Found " + keys.size() + " in keys file."); } keyHash.putAll(keys); if (log.isInfoEnabled()) { log.info(logCacheName + "Loaded keys from [" + fileName + "], key count: " + keyHash.size() + "; up to " + maxKeySize + " will be available."); } } } catch (Exception e) { log.error(logCacheName + "Problem loading keys for file " + fileName, e); } } /** * Gets the entry set. * <p> * @return entry set. */ public Set entrySet() { return this.keyHash.entrySet(); } /** * Gets the key set. * <p> * @return key set. */ public Set keySet() { return this.keyHash.keySet(); } /** * Gets the size of the key hash. * <p> * @return the number of keys. */ public int size() { return this.keyHash.size(); } /** * gets the object for the key. * <p> * @param key * @return Object */ public int[] get(Object key) { return (int[]) this.keyHash.get(key); } /** * Puts a int[] in the keyStore. * <p> * @param key * @param value */ public void put(Object key, int[] value) { this.keyHash.put(key, value); } /** * Remove by key. * <p> * @param key * @return BlockDiskElementDescriptor if it was present, else null */ public int[] remove(Object key) { return (int[]) this.keyHash.remove(key); } /** * Class for recylcing and lru. This implments the LRU overflow callback, so we can mark the * blocks as free. */ public class LRUMap extends LRUMapJCS { /** Don't change */ private static final long serialVersionUID = 4955079991472142198L; /** * <code>tag</code> tells us which map we are working on. */ public String tag = "orig"; /** * Default */ public LRUMap() { super(); } /** * @param maxKeySize */ public LRUMap(int maxKeySize) { super(maxKeySize); } /** * This is called when the may key size is reaced. The least recently used item will be * passed here. We will store the position and size of the spot on disk in the recycle bin. * <p> * @param key * @param value */ protected void processRemovedLRU(Object key, Object value) { blockDiskCache.freeBlocks((int[]) value); if (log.isDebugEnabled()) { log.debug(logCacheName + "Removing key: [" + key + "] from key store."); log.debug(logCacheName + "Key store size: [" + this.size() + "]."); } } } /** * Allows us to set the daemon status on the clockdaemon * @author aaronsm */ class MyThreadFactory implements ThreadFactory { /** * Ensures that we create daemon threads. * <p> * (non-Javadoc) * @see EDU.oswego.cs.dl.util.concurrent.ThreadFactory#newThread(java.lang.Runnable) */ public Thread newThread(Runnable runner) { Thread t = new Thread(runner); t.setDaemon(true); t.setPriority(Thread.MIN_PRIORITY); return t; } } }