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 
[EMAIL PROTECTED] Map} interface. By using
  - * optimistic transaction control this wrapper has better isolation than [EMAIL 
PROTECTED] TransactionalMapWrapper}, but
  + * a naive optimistic transaction control this wrapper has better isolation than 
[EMAIL PROTECTED] TransactionalMapWrapper}, 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 calls [EMAIL PROTECTED] #commitTransaction()} to make your 
changes permanent or [EMAIL PROTECTED] #rollbackTransaction()} to
  @@ -37,8 +41,9 @@
    * <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 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:[EMAIL PROTECTED]">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 [EMAIL PROTECTED] HashMap} and [EMAIL PROTECTED] 
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 [EMAIL PROTECTED] MapFactory} and 
[EMAIL PROTECTED] 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 [EMAIL PROTECTED] HashMap} and [EMAIL PROTECTED] 
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 [EMAIL PROTECTED] MapFactory} and 
[EMAIL PROTECTED] 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:[EMAIL PROTECTED]">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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to