Author: ozeigermann
Date: Fri Jul 20 09:13:20 2007
New Revision: 558031
URL: http://svn.apache.org/viewvc?view=rev&rev=558031
Log:
Reintroduces transactional memory implementations. However, they no longer wrap
a map, but hide the wrapped map as an implementation detail.
Added:
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
Added:
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java?view=auto&rev=558031
==============================================================================
---
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
(added)
+++
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/OptimisticTxMap.java
Fri Jul 20 09:13:20 2007
@@ -0,0 +1,340 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.transaction.memory;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.transaction.TransactionalResourceManager;
+import org.apache.commons.transaction.locking.LockException;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. By using a naive optimistic
transaction control
+ * this wrapper has better isolation than [EMAIL PROTECTED] TxMap}, but
+ * may also fail to commit.
+ *
+ * <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED]
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>SERIALIZABLE</code>. <br>
+ * <em>Caution:</em> This implementation might be slow when large amounts of
+ * data is changed in a transaction as much references will need to be copied
+ * around.
+ *
+ * @version $Id: OptimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $
+ * @see TxMap
+ * @see PessimisticTxMap
+ */
+public class OptimisticTxMap<K, V> extends TxMap<K, V> implements Map<K, V>,
+TransactionalResourceManager {
+
+ private Set<CopyingTxContext> activeTransactions = new
HashSet<CopyingTxContext>();
+ private ReadWriteLock commitLock = new ReentrantReadWriteLock();
+
+ private long commitTimeout = 1000 * 60; // 1 minute
+
+ private long accessTimeout = 1000 * 30; // 30 seconds
+ public void rollbackTransaction() {
+ MapTxContext txContext = getActiveTx();
+ super.rollbackTransaction();
+ activeTransactions.remove(txContext);
+ }
+
+ public void commitTransaction() throws LockException {
+ commitTransaction(false);
+ }
+
+ public void commitTransaction(boolean force) throws LockException {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ throw new IllegalStateException(
+ "Active thread " + Thread.currentThread() + " not associated
with a transaction!");
+ }
+
+ if (txContext.isMarkedForRollback()) {
+ throw new IllegalStateException("Active thread " +
Thread.currentThread() + " is marked for rollback!");
+ }
+
+ try {
+ // in this final commit phase we need to be the only one access the
+ // map
+ // to make sure no one adds an entry after we checked for conflicts
+ commitLock.writeLock().tryLock(getCommitTimeout(),
TimeUnit.MILLISECONDS);
+
+ if (!force) {
+ Object conflictKey = checkForConflicts();
+ if (conflictKey != null) {
+ throw new LockException(LockException.Code.CONFLICT,
conflictKey);
+ }
+ }
+
+ activeTransactions.remove(txContext);
+ copyChangesToConcurrentTransactions();
+ super.commitTransaction();
+
+ } catch (InterruptedException e) {
+ throw new LockException(e);
+ } finally {
+ commitLock.writeLock().unlock();
+ }
+ }
+
+ // TODO: Shouldn't we return a collection rather than a single key here?
+ public Object checkForConflicts() {
+ CopyingTxContext txContext = (CopyingTxContext) getActiveTx();
+
+ Set keys = txContext.changedKeys();
+ Set externalKeys = txContext.externalChangedKeys();
+
+ for (Iterator it2 = keys.iterator(); it2.hasNext();) {
+ Object key = it2.next();
+ if (externalKeys.contains(key)) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ protected void copyChangesToConcurrentTransactions() {
+ CopyingTxContext thisTxContext = (CopyingTxContext) getActiveTx();
+
+ synchronized (activeTransactions) {
+ for (Iterator it = activeTransactions.iterator(); it.hasNext();) {
+ CopyingTxContext otherTxContext = (CopyingTxContext) it.next();
+
+ // no need to copy data if the other transaction does not
access
+ // global map anyway
+ if (otherTxContext.cleared)
+ continue;
+
+ if (thisTxContext.cleared) {
+ // we will clear everything, so we have to copy everything
+ // before
+ otherTxContext.externalChanges.putAll(wrapped);
+ } else // no need to check if we have already copied everthing
+ {
+ for (Iterator it2 =
thisTxContext.changes.entrySet().iterator(); it2.hasNext();) {
+ Map.Entry<K, V> entry = (Map.Entry) it2.next();
+ V value = wrapped.get(entry.getKey());
+ if (value != null) {
+ // undo change
+ otherTxContext.externalChanges.put(entry.getKey(),
value);
+ } else {
+ // undo add
+ otherTxContext.externalDeletes.add(entry.getKey());
+ }
+ }
+
+ for (Iterator it2 = thisTxContext.deletes.iterator();
it2.hasNext();) {
+ // undo delete
+ K key = (K) it2.next(); /* FIXME: This could crash */
+ V value = wrapped.get(key);
+ otherTxContext.externalChanges.put(key, value);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected CopyingTxContext createContext() {
+ return new CopyingTxContext();
+ }
+
+ public class CopyingTxContext extends MapTxContext {
+ protected Map<K, V> externalChanges;
+
+ protected Map<K, V> externalAdds;
+
+ protected Set externalDeletes;
+
+ protected CopyingTxContext() {
+ super();
+ externalChanges = new HashMap();
+ externalDeletes = new HashSet();
+ externalAdds = new HashMap();
+ }
+
+ protected Set externalChangedKeys() {
+ Set keySet = new HashSet();
+ keySet.addAll(externalDeletes);
+ keySet.addAll(externalChanges.keySet());
+ keySet.addAll(externalAdds.keySet());
+ return keySet;
+ }
+
+ protected Set changedKeys() {
+ Set keySet = new HashSet();
+ keySet.addAll(deletes);
+ keySet.addAll(changes.keySet());
+ keySet.addAll(adds.keySet());
+ return keySet;
+ }
+
+ protected Set keys() {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ Set keySet = super.keys();
+ keySet.removeAll(externalDeletes);
+ keySet.addAll(externalAdds.keySet());
+ return keySet;
+ } catch (InterruptedException e) {
+ return null;
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ protected V get(Object key) {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+
+ if (deletes.contains(key)) {
+ // reflects that entry has been deleted in this tx
+ return null;
+ }
+
+ V changed = changes.get(key);
+ if (changed != null) {
+ return changed;
+ }
+
+ V added = adds.get(key);
+ if (added != null) {
+ return added;
+ }
+
+ if (cleared) {
+ return null;
+ } else {
+ if (externalDeletes.contains(key)) {
+ // reflects that entry has been deleted in this tx
+ return null;
+ }
+
+ changed = externalChanges.get(key);
+ if (changed != null) {
+ return changed;
+ }
+
+ added = externalAdds.get(key);
+ if (added != null) {
+ return added;
+ }
+
+ // not modified in this tx
+ return wrapped.get(key);
+ }
+ } catch (InterruptedException e) {
+ return null;
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ protected void put(K key, V value) {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ super.put(key, value);
+ } catch (InterruptedException e) {
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ protected void remove(Object key) {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ super.remove(key);
+ } catch (InterruptedException e) {
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ protected int size() {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ int size = super.size();
+
+ size -= externalDeletes.size();
+ size += externalAdds.size();
+
+ return size;
+ } catch (InterruptedException e) {
+ return -1;
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ protected void clear() {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ super.clear();
+ externalDeletes.clear();
+ externalChanges.clear();
+ externalAdds.clear();
+ } catch (InterruptedException e) {
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ public void commit() {
+ try {
+ commitLock.readLock().tryLock(getAccessTimeout(),
TimeUnit.MILLISECONDS);
+ super.commit();
+ } catch (InterruptedException e) {
+ } finally {
+ commitLock.readLock().unlock();
+ }
+ }
+
+ }
+
+ public long getAccessTimeout() {
+ return accessTimeout;
+ }
+
+ public void setAccessTimeout(long accessTimeout) {
+ this.accessTimeout = accessTimeout;
+ }
+
+ public long getCommitTimeout() {
+ return commitTimeout;
+ }
+
+ public void setCommitTimeout(long commitTimeout) {
+ this.commitTimeout = commitTimeout;
+ }
+}
Added:
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java?view=auto&rev=558031
==============================================================================
---
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
(added)
+++
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/PessimisticTxMap.java
Fri Jul 20 09:13:20 2007
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.transaction.memory;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.transaction.TransactionalResourceManager;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. By using pessimistic transaction
control (blocking
+ * locks) this wrapper has better isolation than [EMAIL PROTECTED] TxMap},
+ * but also has less possible concurrency and may even deadlock. A commit,
+ * however, will never fail. <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED]
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>SERIALIZABLE</code>.
+ *
+ * @version $Id: PessimisticMapWrapper.java 493628 2007-01-07 01:42:48Z joerg $
+ * @see TxMap
+ * @see OptimisticTxMap
+ */
+public class PessimisticTxMap<K, V> extends TxMap<K, V> implements Map<K, V>,
+TransactionalResourceManager {
+
+ protected static final Object GLOBAL_LOCK = "GLOBAL";
+
+ public Collection values() {
+ assureGlobalReadLock();
+ return super.values();
+ }
+
+ public Set entrySet() {
+ assureGlobalReadLock();
+ return super.entrySet();
+ }
+
+ public Set keySet() {
+ assureGlobalReadLock();
+ return super.keySet();
+ }
+
+ public V remove(Object key) {
+ // assure we get a write lock before super can get a read lock to avoid
+ // lots
+ // of deadlocks
+ assureWriteLock(key);
+ return super.remove(key);
+ }
+
+ public V put(K key, V value) {
+ // assure we get a write lock before super can get a read lock to avoid
+ // lots
+ // of deadlocks
+ assureWriteLock(key);
+ return super.put(key, value);
+ }
+
+ protected void assureWriteLock(Object key) {
+ LockingTxContext txContext = (LockingTxContext) getActiveTx();
+ if (txContext != null) {
+ txContext.writeLock(key);
+ // XXX fake intention lock (prohibits global WRITE)
+ txContext.readLock(GLOBAL_LOCK);
+ }
+ }
+
+ protected void assureGlobalReadLock() {
+ LockingTxContext txContext = (LockingTxContext) getActiveTx();
+ if (txContext != null) {
+ // XXX fake intention lock (prohibits global WRITE)
+ txContext.readLock(GLOBAL_LOCK);
+ }
+ }
+
+ @Override
+ protected LockingTxContext createContext() {
+ return new LockingTxContext();
+ }
+
+ public class LockingTxContext extends MapTxContext {
+
+ protected Set keys() {
+ readLock(GLOBAL_LOCK);
+ return super.keys();
+ }
+
+ protected V get(Object key) {
+ readLock(key);
+ // XXX fake intention lock (prohibits global WRITE)
+ readLock(GLOBAL_LOCK);
+ return super.get(key);
+ }
+
+ protected void put(K key, V value) {
+ writeLock(key);
+ // XXX fake intention lock (prohibits global WRITE)
+ readLock(GLOBAL_LOCK);
+ super.put(key, value);
+ }
+
+ protected void remove(Object key) {
+ writeLock(key);
+ // XXX fake intention lock (prohibits global WRITE)
+ readLock(GLOBAL_LOCK);
+ super.remove(key);
+ }
+
+ protected int size() {
+ // XXX this is bad luck, we need a global read lock just for the
+ // size :( :( :(
+ readLock(GLOBAL_LOCK);
+ return super.size();
+ }
+
+ protected void clear() {
+ writeLock(GLOBAL_LOCK);
+ super.clear();
+ }
+
+ }
+
+}
Added:
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
URL:
http://svn.apache.org/viewvc/jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java?view=auto&rev=558031
==============================================================================
---
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
(added)
+++
jakarta/commons/proper/transaction/branches/TRANSACTION_2/src/java/org/apache/commons/transaction/memory/TxMap.java
Fri Jul 20 09:13:20 2007
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.transaction.memory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.transaction.AbstractTransactionalResourceManager;
+import org.apache.commons.transaction.TransactionalResourceManager;
+import
org.apache.commons.transaction.AbstractTransactionalResourceManager.AbstractTxContext;
+
+/**
+ * Wrapper that adds transactional control to all kinds of maps that implement
+ * the [EMAIL PROTECTED] Map} interface. This wrapper has rather weak
isolation, but is
+ * simply, neven blocks and commits will never fail for logical reasons. <br>
+ * Start a transaction by calling [EMAIL PROTECTED] #startTransaction()}. Then
perform the
+ * normal actions on the map and finally either call
+ * [EMAIL PROTECTED] #commitTransaction()} to make your changes permanent or
+ * [EMAIL PROTECTED] #rollbackTransaction()} to undo them. <br>
+ * <em>Caution:</em> Do not modify values retrieved by [EMAIL PROTECTED]
#get(Object)} as
+ * this will circumvent the transactional mechanism. Rather clone the value or
+ * copy it in a way you see fit and store it back using
+ * [EMAIL PROTECTED] #put(Object, Object)}. <br>
+ * <em>Note:</em> This wrapper guarantees isolation level
+ * <code>READ COMMITTED</code> only. I.e. as soon a value is committed in one
+ * transaction it will be immediately visible in all other concurrent
+ * transactions.
+ *
+ * @see OptimisticTxMap
+ * @see PessimisticTxMap
+ */
+public class TxMap<K, V> extends
+ AbstractTransactionalResourceManager<TxMap.MapTxContext> implements
Map<K, V>,
+ TransactionalResourceManager {
+
+ protected Map<K, V> wrapped = new ConcurrentHashMap<K, V>();
+
+ //
+ // Map methods
+ //
+
+ /**
+ * @see Map#clear()
+ */
+ public void clear() {
+ MapTxContext txContext = getActiveTx();
+ if (txContext != null) {
+ txContext.clear();
+ } else {
+ wrapped.clear();
+ }
+ }
+
+ /**
+ * @see Map#size()
+ */
+ public int size() {
+ MapTxContext txContext = getActiveTx();
+ if (txContext != null) {
+ return txContext.size();
+ } else {
+ return wrapped.size();
+ }
+ }
+
+ /**
+ * @see Map#isEmpty()
+ */
+ public boolean isEmpty() {
+ MapTxContext txContext = getActiveTx();
+ if (txContext == null) {
+ return wrapped.isEmpty();
+ } else {
+ return txContext.isEmpty();
+ }
+ }
+
+ /**
+ * @see Map#containsKey(java.lang.Object)
+ */
+ public boolean containsKey(Object key) {
+ return keySet().contains(key);
+ }
+
+ /**
+ * @see Map#containsValue(java.lang.Object)
+ */
+ public boolean containsValue(Object value) {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ return wrapped.containsValue(value);
+ } else {
+ return values().contains(value);
+ }
+ }
+
+ /**
+ * @see Map#values()
+ */
+ public Collection values() {
+
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ return wrapped.values();
+ } else {
+ // XXX expensive :(
+ Collection values = new ArrayList();
+ for (Iterator it = keySet().iterator(); it.hasNext();) {
+ Object key = it.next();
+ Object value = get(key);
+ // XXX we have no isolation, so get entry might have been
+ // deleted in the meantime
+ if (value != null) {
+ values.add(value);
+ }
+ }
+ return values;
+ }
+ }
+
+ /**
+ * @see Map#putAll(java.util.Map)
+ */
+ public void putAll(Map map) {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ wrapped.putAll(map);
+ } else {
+ for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<K, V> entry = (Map.Entry) it.next();
+ txContext.put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * @see Map#entrySet()
+ */
+ public Set entrySet() {
+ MapTxContext txContext = getActiveTx();
+ if (txContext == null) {
+ return wrapped.entrySet();
+ } else {
+ Set entrySet = new HashSet();
+ // XXX expensive :(
+ for (Iterator it = keySet().iterator(); it.hasNext();) {
+ Object key = it.next();
+ Object value = get(key);
+ // XXX we have no isolation, so get entry might have been
+ // deleted in the meantime
+ if (value != null) {
+ entrySet.add(new HashEntry(key, value));
+ }
+ }
+ return entrySet;
+ }
+ }
+
+ /**
+ * @see Map#keySet()
+ */
+ public Set keySet() {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ return wrapped.keySet();
+ } else {
+ return txContext.keys();
+ }
+ }
+
+ /**
+ * @see Map#get(java.lang.Object)
+ */
+ public V get(Object key) {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext != null) {
+ return txContext.get(key);
+ } else {
+ return wrapped.get(key);
+ }
+ }
+
+ /**
+ * @see Map#remove(java.lang.Object)
+ */
+ public V remove(Object key) {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ return wrapped.remove(key);
+ } else {
+ V oldValue = get(key);
+ txContext.remove(key);
+ return oldValue;
+ }
+ }
+
+ /**
+ * @see Map#put(java.lang.Object, java.lang.Object)
+ */
+ public V put(K key, V value) {
+ MapTxContext txContext = getActiveTx();
+
+ if (txContext == null) {
+ return wrapped.put(key, value);
+ } else {
+ V oldValue = get(key);
+ txContext.put(key, value);
+ return oldValue;
+ }
+
+ }
+
+ @Override
+ protected MapTxContext createContext() {
+ return new MapTxContext();
+ }
+
+ @Override
+ protected MapTxContext getActiveTx() {
+ return activeTx.get();
+ }
+
+ // mostly copied from org.apache.commons.collections.map.AbstractHashedMap
+ protected static class HashEntry implements Map.Entry {
+ /** The key */
+ protected Object key;
+
+ /** The value */
+ protected Object value;
+
+ protected HashEntry(Object key, Object value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public Object getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Object setValue(Object value) {
+ Object old = this.value;
+ this.value = value;
+ return old;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry other = (Map.Entry) obj;
+ return (getKey() == null ? other.getKey() == null :
getKey().equals(other.getKey()))
+ && (getValue() == null ? other.getValue() == null :
getValue().equals(
+ other.getValue()));
+ }
+
+ public int hashCode() {
+ return (getKey() == null ? 0 : getKey().hashCode())
+ ^ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ public String toString() {
+ return new
StringBuffer().append(getKey()).append('=').append(getValue()).toString();
+ }
+ }
+
+ public class MapTxContext extends AbstractTxContext {
+ protected Set deletes;
+
+ protected Map<K, V> changes;
+
+ protected Map<K, V> adds;
+
+ protected boolean cleared;
+
+ protected MapTxContext() {
+ deletes = new HashSet();
+ changes = new HashMap<K, V>();
+ adds = new HashMap<K, V>();
+ cleared = false;
+ }
+
+ protected Set keys() {
+ Set keySet = new HashSet();
+ if (!cleared) {
+ keySet.addAll(wrapped.keySet());
+ keySet.removeAll(deletes);
+ }
+ keySet.addAll(adds.keySet());
+ return keySet;
+ }
+
+ protected V get(Object key) {
+
+ if (deletes.contains(key)) {
+ // reflects that entry has been deleted in this tx
+ return null;
+ }
+
+ if (changes.containsKey(key)) {
+ return changes.get(key);
+ }
+
+ if (adds.containsKey(key)) {
+ return adds.get(key);
+ }
+
+ if (cleared) {
+ return null;
+ } else {
+ // not modified in this tx
+ return wrapped.get(key);
+ }
+ }
+
+ protected void put(K key, V value) {
+ try {
+ setReadOnly(false);
+ deletes.remove(key);
+ if (wrapped.containsKey(key)) {
+ changes.put(key, value);
+ } else {
+ adds.put(key, value);
+ }
+ } catch (RuntimeException e) {
+ markForRollback();
+ throw e;
+ } catch (Error e) {
+ markForRollback();
+ throw e;
+ }
+ }
+
+ protected void remove(Object key) {
+
+ try {
+ setReadOnly(false);
+ changes.remove(key);
+ adds.remove(key);
+ if (wrapped.containsKey(key) && !cleared) {
+ deletes.add(key);
+ }
+ } catch (RuntimeException e) {
+ markForRollback();
+ throw e;
+ } catch (Error e) {
+ markForRollback();
+ throw e;
+ }
+ }
+
+ protected int size() {
+ int size = (cleared ? 0 : wrapped.size());
+
+ size -= deletes.size();
+ size += adds.size();
+
+ return size;
+ }
+
+ protected void clear() {
+ setReadOnly(false);
+ cleared = true;
+ deletes.clear();
+ changes.clear();
+ adds.clear();
+ }
+
+ protected boolean isEmpty() {
+ return (size() == 0);
+ }
+
+ public void commit() {
+ if (!isReadOnly()) {
+
+ if (cleared) {
+ wrapped.clear();
+ }
+
+ wrapped.putAll(changes);
+ wrapped.putAll(adds);
+
+ for (Iterator it = deletes.iterator(); it.hasNext();) {
+ Object key = it.next();
+ wrapped.remove(key);
+ }
+ }
+ }
+
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]