001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.transaction.memory;
019
020import java.util.Collection;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.commons.transaction.locking.ReadWriteLockManager;
025import org.apache.commons.transaction.util.LoggerFacade;
026
027/**
028 * Wrapper that adds transactional control to all kinds of maps that implement the {@link Map} interface. By using
029 * pessimistic transaction control (blocking locks) this wrapper has better isolation than {@link TransactionalMapWrapper}, but
030 * also has less possible concurrency and may even deadlock. A commit, however, will never fail.
031 * <br>
032 * Start a transaction by calling {@link #startTransaction()}. Then perform the normal actions on the map and
033 * finally either call {@link #commitTransaction()} to make your changes permanent or {@link #rollbackTransaction()} to
034 * undo them.
035 * <br>
036 * <em>Caution:</em> Do not modify values retrieved by {@link #get(Object)} as this will circumvent the transactional mechanism.
037 * Rather clone the value or copy it in a way you see fit and store it back using {@link #put(Object, Object)}.
038 * <br>
039 * <em>Note:</em> This wrapper guarantees isolation level <code>SERIALIZABLE</code>.
040 * 
041 * @version $Id: PessimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $
042 * @see TransactionalMapWrapper
043 * @see OptimisticMapWrapper
044 */
045public class PessimisticMapWrapper extends TransactionalMapWrapper {
046
047    protected static final int READ = 1;
048    protected static final int WRITE = 2;
049
050    protected static final Object GLOBAL_LOCK = "GLOBAL";
051
052    protected ReadWriteLockManager lockManager;
053//    protected MultiLevelLock globalLock;
054    protected long readTimeOut = 60000; /* FIXME: pass in ctor */
055
056    /**
057     * Creates a new pessimistic transactional map wrapper. Temporary maps and sets to store transactional
058     * data will be instances of {@link java.util.HashMap} and {@link java.util.HashSet}. 
059     * 
060     * @param wrapped map to be wrapped
061     * @param logger
062     *            generic logger used for all kinds of logging
063     */
064    public PessimisticMapWrapper(Map wrapped, LoggerFacade logger) {
065        this(wrapped, new HashMapFactory(), new HashSetFactory(), logger);
066    }
067
068    /**
069     * Creates a new pessimistic transactional map wrapper. Temporary maps and sets to store transactional
070     * data will be created and disposed using {@link MapFactory} and {@link SetFactory}.
071     * 
072     * @param wrapped map to be wrapped
073     * @param mapFactory factory for temporary maps
074     * @param setFactory factory for temporary sets
075     * @param logger
076     *            generic logger used for all kinds of logging
077     */
078    public PessimisticMapWrapper(Map wrapped, MapFactory mapFactory, SetFactory setFactory, LoggerFacade logger) {
079        super(wrapped, mapFactory, setFactory);
080        lockManager = new ReadWriteLockManager(logger, readTimeOut);
081//        globalLock = new GenericLock(GLOBAL_LOCK_NAME, WRITE, logger);
082    }
083
084    public void startTransaction() {
085        if (getActiveTx() != null) {
086            throw new IllegalStateException(
087                "Active thread " + Thread.currentThread() + " already associated with a transaction!");
088        }
089        LockingTxContext context = new LockingTxContext();
090        setActiveTx(context);
091    }
092
093    public Collection values() {
094        assureGlobalReadLock();
095        return super.values();
096    }
097
098    public Set entrySet() {
099        assureGlobalReadLock();
100        return super.entrySet();
101    }
102
103    public Set keySet() {
104        assureGlobalReadLock();
105        return super.keySet();
106    }
107
108    public Object remove(Object key) {
109        // assure we get a write lock before super can get a read lock to avoid lots
110        // of deadlocks
111        assureWriteLock(key);
112        return super.remove(key);
113    }
114
115    public Object put(Object key, Object value) {
116        // assure we get a write lock before super can get a read lock to avoid lots
117        // of deadlocks
118        assureWriteLock(key);
119        return super.put(key, value);
120    }
121
122    protected void assureWriteLock(Object key) {
123        LockingTxContext txContext = (LockingTxContext) getActiveTx();
124        if (txContext != null) {
125            lockManager.writeLock(txContext, key);
126            // XXX fake intention lock (prohibits global WRITE)
127            lockManager.readLock(txContext, GLOBAL_LOCK); 
128        }
129    }
130    
131    protected void assureGlobalReadLock() {
132        LockingTxContext txContext = (LockingTxContext) getActiveTx();
133        if (txContext != null) {
134            // XXX fake intention lock (prohibits global WRITE)
135            lockManager.readLock(txContext, GLOBAL_LOCK); 
136        }
137    }
138    
139    public class LockingTxContext extends TxContext {
140
141        protected Set keys() {
142            lockManager.readLock(this, GLOBAL_LOCK); 
143            return super.keys();
144        }
145
146        protected Object get(Object key) {
147            lockManager.readLock(this, key);
148            // XXX fake intention lock (prohibits global WRITE)
149            lockManager.readLock(this, GLOBAL_LOCK);
150            return super.get(key);
151        }
152
153        protected void put(Object key, Object value) {
154            lockManager.writeLock(this, key);
155            // XXX fake intention lock (prohibits global WRITE)
156            lockManager.readLock(this, GLOBAL_LOCK);
157            super.put(key, value);
158        }
159
160        protected void remove(Object key) {
161            lockManager.writeLock(this, key);
162            // XXX fake intention lock (prohibits global WRITE)
163            lockManager.readLock(this, GLOBAL_LOCK);
164            super.remove(key);
165        }
166
167        protected int size() {
168            // XXX this is bad luck, we need a global read lock just for the size :( :( :(
169            lockManager.readLock(this, GLOBAL_LOCK);
170            return super.size();
171        }
172
173        protected void clear() {
174            lockManager.writeLock(this, GLOBAL_LOCK);
175            super.clear();
176        }
177
178        protected void dispose() {
179            super.dispose();
180            lockManager.releaseAll(this);
181        }
182
183        protected void finalize() throws Throwable {
184            dispose();
185            super.finalize();
186        }
187    }
188
189}