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    
018    package org.apache.commons.transaction.memory;
019    
020    import java.util.Collection;
021    import java.util.Map;
022    import java.util.Set;
023    
024    import org.apache.commons.transaction.locking.ReadWriteLockManager;
025    import 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     */
045    public 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    }