You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2013/08/12 17:33:12 UTC

svn commit: r1513170 - in /ace/trunk: org.apache.ace.range.api/src/org/apache/ace/range/ org.apache.ace.range.api/test/org/apache/ace/range/ org.apache.ace.repository/src/org/apache/ace/repository/ org.apache.ace.repository/src/org/apache/ace/repositor...

Author: marrs
Date: Mon Aug 12 15:33:12 2013
New Revision: 1513170

URL: http://svn.apache.org/r1513170
Log:
ACE-331 Repositories can now be configured to store a limited number of versions. Replication takes this into account during synchronisation. Extended the SortedRangeSet with some new features for this.

Modified:
    ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/Range.java
    ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/RangeIterator.java
    ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/SortedRangeSet.java
    ace/trunk/org.apache.ace.range.api/test/org/apache/ace/range/SortedRangeSetTest.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java
    ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/RepositoryReplicationTask.java
    ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java

Modified: ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/Range.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/Range.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/Range.java (original)
+++ ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/Range.java Mon Aug 12 15:33:12 2013
@@ -21,8 +21,7 @@ package org.apache.ace.range;
 /**
  * Class that captures a simple, modifiable range.
  */
-public class Range
-{
+public class Range {
     private long m_low;
     private long m_high;
 
@@ -133,7 +132,8 @@ public class Range
     /**
      * Converts the range to a string representation that can be parsed
      * back to a new <code>Range</code> object.
-     * @return
+     * 
+     * @return string representation
      */
     public String toRepresentation() {
         if (m_low == m_high) {
@@ -143,4 +143,9 @@ public class Range
             return Long.toString(m_low) + '-' + Long.toString(m_high);
         }
     }
+    
+    @Override
+    public String toString() {
+        return "Range[" + toRepresentation() + "]";
+    }
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/RangeIterator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/RangeIterator.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/RangeIterator.java (original)
+++ ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/RangeIterator.java Mon Aug 12 15:33:12 2013
@@ -18,7 +18,7 @@
  */
 package org.apache.ace.range;
 
-import java.util.Iterator;
+import java.util.ListIterator;
 import java.util.NoSuchElementException;
 
 /**
@@ -28,17 +28,26 @@ import java.util.NoSuchElementException;
  * is not thread-safe and results are unpredictable if the underlying set is
  * modified.
  */
-public class RangeIterator
-{
-    private final Iterator m_iterator;
+public class RangeIterator {
+    private final ListIterator m_iterator;
     private Range m_current;
     private long m_number;
+    private boolean m_reverseOrder;
 
-    RangeIterator(Iterator iterator) {
+    RangeIterator(ListIterator iterator, boolean reverseOrder) {
         m_iterator = iterator;
+        m_reverseOrder = reverseOrder;
     }
 
     public boolean hasNext() {
+        return m_reverseOrder ? hasPreviousElement() : hasNextElement();
+    }
+    
+    public long next() {
+        return m_reverseOrder ? previousElement() : nextElement();
+    }
+    
+    private boolean hasNextElement() {
         if (m_current == null) {
             return m_iterator.hasNext();
         }
@@ -49,8 +58,20 @@ public class RangeIterator
             return true;
         }
     }
+    
+    private boolean hasPreviousElement() {
+        if (m_current == null) {
+            return m_iterator.hasPrevious();
+        }
+        if (m_number == m_current.getLow()) {
+            return m_iterator.hasPrevious();
+        }
+        else {
+            return true;
+        }
+    }
 
-    public long next() {
+    private long nextElement() {
         if (m_current == null) {
             if (m_iterator.hasNext()) {
                 m_current = (Range) m_iterator.next();
@@ -73,4 +94,28 @@ public class RangeIterator
         }
         throw new NoSuchElementException();
     }
+
+    private long previousElement() {
+        if (m_current == null) {
+            if (m_iterator.hasPrevious()) {
+                m_current = (Range) m_iterator.previous();
+                m_number = m_current.getHigh();
+                return m_number;
+            }
+        }
+        else {
+            if (m_number == m_current.getLow()) {
+                if (m_iterator.hasPrevious()) {
+                    m_current = (Range) m_iterator.previous();
+                    m_number = m_current.getHigh();
+                    return m_number;
+                }
+            }
+            else {
+                m_number--;
+                return m_number;
+            }
+        }
+        throw new NoSuchElementException();
+    }
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/SortedRangeSet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/SortedRangeSet.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/SortedRangeSet.java (original)
+++ ace/trunk/org.apache.ace.range.api/src/org/apache/ace/range/SortedRangeSet.java Mon Aug 12 15:33:12 2013
@@ -19,6 +19,7 @@
 package org.apache.ace.range;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
@@ -28,8 +29,7 @@ import java.util.StringTokenizer;
  * Collection that stores a sorted set of ranges and is able to represent them
  * as a string.
  */
-public class SortedRangeSet
-{
+public class SortedRangeSet {
     /**
      * A static set which contains all possible values.
      */
@@ -55,19 +55,23 @@ public class SortedRangeSet
     }
 
     /**
-     * Creates a new instance from an array of longs.
+     * Creates a new instance from an array of longs. The array can contain the longs in random order,
+     * and duplicates will be filtered out.
      *
      * @param items Array of longs
      */
     public SortedRangeSet(long[] items) {
-        // TODO: deal with items not being in ascending order
+        Arrays.sort(items);
         Range r = null;
         for (int i = 0; i < items.length; i++) {
             if (r == null) {
                 r = new Range(items[i]);
             }
             else {
-                if (items[i] == r.getHigh() + 1) {
+                if (items[i] == r.getHigh()) {
+                    // ignore this duplicate
+                }
+                else if (items[i] == r.getHigh() + 1) {
                     r.setHigh(items[i]);
                 }
                 else {
@@ -107,6 +111,7 @@ public class SortedRangeSet
      * <code>result = dest \ this</code>,<br>
      * that is, if <code>dest = {1, 2}</code> and <code>this = {2, 3}</code>, then
      * <code>result = {1, 2} \ {2, 3} = {1}</code>
+     * 
      * @param dest The set from which this set should be 'set-minussed'.
      * @return The resulting set after the diff.
      */
@@ -155,7 +160,7 @@ public class SortedRangeSet
             long low = r.getLow();
             long high = r.getHigh();
             if (number < low) {
-                if (number == low - 1) {
+                if (number == (low - 1)) {
                     r.setLow(number);
                     return;
                 }
@@ -165,11 +170,11 @@ public class SortedRangeSet
                     return;
                 }
             }
-            if (number == high + 1) {
+            if (number == (high + 1)) {
                 r.setHigh(number);
                 if (i.hasNext()) {
                     Range nr = (Range) i.next();
-                    if (number == low - 1) {
+                    if (number == nr.getLow() - 1) {
                         r.setHigh(nr.getHigh());
                         i.remove();
                     }
@@ -187,7 +192,16 @@ public class SortedRangeSet
      * @return a range iterator
      */
     public RangeIterator iterator() {
-        return new RangeIterator(m_ranges.iterator());
+        return new RangeIterator(m_ranges.listIterator(), false);
+    }
+    
+    /**
+     * Returns an iterator that iterates over all the ranges in this set in reverse order.
+     * 
+     * @return a range iterator
+     */
+    public RangeIterator reverseIterator() {
+        return new RangeIterator(m_ranges.listIterator(m_ranges.size()), true);
     }
     
     /**
@@ -215,4 +229,30 @@ public class SortedRangeSet
             return 0;
         }
     }
+
+    /**
+     * Returns the union of this set and the provided set.
+     * 
+     * @param dest a set to union with ourselves
+     * @return the resulting set
+     */
+    public SortedRangeSet union(SortedRangeSet dest) {
+        SortedRangeSet result = new SortedRangeSet();
+        RangeIterator i = dest.iterator();
+        while (i.hasNext()) {
+            long number = i.next();
+            result.add(number);
+        }
+        i = iterator();
+        while (i.hasNext()) {
+            long number = i.next();
+            result.add(number);
+        }
+        return result;
+    }
+    
+    @Override
+    public String toString() {
+        return "SortedRangeSet[" + toRepresentation() + "]";
+    }
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.range.api/test/org/apache/ace/range/SortedRangeSetTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.range.api/test/org/apache/ace/range/SortedRangeSetTest.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.range.api/test/org/apache/ace/range/SortedRangeSetTest.java (original)
+++ ace/trunk/org.apache.ace.range.api/test/org/apache/ace/range/SortedRangeSetTest.java Mon Aug 12 15:33:12 2013
@@ -22,6 +22,7 @@ import static org.apache.ace.test.utils.
 
 import java.util.Iterator;
 
+import org.testng.Assert;
 import org.testng.annotations.Test;
 
 public class SortedRangeSetTest {
@@ -68,6 +69,12 @@ public class SortedRangeSetTest {
         assert new SortedRangeSet("1-20").diffDest(new SortedRangeSet("5-25")).toRepresentation().equals("21-25") : "Result of diff should be 21-25";
         assert new SortedRangeSet(new long[] {1,3,5,7,9}).diffDest(new SortedRangeSet("1-10")).toRepresentation().equals("2,4,6,8,10") : "Result of diff should be 2,4,6,8,10";
         assert new SortedRangeSet("1-5,8,12").diffDest(new SortedRangeSet("1-5,7,9,12,20")).toRepresentation().equals("7,9,20") : "Result of diff should be 7,9,20";
+        assert new SortedRangeSet("1").union(new SortedRangeSet("2")).toRepresentation().equals("1-2") : "Result of union should be 1-2";
+        assert new SortedRangeSet("1-4").union(new SortedRangeSet("6-9")).toRepresentation().equals("1-4,6-9") : "Result of union should be 1-4,6-9";
+        Assert.assertEquals(new SortedRangeSet("1-3").union(new SortedRangeSet("4")).toRepresentation(), "1-4", "Result of union failed.");
+        Assert.assertEquals(new SortedRangeSet("5-10").union(new SortedRangeSet("4")).toRepresentation(), "4-10", "Result of union failed.");
+        Assert.assertEquals(new SortedRangeSet("1-3,5-10").union(new SortedRangeSet("4")).toRepresentation(), "1-10", "Result of union failed.");
+        Assert.assertEquals(new SortedRangeSet("1-5,8,12").union(new SortedRangeSet("4-8,9-11")).toRepresentation(), "1-12", "Result of union failed.");
     }
     
     @Test(groups = { UNIT })
@@ -86,6 +93,51 @@ public class SortedRangeSetTest {
         SortedRangeSet srs3 = new SortedRangeSet("");
         assert !srs3.iterator().hasNext() : "Iterator should be empty.";
     }
+    
+    @Test(groups = { UNIT })
+    public void validateReverseRangeIterators() throws Exception {
+        SortedRangeSet srs1 = new SortedRangeSet("1-10");
+        RangeIterator i1 = srs1.reverseIterator();
+        for (long i = 10; i > 0; i--) {
+            Assert.assertEquals(i1.next(), i);
+        }
+        Assert.assertFalse(i1.hasNext(), "We should have iterated over all elements of our simple range.");
+        SortedRangeSet srs2 = new SortedRangeSet("1-5,8,10-15");
+        RangeIterator i2 = srs2.reverseIterator();
+        long[] i2s = { 15, 14, 13, 12, 11, 10, 8, 5, 4, 3, 2, 1};
+        for (int i = 0; i < i2s.length; i++) {
+            Assert.assertEquals(i2.next(), i2s[i]);
+        }
+        Assert.assertFalse(i2.hasNext(), "We should have iterated over all elements of our complex range.");
+
+        SortedRangeSet srs3 = new SortedRangeSet("");
+        assert !srs3.reverseIterator().hasNext() : "Iterator should be empty.";
+    }
+
+    @Test(groups = { UNIT })
+    public void validateSortedRangeSetArrayConstructor() throws Exception {
+        long[] a1 = new long[] {1,2,3,5,10};
+        SortedRangeSet s1 = new SortedRangeSet(a1);
+        RangeIterator i1 = s1.iterator();
+        for (int i = 0; i < a1.length; i++) {
+            Assert.assertEquals(i1.next(), a1[i]);
+        }
+        Assert.assertEquals(s1.toRepresentation(), "1-3,5,10");
+        Assert.assertFalse(i1.hasNext(), "We should have iterated over all elements of our range.");
+
+        long[] a2 = new long[] {10,2,3,5,10,2,1,5,1};
+        long[] a2s = new long[] {1,2,3,5,10};
+        SortedRangeSet s2 = new SortedRangeSet(a2);
+        RangeIterator i2 = s2.iterator();
+        for (int i = 0; i < a2s.length; i++) {
+            Assert.assertEquals(i2.next(), a2s[i]);
+        }
+        Assert.assertEquals(s2.toRepresentation(), "1-3,5,10");
+        Assert.assertFalse(i2.hasNext(), "We should have iterated over all elements of our range.");
+        Assert.assertEquals((new SortedRangeSet(new long[] {})).toRepresentation(), (new SortedRangeSet(new long[] {})).toRepresentation());
+        Assert.assertEquals((new SortedRangeSet(new long[] {1})).toRepresentation(), (new SortedRangeSet(new long[] {1,1,1})).toRepresentation());
+        Assert.assertEquals((new SortedRangeSet(new long[] {3,2,1})).toRepresentation(), (new SortedRangeSet(new long[] {1,2,3,2,1})).toRepresentation());
+    }
 
     @Test(groups = { UNIT }, expectedExceptions = IllegalArgumentException.class)
     public void invalidRange() {

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/RepositoryReplication.java Mon Aug 12 15:33:12 2013
@@ -53,4 +53,9 @@ public interface RepositoryReplication
      * @throws IllegalArgumentException If the version number is not greater than 0.
      */
     public boolean put(InputStream data, long version) throws IOException, IllegalArgumentException;
+    
+    /**
+     * Returns the maximum number of versions this repository will store.
+     */
+    public long getLimit();
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryFactory.java Mon Aug 12 15:33:12 2013
@@ -145,6 +145,20 @@ public class RepositoryFactory implement
         if ((m_baseDir != null) && !m_baseDir.isDirectory() && !m_baseDir.mkdirs()) {
             throw new IllegalArgumentException("Unable to create base directory (" + m_baseDir.getAbsolutePath() + ")");
         }
+        
+        String limit = (String) dict.get(RepositoryConstants.REPOSITORY_LIMIT);
+        long limitValue = Long.MAX_VALUE;
+        if (limit != null) {
+            try {
+                limitValue = Long.parseLong(limit);
+            }
+            catch (NumberFormatException nfe) {
+                throw new ConfigurationException(RepositoryConstants.REPOSITORY_LIMIT, "Limit has to be a number, was: " + limit);
+            }
+            if (limitValue < 1) {
+                throw new ConfigurationException(RepositoryConstants.REPOSITORY_LIMIT, "Limit has to be at least 1, was " + limit);
+            }
+        }
 
         String initialContents = (String) dict.get(RepositoryConstants.REPOSITORY_INITIAL_CONTENT);
         if (m_prefs == null) {
@@ -173,7 +187,7 @@ public class RepositoryFactory implement
         if (service == null) {
             // new instance
             File dir = new File(m_baseDir, pid);
-            RepositoryImpl store = new RepositoryImpl(dir, m_tempDir, fileExtension, isMaster);
+            RepositoryImpl store = new RepositoryImpl(dir, m_tempDir, fileExtension, isMaster, limitValue);
             if ((initialContents != null) && isMaster) {
                 try {
                     store.commit(new ByteArrayInputStream(initialContents.getBytes()), 0);
@@ -192,7 +206,7 @@ public class RepositoryFactory implement
         else {
             // update existing instance
             RepositoryImpl store = (RepositoryImpl) service.getService();
-            store.updated(isMaster);
+            store.updated(isMaster, limitValue);
         }
     }
 }

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/RepositoryImpl.java Mon Aug 12 15:33:12 2013
@@ -31,6 +31,7 @@ import java.util.Arrays;
 import org.apache.ace.range.SortedRangeSet;
 import org.apache.ace.repository.Repository;
 import org.apache.ace.repository.RepositoryReplication;
+import org.apache.ace.repository.impl.constants.RepositoryConstants;
 import org.osgi.service.cm.ConfigurationException;
 import org.osgi.service.log.LogService;
 
@@ -49,6 +50,7 @@ public class RepositoryImpl implements R
 
     private volatile LogService m_log; /* will be injected by dependency manager */
     private volatile boolean m_isMaster;
+    private volatile long m_limit;
 
     private final File m_tempDir;
     private final File m_dir;
@@ -78,6 +80,35 @@ public class RepositoryImpl implements R
      *             directory.
      */
     public RepositoryImpl(File dir, File temp, String fileExtension, boolean isMaster) {
+        this(dir, temp, fileExtension, isMaster, Long.MAX_VALUE);
+    }
+
+    /**
+     * Creates a new repository.
+     * 
+     * @param dir Directory to be used for storage of the repository data, will be created if needed.
+     * @param temp Directory to be used as temp directory, will be created if needed.
+     * @param isMaster True if this repository is a master repository, false otherwise.
+     * @param limit The maximum number of versions to store in this repository.
+     * @throws IllegalArgumentException If <code>dir</code> and/or <code>temp</code> could not be created or is not a
+     *             directory.
+     */
+    public RepositoryImpl(File dir, File temp, boolean isMaster, long limit) {
+        this(dir, temp, "", isMaster, limit);
+    }
+    
+    /**
+     * Creates a new repository.
+     * 
+     * @param dir Directory to be used for storage of the repository data, will be created if needed.
+     * @param temp Directory to be used as temp directory, will be created if needed.
+     * @param fileExtension Extension to be used for repository files.
+     * @param isMaster True if this repository is a master repository, false otherwise.
+     * @param limit The maximum number of versions to store in this repository.
+     * @throws IllegalArgumentException If <code>dir</code> and/or <code>temp</code> could not be created or is not a
+     *             directory.
+     */
+    public RepositoryImpl(File dir, File temp, String fileExtension, boolean isMaster, long limit) {
         m_isMaster = isMaster;
         if (!dir.isDirectory() && !dir.mkdirs()) {
             throw new IllegalArgumentException("Repository location is not a valid directory (" + dir.getAbsolutePath() + ")");
@@ -88,10 +119,15 @@ public class RepositoryImpl implements R
         if (fileExtension == null) {
             throw new IllegalArgumentException("File extension must not be null");
         }
+        if (limit < 1) {
+            throw new IllegalArgumentException("Limit must be at least 1, was " + limit);
+        }
         m_tempDir = temp;
         m_dir = dir;
         m_fileExtension = fileExtension;
+        m_limit = limit;
     }
+    
 
     public InputStream get(long version) throws IOException, IllegalArgumentException {
         return checkout(version);
@@ -176,6 +212,13 @@ public class RepositoryImpl implements R
         long lastVersion = versions[versions.length - 1];
         if (lastVersion == fromVersion) {
             put(data, fromVersion + 1);
+            long length = versions.length + 1;
+            int index = 0;
+            while (length > m_limit) {
+                delete(versions[index]);
+                index++;
+                length--;
+            }
             return true;
         }
         else {
@@ -214,8 +257,33 @@ public class RepositoryImpl implements R
      * @param isMaster True if the repository is a master repository, false otherwise.
      * @throws ConfigurationException If it was impossible to use the new configuration.
      */
-    public void updated(boolean isMaster) throws ConfigurationException {
+    public void updated(boolean isMaster, long limit) throws ConfigurationException {
+        if (limit < 1) {
+            throw new ConfigurationException(RepositoryConstants.REPOSITORY_LIMIT, "Limit must be at least 1, was " + limit);
+        }
         m_isMaster = isMaster;
+        if (limit < m_limit) {
+            // limit was decreased, we might need to delete some old versions
+            try {
+                long[] versions = getVersions();
+                int length = versions.length;
+                int index = 0;
+                while (length > limit) {
+                    delete(versions[index]);
+                    length--;
+                    index++;
+                }
+            }
+            catch (IOException e) {
+                throw new ConfigurationException(RepositoryConstants.REPOSITORY_LIMIT, "Could not set new limit to " + limit, e);
+            }
+        }
+        m_limit = limit;
+    }
+    
+    @Override
+    public long getLimit() {
+        return m_limit;
     }
 
     /**
@@ -316,4 +384,12 @@ public class RepositoryImpl implements R
             // Ignored...
         }
     }
+
+    private boolean delete(long version) throws IOException, IllegalArgumentException {
+        if (version <= 0) {
+            throw new IllegalArgumentException("Version must be greater than 0.");
+        }
+        File file = new File(m_dir, String.valueOf(version) + m_fileExtension);
+        return file.delete();
+    }
 }

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/impl/constants/RepositoryConstants.java Mon Aug 12 15:33:12 2013
@@ -26,4 +26,5 @@ public interface RepositoryConstants
     public static final String REPOSITORY_INITIAL_CONTENT = "initial";
     public static final String REPOSITORY_BASE_DIR = "basedir";
     public static final String REPOSITORY_FILE_EXTENSION = "fileextension";
+    public static final String REPOSITORY_LIMIT = "limit";
 }

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/Activator.java Mon Aug 12 15:33:12 2013
@@ -22,6 +22,7 @@ import java.util.Properties;
 
 import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.ace.discovery.Discovery;
+import org.apache.ace.repository.RepositoryReplication;
 import org.apache.ace.scheduler.constants.SchedulerConstants;
 import org.apache.felix.dm.DependencyActivatorBase;
 import org.apache.felix.dm.DependencyManager;
@@ -41,6 +42,7 @@ public class Activator extends Dependenc
             .setImplementation(RepositoryReplicationTask.class)
             .add(createServiceDependency().setService(Discovery.class).setRequired(true))
             .add(createServiceDependency().setService(ConnectionFactory.class).setRequired(true))
+            .add(createServiceDependency().setService(RepositoryReplication.class).setRequired(false).setCallbacks("add", "remove"))
             .add(createServiceDependency().setService(LogService.class).setRequired(false))
             );
     }

Modified: ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/RepositoryReplicationTask.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/RepositoryReplicationTask.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/RepositoryReplicationTask.java (original)
+++ ace/trunk/org.apache.ace.repository/src/org/apache/ace/repository/task/RepositoryReplicationTask.java Mon Aug 12 15:33:12 2013
@@ -19,9 +19,16 @@
 package org.apache.ace.repository.task;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
 
 import javax.servlet.http.HttpServletResponse;
 
@@ -45,69 +52,93 @@ public class RepositoryReplicationTask i
     private volatile Discovery m_discovery;
     private volatile ConnectionFactory m_connectionFactory;
     private volatile LogService m_log;
-
+    private final Map<ServiceReference, RepositoryReplication> m_replicators = new HashMap<ServiceReference, RepositoryReplication>();
+    
+    public void add(ServiceReference ref, RepositoryReplication service) {
+        synchronized (m_replicators) {
+            m_replicators.put(ref,  service);
+        }
+    }
+    
+    public void remove(ServiceReference ref) {
+        synchronized (m_replicators) {
+            m_replicators.remove(ref);
+        }
+    }
+    
     public void run() {
+        Entry<ServiceReference,RepositoryReplication>[] replicators;
+        synchronized (m_replicators) {
+            Set<Entry<ServiceReference,RepositoryReplication>> entries = m_replicators.entrySet();
+            replicators = entries.toArray(new Entry[entries.size()]);
+        }
+        
         try {
-            ServiceReference[] refs = m_context.getServiceReferences(RepositoryReplication.class.getName(), null);
-            if (refs == null) {
-                return;
+            for (Entry<ServiceReference,RepositoryReplication> entry : replicators) {
+                replicate(entry);
             }
+        }
+        catch (Exception e) {
+            m_log.log(LogService.LOG_WARNING, "Error while replicating", e);
+        }
+    }
 
-            for (ServiceReference ref : refs) {
-                RepositoryReplication repository = (RepositoryReplication) m_context.getService(ref);
-
-                try {
-                    String filter = getQueryFilter(ref);
-                    URL host = m_discovery.discover();
-                    URL query = new URL(host, "/replication/query?" + filter);
-
-                    HttpURLConnection connection = (HttpURLConnection) m_connectionFactory.createConnection(query);
-
-                    if (connection.getResponseCode() == HttpServletResponse.SC_OK) {
-                        SortedRangeSet localRange = repository.getRange();
-
-                        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
-                        try {
-                            String line = reader.readLine();
-                            int i = line.lastIndexOf(',');
-                            if (i > 0) {
-                                SortedRangeSet remoteRange = new SortedRangeSet(line.substring(i + 1));
-                                SortedRangeSet delta = localRange.diffDest(remoteRange);
-                                RangeIterator iterator = delta.iterator();
-
-                                while (iterator.hasNext()) {
-                                    long version = iterator.next();
-                                    URL get = new URL(host, "/replication/get?" + filter + "&version=" + version);
-                                    
-                                    HttpURLConnection connection2 = (HttpURLConnection) m_connectionFactory.createConnection(get);
-
-                                    repository.put(connection2.getInputStream(), version);
-                                }
-                            }
-                        }
-                        catch (Exception e) {
-                            m_log.log(LogService.LOG_WARNING, "Error parsing remote range", e);
+    private void replicate(Entry<ServiceReference, RepositoryReplication> entry) throws MalformedURLException, IOException {
+        ServiceReference ref = entry.getKey();
+        RepositoryReplication repository = entry.getValue();
+        String filter = "customer=" + ref.getProperty("customer") + "&name=" + ref.getProperty("name");
+        URL host = m_discovery.discover();
+        URL query = new URL(host, "/replication/query?" + filter);
+   
+        HttpURLConnection connection = (HttpURLConnection) m_connectionFactory.createConnection(query);
+        if (connection.getResponseCode() == HttpServletResponse.SC_OK) {
+            SortedRangeSet localRange = repository.getRange();
+   
+            BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+            try {
+                String line = reader.readLine();
+                int i = line.lastIndexOf(',');
+                if (i > 0) {
+                    SortedRangeSet remoteRange = new SortedRangeSet(line.substring(i + 1));
+        
+                    // check the limit of the repository
+                    long limit = repository.getLimit();
+                    if (limit == Long.MAX_VALUE) {
+                        // no limit, sync all
+                        SortedRangeSet delta = localRange.diffDest(remoteRange);
+                        RangeIterator iterator = delta.iterator();
+                        while (iterator.hasNext()) {
+                            long version = iterator.next();
+                            URL get = new URL(host, "/replication/get?" + filter + "&version=" + version);
+                            HttpURLConnection connection2 = (HttpURLConnection) m_connectionFactory.createConnection(get);
+                            repository.put(connection2.getInputStream(), version);
                         }
                     }
                     else {
-                        m_log.log(LogService.LOG_WARNING, "Could not sync repository for customer: " + ref.getProperty("customer") + ", name: " + ref.getProperty("name") + ", because: " + connection.getResponseMessage() + " (" + connection.getResponseCode() + ")");
+                        // limit, try to get the the 'limit' newest versions
+                        SortedRangeSet union = localRange.union(remoteRange);
+                        RangeIterator iterator = union.reverseIterator();
+                        while (iterator.hasNext() && limit > 0) {
+                            long version = iterator.next();
+                            if (!localRange.contains(version)) {
+                                URL get = new URL(host, "/replication/get?" + filter + "&version=" + version);
+                                HttpURLConnection connection2 = (HttpURLConnection) m_connectionFactory.createConnection(get);
+                                repository.put(connection2.getInputStream(), version);
+                            }
+                            limit--;
+                        }
                     }
                 }
-                finally {
-                    m_context.ungetService(ref);
-                }
+            }
+            catch (Exception e) {
+                m_log.log(LogService.LOG_WARNING, "Error parsing remote range", e);
+            }
+            finally {
+                reader.close();
             }
         }
-        catch (Exception e) {
-            m_log.log(LogService.LOG_WARNING, "Error while replicating", e);
+        else {
+            m_log.log(LogService.LOG_WARNING, "Could not sync repository for customer: " + ref.getProperty("customer") + ", name: " + ref.getProperty("name") + ", because: " + connection.getResponseMessage() + " (" + connection.getResponseCode() + ")");
         }
     }
-
-    /**
-     * @param ref
-     * @return
-     */
-    public String getQueryFilter(ServiceReference ref) {
-        return "customer=" + ref.getProperty("customer") + "&name=" + ref.getProperty("name");
-    }
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java?rev=1513170&r1=1513169&r2=1513170&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java (original)
+++ ace/trunk/org.apache.ace.repository/test/org/apache/ace/repository/impl/RepositoryImplTest.java Mon Aug 12 15:33:12 2013
@@ -28,6 +28,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
+import org.apache.ace.range.SortedRangeSet;
+import static org.testng.Assert.*;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
@@ -129,7 +131,7 @@ public class RepositoryImplTest {
     @Test(groups = { UNIT }, expectedExceptions = { IllegalStateException.class })
     public void testUpdated() throws Exception {
         RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
-        repo.updated(false);
+        repo.updated(false, Long.MAX_VALUE);
         assert !repo.commit(new ByteArrayInputStream("abc".getBytes()), 0) : "Committing should not be allowed on slave repositories.";
         assert repo.put(new ByteArrayInputStream("abc".getBytes()), 1) : "'put'ting a replica should be allowed on slave repositories.";
         File file = new File(m_baseDir, "newLocation" + File.separator + "1");
@@ -147,4 +149,32 @@ public class RepositoryImplTest {
         BufferedReader reader = new BufferedReader(new FileReader(file));
         assert "abc".equals(reader.readLine()) : "File " + file.getAbsolutePath() + " should have contained 'abc'.";
     }
+
+    @Test(groups = { UNIT })
+    public void testCommitMultiple() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true);
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-1".getBytes()), 0), "Commit should have worked.");
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-2".getBytes()), 1), "Commit should have worked.");
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-3".getBytes()), 2), "Commit should have worked.");
+        SortedRangeSet range = repo.getRange();
+        assertTrue(range.getHigh() == 3, "We should have 3 versions in the repository.");
+    }
+
+    @Test(groups = { UNIT })
+    public void testCommitToLimitedRepository() throws Exception {
+        RepositoryImpl repo = new RepositoryImpl(new File(m_baseDir, "data"), new File(m_baseDir, "tmp"), true, 2);
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-1".getBytes()), 0), "Commit should have worked.");
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-2".getBytes()), 1), "Commit should have worked.");
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-3".getBytes()), 2), "Commit should have worked.");
+        assertNotNull(repo.checkout(3));
+        assertNotNull(repo.checkout(2));
+        assertNull(repo.checkout(1));
+        repo.updated(true, 3);
+        assertTrue(repo.commit(new ByteArrayInputStream("abc-4".getBytes()), 3), "Commit should have worked.");
+        assertNotNull(repo.checkout(2));
+        repo.updated(true, 1);
+        assertNull(repo.checkout(2));
+        assertNull(repo.checkout(3));
+        assertNotNull(repo.checkout(4));
+    }
 }