Martin Holz wrote:
I think setting the log chanel name to TxLRUObjectCache.name or org.apache.slide.util.TxLRUObjectCache.name in TxLRUObjectCache.java, line 113 would make it easier to configure it in log4j.

I agree. As there can be multiple top level stores I would also suggest to include the name of the store (in our example tx). This might result in channels like


TxLRUObjectCache(tx(object)).

Or is this too much?

I will attach the according version. I also added configurable cache sizes. You can configure them in Domain.xml like this:

...
<store name="tx" classname="org.apache.slide.store.TxCacheStore">
  <parameter name="cache-size">1000</parameter>
  ...
</store>
...

Comments, please...

Again: Any ideas about simple caching of content? Or do you think this is not so important?

Oliver
/*
 * $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 [EMAIL PROTECTED]
 *
 * 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. This is needed to assure correctness of global cache.   
 * 
 * @author <a href="mailto:[EMAIL PROTECTED]">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;

    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);
        }
    }
}
/*
 * $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 [EMAIL PROTECTED]
 *
 * 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.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:[EMAIL PROTECTED]">Oliver Zeigermann</a>
 * @version $Revision: 1.0$
 */
public class TxCacheStore extends StandardStore {

    private static final String LOG_CHANNEL = TxCacheStore.class.getName();
    protected static final int DEFAULT_GLOBAL_CACHE_SIZE = 10000;
    protected static final String GLOBAL_CACHE_SIZE_PARAMETER = "cache-size";

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

    public void setParameters(Hashtable parameters)
        throws ServiceParameterErrorException, ServiceParameterMissingException {
        super.setParameters(parameters);
        String globalCacheSizeString = (String) parameters.get(GLOBAL_CACHE_SIZE_PARAMETER);
        int globalCacheSize = DEFAULT_GLOBAL_CACHE_SIZE;

        if (globalCacheSizeString != null) {
            try {
                globalCacheSize = Integer.parseInt(globalCacheSizeString);
            } catch (NumberFormatException nfe) {
                getLogger().log("Cache size must be an integer! Using default size", Logger.WARNING);
            }
        }
        getLogger().log("Setting cache size for store " + getName() + " to " + globalCacheSize, Logger.INFO);
        init(globalCacheSize);
    }

    public void forget(Xid xid) throws XAException {
        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);
    }

    public void rollback(Xid xid) throws XAException {
        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);
    }

    public void commit(Xid xid, boolean onePhase) throws XAException {
        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);
    }

    public void start(Xid xid, int flags) throws XAException {
        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);
        }
    }

    // have it outside ctor to make it overloadable
    protected void init(int globalCacheSize) {
        objectsCache = new TxCacheWrapper(globalCacheSize, "object");
        permissionsCache = new TxCacheWrapper(globalCacheSize, "permission");
        locksCache = new TxCacheWrapper(globalCacheSize, "locks");
        descriptorsCache = new TxCacheWrapper(globalCacheSize, "descriptors");
        descriptorCache = new TxCacheWrapper(globalCacheSize, "descriptor");
    }

    protected 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 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  
    }

    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) {
            Xid txId = (Xid) activeTransactionBranch.get();
            return txCache.get(txId, key);
        }

        public void put(Object key, Object value) {
            Xid txId = (Xid) activeTransactionBranch.get();
            txCache.put(txId, key, value);
        }

        public void remove(Object key) {
            Xid txId = (Xid) activeTransactionBranch.get();
            txCache.remove(txId, key);
        }

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

        public TxLRUObjectCache getTxCache() {
            return txCache;
        }

    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to