commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ozeigerm...@apache.org
Subject cvs commit: jakarta-commons-sandbox/transaction/src/test/org/apache/commons/transaction/memory OptimisticMapWrapperTest.java
Date Thu, 03 Jun 2004 14:09:40 GMT
ozeigermann    2004/06/03 07:09:40

  Modified:    transaction/src/java/org/apache/commons/transaction/memory
                        OptimisticMapWrapper.java
  Added:       transaction/src/test/org/apache/commons/transaction/memory
                        OptimisticMapWrapperTest.java
  Log:
  Added initial version of optimistic serializable map plus test. *This is work in progress*
  
  Revision  Changes    Path
  1.2       +212 -28   jakarta-commons-sandbox/transaction/src/java/org/apache/commons/transaction/memory/OptimisticMapWrapper.java
  
  Index: OptimisticMapWrapper.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/transaction/src/java/org/apache/commons/transaction/memory/OptimisticMapWrapper.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- OptimisticMapWrapper.java	2 Jun 2004 21:54:04 -0000	1.1
  +++ OptimisticMapWrapper.java	3 Jun 2004 14:09:40 -0000	1.2
  @@ -23,12 +23,16 @@
   
   package org.apache.commons.transaction.memory;
   
  +import java.util.HashSet;
  +import java.util.Iterator;
   import java.util.Map;
  +import java.util.Set;
   
   /**
    * Wrapper that adds transactional control to all kinds of maps that implement the {@link
Map} interface. By using
  - * optimistic transaction control this wrapper has better isolation than {@link TransactionalMapWrapper},
but
  + * a naive optimistic transaction control this wrapper has better isolation than {@link
TransactionalMapWrapper}, but
    * may also fail to commit. 
  + *  
    * <br>
    * Start a transaction by calling {@link #startTransaction()}. Then perform the normal
actions on the map and
    * finally either calls {@link #commitTransaction()} to make your changes permanent or
{@link #rollbackTransaction()} to
  @@ -37,8 +41,9 @@
    * <em>Caution:</em> Do not modify values retrieved by {@link #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 {@link
#put(Object, Object)}.
    * <br>
  - * <em>Note:</em> This wrapper guarantees isolation levels <code>REPEATABLE
READ</code> or <code>SERIALIZABLE</code>.
  - * 
  + * <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.
    * @author <a href="mailto:ozeigermann@apache.org">Oliver Zeigermann</a>
    * @version $Revision$
    * @see TransactionalMapWrapper
  @@ -46,25 +51,204 @@
    */
   public class OptimisticMapWrapper extends TransactionalMapWrapper {
   
  -	/**
  -	 * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store
transactional
  -	 * data will be instances of {@link HashMap} and {@link HashSet}. 
  -	 * 
  -	 * @param wrapped map to be wrapped
  -	 */
  -	public OptimisticMapWrapper(Map wrapped) {
  -		super(wrapped, new HashMapFactory(), new HashSetFactory());
  -	}
  -
  -	/**
  -	 * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store
transactional
  -	 * data will be created and disposed using {@link MapFactory} and {@link SetFactory}.
  -	 * 
  -	 * @param wrapped map to be wrapped
  -	 * @param mapFactory factory for temporary maps
  -	 * @param setFactory factory for temporary sets
  -	 */
  -	public OptimisticMapWrapper(Map wrapped, MapFactory mapFactory, SetFactory setFactory)
{
  -		super(wrapped, mapFactory, setFactory);
  -	}
  +    protected Set activeTransactions;
  +
  +    /**
  +     * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store
transactional
  +     * data will be instances of {@link HashMap} and {@link HashSet}. 
  +     * 
  +     * @param wrapped map to be wrapped
  +     */
  +    public OptimisticMapWrapper(Map wrapped) {
  +        this(wrapped, new HashMapFactory(), new HashSetFactory());
  +    }
  +
  +    /**
  +     * Creates a new optimistic transactional map wrapper. Temporary maps and sets to store
transactional
  +     * data will be created and disposed using {@link MapFactory} and {@link SetFactory}.
  +     * 
  +     * @param wrapped map to be wrapped
  +     * @param mapFactory factory for temporary maps
  +     * @param setFactory factory for temporary sets
  +     */
  +    public OptimisticMapWrapper(
  +        Map wrapped,
  +        MapFactory mapFactory,
  +        SetFactory setFactory) {
  +        super(wrapped, mapFactory, setFactory);
  +        activeTransactions = new HashSet();
  +    }
  +
  +    public synchronized void startTransaction() {
  +        if (getActiveTx() != null) {
  +            throw new IllegalStateException(
  +                "Active thread "
  +                    + Thread.currentThread()
  +                    + " already associated with a transaction!");
  +        }
  +        CopyingTxContext context = new CopyingTxContext();
  +        activeTransactions.add(context);
  +        setActiveTx(context);
  +    }
  +
  +    public synchronized void commitTransaction() {
  +        TxContext txContext = getActiveTx();
  +
  +        if (txContext == null) {
  +            throw new IllegalStateException(
  +                "Active thread "
  +                    + Thread.currentThread()
  +                    + " not associated with a transaction!");
  +        }
  +
  +        if (txContext.rollbackOnly) {
  +            throw new IllegalStateException(
  +                "Active thread "
  +                    + Thread.currentThread()
  +                    + " is marked for rollback!");
  +        }
  +
  +        checkForConflicts();
  +
  +        copyChangesToConcurrentTransactions();
  +
  +        super.commitTransaction();
  +        activeTransactions.remove(Thread.currentThread());
  +
  +    }
  +
  +    protected void checkForConflicts() {
  +		// TODO
  +    }
  +
  +    protected void copyChangesToConcurrentTransactions() {
  +        CopyingTxContext thisTxContext = (CopyingTxContext) getActiveTx();
  +
  +        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 entry = (Map.Entry) it2.next();
  +                    Object 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
  +                    Object key = it2.next();
  +                    Object value = wrapped.get(key);
  +                    otherTxContext.externalChanges.put(key, value);
  +                }
  +            }
  +        }
  +
  +    }
  +
  +    public class CopyingTxContext extends TxContext {
  +        protected Map externalChanges;
  +		protected Map externalAdds;
  +		protected Set externalDeletes;
  +
  +        protected CopyingTxContext() {
  +            super();
  +            externalChanges = mapFactory.createMap();
  +			externalDeletes = setFactory.createSet();
  +			externalAdds = mapFactory.createMap();
  +        }
  +        
  +		protected Set keys() {
  +			Set keySet = super.keys();
  +			keySet.removeAll(externalDeletes);
  +			keySet.addAll(externalAdds.keySet());
  +			return keySet;
  +		}
  +
  +		protected Object get(Object key) {
  +			
  +			if (deletes.contains(key)) {
  +				// reflects that entry has been deleted in this tx 
  +				return null;
  +			}
  +
  +			Object changed = changes.get(key);
  +			if (changed != null) {
  +				return changed;
  +			}
  +
  +			Object 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);
  +			}
  +		}
  +
  +		protected int size() {
  +			int size = super.size();
  +
  +			size -= externalDeletes.size();
  +			size += externalAdds.size();
  +
  +			return size;
  +		}
  +
  +		protected void clear() {
  +			super.clear();
  +			externalDeletes.clear();
  +			externalChanges.clear();
  +			externalAdds.clear();
  +		}
  +
  +		protected void dispose() {
  +			super.dispose();
  +			setFactory.disposeSet(externalDeletes);
  +			externalDeletes = null;
  +			mapFactory.disposeMap(externalChanges);
  +			externalChanges = null;
  +			mapFactory.disposeMap(externalAdds);
  +			externalAdds = null;
  +		}
  +
  +    }
   }
  
  
  
  1.1                  jakarta-commons-sandbox/transaction/src/test/org/apache/commons/transaction/memory/OptimisticMapWrapperTest.java
  
  Index: OptimisticMapWrapperTest.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons-sandbox/transaction/src/test/org/apache/commons/transaction/memory/OptimisticMapWrapperTest.java,v
1.1 2004/06/03 14:09:40 ozeigermann Exp $
   * $Revision: 1.1 $
   * $Date: 2004/06/03 14:09:40 $
   *
   * ====================================================================
   *
   * Copyright 1999-2002 The Apache Software Foundation 
   *
   * Licensed 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 junit.framework.*;
  
  import java.util.HashMap;
  import java.util.Map;
  import java.util.logging.*;
  
  import org.apache.commons.transaction.util.Jdk14Logger;
  import org.apache.commons.transaction.util.LoggerFacade;
  import org.apache.commons.transaction.util.RendezvousBarrier;
  
  /**
   * Tests for map wrapper. 
   *
   * @author <a href="mailto:ozeigermann@apache.org">Oliver Zeigermann</a>
   */
  public class OptimisticMapWrapperTest extends TestCase {
  
      private static final Logger logger =
          Logger.getLogger(OptimisticMapWrapperTest.class.getName());
      private static final LoggerFacade sLogger = new Jdk14Logger(logger);
  
      private static final long BARRIER_TIMEOUT = 2000;
  
      // XXX need this, as JUnit seems to print only part of these strings
      private static void report(String should, String is) {
          if (!should.equals(is)) {
              fail(
                  "\nWrong output:\n'"
                      + is
                      + "'\nShould be:\n'"
                      + should
                      + "'\n");
          }
      }
  
      public static Test suite() {
          TestSuite suite = new TestSuite(OptimisticMapWrapperTest.class);
          return suite;
      }
  
      public static void main(java.lang.String[] args) {
          junit.textui.TestRunner.run(suite());
      }
  
      public OptimisticMapWrapperTest(String testName) {
          super(testName);
      }
  
      public void testBasic() throws Throwable {
  
          logger.info("Checking basic transaction features");
  
          final Map map1 = new HashMap();
  
          final OptimisticMapWrapper txMap1 =
              new OptimisticMapWrapper(map1);
  
          // make sure changes are propagated to wrapped map outside tx
          txMap1.put("key1", "value1");
          report("value1", (String) map1.get("key1"));
  
          // make sure changes are progated to wrapped map only after commit
          txMap1.startTransaction();
          txMap1.put("key1", "value2");
          report("value1", (String) map1.get("key1"));
          report("value2", (String) txMap1.get("key1"));
          txMap1.commitTransaction();
          report("value2", (String) map1.get("key1"));
          report("value2", (String) txMap1.get("key1"));
  
          // make sure changes are reverted after rollback
          txMap1.startTransaction();
          txMap1.put("key1", "value3");
          txMap1.rollbackTransaction();
          report("value2", (String) map1.get("key1"));
          report("value2", (String) txMap1.get("key1"));
      }
  
      public void testMulti() throws Throwable {
          logger.info("Checking concurrent transaction features");
  
          final Map map1 = new HashMap();
  
          final OptimisticMapWrapper txMap1 =
              new OptimisticMapWrapper(map1);
  
  		final RendezvousBarrier beforeCommitBarrier =
  			new RendezvousBarrier("Before Commit", 2, BARRIER_TIMEOUT, sLogger);
  
  		final RendezvousBarrier afterCommitBarrier =
  			new RendezvousBarrier("After Commit", 2, BARRIER_TIMEOUT, sLogger);
  
  		Thread thread1 = new Thread(new Runnable() {
  			public void run() {
  				txMap1.startTransaction();
  				try {
  					beforeCommitBarrier.meet();
  					txMap1.put("key1", "value2");
  					txMap1.commitTransaction();
  					afterCommitBarrier.call();
  				} catch (InterruptedException e) {
  					logger.log(Level.WARNING, "Thread interrupted", e);
  					afterCommitBarrier.reset();
  					beforeCommitBarrier.reset();
  				}
  			}
  		}, "Thread1");
  
  		txMap1.put("key1", "value1");
  
  
  		txMap1.startTransaction();
  		thread1.start();
  		
  		report("value1", (String) txMap1.get("key1"));
  		beforeCommitBarrier.call();
  		afterCommitBarrier.meet();
  		// we have serializable as isolation level, that's why I will still see the old value
  		report("value1", (String) txMap1.get("key1"));
  		
  		// now when I override it it should of course be my value
  		txMap1.put("key1", "value3");
  		report("value3", (String) txMap1.get("key1"));
  		
  		// after rollback it must be the value written by the other thread
  		txMap1.rollbackTransaction();
  		report("value2", (String) txMap1.get("key1"));
      }
  
      public void testTxControl() throws Throwable {
          logger.info("Checking advanced transaction control (heavily used in JCA implementation)");
          
      }
  
  }
  
  
  

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


Mime
View raw message