[prev in list] [next in list] [prev in thread] [next in thread] 

List:       slide-dev
Subject:    Re: Caching (including content) store
From:       Oliver Zeigermann <ozeigermann () c1-fse ! de>
Date:       2003-10-24 13:21:09
[Download RAW message or body]

Next version of caching store. Fixed some tx stuff, added precashing to 
ensure multiple usages of content and made things a bit more robust.

Oliver Zeigermann wrote:
> Hi,
> 
> attached to this post are the new classes for caching.
> 
> Caching now uses A.B.C notation for logging and is fully configurable 
> over Domain.xml
> 
> Here is an excerpt from my Domain.xml; parameters should be pretty much 
> self-explanatory, values are non-sense, as chosen for testing only:
> 
> <store name="tx" classname="org.apache.slide.store.TxCacheStore">
>   <parameter name="object-cache-size">1000</parameter>
>   <parameter name="permission-cache-size">1000</parameter>
>   <parameter name="lock-cache-size">100</parameter>
>   <parameter name="descriptors-cache-size">1000</parameter>
>   <parameter name="descriptor-cache-size">1000</parameter>
>   <parameter name="enable-content-caching">true</parameter>
>   <parameter name="content-cache-size">1000</parameter>
>   <parameter name="content-cache-bytes">100000</parameter>
>   <parameter name="tx-content-cache-bytes">100000</parameter>
>   <parameter name="tx-content-cache-size">1000</parameter>
>   <parameter name="max-content-bytes-per-entry">5000000</parameter>
>   ...
> </store>
> 
> Additionally, content caching has been added, although in a somewhat 
> weird way using LRUMap. Because it turned out to be much more 
> complicated than descriptors-caching it is likely to be buggy. So, 
> either turn it off or help me debugging by providing hints or bug reports.
> 
> Any comments are welcome!
> 
> Thanks,
> 
> Oliver

["ByteSizeLimitedObjectCache.java" (text/x-java)]

/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v \
                1.19 2002/11/11 13:54:19 pnever Exp $
 * $Revision: 1.19 $
 * $Date: 2002/11/11 13:54:19 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.slide.util;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Iterator;

import org.apache.slide.util.logger.Logger;

import org.apache.commons.collections.LRUMap;

/**
 * Transactional LRU byte counting object cache. Caches objects using a \
                least-recently-used strategy. 
 * Asserts a given overall size of all cached objects does not exceed a specified \
                limit.   
 * 
 * It provides basic isolation from other transactions and atomicity of all \
                operations. As
 * no locking is used (which is undesirable mainly as a cache should never block and \
                a commit must never fail) 
 * serializability needs to be guaranteed by underlying stores. 
 * 
 * <em>Note</em>: Unlike {@link TxLRUObjectCache} this cache also limits the size of \
                temporary caches inside transactions.
 * This is necessary as size of bytes in a transaction can become really big. In case \
                an entry can not be cached inisde a 
 * transaction it will be marked as invalid after commit of that tx.
 *  
 * @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
 * @version $Revision: 1.0$
 * @see TxLRUObjectCache
 */
public class ByteSizeLimitedObjectCache extends TxLRUObjectCache {

    // amount of entries that are at most removed from cache to make room for a new \
entry  protected static final int MAX_FREEING_TRIES = 10;

    protected long globalByteSize;
    protected int txCacheSize;
    protected long txByteSize;
    protected long maxByteSizePerEntry;

    /**
     * Creates a new object cache. The maximum cache size can be configured for local \
                transaction
     * caches as well as on a global level. 
     * 
     * The idea of having a maximum size in bytes per entry is to prevent a large \
                entry
     * to displace many small entries.
     * 
     * @param globalCacheSize maximum size in objects of global cache
     * @param txCacheSize maximum size in objects for local transaction cache 
     * @param globalByteSize maximum size in bytes of global cache
     * @param txByteSize maximum size in bytes for local transaction cache
     * @param maxByteSizePerEntry maximum size of a single cache entry in bytes
     * @param name the name used to construct logging category / channel
     * @param logger Slide logger to be used for logging 
     */
    public ByteSizeLimitedObjectCache(
        int globalCacheSize,
        int txCacheSize,
        long globalByteSize,
        long txByteSize,
        long maxByteSizePerEntry,
        String name,
        Logger logger) {
        super(globalCacheSize, name, logger);
        globalCache = new SizeCountingLRUMap(globalCacheSize, globalByteSize, \
maxByteSizePerEntry);  this.globalByteSize = globalByteSize;
        this.txCacheSize = txCacheSize;
        this.txByteSize = txByteSize;
        this.maxByteSizePerEntry = maxByteSizePerEntry;
        logChannel = "ByteSizeLimitedObjectCache";
        if (name != null) {
            logChannel += "." + name;
        }

    }

    public synchronized void clear() {
        super.clear();
    }

    public synchronized boolean canCache(Object txId, long byteSize) {
        long maxSize;
        if (txId != null) {
            maxSize = globalByteSize;
        } else {
            maxSize = txByteSize;
        }
        return (maxSize >= byteSize && maxByteSizePerEntry >= byteSize);
    }

    public synchronized void put(Object txId, Object key, Object value, long \
byteSize) {  if (key == null) {
            logger.log(txId + " adding null key with byte size " + byteSize, \
logChannel, Logger.WARNING);  }

        if (txId != null) {
            // if it has been deleted before, undo this
            Set deleteCache = (Set) txDeleteCaches.get(txId);
            deleteCache.remove(key);

            SizeCountingLRUMap changeCache = (SizeCountingLRUMap) \
txChangeCaches.get(txId);  changeCache.put(key, value, byteSize);

            if (loggingEnabled) {
                logger.log(txId + " added '" + key + "' with byte size " + byteSize, \
logChannel, Logger.DEBUG);  }
        } else {
            ((SizeCountingLRUMap) globalCache).put(key, value, byteSize);
            if (loggingEnabled) {
                logger.log("Added '" + key + "' with byte size " + byteSize, \
logChannel, Logger.DEBUG);  }
        }
    }

    public synchronized void start(Object txId) {
        if (txId != null) {
            txChangeCaches.put(txId, new SizeCountingLRUMap(txCacheSize, txByteSize, \
maxByteSizePerEntry, txId));  txDeleteCaches.put(txId, new HashSet());
        }
    }

    public synchronized void commit(Object txId) {
        if (txId != null) {
            // apply local changes and deletes (is atomic as we have a global lock on \
this TxCache)

            SizeCountingLRUMap changeCache = (SizeCountingLRUMap) \
                txChangeCaches.get(txId);
            for (Iterator it = changeCache.entrySet().iterator(); it.hasNext();) {
                Map.Entry entry = (Map.Entry) it.next();
                long byteSize = changeCache.getByteSize(entry.getKey());
                if (byteSize == -1L) {
                    globalCache.put(entry.getKey(), entry.getValue());
                } else {
                    ((SizeCountingLRUMap) globalCache).put(entry.getKey(), \
entry.getValue(), byteSize);  }
            }

            Set deleteCache = (Set) txDeleteCaches.get(txId);
            for (Iterator it = deleteCache.iterator(); it.hasNext();) {
                Object key = it.next();
                globalCache.remove(key);
            }

            if (loggingEnabled) {
                logger.log(
                    txId
                        + " committed "
                        + changeCache.size()
                        + " changes and "
                        + deleteCache.size()
                        + " scheduled deletes",
                    logChannel,
                    Logger.DEBUG);
            }

            // finally forget about tx
            forget(txId);
        }
    }

    protected class SizeCountingLRUMap extends LRUMap {

        private Map shadowSizes;
        private long globalSize;
        private long maxByteSize;
        private long maxByteSizePerEntry;
        private Object txId;

        public SizeCountingLRUMap(int size, long maxByteSize, long \
maxByteSizePerEntry, Object txId) {  this(size, maxByteSize, maxByteSizePerEntry);
            this.txId = txId;
        }

        public SizeCountingLRUMap(int size, long maxByteSize, long \
maxByteSizePerEntry) {  super(size);
            this.shadowSizes = new HashMap();
            this.globalSize = 0;
            this.maxByteSize = maxByteSize;
            this.maxByteSizePerEntry = maxByteSizePerEntry;
        }

        public Object put(Object key, Object value, long byteSize) {
            // is it too big to be cached?
            if (byteSize > maxByteSizePerEntry || byteSize > maxByteSize) {
                if (loggingEnabled) {
                    logger.log(txId + " for '" + key + "' is too big to be cached", \
logChannel, Logger.DEBUG);  }
                Object oldValue = get(key);
                // invalidate previous entry if present
                // XXX this relies on an implementation detail in TxLRUByteCache.put
                // removal from delete cache must be done before trying to add to
                // change cache using this method; if not our undoing will be partly
                // undone (again) in TxLRUObjectCache.put
                invalidate(key);
                return oldValue;
            } else {
                // be sure to return allocated bytes before readding
                freeBytes(key);

                // ok, we decided to cache this entry, make room for it
                for (int i = 0; globalSize + byteSize > maxByteSize && i < \
MAX_FREEING_TRIES; i++)  if (loggingEnabled) {
                        logger.log(
                            txId
                                + " for '"
                                + key
                                + "' needs "
                                + Long.toString(globalSize + byteSize - maxByteSize)
                                + " bytes more to be cached. Freeing bytes!",
                            logChannel,
                            Logger.DEBUG);
                    }
                // this will call back processRemovedLRU and will thus free bytes 
                removeLRU();
            }
            // was this successful?
            if (globalSize + byteSize > maxByteSize) {
                shadowSizes.put(key, new Long(byteSize));
                globalSize += byteSize;
                return super.put(key, value);
            } else {
                Object oldValue = get(key);
                invalidate(key);
                return oldValue;
            }
        }

        public long getByteSize(Object key) {
            Long lSize = (Long) shadowSizes.get(key);
            if (lSize != null) {
                return lSize.longValue();
            } else {
                return -1L;
            }
        }

        protected void freeBytes(Object key) {
            Long lSize = (Long) shadowSizes.remove(key);
            if (lSize != null) {
                long size = lSize.longValue();
                globalSize -= size;
            }
        }

        protected void invalidate(Object key) {
            // if it is no longer cached, it still means entry currently
            // in global map gets invalid upon commit of this tx
            freeBytes(key);
            ByteSizeLimitedObjectCache.this.remove(txId, key);
            if (loggingEnabled) {
                logger.log(txId + " invalidated '" + key + "'", logChannel, \
Logger.DEBUG);  }
        }

        // notify cache that this entry has been removed by LRU strategy (called back \
by LRUMap)  protected void processRemovedLRU(Object key, Object value) {
            invalidate(key);
        }
    }
}


["TxLRUObjectCache.java" (text/x-java)]

/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v \
                1.19 2002/11/11 13:54:19 pnever Exp $
 * $Revision: 1.19 $
 * $Date: 2002/11/11 13:54:19 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.slide.util;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.slide.util.logger.Logger;

import org.apache.commons.collections.LRUMap;

/**
 * Transactional LRU object cache. Caches objects using a least-recently-used \
                strategy.
 * 
 * It provides basic isolation from other transactions and atomicity of all \
                operations. As
 * no locking is used (which is undesirable mainly as a cache should never block and \
                a commit must never fail) 
 * serializability needs to be guaranteed by underlying stores. 
 * <br>
 * <br>
 * <em>Caution</em>: Only global caches are limited by given size. 
 * Size of temporary data inside a transaction is unlimited.   
 * <br>
 * <br>
 * <em>Note</em>: This cache has no idea if the data it caches in transactions are \
                read from or written to store. 
 * It thus handles both access types the same way. This means read accesses are \
                cached in transactions even though they
 * could be cached globally. Like write accesses they will be moved to global cache \
                at commit time. 
 * 
 * @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
 * @version $Revision: 1.0$
 */
public class TxLRUObjectCache {

    protected Map globalCache;

    protected Map txChangeCaches;
    protected Map txDeleteCaches;

    protected int hits = 0;
    protected int misses = 0;

    protected String name;
    protected Logger logger;
    protected String logChannel;
    protected final boolean loggingEnabled;

    /**
     * Creates a new object cache.
     * 
     * @param globalCacheSize maximum size in objects of global cache
     * @param name the name used to construct logging category / channel
     * @param logger Slide logger to be used for logging 
     */
    public TxLRUObjectCache(int globalCacheSize, String name, Logger logger) {
        globalCache = new LRUMap(globalCacheSize);
        txChangeCaches = new HashMap();
        txDeleteCaches = new HashMap();

        this.name = name;
        this.logger = logger;

        logChannel = "TxLRUObjectCache";
        if (name != null) {
            logChannel += "." + name;
        }

        // used for guarded logging as preparation is expensive
        loggingEnabled = logger.isEnabled(logChannel, Logger.DEBUG);
    }

    public TxLRUObjectCache(int globalCacheSize) {
        this(globalCacheSize, null, null);
    }

    public synchronized void clear() {
        globalCache.clear();
        txChangeCaches.clear();
        txDeleteCaches.clear();
    }

    public synchronized Object get(Object txId, Object key) {
        if (txId != null) {
            Set deleteCache = (Set) txDeleteCaches.get(txId);
            if (deleteCache.contains(key)) {
                hit(txId, key);
                // reflects that entry has been deleted in this tx 
                return null;
            }

            Map changeCache = (Map) txChangeCaches.get(txId);
            Object changed = changeCache.get(key);
            if (changed != null) {
                hit(txId, key);
                // if object has been changed in this tx, get the local one
                return changed;
            }
        }

        // as fall back return value from global cache (if present)
        Object global = globalCache.get(key);
        if (global != null) {
            hit(txId, key);
        } else {
            miss(txId, key);
        }
        return global;
    }

    public synchronized void put(Object txId, Object key, Object value) {
        if (txId != null) {
            // if it has been deleted before, undo this
            Set deleteCache = (Set) txDeleteCaches.get(txId);
            deleteCache.remove(key);

            Map changeCache = (Map) txChangeCaches.get(txId);
            changeCache.put(key, value);

            if (loggingEnabled) {
                logger.log(txId + " added '" + key + "'", logChannel, Logger.DEBUG);
            }
        } else {
            globalCache.put(key, value);
            if (loggingEnabled) {
                logger.log("Added '" + key + "'", logChannel, Logger.DEBUG);
            }
        }
    }

    public synchronized void remove(Object txId, Object key) {
        if (txId != null) {
            // if it has been changed before, undo this
            Map changeCache = (Map) txChangeCaches.get(txId);
            changeCache.remove(key);

            Set deleteCache = (Set) txDeleteCaches.get(txId);
            deleteCache.add(key);

            // guard logging as preparation is expensive
            if (loggingEnabled) {
                logger.log(txId + " removed '" + key + "'", logChannel, \
Logger.DEBUG);  }
        } else {
            globalCache.remove(key);
            if (loggingEnabled) {
                logger.log("Removed '" + key + "'", logChannel, Logger.DEBUG);
            }
        }
    }

    public synchronized void start(Object txId) {
        if (txId != null) {
            txChangeCaches.put(txId, new HashMap());
            txDeleteCaches.put(txId, new HashSet());
        }
    }

    public synchronized void rollback(Object txId) {
        if (txId != null) {

            if (loggingEnabled) {
                Map changeCache = (Map) txChangeCaches.get(txId);
                Set deleteCache = (Set) txDeleteCaches.get(txId);
                logger.log(
                    txId
                        + " rolled back "
                        + changeCache.size()
                        + " changes and "
                        + deleteCache.size()
                        + " scheduled deletes",
                    logChannel,
                    Logger.DEBUG);
            }

            // simply forget about tx
            forget(txId);
        }
    }

    public synchronized void commit(Object txId) {
        if (txId != null) {
            // apply local changes and deletes (is atomic as we have a global lock on \
this TxCache)

            Map changeCache = (Map) txChangeCaches.get(txId);
            for (Iterator it = changeCache.entrySet().iterator(); it.hasNext();) {
                Map.Entry entry = (Map.Entry) it.next();
                globalCache.put(entry.getKey(), entry.getValue());
            }

            Set deleteCache = (Set) txDeleteCaches.get(txId);
            for (Iterator it = deleteCache.iterator(); it.hasNext();) {
                Object key = it.next();
                globalCache.remove(key);
            }

            if (loggingEnabled) {
                logger.log(
                    txId
                        + " committed "
                        + changeCache.size()
                        + " changes and "
                        + deleteCache.size()
                        + " scheduled deletes",
                    logChannel,
                    Logger.DEBUG);
            }

            // finally forget about tx
            forget(txId);
        }
    }

    public synchronized void forget(Object txId) {
        if (txId != null) {
            txChangeCaches.remove(txId);
            txDeleteCaches.remove(txId);
        }
    }

    protected void hit(Object txId, Object key) {
        hits++;
        log(txId, key, true);
    }

    protected void miss(Object txId, Object key) {
        misses++;
        log(txId, key, false);
    }

    protected void log(Object txId, Object key, boolean hit) {
        if (loggingEnabled) {
            StringBuffer log = new StringBuffer();

            if (txId != null) {
                Map changeCache = (Map) txChangeCaches.get(txId);
                Set deleteCache = (Set) txDeleteCaches.get(txId);
                log.append(txId + " (" + changeCache.size() + ", " + \
deleteCache.size() + ") ");  }

            log.append(
                (hit ? "Cache Hit: '" : "Cache Miss: '")
                    + key
                    + "' "
                    + hits
                    + " / "
                    + misses
                    + " / "
                    + globalCache.size());
            logger.log(log.toString(), logChannel, Logger.DEBUG);
        }
    }
}


["TxCacheStore.java" (text/x-java)]

/*
 * $Header: /home/cvspublic/jakarta-slide/src/share/org/apache/slide/store/StandardStore.java,v \
                1.19 2002/11/11 13:54:19 pnever Exp $
 * $Revision: 1.19 $
 * $Date: 2002/11/11 13:54:19 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Slide", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.slide.store;

import java.util.Hashtable;

import org.apache.slide.common.Service;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.Uri;
import org.apache.slide.content.NodeRevisionContent;
import org.apache.slide.content.NodeRevisionDescriptor;
import org.apache.slide.content.RevisionAlreadyExistException;
import org.apache.slide.content.RevisionNotFoundException;
import org.apache.slide.store.txfile.rm.impl.XidWrapper;
import org.apache.slide.util.*;
import org.apache.slide.util.ObjectCache;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;
import org.apache.slide.util.logger.Logger;

/**
 * Store that allows for transactional caching of data. 
 *
 * @author <a href="mailto:ozeigermann@c1-fse.de">Oliver Zeigermann</a>
 * @version $Revision: 1.0$
 */
public class TxCacheStore extends StandardStore {

    private static final String LOG_CHANNEL = TxCacheStore.class.getName();
    private static final boolean globalCacheOff = false; 

    protected static final int DEFAULT_OBJECT_GLOBAL_CACHE_SIZE = 10000;
    protected static final String GLOBAL_OBJECT_CACHE_SIZE_PARAMETER = \
"object-cache-size";

    protected static final int DEFAULT_GLOBAL_PERMISSION_CACHE_SIZE = 10000;
    protected static final String GLOBAL_PERMISSION_CACHE_SIZE_PARAMETER = \
"permission-cache-size";

    protected static final int DEFAULT_GLOBAL_LOCK_CACHE_SIZE = 100;
    protected static final String GLOBAL_LOCK_CACHE_SIZE_PARAMETER = \
"lock-cache-size";

    protected static final int DEFAULT_GLOBAL_DESCRIPTORS_CACHE_SIZE = 10000;
    protected static final String GLOBAL_DESCRIPTORS_CACHE_SIZE_PARAMETER = \
"descriptors-cache-size";

    protected static final int DEFAULT_GLOBAL_DESCRIPTOR_CACHE_SIZE = 10000;
    protected static final String GLOBAL_DESCRIPTOR_CACHE_SIZE_PARAMETER = \
"descriptor-cache-size";

    protected static final int DEFAULT_GLOBAL_CONTENT_CACHE_SIZE = 10000;
    protected static final String GLOBAL_CONTENT_CACHE_SIZE_PARAMETER = \
"content-cache-size";

    protected static final boolean DEFAULT_ENABLE_CONTENT_CACHING = false;
    protected static final String ENABLE_CONTENT_CACHING_PARAMETER = \
"enable-content-caching";

    protected static final int DEFAULT_TX_CONTENT_CACHE_SIZE = 1000;
    protected static final String TX_CONTENT_CACHE_SIZE_PARAMETER = \
"tx-content-cache-size";

    protected static final long DEFAULT_CONTENT_CACHE_BYTES = 10000000; // 10 M
    protected static final String CONTENT_CACHE_BYTES_PARAMETER = \
"content-cache-bytes";

    protected static final long DEFAULT_TX_CONTENT_CACHE_BYTES = 1000000; // 1 M
    protected static final String TX_CONTENT_CACHE_BYTES_PARAMETER = \
"tx-content-cache-bytes";

    protected static final long DEFAULT_MAX_CONTENT_BYTES_PER_ENTRY = 50000; // 50 K
    protected static final String MAX_CONTENT_BYTES_PER_ENTRY_PARAMETER = \
"max-content-bytes-per-entry";

    // there might be at least one active transaction branch per thread
    protected ThreadLocal activeTransactionBranch = new ThreadLocal();

    protected TxContentCacheWrapper contentCache;

    public void setParameters(Hashtable parameters)
        throws ServiceParameterErrorException, ServiceParameterMissingException {
        super.setParameters(parameters);

        int globalObjectCacheSize = DEFAULT_OBJECT_GLOBAL_CACHE_SIZE;
        int globalPermissionCacheSize = DEFAULT_GLOBAL_PERMISSION_CACHE_SIZE;
        int globalLockCacheSize = DEFAULT_GLOBAL_LOCK_CACHE_SIZE;
        int globalDescrtiptorsCacheSize = DEFAULT_GLOBAL_DESCRIPTORS_CACHE_SIZE;
        int globalDescrtiptorCacheSize = DEFAULT_GLOBAL_DESCRIPTOR_CACHE_SIZE;
        boolean contentCachingEnabled = DEFAULT_ENABLE_CONTENT_CACHING;
        int globalContentCacheSize = DEFAULT_GLOBAL_CONTENT_CACHE_SIZE;
        long contentCacheBytes = DEFAULT_CONTENT_CACHE_BYTES;
        int txContentCacheSize = DEFAULT_TX_CONTENT_CACHE_SIZE;
        long txContentCacheBytes = DEFAULT_TX_CONTENT_CACHE_BYTES;
        long maxByteSizePerEntry = DEFAULT_MAX_CONTENT_BYTES_PER_ENTRY;

        String globalObjectCacheSizeString = (String) \
parameters.get(GLOBAL_OBJECT_CACHE_SIZE_PARAMETER);  if (globalObjectCacheSizeString \
!= null) {  try {
                globalObjectCacheSize = \
Integer.parseInt(globalObjectCacheSizeString);  } catch (NumberFormatException nfe) {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting object cache size for store " + getName() + " to " + \
globalObjectCacheSize,  Logger.INFO);

        String globalPermissionCacheSizeString = (String) \
parameters.get(GLOBAL_PERMISSION_CACHE_SIZE_PARAMETER);  if \
(globalPermissionCacheSizeString != null) {  try {
                globalPermissionCacheSize = \
Integer.parseInt(globalPermissionCacheSizeString);  } catch (NumberFormatException \
                nfe) {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting permission cache size for store " + getName() + " to " + \
globalPermissionCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String globalLockCacheSizeString = (String) \
parameters.get(GLOBAL_LOCK_CACHE_SIZE_PARAMETER);  if (globalLockCacheSizeString != \
null) {  try {
                globalLockCacheSize = Integer.parseInt(globalLockCacheSizeString);
            } catch (NumberFormatException nfe) {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting lock cache size for store " + getName() + " to " + \
globalLockCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String globalDescriptorsCacheSizeString = (String) \
parameters.get(GLOBAL_DESCRIPTORS_CACHE_SIZE_PARAMETER);  if \
(globalDescriptorsCacheSizeString != null) {  try {
                globalDescrtiptorsCacheSize = \
Integer.parseInt(globalDescriptorsCacheSizeString);  } catch (NumberFormatException \
                nfe) {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting descriptors cache size for store " + getName() + " to " + \
globalDescrtiptorsCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String globalDescriptorCacheSizeString = (String) \
parameters.get(GLOBAL_DESCRIPTOR_CACHE_SIZE_PARAMETER);  if \
(globalDescriptorCacheSizeString != null) {  try {
                globalDescrtiptorCacheSize = \
Integer.parseInt(globalDescriptorCacheSizeString);  } catch (NumberFormatException \
                nfe) {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting descriptor cache size for store " + getName() + " to " + \
globalDescrtiptorCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String enableContentCachingString = (String) \
parameters.get(ENABLE_CONTENT_CACHING_PARAMETER);  if (enableContentCachingString != \
                null) {
            contentCachingEnabled = \
Boolean.valueOf(enableContentCachingString).booleanValue();  }
        getLogger().log(
            "Setting content caching for store " + getName() + " to " + \
contentCachingEnabled,  LOG_CHANNEL,
            Logger.INFO);

        String globalContentCacheSizeString = (String) \
parameters.get(GLOBAL_CONTENT_CACHE_SIZE_PARAMETER);  if \
(globalContentCacheSizeString != null) {  try {
                globalContentCacheSize = \
Integer.parseInt(globalContentCacheSizeString);  } catch (NumberFormatException nfe) \
                {
                getLogger().log("Cache size must be an integer! Using default size", \
LOG_CHANNEL, Logger.WARNING);  }
        }
        getLogger().log(
            "Setting content cache size for store " + getName() + " to " + \
globalContentCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String contentCacheBytesString = (String) \
parameters.get(CONTENT_CACHE_BYTES_PARAMETER);  if (contentCacheBytesString != null) \
{  try {
                contentCacheBytes = Long.parseLong(contentCacheBytesString);
            } catch (NumberFormatException nfe) {
                getLogger().log(
                    "Content cache byte size must be an integer! Using default size",
                    LOG_CHANNEL,
                    Logger.WARNING);
            }
        }
        getLogger().log(
            "Setting content cache byte size for store " + getName() + " to " + \
contentCacheBytes,  LOG_CHANNEL,
            Logger.INFO);

        String txContentCacheSizeString = (String) \
parameters.get(TX_CONTENT_CACHE_SIZE_PARAMETER);  if (txContentCacheSizeString != \
null) {  try {
                txContentCacheSize = Integer.parseInt(txContentCacheSizeString);
            } catch (NumberFormatException nfe) {
                getLogger().log(
                    "Content transaction cache size must be an integer! Using default \
size",  LOG_CHANNEL,
                    Logger.WARNING);
            }
        }
        getLogger().log(
            "Setting transaction content cache size for store " + getName() + " to " \
+ txContentCacheSize,  LOG_CHANNEL,
            Logger.INFO);

        String txContentCacheBytesString = (String) \
parameters.get(TX_CONTENT_CACHE_BYTES_PARAMETER);  if (txContentCacheBytesString != \
null) {  try {
                txContentCacheBytes = Long.parseLong(txContentCacheBytesString);
            } catch (NumberFormatException nfe) {
                getLogger().log(
                    "Transaction content cache byte size must be an integer! Using \
default size",  LOG_CHANNEL,
                    Logger.WARNING);
            }
        }
        getLogger().log(
            "Setting transaction content cache byte size for store " + getName() + " \
to " + txContentCacheBytes,  LOG_CHANNEL,
            Logger.INFO);

        String maxByteSizePerEntryString = (String) \
parameters.get(MAX_CONTENT_BYTES_PER_ENTRY_PARAMETER);  if (maxByteSizePerEntryString \
!= null) {  try {
                maxByteSizePerEntry = Long.parseLong(maxByteSizePerEntryString);
            } catch (NumberFormatException nfe) {
                getLogger().log(
                    "Maximum byte size for content cache entry must be an integer! \
Using default size",  LOG_CHANNEL,
                    Logger.WARNING);
            }
        }
        getLogger().log(
            "Setting maximum byte size for content cache entry for store " + \
getName() + " to " + maxByteSizePerEntry,  LOG_CHANNEL,
            Logger.INFO);

        init(
            globalObjectCacheSize,
            globalPermissionCacheSize,
            globalLockCacheSize,
            globalDescrtiptorsCacheSize,
            globalDescrtiptorCacheSize,
            contentCachingEnabled,
            globalContentCacheSize,
            contentCacheBytes,
            txContentCacheSize,
            txContentCacheBytes,
            maxByteSizePerEntry);
    }

    //
    // overloaded content methods with caching 
    //

    public NodeRevisionContent retrieveRevisionContent(Uri uri, \
NodeRevisionDescriptor revisionDescriptor)  throws ServiceAccessException, \
RevisionNotFoundException {  if (contentStore.cacheResults() && contentCache != null) \
                {
            String key = uri.toString() + "_" + \
revisionDescriptor.getRevisionNumber();  Object result = contentCache.get(key);
            if (result != null) {
                getLogger().log("Retrieving content at '" + key + "' from cache", \
LOG_CHANNEL, Logger.DEBUG);  // TODO make a copy?! how?
                return (NodeRevisionContent) result;
            } else {
                NodeRevisionContent revisionContent = \
super.retrieveRevisionContent(uri, revisionDescriptor);  long size = \
revisionDescriptor.getContentLength();  contentCache.putForRead(key, revisionContent, \
size);  return revisionContent;
            }
        } else {
            return super.retrieveRevisionContent(uri, revisionDescriptor);
        }
    }

    public void createRevisionContent(
        Uri uri,
        NodeRevisionDescriptor revisionDescriptor,
        NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionAlreadyExistException {

        long size = -1L;
        if (contentStore.cacheResults() && contentCache != null) {
            size = revisionDescriptor.getContentLength();
            contentCache.precache(revisionContent, size);
        }

        super.createRevisionContent(uri, revisionDescriptor, revisionContent);

        if (contentStore.cacheResults() && contentCache != null) {
            String key = uri.toString() + "_" + \
revisionDescriptor.getRevisionNumber();  contentCache.put(key, revisionContent, \
size);  }
    }

    public void storeRevisionContent(
        Uri uri,
        NodeRevisionDescriptor revisionDescriptor,
        NodeRevisionContent revisionContent)
        throws ServiceAccessException, RevisionNotFoundException {

        long size = -1L;
        if (contentStore.cacheResults() && contentCache != null) {
            size = revisionDescriptor.getContentLength();
            contentCache.precache(revisionContent, size);
        }

        super.storeRevisionContent(uri, revisionDescriptor, revisionContent);

        if (contentStore.cacheResults() && contentCache != null) {
            String key = uri.toString() + "_" + \
revisionDescriptor.getRevisionNumber();  contentCache.put(key, revisionContent, \
size);  }
    }

    public void removeRevisionContent(Uri uri, NodeRevisionDescriptor \
revisionDescriptor)  throws ServiceAccessException {
        if (contentStore.cacheResults() && contentCache != null) {
            String key = uri.toString() + "_" + \
revisionDescriptor.getRevisionNumber();  contentCache.remove(key);
        }
        super.removeRevisionContent(uri, revisionDescriptor);
    }

    //
    // overloaded XAResource methods for transaction support
    // 

    public void forget(Xid xid) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        super.forget(xid);

        Xid txId = (Xid) XidWrapper.wrap(xid);
        activeTransactionBranch.set(null);

        ((TxCacheWrapper) objectsCache).getTxCache().forget(txId);
        ((TxCacheWrapper) permissionsCache).getTxCache().forget(txId);
        ((TxCacheWrapper) locksCache).getTxCache().forget(txId);
        ((TxCacheWrapper) descriptorsCache).getTxCache().forget(txId);
        ((TxCacheWrapper) descriptorCache).getTxCache().forget(txId);
        if (contentCache != null)
            contentCache.getTxCache().forget(txId);
    }

    public void rollback(Xid xid) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        super.rollback(xid);

        Xid txId = (Xid) XidWrapper.wrap(xid);
        activeTransactionBranch.set(null);

        ((TxCacheWrapper) objectsCache).getTxCache().rollback(txId);
        ((TxCacheWrapper) permissionsCache).getTxCache().rollback(txId);
        ((TxCacheWrapper) locksCache).getTxCache().rollback(txId);
        ((TxCacheWrapper) descriptorsCache).getTxCache().rollback(txId);
        ((TxCacheWrapper) descriptorCache).getTxCache().rollback(txId);
        if (contentCache != null)
            contentCache.getTxCache().rollback(txId);
    }

    public void commit(Xid xid, boolean onePhase) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        super.commit(xid, onePhase);

        Xid txId = (Xid) XidWrapper.wrap(xid);
        activeTransactionBranch.set(null);

        ((TxCacheWrapper) objectsCache).getTxCache().commit(txId);
        ((TxCacheWrapper) permissionsCache).getTxCache().commit(txId);
        ((TxCacheWrapper) locksCache).getTxCache().commit(txId);
        ((TxCacheWrapper) descriptorsCache).getTxCache().commit(txId);
        ((TxCacheWrapper) descriptorCache).getTxCache().commit(txId);
        if (contentCache != null)
            contentCache.getTxCache().commit(txId);
    }

    public int prepare(Xid xid) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        return super.prepare(xid);

        // failure is notifyed directly on transaction, so there is no need to check \
it here  return XA_OK;   
    }

    public void start(Xid xid, int flags) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        super.start(xid, flags);

        // XXX we do not suspend, so we do not resume
        if (flags == TMNOFLAGS || flags == TMJOIN) {
            Xid txId = (Xid) XidWrapper.wrap(xid);
            activeTransactionBranch.set(txId);

            ((TxCacheWrapper) objectsCache).getTxCache().start(txId);
            ((TxCacheWrapper) permissionsCache).getTxCache().start(txId);
            ((TxCacheWrapper) locksCache).getTxCache().start(txId);
            ((TxCacheWrapper) descriptorsCache).getTxCache().start(txId);
            ((TxCacheWrapper) descriptorCache).getTxCache().start(txId);
            if (contentCache != null)
                contentCache.getTxCache().start(txId);
        }
    }

    public void end(Xid xid, int flags) throws XAException {
        // XXX super implementation does not allow for more than one transaction at \
it time, so better leave it out  //        super.end(xid, flags);
    }

    //

    protected synchronized void enlist(Service service) throws ServiceAccessException \
{  // we need to register ourselves to be part of the tx
        // XXX be careful not to call super.enlist() as this will call this method \
recursively  super.enlist(this);
        super.enlist(service);
    }

    protected synchronized void delist(Service service, boolean success) throws \
ServiceAccessException {  // as we have registered we need to deregister
        // XXX be careful not to call super.delist(success) as this will call this \
method recursively  super.delist(this, success);
        super.delist(service, success);
    }

    protected void resetCaches() {
        // StandardStore calls this upon delist for a rudimentary tx control
        // we have one of our own tx control, so do nothing here  
    }

    //

    // have it outside ctor to make it overloadable
    protected void init(
        int globalObjectCacheSize,
        int globalPermissionCacheSize,
        int globalLockCacheSize,
        int globalDescrtiptorsCacheSize,
        int globalDescrtiptorCacheSize,
        boolean contentCachingEnabled,
        int globalContentCacheSize,
        long contentCacheBytes,
        int txContentCacheSize,
        long txContentCacheBytes,
        long maxByteSizePerEntry) {
        try {
            objectsCache = new TxCacheWrapper(globalObjectCacheSize, "object");
            permissionsCache = new TxCacheWrapper(globalPermissionCacheSize, \
"permission");  locksCache = new TxCacheWrapper(globalLockCacheSize, "lock");
            descriptorsCache = new TxCacheWrapper(globalDescrtiptorsCacheSize, \
                "descriptors");
            descriptorCache = new TxCacheWrapper(globalDescrtiptorCacheSize, \
"descriptor");

            if (contentCachingEnabled) {
                contentCache =
                    new TxContentCacheWrapper(
                        new ByteSizeLimitedObjectCache(
                            globalContentCacheSize,
                            txContentCacheSize,
                            contentCacheBytes,
                            txContentCacheBytes,
                            maxByteSizePerEntry,
                            getName() + ".content",
                            getLogger()));
            } else {
                contentCache = null;
            }
        } catch (Error e) {
            fatalError(e);
        } catch (RuntimeException re) {
            fatalError(re);
        }
    }

    // could be out of memory etc.
    protected void fatalError(Error e) {
        getLogger().log("Fatal error: " + e, LOG_CHANNEL, Logger.CRITICAL);
        // XXX needed to guarantee, stack trace will be displayed
        getLogger().log(e, LOG_CHANNEL, Logger.CRITICAL);
        setRollbackOnly();
        throw e;
    }

    // could be  null pointer or any other programming error
    protected void fatalError(RuntimeException re) {
        getLogger().log("Fatal error: " + re, LOG_CHANNEL, Logger.CRITICAL);
        // XXX needed to guarantee, stack trace will be displayed
        getLogger().log(re, LOG_CHANNEL, Logger.CRITICAL);
        setRollbackOnly();
        throw re;
    }

    protected class TxCacheWrapper implements ObjectCache {

        private TxLRUObjectCache txCache;

        public TxCacheWrapper(TxLRUObjectCache txCache) {
            this.txCache = txCache;
        }

        public TxCacheWrapper(int globalCacheSize, String name) {
            this(new TxLRUObjectCache(globalCacheSize, getName() + "." + name, \
getLogger()));  }

        public Object get(Object key) {
            if (globalCacheOff) return null;
            try {
                Xid txId = (Xid) activeTransactionBranch.get();
                return txCache.get(txId, key);
            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
            // XXX will never be called, as fatalError will always throw a throwable, \
just satisfy compiler  return null;
        }

        public void put(Object key, Object value) {
            if (globalCacheOff) return;
            try {
                Xid txId = (Xid) activeTransactionBranch.get();
                txCache.put(txId, key, value);
            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

        public void remove(Object key) {
            if (globalCacheOff) return;
            try {
                Xid txId = (Xid) activeTransactionBranch.get();
                txCache.remove(txId, key);
                getLogger().log("Removing content at '" + key + "' from cache", \
LOG_CHANNEL, Logger.DEBUG);  } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

        public void clear() {
            try {
                txCache.clear();
            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

        public TxLRUObjectCache getTxCache() {
            return txCache;
        }

    }

    protected class TxContentCacheWrapper extends TxCacheWrapper {

        public TxContentCacheWrapper(ByteSizeLimitedObjectCache txCache) {
            super(txCache);
        }

        /*
         * Ensures that if revisionContent will be cashed later, it will be \
                accessible multiple times.
         * It does so by copying its input stream into a byte array. To avoid \
                excessively large byte 
         * arrays this is only done when revisionContent fits into the cache and will \
                be cached later anyway.
         * <br>
         * <br>
         * <em>Caution</em>: This method must be called before any other method \
                touches accesses revisionContent.
         *   
         */
        public void precache(NodeRevisionContent revisionContent, long byteSize) {
            if (globalCacheOff) return;
            try {
                Xid txId = (Xid) activeTransactionBranch.get();
                if (((ByteSizeLimitedObjectCache) getTxCache()).canCache(txId, \
                byteSize)) {
                    // buffer stream content into byte array for multiple access
                    revisionContent.getContentBytes();
                }
            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

        public void putForRead(Object key, NodeRevisionContent revisionContent, long \
byteSize) {  if (globalCacheOff) return;
            try {
                if (((ByteSizeLimitedObjectCache) getTxCache()).canCache(null, \
byteSize)) {

                    // buffer stream content into byte array for multiple access
                    revisionContent.getContentBytes();

                    ((ByteSizeLimitedObjectCache) getTxCache()).put(null, key, \
revisionContent, byteSize);  getLogger().log(
                        "Globally caching content at '" + key + "' with " + byteSize \
+ " bytes",  LOG_CHANNEL,
                        Logger.DEBUG);
                }

            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

        public void put(Object key, NodeRevisionContent revisionContent, long \
byteSize) {  if (globalCacheOff) return;
            try {
                Xid txId = (Xid) activeTransactionBranch.get();
                if (((ByteSizeLimitedObjectCache) getTxCache()).canCache(txId, \
byteSize)) {

                    // buffer stream content into byte array for multiple access
                    revisionContent.getContentBytes();

                    ((ByteSizeLimitedObjectCache) getTxCache()).put(txId, key, \
revisionContent, byteSize);  getLogger().log(
                        "Caching content at '" + key + "' with " + byteSize + " \
bytes",  LOG_CHANNEL,
                        Logger.DEBUG);
                } else {
                    // if we can not cache it, we need to invalidate global entry \
upon commit  getTxCache().remove(txId, key);
                }

            } catch (Error e) {
                fatalError(e);
            } catch (RuntimeException re) {
                fatalError(re);
            }
        }

    }

}



---------------------------------------------------------------------
To unsubscribe, e-mail: slide-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: slide-dev-help@jakarta.apache.org

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic