go4it

just do it

Jive学习(四)--Jive缓存

表现层的cache

1.Cache类

/**
 * $RCSfile: Cache.java,v $
 * $Revision: 1.3 $
 * $Date: 2001/10/03 21:55:12 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */

package com.jivesoftware.util;

import java.util.*;
import com.jivesoftware.util.LinkedList;

/**
 * General purpose cache implementation. It stores objects associated with
 * unique keys in memory for fast access. All objects added to the cache must
 * implement the Cacheable interface, which requires objects to know their
 * size in memory. This restrictions allows the cache to never grow larger
 * than a specified amount.<p>
 *
 * If the cache does grow too large, objects will be removed such that those
 * that are accessed least frequently are removed first. Because expiration
 * happens automatically, the cache makes <b>no</b> gaurantee as to how long
 * an object will remain in cache after it is put in. The cache will return
 * null if the requested object is not found.<p>
 *
 * Optionally, a maximum lifetime for all objects can be specified. In that
 * case, objects will be deleted from cache after that amount of time, even
 * if they are frequently accessed. This feature is useful if objects put in
 * cache represent data that should be periodically refreshed; for example,
 * information from a database.<p>
 *
 * Cache is optimized for fast data access. The getObject() method has 0(n)
 * performance regardless of cache size. The other cache operations also
 * perform quite fast.<p>
 *
 * Cache objects are thread safe.<p>
 *
 * The algorithm for cache is as follows: a HashMap is maintained for fast
 * object lookup. Two linked lists are maintained: one keeps objects in the
 * order they are accessed from cache, the other keeps objects in the order
 * they were originally added to cache. When objects are added to cache, they
 * are first wrapped by a CacheObject which maintains the following pieces
 * of information:<ul>
 *    <li> The size of the object (in bytes).
 *    <li> A pointer to the node in the linked list that maintains accessed
 *         order for the object. Keeping a reference to the node lets us avoid
 *         linear scans of the linked list.
 *    <li> A pointer to the node in the linked list that maintains the age
 *         of the object in cache. Keeping a reference to the node lets us avoid
 *         linear scans of the linked list.</ul>
 *
 * To get an object from cache, a hash lookup is performed to get a reference
 * to the CacheObject that wraps the real object we are looking for.
 * The object is subsequently moved to the front of the accessed linked list
 * and any necessary cache cleanups are performed. Cache deletion and expiration
 * is performed as needed.
 *
 * @see Cacheable
 */
public class Cache implements Cacheable {

    /**
     * One of the major potential bottlenecks of the cache is performing
     * System.currentTimeMillis() for every cache get operation. Instead,
     * we maintain a global timestamp that gets updated once a second. This
     * means that cache expirations can be no more than one second accurate.
     */
    protected static long currentTime = CacheTimer.currentTime;

    /**
     * Maintains the hash of cached objects. Hashing provides the best
     * performance for fast lookups.
     */
    protected HashMap cachedObjectsHash;

    /**
     * Linked list to maintain order that cache objects are accessed
     * in, most used to least used.
     */
    protected LinkedList lastAccessedList;

    /**
     * Linked list to maintain time that cache objects were initially added
     * to the cache, most recently added to oldest added.
     */
    protected LinkedList ageList;

   /**
    * Maximum size in bytes that the cache can grow to. Default
    * maximum size is 128 K.
    */
    protected int maxSize =  128 * 1024;

    /**
     * Maintains the current size of the cache in bytes.
     */
    protected int size = 0;

    /**
     * Maximum length of time objects can exist in cache before expiring.
     * Default is that objects have no maximum lifetime.
     */
    protected long maxLifetime = -1;

    /**
     * Maintain the number of cache hits and misses. A cache hit occurs every
     * time the get method is called and the cache contains the requested
     * object. A cache miss represents the opposite occurence.<p>
     *
     * Keeping track of cache hits and misses lets one measure how efficient
     * the cache is; the higher the percentage of hits, the more efficient.
     */
    protected long cacheHits, cacheMisses = 0L;

    /**
     * Create a new cache with default values. Default cache size is 128K with
     * no maximum lifetime.
     */
    public Cache() {
        // Our primary data structure is a hash map. The default capacity of 11
        // is too small in almost all cases, so we set it bigger.
        cachedObjectsHash = new HashMap(103);

        lastAccessedList = new LinkedList();
        ageList = new LinkedList();
    }

    /**
     * Create a new cache and specify the maximum size for the cache in bytes.
     * Items added to the cache will have no maximum lifetime.
     *
     * @param maxSize the maximum size of the cache in bytes.
     */
    public Cache(int maxSize) {
        this();
        this.maxSize = maxSize;
    }

    /**
     * Create a new cache and specify the maximum lifetime of objects. The
     * time should be specified in milleseconds. The minimum lifetime of any
     * cache object is 1000 milleseconds (1 second). Additionally, cache
     * expirations have a 1000 millesecond resolution, which means that all
     * objects are guaranteed to be expired within 1000 milliseconds of their
     * maximum lifetime.
     *
     * @param maxLifetime the maximum amount of time objects can exist in
     *    cache before being deleted.
     */
    public Cache(long maxLifetime) {
        this();
        this.maxLifetime = maxLifetime;
    }

    /**
     * Create a new cache and specify the maximum size of for the cache in
     * bytes, and the maximum lifetime of objects.
     *
     * @param maxSize the maximum size of the cache in bytes.
     * @param maxLifetime the maximum amount of time objects can exist in
     *    cache before being deleted.
     */
    public Cache(int maxSize, long maxLifetime) {
        this();
        this.maxSize = maxSize;
        this.maxLifetime = maxLifetime;
    }

    /**
     * Returns the current size of the cache in bytes.
     *
     * @return the size of the cache in bytes.
     */
    public int getSize() {
        return size;
    }

    /**
     * Returns the maximum size of the cache in bytes. If the cache grows too
     * large, the least frequently used items will automatically be deleted so
     * that the cache size doesn't exceed the maximum.
     *
     * @return the maximum size of the cache in bytes.
     */
    public int getMaxSize() {
        return maxSize;
    }

    /**
     * Sets the maximum size of the cache in bytes. If the cache grows too
     * large, the least frequently used items will automatically be deleted so
     * that the cache size doesn't exceed the maximum.
     *
     * @param maxSize the maximum size of the cache in bytes.
     */
    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
        // It's possible that the new max size is smaller than our current cache
        // size. If so, we need to delete infrequently used items.
        cullCache();
    }

    /**
     * Returns the number of objects in the cache.
     *
     * @return the number of objects in the cache.
     */
    public synchronized int getNumElements() {
        return cachedObjectsHash.size();
    }

    /**
     * Adds a new Cacheable object to the cache. The key must be unique.
     *
     * @param key a unique key for the object being put into cache.
     * @param object the Cacheable object to put into cache.
     */
    public synchronized void add(Object key, Cacheable object) {
        // Delete an old entry if it exists.
        remove(key);

        int objectSize = object.getSize();
        // If the object is bigger than the entire cache, simply don't add it.
        if (objectSize > maxSize * .90) {
            return;
        }
        size += objectSize;
        CacheObject cacheObject = new CacheObject(object, objectSize);
        cachedObjectsHash.put(key, cacheObject);
        // Make an entry into the cache order list.
        LinkedListNode lastAccessedNode = lastAccessedList.addFirst(key);
        // Store the cache order list entry so that we can get back to it
        // during later lookups.
        cacheObject.lastAccessedListNode = lastAccessedNode;
        // Add the object to the age list
        LinkedListNode ageNode = ageList.addFirst(key);
        // We make an explicit call to currentTimeMillis() so that total accuracy
        // of lifetime calculations is better than one second.
        ageNode.timestamp = System.currentTimeMillis();
        cacheObject.ageListNode = ageNode;

        // If cache is too full, remove least used cache entries until it is
        // not too full.
        cullCache();
    }

    /**
     * Gets an object from cache. This method will return null under two
     * conditions:<ul>
     *    <li>The object referenced by the key was never added to cache.
     *    <li>The object referenced by the key has expired from cache.</ul>
     *
     * @param key the unique key of the object to get.
     * @return the Cacheable object corresponding to unique key.
     */
    public synchronized Cacheable get(Object key) {
        // First, clear all entries that have been in cache longer than the
        // maximum defined age.
        deleteExpiredEntries();

        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
        if (cacheObject == null) {
            // The object didn't exist in cache, so increment cache misses.
            cacheMisses++;
            return null;
        }

        // The object exists in cache, so increment cache hits.
        cacheHits++;

        // Remove the object from it's current place in the cache order list,
        // and re-insert it at the front of the list.
        cacheObject.lastAccessedListNode.remove();
        lastAccessedList.addFirst(cacheObject.lastAccessedListNode);

        return cacheObject.object;
    }

    /**
     * Removes an object from cache.
     *
     * @param key the unique key of the object to remove.
     */
    public synchronized void remove(Object key) {
        CacheObject cacheObject = (CacheObject)cachedObjectsHash.get(key);
        // If the object is not in cache, stop trying to remove it.
        if (cacheObject == null) {
            return;
        }
        // remove from the hash map
        cachedObjectsHash.remove(key);
        // remove from the cache order list
        cacheObject.lastAccessedListNode.remove();
        cacheObject.ageListNode.remove();
        // remove references to linked list nodes
        cacheObject.ageListNode = null;
        cacheObject.lastAccessedListNode = null;
        // removed the object, so subtract its size from the total.
        size -= cacheObject.size;
    }

    /**
     * Clears the cache of all objects. The size of the cache is reset to 0.
     */
    public synchronized void clear() {
        Object [] keys = cachedObjectsHash.keySet().toArray();
        for (int i=0; i<keys.length; i++) {
            remove(keys[i]);
        }

        // Now, reset all containers.
        cachedObjectsHash.clear();
        cachedObjectsHash = new HashMap(103);
        lastAccessedList.clear();
        lastAccessedList = new LinkedList();
        ageList.clear();
        ageList = new LinkedList();

        size = 0;
        cacheHits = 0;
        cacheMisses = 0;
    }

    /**
     * Returns an array of the keys contained in the cache.
     *
     * @return an array of the keys present in the cache.
     */
    public Object [] keys() {
        return cachedObjectsHash.keySet().toArray();
    }

    /**
     * Returns an array of the values contained in the cache.
     *
     * @return a array of the cache entries.
     */
    public Object [] values() {
        Object [] cacheObjects = cachedObjectsHash.values().toArray();
        Object [] values = new Object[cacheObjects.length];
        for (int i=0; i<cacheObjects.length; i++) {
            values[i] = ((CacheObject)cacheObjects[i]).object;
        }
        return values;
    }

    /**
     * Returns the number of cache hits. A cache hit occurs every
     * time the get method is called and the cache contains the requested
     * object.<p>
     *
     * Keeping track of cache hits and misses lets one measure how efficient
     * the cache is; the higher the percentage of hits, the more efficient.
     *
     * @return the number of cache hits.
     */
    public long getCacheHits() {
        return cacheHits;
    }

    /**
     * Returns the number of cache misses. A cache miss occurs every
     * time the get method is called and the cache does not contain the
     * requested object.<p>
     *
     * Keeping track of cache hits and misses lets one measure how efficient
     * the cache is; the higher the percentage of hits, the more efficient.
     *
     * @return the number of cache hits.
     */
    public long getCacheMisses() {
        return cacheMisses;
    }

    /**
     * Clears all entries out of cache where the entries are older than the
     * maximum defined age.
     */
    private final void deleteExpiredEntries() {
        //Check if expiration is turned on.
        if (maxLifetime <= 0) {
            return;
        }

        // Remove all old entries. To do this, we remove objects from the end
        // of the linked list until they are no longer too old. We get to avoid
        // any hash lookups or looking at any more objects than is strictly
        // neccessary.
        LinkedListNode node = ageList.getLast();
        //If there are no entries in the age list, return.
        if (node == null) {
            return;
        }

        // Determine the expireTime, which is the moment in time that elements
        // should expire from cache. Then, we can do an easy to check to see
        // if the expire time is greater than the expire time.
        long expireTime = currentTime - maxLifetime;

        while(expireTime > node.timestamp) {
            // Remove the object
            remove(node.object);

            // Get the next node.
            node = ageList.getLast();
            // If there are no more entries in the age list, return.
            if (node == null) {
                return;
            }
        }
    }

    /**
     * Removes objects from cache if the cache is too full. "Too full" is
     * defined as within 3% of the maximum cache size. Whenever the cache is
     * is too big, the least frequently used elements are deleted until the
     * cache is at least 10% empty.
     */
    private final void cullCache() {
        // See if the cache size is within 3% of being too big. If so, clean out
        // cache until it's 10% free.
        if (size >= maxSize * .97) {
            // First, delete any old entries to see how much memory that frees.
            deleteExpiredEntries();
            int desiredSize = (int)(maxSize * .90);
            while (size > desiredSize) {
                // Get the key and invoke the remove method on it.
                remove(lastAccessedList.getLast().object);
            }
        }
    }
}
 
#################################################################################################################
2.LinkedList的addFirst方法
   public LinkedListNode addFirst(LinkedListNode node) {
        node.next = head.next;
        node.previous = head;
        node.previous.next = node;
        node.next.previous = node;
        return node;
    }
 

3.LinkedListNode的remove方法

 public void remove() {
        previous.next = next;
        next.previous = previous;
    }
 
#########################################################################################
 
数据层的cache:
 
4.DatabaseCacheManager
/**
 * $RCSfile: DatabaseCacheManager.java,v $
 * $Revision: 1.7 $
 * $Date: 2001/09/24 02:54:33 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */

package com.jivesoftware.forum.database;

import com.jivesoftware.util.*;
import com.jivesoftware.forum.*;

/**
 * Central cache management of all caches used by Jive. Cache sizes are stored
 * as the following Jive following Jive property values: <ul>
 *
 *      <li> <tt>cache.userCache.size</tt>
 *      <li> <tt>cache.groupCache.size</tt>
 *      <li> <tt>cache.forumCache.size</tt>
 *      <li> <tt>cache.threadCache.size</tt>
 *      <li> <tt>cache.messageCache.size</tt> </ul>
 *
 * All values should be in bytes. Cache can also be globally enabled or disabled.
 * This value is stored as the <tt>cache.enabled</tt> Jive property.
 */
public class DatabaseCacheManager {

    public static final int USER_CACHE = 0;
    public static final int GROUP_CACHE = 1;
    public static final int FORUM_CACHE = 2;
    public static final int THREAD_CACHE = 3;
    public static final int MESSAGE_CACHE = 4;
    public static final int USER_PERMS_CACHE = 5;

    public UserCache userCache;
    public GroupCache groupCache;
    public ForumCache forumCache;
    public ForumThreadCache threadCache;
    public ForumMessageCache messageCache;
    public UserPermissionsCache userPermsCache;

    private boolean cacheEnabled = true;

    private DbForumFactory factory;

    /**
     * Creates a new cache manager.
     */
    public DatabaseCacheManager(DbForumFactory factory) {
        this.factory = factory;

        // See if cache is supposed to be enabled.
        String enabled = JiveGlobals.getJiveProperty("cache.enabled");
        if (enabled != null) {
            try { cacheEnabled = Boolean.valueOf(enabled).booleanValue(); }
            catch (Exception e) { }
        }

        // Default cache sizes (small)
        int forumCacheSize = 512*1024;        // 1/2 MB
        int threadCacheSize = 512*1024;       // 1/2 MB
        int messageCacheSize = 1152*1024;     // 5/4 MB
        int userCacheSize = 512*1024;         // 1/2 MB
        int userPermCacheSize = 256*1024;     // 1/4 MB
        int groupCacheSize = 128*1024;        // 1/8 MB

        //Now, see if properties were set.
        String fCacheSize = JiveGlobals.getJiveProperty("cache.forumCache.size");
        if (fCacheSize != null) {
            try {  forumCacheSize = Integer.parseInt(fCacheSize);  }
            catch (Exception e) { }
        }
        String tCacheSize = JiveGlobals.getJiveProperty("cache.threadCache.size");
        if (tCacheSize != null) {
            try {  threadCacheSize = Integer.parseInt(tCacheSize);  }
            catch (Exception e) { }
        }
        String mCacheSize = JiveGlobals.getJiveProperty("cache.messageCache.size");
        if (mCacheSize != null) {
            try {  messageCacheSize = Integer.parseInt(mCacheSize);  }
            catch (Exception e) { }
        }
        String uCacheSize = JiveGlobals.getJiveProperty("cache.userCache.size");
        if (uCacheSize != null) {
            try {  userCacheSize = Integer.parseInt(uCacheSize);  }
            catch (Exception e) { }
        }
        String upCacheSize = JiveGlobals.getJiveProperty("cache.userPermCache.size");
        if (upCacheSize != null) {
            try {  userPermCacheSize = Integer.parseInt(upCacheSize);  }
            catch (Exception e) { }
        }
        String gCacheSize = JiveGlobals.getJiveProperty("cache.groupCache.size");
        if (gCacheSize != null) {
            try {  groupCacheSize = Integer.parseInt(gCacheSize);  }
            catch (Exception e) { }
        }

        int MINUTE = 1000*60;
        int HOUR = MINUTE*60;

        //Initialize all cache structures
        forumCache = new ForumCache(new LongCache(forumCacheSize, 6*HOUR), factory);
        threadCache = new ForumThreadCache(new LongCache(threadCacheSize, 6*HOUR), factory);
        messageCache = new ForumMessageCache(new LongCache(messageCacheSize, 6*HOUR), factory);
        userCache = new UserCache(new LongCache(userCacheSize, 6*HOUR), factory);
        groupCache = new GroupCache(new LongCache(groupCacheSize, 6*HOUR), factory);
        //The user permissions cache is a special one. It's actually a Cache
        //of Cache objects. Each of the cache objects in the main cache
        //corresponds to a particular forum, and is used to cache the
        //permissions that a user has for a forum. In order to handle this
        //requirement, we use a special subclass of Cache.
        userPermsCache = new UserPermissionsCache(
                new UserPermsCache(userPermCacheSize, 24*HOUR), factory
        );
    }

    public void clear(int cacheType) {
        getCache(cacheType).clear();
    }

    public long getHits(int cacheType) {
        return getCache(cacheType).getCacheHits();
    }

    public long getMisses(int cacheType) {
        return getCache(cacheType).getCacheMisses();
    }

    public int getSize(int cacheType) {
         return getCache(cacheType).getSize();
    }

    public int getMaxSize(int cacheType) {
        return getCache(cacheType).getMaxSize();
    }

    public void setMaxSize(int cacheType, int size) {
        getCache(cacheType).setMaxSize(size);
        // Save the size of the cache as a jive property
        switch (cacheType) {
            case FORUM_CACHE:
                JiveGlobals.setJiveProperty("cache.forumCache.size",String.valueOf(size));
                break;
            case THREAD_CACHE:
                JiveGlobals.setJiveProperty("cache.threadCache.size",String.valueOf(size));
                break;
            case MESSAGE_CACHE:
                JiveGlobals.setJiveProperty("cache.messageCache.size",String.valueOf(size));
                break;
            case USER_CACHE:
                JiveGlobals.setJiveProperty("cache.userCache.size",String.valueOf(size));
                break;
            case USER_PERMS_CACHE:
                JiveGlobals.setJiveProperty("cache.userPermsCache.size",String.valueOf(size));
                break;
            case GROUP_CACHE:
                JiveGlobals.setJiveProperty("cache.groupCache.size",String.valueOf(size));
                break;
            default:
                throw new IllegalArgumentException("Invalid cache type: " + cacheType);
        }
    }

    public int getNumElements(int cacheType) {
        return getCache(cacheType).getNumElements();
    }

    private DatabaseCache getCache(int cacheType) {
        switch (cacheType) {
            case FORUM_CACHE:
                return forumCache;
            case THREAD_CACHE:
                return threadCache;
            case MESSAGE_CACHE:
                return messageCache;
            case USER_CACHE:
                return userCache;
            case USER_PERMS_CACHE:
                return userPermsCache;
            case GROUP_CACHE:
                return groupCache;
            default:
                throw new IllegalArgumentException("Invalid cache type: " + cacheType);
        }
    }

    public boolean isCacheEnabled() {
        return cacheEnabled;
    }

    public void setCacheEnabled(boolean cacheEnabled) {
        // If we're setting cacheEnabled to false, clear all caches
        if (cacheEnabled == false) {
            // Iterate through each of the five caches.
            for (int i=USER_CACHE; i<USER_PERMS_CACHE; i++) {
                clear(i);
            }
        }
        this.cacheEnabled = cacheEnabled;
        JiveGlobals.setJiveProperty("cache.enabled", String.valueOf(cacheEnabled));
    }
}
 
###############################################################################################################
 
5.DatabaseCache
/**
 * $RCSfile: DatabaseCache.java,v $
 * $Revision: 1.3 $
 * $Date: 2001/07/31 05:38:47 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */

package com.jivesoftware.forum.database;

import com.jivesoftware.util.LongCache;

/**
 * A base class that defines the basic functionality needed to wrap the general
 * purpose CoolServlets cache classes to caches for specific Jive database
 * objects.
 *
 * @see LongCache
 */
public class DatabaseCache {

    protected LongCache cache;
    protected DbForumFactory factory;

    /**
     * Creates a new database cache object.
     *
     * @param cache a cache object to wrap.
     * @param factory a DbForumFactory to be used to perform Jive operations.
     */
    public DatabaseCache(LongCache cache, DbForumFactory factory) {
        this.cache = cache;
        this.factory = factory;
    }

    /**
     * Pass-thru method for LongCache.remove(long).
     *
     * @see LongCache#remove(long)
     */
    public void remove(long key) {
        cache.remove(key);
    }

    /**
     * Pass-thru method for LongCache.getCacheHits().
     *
     * @see LongCache#getCacheHits()
     */
    public long getCacheHits() {
        return cache.getCacheHits();
    }

    /**
     * Pass-thru for LongCache.getCacheMisses().
     *
     * @see LongCache#getCacheMisses()
     */
    public long getCacheMisses() {
        return cache.getCacheMisses();
    }

    public int getSize() {
        return cache.getSize();
    }

    public void setMaxSize(int maxSize) {
        cache.setMaxSize(maxSize);
    }

    public int getMaxSize() {
        return cache.getMaxSize();
    }

    public int getNumElements() {
        return cache.getNumElements();
    }

    public void clear() {
        cache.clear();
    }

    public boolean isEnabled() {
        return factory.cacheManager.isCacheEnabled();
    }
}
 
##############################################################################################################
 
6.ForumCache---先从缓存里查找,缓存里没有再从数据库中获取,再添入缓存
/**
 * $RCSfile: ForumCache.java,v $
 * $Revision: 1.4 $
 * $Date: 2001/09/18 16:40:03 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */

package com.jivesoftware.forum.database;

import com.jivesoftware.util.*;
import com.jivesoftware.forum.util.*;
import com.jivesoftware.forum.*;

/**
 * Cache for DbForum objects.
 */
public class ForumCache extends DatabaseCache {

    protected Cache forumIDCache = new Cache(128*1024, 6*JiveGlobals.HOUR);

    public ForumCache(LongCache cache, DbForumFactory forumFactory) {
        super(cache, forumFactory);
    }

    public DbForum get(long forumID) throws ForumNotFoundException {
        if (!isEnabled()) {
            return new DbForum(forumID, factory);
        }
        // Cache is enabled.
        DbForum forum = (DbForum)cache.get(forumID);
        if (forum == null) {
            forum = new DbForum(forumID, factory);
            cache.add(forumID, forum);
            // -- CACHE WARMUP DISABLED. SEEMS TO DO MORE HARM THAN GOOD. -- //
            // Finally, schedule a warmup of the cache for the forum.
            //TaskEngine.addTask(new ForumCacheWarmupTask(forum),
            //    Thread.NORM_PRIORITY);
        }
        return forum;
    }

    public Forum get(String name) throws ForumNotFoundException {
        // If cache is not enabled, do a new lookup of object
        if (!isEnabled()) {
            long forumID = factory.getForumID(name);
            return new DbForum(forumID, factory);
        }
        // Cache is enabled.
        CacheableLong forumIDLong = (CacheableLong)forumIDCache.get(name);

        // If id wan't found in cache, load it up and put it there.
        if (forumIDLong == null) {
            long forumID = factory.getForumID(name);
            forumIDLong = new CacheableLong(forumID);
            forumIDCache.add(name, forumIDLong);
        }
        return get(forumIDLong.getLong());
    }

    public void remove(long key) {
        DbForum forum = (DbForum)cache.get(key);
        if (forum == null) {
            return;
        }

        // Find the name of the forum and remove it from the cache.
        try {
            String name = forum.getName();
            forumIDCache.remove(name);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        forum.reset();

        // Now, expire relevant objects out of the factory.
        factory.popularForums = null;
        factory.popularThreads = null;
    }
}
 
#####################################################################################################################
 
7.DBForum
/**
 * $RCSfile: DbForum.java,v $
 * $Revision: 1.25 $
 * $Date: 2001/10/12 23:02:15 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */

package com.jivesoftware.forum.database;

import java.util.*;
import java.util.Date;
import java.sql.*;
import java.io.*;

import com.jivesoftware.forum.*;
import com.jivesoftware.util.*;
import com.jivesoftware.forum.gateway.*;


/**
 * Database implementation of the Forum interface. It loads and stores forum
 * information from a a database.
 *
 * @see Forum
 */
public class DbForum implements Forum, Cacheable {

    /** DATABASE QUERIES **/
    private static final String ALL_THREADS =
        "SELECT threadID from jiveThread WHERE forumID=?";
    private static final String ADD_THREAD =
        "UPDATE jiveThread set forumID=? WHERE threadID=?";
    private static final String MOVE_MESSAGES =
        "UPDATE jiveMessage set forumID=? WHERE threadID=?";
    protected static final String DELETE_THREAD =
        "DELETE FROM jiveThread WHERE threadID=?";
    private static final String DELETE_THREAD_PROPERTIES =
        "DELETE FROM jiveThreadProp WHERE threadID=?";
    private static final String LOAD_PROPERTIES =
        "SELECT name, propValue FROM jiveForumProp WHERE forumID=?";
    private static final String DELETE_PROPERTY =
        "DELETE FROM jiveForumProp WHERE forumID=? AND name=?";
    private static final String DELETE_PROPERTIES =
        "DELETE FROM jiveForumProp WHERE forumID=?";
    private static final String INSERT_PROPERTY =
        "INSERT INTO jiveForumProp(forumID,name,propValue) VALUES(?,?,?)";
    private static final String LOAD_FORUM =
        "SELECT forumID, name, description, modDefaultThreadVal, " +
        "modDefaultMsgVal, modMinThreadVal, modMinMsgVal, modifiedDate, " +
        "creationDate FROM jiveForum WHERE forumID=?";
    private static final String ADD_FORUM =
        "INSERT INTO jiveForum(forumID, name, description, modDefaultThreadVal, " +
        "modDefaultMsgVal, modMinThreadVal, modMinMsgVal, modifiedDate, creationDate)" +
        " VALUES (?,?,?,?,?,?,?,?,?)";
    private static final String SAVE_FORUM =
        "UPDATE jiveForum SET name=?, description=?, modDefaultThreadVal=?, " +
        "modDefaultMsgVal=?, modMinThreadVal=?, modMinMsgVal=?, " +
        "modifiedDate=?, creationDate=? WHERE forumID=?";
    private static final String UPDATE_FORUM_MODIFIED_DATE =
        "UPDATE jiveForum SET modifiedDate=? WHERE forumID=?";
    private static final String POPULAR_THREADS =
        "SELECT threadID, count(1) AS msgCount FROM jiveMessage WHERE " +
        "modifiedDate > ? AND forumID=? GROUP BY threadID ORDER BY msgCount DESC";
    private static final String POPULAR_THREADS_ORACLE =
        "SELECT /*+ INDEX (jiveMessage jiveMessage_mDate_idx) */ threadID, " +
        "count(1) AS msgCount FROM jiveMessage WHERE modifiedDate > ? " +
        "AND forumID=? GROUP BY threadID ORDER BY msgCount DESC";
     /*
        // Note, the above query includes hints for Oracle, which are necessary
        // so that modified date index will always be used. This is a great
        // tradeoff when the time window you're looking at is not excessively
        // large. MySQL also handles the query very quickly. If your own db
        // needs a hint, you may want to edit the sql logic to add it.
    */

    /**
     * Controls whether extended properties should be lazily loaded (not loaded
     * until requested). If the properties are infrequently used, this provides
     * a great speedup in initial object loading time. However, if your
     * application does use extended properties all the time, you may wish to
     * turn lazy loading off, as it's actually faster in total db lookup time
     * to load everything at once.
     */
    private static final boolean LAZY_PROP_LOADING = true;

    /**
     * Number of threadID's per cache block.
     */
    public static final int THREAD_BLOCK_SIZE = 200;
    /**
     * Number of messageID's per cache block.
     */
    public static final int MESSAGE_BLOCK_SIZE = 100;

    // Constant for an empty bock. This is returned in the case that there are
    // no results when trying to load a thread or message block.
    private static final long[] EMPTY_BLOCK = new long[0];

    // A ResultFilter is used to filter and sort the values that are returned
    // from the threads() and messages() methods. We use a default
    private static final ResultFilter DEFAULT_THREAD_FILTER =
            ResultFilter.createDefaultThreadFilter();
    private static final ResultFilter DEFAULT_MESSAGE_FILTER =
            ResultFilter.createDefaultMessageFilter();

    /**
     * Cache for lists of thread id's. The default size is 16K, which should
     * let us hold about 2000 thread id's in memory at once. If you have a lot
     * of memory and very large forums, you may wish to make the size of this
     * cache considerably larger.
     */
    protected Cache threadListCache = new Cache(16*1024, JiveGlobals.HOUR * 6);

    /**
     * Cache for lists of message id's. The default size is 8K, which should
     * let us hold about 100 message id's in memory at once. If you have a lot
     * of memory and very large forums, you may wish to make the size of this
     * cache considerably larger.
     */
    protected Cache messageListCache = new Cache(8*1024, JiveGlobals.HOUR * 6);

    /**
     * Cache for thread counts. Default size is 256 bytes.
     */
    protected Cache threadCountCache = new Cache(256, JiveGlobals.HOUR * 6);

    /**
     * Cache for message counts. Default size is 256 bytes.
     */
    protected Cache messageCountCache = new Cache(256, JiveGlobals.HOUR * 6);

    private long id = -1;
    private String name;
    private String description;
    private java.util.Date creationDate;
    private java.util.Date modifiedDate;
    private int modDefaultThreadValue = 1;
    private int modDefaultMessageValue = 1;
    private int modMinThreadValue = 1 ;
    private int modMinMessageValue = 1;
    private Map properties;

    private DbForumFactory factory;
    private long[] popularThreads = null;
    private DbFilterManager filterManager;

    /**
     * Creates a new forum with the specified name and description.
     *
     * @param name the name of the forum.
     * @param description the description of the forum.
     * @param factory the DbForumFactory the forum is a part of.
     */
    protected DbForum(String name, String description, DbForumFactory factory) {
        this.id = SequenceManager.nextID(JiveGlobals.FORUM);
        this.name = name;
        this.description = description;
        long now = System.currentTimeMillis();
        creationDate = new java.util.Date(now);
        modifiedDate = new java.util.Date(now);
        this.factory = factory;
        insertIntoDb();
        properties = new Hashtable();
        init();
    }

      public void setName(String name) throws ForumAlreadyExistsException {
        // If the new name is the same as the current name, do nothing.
        if (this.name.equals(name)) {
            return;
        }
        // If a forum with the new name already exists, throw an exception.
        try {
            // Check to make sure that's it's not just a case change. If we
            // don't do this check, the db lookup may give us a false-positive
            // that the name is already in use.
            if (!this.name.toLowerCase().equals(name.toLowerCase())) {
                Forum forum = factory.getForum(name);
                // If we get here, the forum must already exist, so throw exception.
                throw new ForumAlreadyExistsException();
            }
        }
        catch (ForumNotFoundException e) { }

        // This is a special case since names of forums and id's are linked
        // and cached. Before changing the name of the forum to the new name,
        // we need to clear from cache to remove the old name to id mapping.
        factory.cacheManager.forumCache.remove(id);

        this.name = name;
        saveToDb();

        // Finally, expire from cache again to revflect name change.
        factory.cacheManager.forumCache.remove(id);
    }

    public void setDescription(String description) {
        this.description = description;
        saveToDb();
        factory.cacheManager.forumCache.remove(id);
    }

    public void setCreationDate(java.util.Date creationDate) {
       this.creationDate = creationDate;
       saveToDb();
       factory.cacheManager.forumCache.remove(id);
    }

    public void setModifiedDate(java.util.Date modifiedDate)
            throws UnauthorizedException
    {
        this.modifiedDate = modifiedDate;
        saveToDb();
        factory.cacheManager.forumCache.remove(id);
    }

    public void setModerationDefaultMessageValue(int value) {
        this.modDefaultMessageValue = value;
        saveToDb();
        factory.cacheManager.forumCache.remove(id);
    }

     public void setModerationMinThreadValue(int value) {
        this.modMinThreadValue = value;
        saveToDb();
        factory.cacheManager.forumCache.remove(id);
    }

     public void setModerationMinMessageValue(int value) {
        this.modMinMessageValue = value;
        saveToDb();
        factory.cacheManager.forumCache.remove(id);
    }

      public void setProperty(String name, String value) {
        if (LAZY_PROP_LOADING) {
            if (properties == null) {
                loadPropertiesFromDb();
            }
        }
        properties.put(name, value);
        savePropertiesToDb();
        factory.cacheManager.forumCache.remove(id);
    }

    public void deleteProperty(String name) {
        if (LAZY_PROP_LOADING) {
            if (properties == null) {
                loadPropertiesFromDb();
            }
        }
        properties.remove(name);
        deletePropertyFromDb(name);
        factory.cacheManager.forumCache.remove(id);
    }

  
    public void addThread(ForumThread thread) {
        boolean abortTransaction = false;
        // Add thread to forum table.
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = ConnectionManager.getTransactionConnection();
            try {
                pstmt = con.prepareStatement(ADD_THREAD);
                pstmt.setLong(1,id);
                pstmt.setLong(2,thread.getID());
                pstmt.executeUpdate();
            }
            finally {
                try {  pstmt.close();   }
                catch (Exception e) { e.printStackTrace(); }
            }

            // Now, insert the thread into the database. Depending on if this
            // method was called internally or not, the thread object might
            // be wrapped by a proxy or not.
            if (thread instanceof ForumThreadProxy) {
                ForumThreadProxy proxyThread = (ForumThreadProxy)thread;
                DbForumThread dbThread = (DbForumThread)proxyThread.getProxiedForumThread();
                dbThread.insertIntoDb(this, con);
            }
            else {
                DbForumThread dbThread = (DbForumThread)thread;
                dbThread.insertIntoDb(this, con);
            }

            // Check the moderation value of the thread. If the value is above
            // the visible threshold for the forum, then update the modified
            // date of the forum. Otherwise, we'll wait to update the modified
            // date to when the thread is moderated to be visible.
            if (thread.getModerationValue() >= getModerationMinThreadValue()) {
                updateModifiedDate(thread.getModifiedDate().getTime(), con);
            }
        }
        catch(Exception e) {
            e.printStackTrace();
            abortTransaction = true;
        }
        finally {
            ConnectionManager.closeTransactionConnection(con, abortTransaction);
        }

        // Thread count has changed, so remove the forum from cache.
        factory.cacheManager.forumCache.remove(this.id);

        // Expire the userMessageCountCache if the root message was not posted
        // anonymously.
        ForumMessage message = thread.getRootMessage();
        if (!message.isAnonymous()) {
            factory.userMessageCountCache.remove(message.getUser().getID());
        }
    }

    public void deleteThread(ForumThread thread) {
        boolean abortTransaction = false;
        Connection con = null;
        try {
            con = ConnectionManager.getTransactionConnection();
            DbForumThread dbThread = (DbForumThread)getThread(thread.getID());
            deleteThread(dbThread, con);
        }
        catch (Exception e) {
            e.printStackTrace();
            abortTransaction = true;
        }
        finally {
            ConnectionManager.closeTransactionConnection(con, abortTransaction);
        }

        // Now, delete thread and forum from cache
        factory.cacheManager.threadCache.remove(thread.getID());
     factory.cacheManager.forumCache.remove(this.id);
    }

    public void moveThread(ForumThread thread, Forum forum)
            throws UnauthorizedException
    {
        // Ensure that thread belongs to this forum
        if (thread.getForum().getID() != this.id) {
            throw new IllegalArgumentException("The thread does not belong to this forum.");
        }

        // Read all messageIDs of the thread into an array so that we can expire
        // each of them later.
        ResultFilter ignoreModerationFilter = ResultFilter.createDefaultMessageFilter();
        ignoreModerationFilter.setModerationRangeMin(Integer.MIN_VALUE+1);
        LongList messageIDList = new LongList();
        Iterator iter = thread.messages(ignoreModerationFilter);
        while (iter.hasNext()) {
            long messageID = ((ForumMessage)iter.next()).getID();
            messageIDList.add(messageID);
        }

        // Modify the SQL record. Only the thread table has information about
        // forumID, so we only need to modify that record. The message records
        // underneath the thread can be left alone.
        boolean abortTransaction = false;
        Connection con = null;
        try {
            con = ConnectionManager.getTransactionConnection();
            PreparedStatement pstmt = null;
            try {
                pstmt = con.prepareStatement(ADD_THREAD);
                pstmt.setLong(1,forum.getID());
                pstmt.setLong(2,thread.getID());
                pstmt.executeUpdate();
                pstmt.close();

                // Move all messages in thread to new forum.
                pstmt = con.prepareStatement(MOVE_MESSAGES);
                pstmt.setLong(1,forum.getID());
                pstmt.setLong(2,thread.getID());
                pstmt.executeUpdate();
            }
            finally {
                try {  pstmt.close(); }
                catch (Exception e) { e.printStackTrace(); }
            }
        }
        catch( SQLException sqle ) {
            sqle.printStackTrace();
            abortTransaction = true;
            return;
        }
        finally {
            ConnectionManager.closeTransactionConnection(con, abortTransaction);
        }

        DatabaseCacheManager cacheManager = factory.cacheManager;
        SearchManager searchManager = factory.getSearchManager();

        // Remove both forums from cache.
        cacheManager.forumCache.remove(this.id);
        cacheManager.forumCache.remove(forum.getID());

        // Update the last modified date of both forums to the most recently
        // updated thread (this may have changed during the move thread operation).
        ResultFilter newestThreadFilter = ResultFilter.createDefaultThreadFilter();
        newestThreadFilter.setNumResults(1);
        Iterator threadIter = threads(newestThreadFilter);
        if (threadIter.hasNext()) {
            ForumThread newestThread = (ForumThread)threadIter.next();
            if (newestThread != null) {
                setModifiedDate(newestThread.getModifiedDate());
            }
        }
        // Updated modified date of other forum.
        newestThreadFilter = ResultFilter.createDefaultThreadFilter();
        newestThreadFilter.setNumResults(1);
        threadIter = forum.threads(newestThreadFilter);
        if (threadIter.hasNext()) {
            ForumThread newestThread = (ForumThread)threadIter.next();
            if (newestThread != null) {
                forum.setModifiedDate(newestThread.getModifiedDate());
            }
        }

        // Remove thread from cache.
        cacheManager.threadCache.remove(thread.getID());

        // Loop through all messages in thread and delete from cache, reset
        // entry in the search index to new thread.
        long [] messageIDArray = messageIDList.toArray();
        for (int i=0; i<messageIDArray.length; i++) {
            long messageID = messageIDArray[i];
            cacheManager.messageCache.remove(messageID);
            try {
                ForumMessage message = thread.getMessage(messageID);
                searchManager.removeFromIndex(message);
                searchManager.addToIndex(message);
            }
            catch (ForumMessageNotFoundException e)  { }
        }
    }

   
       public int getThreadCount(ResultFilter resultFilter) {
        String query = getThreadListSQL(resultFilter, true);
        CacheableInt count = (CacheableInt)threadCountCache.get(query);
        // If already in cache, return the count.
        if (count != null) {
            return count.getInt();
        }
        // Otherwise, we have to load the count from the db.
        else {
            int threadCount = 0;
            Connection con = null;
            Statement stmt = null;
            try {
                con = ConnectionManager.getConnection();
                stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery(query);
                rs.next();
                threadCount = rs.getInt(1);
            }
            catch( SQLException sqle ) {
                sqle.printStackTrace();
            }
            finally {
                try {  stmt.close(); }
                catch (Exception e) { e.printStackTrace(); }
                try {  con.close();   }
                catch (Exception e) { e.printStackTrace(); }
            }
            // Add the thread count to cache
            threadCountCache.add(query, new CacheableInt(threadCount));
            return threadCount;
        }
    }

    public int getMessageCount(ResultFilter resultFilter) {
        String query = getMessageListSQL(resultFilter, true);
        CacheableInt count = (CacheableInt)messageCountCache.get(query);
        // If already in cache, return the count.
        if (count != null) {
            return count.getInt();
        }
        // Otherwise, we have to load the count from the db.
        else {
            int messageCount = 0;
            Connection con = null;
            Statement stmt = null;
            try {
                con = ConnectionManager.getConnection();
                stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery(query);
                rs.next();
                messageCount = rs.getInt(1);
            }
            catch( SQLException sqle ) {
                sqle.printStackTrace();
            }
            finally {
                try {  stmt.close(); }
                catch (Exception e) { e.printStackTrace(); }
                try {  con.close();   }
                catch (Exception e) { e.printStackTrace(); }
            }
            // Add the thread count to cache
            messageCountCache.add(query, new CacheableInt(messageCount));
            return messageCount;
        }
    }

 
    public int hashCode() {
        return (int)id;
    }

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        }
        if (object != null && object instanceof DbForum) {
            return id == ((DbForum)object).getID();
        }
        else {
            return false;
        }
    }

    /**
     * Updates the modified date. It accepts a Connection so that it can
     * participate in trasactions.
     */
    protected void updateModifiedDate(long date, Connection con)
            throws SQLException
    {
        this.modifiedDate.setTime(date);
        PreparedStatement pstmt = null;
        try {
            pstmt = con.prepareStatement(UPDATE_FORUM_MODIFIED_DATE);
            pstmt.setString(1, StringUtils.dateToMillis(modifiedDate));
            pstmt.setLong(2, id);
            pstmt.executeUpdate();
        }
        finally {
            try {  pstmt.close(); }
            catch (Exception e) { e.printStackTrace(); }
        }
    }

    /**
     * Does the actual work of deleteting a thread. It accepts a Connection so
     * that it can participate in transactions.
     */
    protected void deleteThread(DbForumThread thread, Connection con)
            throws SQLException, ForumMessageNotFoundException
    {
        // Delete all messages from the thread. Deleting the root
        // message will delete all submessages.
        thread.deleteMessage(thread.getRootMessage(), con);

        // Delete any watches on the thread.
        factory.watchManager.deleteThreadWatches(thread, con);

        // Now delete thread db entry and thread properties.
        PreparedStatement pstmt = null;
        try {
            pstmt = con.prepareStatement(DELETE_THREAD_PROPERTIES);
            pstmt.setLong(1,thread.getID());
            pstmt.execute();
            pstmt.close();

            pstmt = con.prepareStatement(DELETE_THREAD);
            pstmt.setLong(1,thread.getID());
            pstmt.execute();
        }
        finally {
            try {  pstmt.close(); }
            catch (Exception e) { e.printStackTrace(); }
        }
    }

   
   /**
     * Returns a block of threadID's from a query and performs transparent
     * caching of those blocks. The two parameters specify a database query
     * and a startIndex for the results in that query.
     *
     * @param query the SQL thread list query to cache blocks from.
     * @param startIndex the startIndex in the list to get a block for.
     */
    protected long[] getThreadBlock(String query, int startIndex) {
        // First, discover what block number the results will be in.
        int blockID = startIndex / THREAD_BLOCK_SIZE;
        int blockStart = blockID * THREAD_BLOCK_SIZE;
        // Now, check cache to see if the block is already cached. The key is
        // simply the query plus the blockID.
        String key = query + blockID;
        CacheableLongArray longArray = (CacheableLongArray)threadListCache.get(key);
        //If already in cache, return the block.
        if (longArray != null) {
            /**
             * The actual block may be smaller than THREAD_BLOCK_SIZE. If that's
             * the case, it means two things:
             *  1) We're at the end boundary of all the results.
             *  2) If the start index is greater than the length of the current
             *     block, than there aren't really any results to return.
             */
            long [] threads = longArray.getLongArray();
            if (startIndex >= blockStart + threads.length) {
                // Return an empty array
                return EMPTY_BLOCK;
            }
            else {
                return threads;
            }
        }
        // Otherwise, we have to load up the block from the database.
        else {
            LongList threadsList = new LongList(THREAD_BLOCK_SIZE);
            Connection con = null;
            Statement stmt = null;
            try {
                con = ConnectionManager.getConnection();
                stmt = con.createStatement();
                // Set the maxium number of rows to end at the end of this block.
                ConnectionManager.setMaxRows(stmt, THREAD_BLOCK_SIZE * (blockID+1));
                ResultSet rs = stmt.executeQuery(query);
                // Grab THREAD_BLOCK_ROWS rows at a time.
                ConnectionManager.setFetchSize(rs, THREAD_BLOCK_SIZE);
                // Many JDBC drivers don't implement scrollable cursors the real
                // way, but instead load all results into memory. Looping through
                // the results ourselves is more efficient.
                for (int i=0; i<blockStart; i++) {
                    rs.next();
                }
                // Keep reading results until the result set is exaughsted or
                // we come to the end of the block.
                int count = 0;
                while (rs.next() && count < THREAD_BLOCK_SIZE) {
                    threadsList.add(rs.getLong(1));
                    count++;
                }
            }
            catch( SQLException sqle ) {
                sqle.printStackTrace();
            }
            finally {
                try {  stmt.close(); }
                catch (Exception e) { e.printStackTrace(); }
                try {  con.close();   }
                catch (Exception e) { e.printStackTrace(); }
            }
            long [] threads = threadsList.toArray();
            // Add the thread block to cache
            threadListCache.add(key, new CacheableLongArray(threads));
            /**
             * The actual block may be smaller than THREAD_BLOCK_SIZE. If that's
             * the case, it means two things:
             *  1) We're at the end boundary of all the results.
             *  2) If the start index is greater than the length of the current
             *     block, than there aren't really any results to return.
             */
            if (startIndex >= blockStart + threads.length) {
                // Return an empty array
                return EMPTY_BLOCK;
            }
            else {
                return threads;
            }
        }
    }

    /**
     * Returns a block of messageID's from a query and performs transparent
     * caching of those blocks. The two parameters specify a database query
     * and a startIndex for the results in that query.
     *
     * @param query the SQL message list query to cache blocks from.
     * @param startIndex the startIndex in the list to get a block for.
     */
    protected long[] getMessageBlock(String query, int startIndex) {
        // First, discover what block number the results will be in.
        int blockID = startIndex / MESSAGE_BLOCK_SIZE;
        int blockStart = blockID * MESSAGE_BLOCK_SIZE;
        // Now, check cache to see if the block is already cached. The key is
        // simply the query plus the blockID.
        String key = query + blockID;
        CacheableLongArray longArray = (CacheableLongArray)messageListCache.get(key);
        // If already in cache, return the block.
        if (longArray != null) {
            /**
             * The actual block may be smaller than MESSAGE_BLOCK_SIZE. If that's
             * the case, it means two things:
             *  1) We're at the end boundary of all the results.
             *  2) If the start index is greater than the length of the current
             *     block, than there aren't really any results to return.
             */
            long [] messages = longArray.getLongArray();
            if (startIndex >= blockStart + messages.length) {
                // Return an empty array
                return EMPTY_BLOCK;
            }
            else {
                return messages;
            }
        }
        // Otherwise, we have to load up the block from the database.
        else {
            LongList messagesList = new LongList(MESSAGE_BLOCK_SIZE);
            Connection con = null;
            Statement stmt = null;
            try {
                con = ConnectionManager.getConnection();
                stmt = con.createStatement();
                // Set the maxium number of rows to end at the end of this block.
                ConnectionManager.setMaxRows(stmt, MESSAGE_BLOCK_SIZE * (blockID+1));
                ResultSet rs = stmt.executeQuery(query);
                // Grab MESSAGE_BLOCK_ROWS rows at a time.
                ConnectionManager.setFetchSize(rs, MESSAGE_BLOCK_SIZE);
                // Many JDBC drivers don't implement scrollable cursors the real
                // way, but instead load all results into memory. Looping through
                // the results ourselves is more efficient.
                for (int i=0; i<blockStart; i++) {
                    rs.next();
                }
                // Keep reading results until the result set is exaughsted or
                // we come to the end of the block.
                int count = 0;
                while (rs.next() && count < MESSAGE_BLOCK_SIZE) {
                    messagesList.add(rs.getLong(1));
                    count++;
                }
            }
            catch( SQLException sqle ) {
                sqle.printStackTrace();
            }
            finally {
                try {  stmt.close(); }
                catch (Exception e) { e.printStackTrace(); }
                try {  con.close();   }
                catch (Exception e) { e.printStackTrace(); }
            }
            long [] messages = messagesList.toArray();
            // Add the message block to cache
            messageListCache.add(key, new CacheableLongArray(messages));
            /**
             * The actual block may be smaller than MESSAGE_BLOCK_SIZE. If that's
             * the case, it means two things:
             *  1) We're at the end boundary of all the results.
             *  2) If the start index is greater than the length of the current
             *     block, than there aren't really any results to return.
             */
            if (startIndex >= blockStart + messages.length) {
                //Return an empty array
                return EMPTY_BLOCK;
            }
            else {
                return messages;
            }
        }
    }

}
 
维护缓存与原对象的一致性
/**
 * $RCSfile: DbForumThread.java,v $
 * $Revision: 1.23 $
 * $Date: 2001/09/23 18:54:08 $
 *
 * Copyright (C) 1999-2001 CoolServlets, Inc. All rights reserved.
 *
 * This software is the proprietary information of CoolServlets, Inc.
 * Use is subject to license terms.
 */


    public void addMessage(ForumMessage parentMessage, ForumMessage newMessage)
            throws UnauthorizedException
    {
        // Get the underlying DbForumMessage object.
        DbForumMessage dbMessage = null;
        if (newMessage instanceof ForumMessageProxy) {
            ForumMessageProxy proxyMessage = (ForumMessageProxy)newMessage;
            dbMessage = (DbForumMessage)proxyMessage.getProxiedForumMessage();
        }
        else {
            dbMessage = (DbForumMessage)newMessage;
        }
        DbForum dbForum = null;

        boolean abortTransaction = false;
        Connection con = null;
        try {
            con = ConnectionManager.getTransactionConnection();

            // Insert the message into the database.
            dbMessage.insertIntoDb(this, parentMessage.getID(), con);

            // Check the moderation value of the message. If the value is above
            // the visible threshold for the forum, then update the modified
            // date of the thread and forum. Otherwise, we'll wait to update
            // the modified dates to when the message is moderated to be visible.
            dbForum = factory.cacheManager.forumCache.get(forumID);
            if (newMessage.getModerationValue() >=
                    dbForum.getModerationMinMessageValue())
            {
                long modifiedDate = newMessage.getModifiedDate().getTime();
                updateModifiedDate(modifiedDate, con);
                dbForum.updateModifiedDate(modifiedDate, con);
            }
        }
        catch( Exception e ) {
            e.printStackTrace();
            abortTransaction = true;
        }
        finally {
            ConnectionManager.closeTransactionConnection(con, abortTransaction);
        }

        // Thread count has been modified, remove thread and forum from cache.
        factory.cacheManager.threadCache.remove(this.id);
        factory.cacheManager.forumCache.remove(this.forumID);

        // Expire the userMessageCountCache if this message was not posted
        // anonymously.
        if (!newMessage.isAnonymous()) {
            factory.userMessageCountCache.remove(newMessage.getUser().getID());
        }

        // If above the moderation threshold...
        if (newMessage.getModerationValue() >=
                    dbForum.getModerationMinMessageValue())
        {
            // Notify the watch manager that the thread has been updated.
            factory.watchManager.notifyWatches(this);
            // Notify the gateway manager of a new message.
            dbForum.getGatewayManager().exportData(dbMessage);
        }
    }

 }

posted on 2009-04-12 10:59  cxccbv  阅读(426)  评论(0编辑  收藏  举报

导航