You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by an...@apache.org on 2013/10/22 23:16:48 UTC

svn commit: r1534794 - in /ace/trunk/org.apache.ace.deployment: src/org/apache/ace/deployment/provider/ src/org/apache/ace/deployment/provider/filebased/ src/org/apache/ace/deployment/provider/repositorybased/ src/org/apache/ace/deployment/servlet/ src...

Author: angelos
Date: Tue Oct 22 21:16:47 2013
New Revision: 1534794

URL: http://svn.apache.org/r1534794
Log:
ACE-388 Deployment can now result in an OverloadedException, which in turn causes a "Retry-After" header to be set.

Added:
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/OverloadedException.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/OverloadedFilter.java
    ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderConcurrencyTest.java
Modified:
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/filebased/FileBasedProvider.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java
    ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/StreamGeneratorImpl.java
    ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/MockDeploymentRepository.java
    ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java
    ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java Tue Oct 22 21:16:47 2013
@@ -33,9 +33,10 @@ public interface DeploymentProvider {
      *
      * @return a collection of bundledata. If there are no bundles in this version, return an empty list
      * @throws IllegalArgumentException if the target or version do not exist
+     * @throws OverloadedException if the provider is overloaded
      * @throws java.io.IOException If an IOException occurs.
      */
-    public List<ArtifactData> getBundleData(String targetId, String version) throws IllegalArgumentException, IOException;
+    public List<ArtifactData> getBundleData(String targetId, String version) throws OverloadedException, IllegalArgumentException, IOException;
 
     /**
      * This data can be used to generate a fix package. It gives the differences between the versionFrom and versionTo.
@@ -50,10 +51,11 @@ public interface DeploymentProvider {
      *
      * @return a list of bundles.
      * @throws IllegalArgumentException if the target, the versionFrom or versionTo do no exist
+     * @throws OverloadedException if the provider is overloaded
      * @throws java.io.IOException If an IOException occurs.
      */
 
-    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException;
+    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws OverloadedException, IllegalArgumentException, IOException;
 
     /**
      * Returns a list of versions for a specific target. The list is sorted in
@@ -65,6 +67,7 @@ public interface DeploymentProvider {
      *         return an empty List.
      *         If the target doesn't exist, an IllegalArgumentException is thrown
      * @throws java.io.IOException If an IOException occurs.
+     * @throws OverloadedException if the provider is overloaded
      */
-    public List<String> getVersions(String targetId) throws IllegalArgumentException, IOException;
+    public List<String> getVersions(String targetId) throws OverloadedException, IllegalArgumentException, IOException;
 }
\ No newline at end of file

Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/OverloadedException.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/OverloadedException.java?rev=1534794&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/OverloadedException.java (added)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/OverloadedException.java Tue Oct 22 21:16:47 2013
@@ -0,0 +1,50 @@
+/*
+ * 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.ace.deployment.provider;
+
+/**
+ * Exception that indicates that the deployment provider is overloaded.
+ * Callers that receive this exception should 
+ */
+public class OverloadedException extends RuntimeException {
+
+    private static final long serialVersionUID = 915400242733422258L;
+
+    private final int m_backoffTime;
+    
+    /**
+     * Exception that indicates that the caller should try again after at least the specified backoffTime
+     * 
+     * @param message the error message
+     * @param backoffTime the requested backoff time in seconds
+     */
+    public OverloadedException(String message, int backoffTime) {
+        super(message);
+        m_backoffTime = backoffTime;
+    }
+    
+    /**
+     * Returns the time to "back off" from trying again.
+     * 
+     * @return a back off time, in seconds.
+     */
+    public int getBackoffTime() {
+        return m_backoffTime;
+    }
+}

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/filebased/FileBasedProvider.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/filebased/FileBasedProvider.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/filebased/FileBasedProvider.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/filebased/FileBasedProvider.java Tue Oct 22 21:16:47 2013
@@ -34,12 +34,14 @@ import java.util.Dictionary;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.jar.Attributes;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 
 import org.apache.ace.deployment.provider.ArtifactData;
 import org.apache.ace.deployment.provider.DeploymentProvider;
+import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Version;
@@ -59,17 +61,160 @@ public class FileBasedProvider implement
     private static final String DIRECTORY_NAME = "BaseDirectoryName";
     /** Fallback directory for all targets that have no specific versions. Defaults to BaseDirectoryName if not specified. */
     private static final String DEFAULT_DIRECTORY_NAME = "DefaultDirectoryName";
+    /** Configuration key for the number of concurrent users */
+    private static final String MAXIMUM_NUMBER_OF_USERS = "MaximumNumberOfUsers";
     private static final int OSGI_R4_MANIFEST_VERSION = 2;
     private volatile File m_baseDirectory;
     private volatile File m_defaultDirectory;
     private volatile LogService m_log;
     private final Semaphore m_disk = new Semaphore(1, true);
+    
+    private final AtomicInteger m_usageCounter = new AtomicInteger();
+    /** Maximum number of concurrent users. Value 0 is used for unlimited users. */
+    private int m_maximumNumberOfUsers = 0;
+    /** The default backoff time for each new user over the limit */
+    private static final int BACKOFF_TIME_PER_USER = 5; 
 
     /**
      * Get the bundle data from the bundles in the &lt;data dir&gt;/&lt;target&gt;/&lt;version&gt; directory It reads the manifest from all the
      * .jar files in that directory. If the manifest cannot be found, This method can only parse OSGi R4 bundles
      */
-    public List<ArtifactData> getBundleData(String targetId, String version) throws IllegalArgumentException {
+    public List<ArtifactData> getBundleData(String targetId, String version) throws OverloadedException, IllegalArgumentException {
+        try {
+            int concurrentUsers = m_usageCounter.incrementAndGet();
+            if (m_maximumNumberOfUsers != 0  && m_maximumNumberOfUsers < concurrentUsers) {
+                throw new OverloadedException("Too many users, maximum allowed = " + m_maximumNumberOfUsers + ", current = " + concurrentUsers,  (concurrentUsers - m_maximumNumberOfUsers) * BACKOFF_TIME_PER_USER);
+            }
+            return internalGetBundleData(targetId, version);
+        } finally {
+            m_usageCounter.getAndDecrement();
+        }
+
+    }
+
+    /**
+     * Version folder and requested version do not always match (see implementation of getVersions, which uses Versions.parseVersion to allow different styles)
+     * like 1 instead of 1.0.0 and alike.
+     * So we need to do some crawling to map them.
+     *
+     * @param targetDirectory store directory
+     * @param version          that has been requested.
+     *
+     * @return the matching folder.
+     *
+     * @throws IllegalArgumentException if no matching folder has been found. If this happens something is weirdly wrong.
+     */
+    private File findMatchingVersionDirectory(File targetDirectory, String version) {
+        // first try the direct way:
+        File directTry = new File(targetDirectory, version);
+        if ((directTry != null) && directTry.isDirectory()) {
+            return directTry;
+        }
+        // otherwise try to find it:
+        Version requestedVersion;
+        try {
+            requestedVersion = Version.parseVersion(version);
+        }
+        catch (IllegalArgumentException iae) {
+            throw new IllegalArgumentException("Requested version " + version + " has no matching folder in store: " + targetDirectory.getAbsolutePath());
+        }
+
+        File[] files = targetDirectory.listFiles();
+        for (int i = 0; i < files.length; i++) {
+            File possibleVersionDirectory = files[i];
+            if (possibleVersionDirectory.isDirectory()) {
+                // ok, it is a directory. Now see if it is a version
+                try {
+                    Version foundVersion = Version.parseVersion(possibleVersionDirectory.getName());
+                    // no exception, but is could still be an empty version
+                    if ((requestedVersion != null) && requestedVersion.equals(foundVersion)) {
+                        return new File(targetDirectory, possibleVersionDirectory.getName());
+                    }
+                }
+                catch (IllegalArgumentException iae) {
+                    // dont' care at this point.
+                }
+            }
+        }
+        throw new IllegalArgumentException("Requested version " + version + " has no matching folder in store: " + targetDirectory.getAbsolutePath());
+    }
+
+    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws OverloadedException, IllegalArgumentException {
+        try {
+            int concurrentUsers = m_usageCounter.incrementAndGet();
+            if (m_maximumNumberOfUsers != 0  && m_maximumNumberOfUsers < concurrentUsers) {
+                throw new OverloadedException("Too many users, maximum allowed = " + m_maximumNumberOfUsers + ", current = " + concurrentUsers,  (concurrentUsers - m_maximumNumberOfUsers) * BACKOFF_TIME_PER_USER);
+            }
+            List<ArtifactData> dataVersionFrom = internalGetBundleData(targetId, versionFrom);
+            List<ArtifactData> dataVersionTo = internalGetBundleData(targetId, versionTo);
+    
+            Iterator<ArtifactData> it = dataVersionTo.iterator();
+            while (it.hasNext()) {
+                ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
+                // see if there was previously a version of this bundle.
+                ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
+                bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
+            }
+            return dataVersionTo;
+        } finally {
+            m_usageCounter.getAndDecrement();
+        }
+    }
+
+    /**
+     * Check for the existence of bundledata in the collection for a bundle with the given symbolic name
+     *
+     * @param symbolicName
+     */
+    private ArtifactData getBundleData(String symbolicName, Collection<ArtifactData> data) {
+        Iterator<ArtifactData> it = data.iterator();
+        while (it.hasNext()) {
+            ArtifactData bundle = it.next();
+            if (bundle.getSymbolicName().equals(symbolicName)) {
+                return bundle;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Look in the baseDirectory for the specified target. If it exists, get the list of directories in there and check if they
+     * conform to the <code>org.osgi.framework.Version</code> format. If it does, it will be in the list of versions that are
+     * returned. If there are no valid versions, return an empty list. If the target cannot be found, an
+     * IllegalArgumentException is thrown. The list will be sorted on version.
+     */
+    @SuppressWarnings("unchecked")
+    public List<String> getVersions(String targetId) throws OverloadedException, IllegalArgumentException {
+        try {
+            int concurrentUsers = m_usageCounter.incrementAndGet();
+            if (m_maximumNumberOfUsers != 0  && m_maximumNumberOfUsers < concurrentUsers) {
+                throw new OverloadedException("Too many users, maximum allowed = " + m_maximumNumberOfUsers + ", current = " + concurrentUsers,  (concurrentUsers - m_maximumNumberOfUsers) * BACKOFF_TIME_PER_USER);
+            }
+            List<Version> versionList = new ArrayList<Version>();
+            File targetDirectory = new File(m_baseDirectory.getAbsolutePath(), targetId);
+            if (targetDirectory.isDirectory()) {
+                getVersions(targetId, versionList, targetDirectory);
+            }
+            else {
+                // try the default
+                getVersions(targetId, versionList, m_defaultDirectory);
+            }
+    
+            // now sort the list of versions and convert all values to strings.
+            Collections.sort(versionList);
+            List<String> stringVersionList = new ArrayList<String>();
+            Iterator<Version> it = versionList.iterator();
+            while (it.hasNext()) {
+                String version = (it.next()).toString();
+                stringVersionList.add(version);
+            }
+            return stringVersionList;
+        } finally {
+            m_usageCounter.getAndDecrement();
+        }
+    }
+
+    private List<ArtifactData> internalGetBundleData(String targetId, String version) throws OverloadedException, IllegalArgumentException {
         List<String> versions = getVersions(targetId);
         if (!versions.contains(version)) {
             throw new IllegalArgumentException("Unknown version " + version + " requested");
@@ -197,113 +342,7 @@ public class FileBasedProvider implement
 
         return bundleData;
     }
-
-    /**
-     * Version folder and requested version do not always match (see implementation of getVersions, which uses Versions.parseVersion to allow different styles)
-     * like 1 instead of 1.0.0 and alike.
-     * So we need to do some crawling to map them.
-     *
-     * @param targetDirectory store directory
-     * @param version          that has been requested.
-     *
-     * @return the matching folder.
-     *
-     * @throws IllegalArgumentException if no matching folder has been found. If this happens something is weirdly wrong.
-     */
-    private File findMatchingVersionDirectory(File targetDirectory, String version) {
-        // first try the direct way:
-        File directTry = new File(targetDirectory, version);
-        if ((directTry != null) && directTry.isDirectory()) {
-            return directTry;
-        }
-        // otherwise try to find it:
-        Version requestedVersion;
-        try {
-            requestedVersion = Version.parseVersion(version);
-        }
-        catch (IllegalArgumentException iae) {
-            throw new IllegalArgumentException("Requested version " + version + " has no matching folder in store: " + targetDirectory.getAbsolutePath());
-        }
-
-        File[] files = targetDirectory.listFiles();
-        for (int i = 0; i < files.length; i++) {
-            File possibleVersionDirectory = files[i];
-            if (possibleVersionDirectory.isDirectory()) {
-                // ok, it is a directory. Now see if it is a version
-                try {
-                    Version foundVersion = Version.parseVersion(possibleVersionDirectory.getName());
-                    // no exception, but is could still be an empty version
-                    if ((requestedVersion != null) && requestedVersion.equals(foundVersion)) {
-                        return new File(targetDirectory, possibleVersionDirectory.getName());
-                    }
-                }
-                catch (IllegalArgumentException iae) {
-                    // dont' care at this point.
-                }
-            }
-        }
-        throw new IllegalArgumentException("Requested version " + version + " has no matching folder in store: " + targetDirectory.getAbsolutePath());
-    }
-
-    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws IllegalArgumentException {
-        List<ArtifactData> dataVersionFrom = getBundleData(targetId, versionFrom);
-        List<ArtifactData> dataVersionTo = getBundleData(targetId, versionTo);
-
-        Iterator<ArtifactData> it = dataVersionTo.iterator();
-        while (it.hasNext()) {
-            ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
-            // see if there was previously a version of this bundle.
-            ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
-            bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
-        }
-        return dataVersionTo;
-    }
-
-    /**
-     * Check for the existence of bundledata in the collection for a bundle with the given symbolic name
-     *
-     * @param symbolicName
-     */
-    private ArtifactData getBundleData(String symbolicName, Collection<ArtifactData> data) {
-        Iterator<ArtifactData> it = data.iterator();
-        while (it.hasNext()) {
-            ArtifactData bundle = it.next();
-            if (bundle.getSymbolicName().equals(symbolicName)) {
-                return bundle;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Look in the baseDirectory for the specified target. If it exists, get the list of directories in there and check if they
-     * conform to the <code>org.osgi.framework.Version</code> format. If it does, it will be in the list of versions that are
-     * returned. If there are no valid versions, return an empty list. If the target cannot be found, an
-     * IllegalArgumentException is thrown. The list will be sorted on version.
-     */
-    @SuppressWarnings("unchecked")
-    public List<String> getVersions(String targetId) throws IllegalArgumentException {
-        List<Version> versionList = new ArrayList<Version>();
-        File targetDirectory = new File(m_baseDirectory.getAbsolutePath(), targetId);
-        if (targetDirectory.isDirectory()) {
-            getVersions(targetId, versionList, targetDirectory);
-        }
-        else {
-            // try the default
-            getVersions(targetId, versionList, m_defaultDirectory);
-        }
-
-        // now sort the list of versions and convert all values to strings.
-        Collections.sort(versionList);
-        List<String> stringVersionList = new ArrayList<String>();
-        Iterator<Version> it = versionList.iterator();
-        while (it.hasNext()) {
-            String version = (it.next()).toString();
-            stringVersionList.add(version);
-        }
-        return stringVersionList;
-    }
-
+    
     /**
      *
      * @param targetId ID that requested versions
@@ -339,6 +378,11 @@ public class FileBasedProvider implement
      */
     public void updated(Dictionary settings) throws ConfigurationException {
         if (settings != null) {
+            String maximumNumberOfUsers = (String) settings.get(MAXIMUM_NUMBER_OF_USERS);
+            if (maximumNumberOfUsers != null) {
+                m_maximumNumberOfUsers = Integer.parseInt(maximumNumberOfUsers);
+            }
+            
             String baseDirectoryName = getNotNull(settings, DIRECTORY_NAME, "The base directory cannot be null");
             File baseDirectory = new File(baseDirectoryName);
             if (!baseDirectory.exists() || !baseDirectory.isDirectory()) {

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java Tue Oct 22 21:16:47 2013
@@ -32,15 +32,17 @@ import java.util.Dictionary;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.zip.GZIPInputStream;
 
 import javax.xml.parsers.SAXParserFactory;
 
+import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.ace.deployment.provider.ArtifactData;
 import org.apache.ace.deployment.provider.DeploymentProvider;
+import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
 import org.apache.ace.deployment.provider.repositorybased.BaseRepositoryHandler.XmlDeploymentArtifact;
-import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.ace.range.RangeIterator;
 import org.apache.ace.repository.Repository;
 import org.apache.ace.repository.ext.BackupRepository;
@@ -97,8 +99,12 @@ public class RepositoryBasedProvider imp
     public static final String KEY_RESOURCE_PROCESSOR_PID = "Deployment-ProvidesResourceProcessor";
 
     public static final String MIMETYPE = "application/vnd.osgi.bundle";
-
     
+    /**
+     * Key, intended for configurations that specifies the maximum number of concurrent users for this repository provider.
+     */
+    private static final String MAXIMUM_NUMBER_OF_USERS = "MaximumNumberOfUsers";
+
     private volatile LogService m_log;
 
     /** This variable is volatile since it can be changed by the Updated() method. */
@@ -114,143 +120,169 @@ public class RepositoryBasedProvider imp
     private final SAXParserFactory m_saxParserFactory;
     private final Map<String,List<String>> m_cachedVersionLists;
 
+    private final AtomicInteger m_usageCounter = new AtomicInteger();
+    /** Maximum number of concurrent users. Values <= 0 are used for unlimited users. */
+    private int m_maximumNumberOfUsers = 0;
+    /** The default backoff time for each new user over the limit */
+    private static final int BACKOFF_TIME_PER_USER = 5; 
+
     public RepositoryBasedProvider() {
         m_saxParserFactory = SAXParserFactory.newInstance();
         m_cachedVersionLists = new LRUMap<String, List<String>>();
     }
 
-    public List<ArtifactData> getBundleData(String targetId, String version) throws IllegalArgumentException, IOException {
+    public List<ArtifactData> getBundleData(String targetId, String version) throws OverloadedException, IllegalArgumentException, IOException {
         return getBundleData(targetId, null, version);
     }
 
-    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws IllegalArgumentException, IOException {
+    public List<ArtifactData> getBundleData(String targetId, String versionFrom, String versionTo) throws OverloadedException, IllegalArgumentException, IOException {
         try {
-            if (versionFrom != null) {
-                Version.parseVersion(versionFrom);
+            int concurrentUsers = m_usageCounter.incrementAndGet();
+            if (m_maximumNumberOfUsers != 0  && m_maximumNumberOfUsers < concurrentUsers) {
+                throw new OverloadedException("Too many users, maximum allowed = " + m_maximumNumberOfUsers + ", current = " + concurrentUsers,  (concurrentUsers - m_maximumNumberOfUsers) * BACKOFF_TIME_PER_USER);
             }
-            Version.parseVersion(versionTo);
-        }
-        catch (NumberFormatException nfe) {
-            throw new IllegalArgumentException(nfe);
-        }
-
-        InputStream input = null;
-        List<ArtifactData> dataVersionTo = null;
-        List<ArtifactData> dataVersionFrom = null;
 
-        List<XmlDeploymentArtifact>[] pairs = null;
-        try {
-            // ACE-240: do NOT allow local/remote repositories to be empty. If we're 
-            // asking for real artifacts, it means we must have a repository...
-            input = getRepositoryStream(true /* fail */);
-            if (versionFrom == null) {
-                pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionTo });
+            try {
+                if (versionFrom != null) {
+                    Version.parseVersion(versionFrom);
+                }
+                Version.parseVersion(versionTo);
             }
-            else {
-                pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionFrom, versionTo });
+            catch (NumberFormatException nfe) {
+                throw new IllegalArgumentException(nfe);
             }
-        }
-        catch (IOException ioe) {
-            m_log.log(LogService.LOG_WARNING, "Problem parsing source version.", ioe);
-            throw ioe;
-        }
-        finally {
-            if (input != null) {
-                try {
-                    input.close();
+
+            InputStream input = null;
+            List<ArtifactData> dataVersionTo = null;
+            List<ArtifactData> dataVersionFrom = null;
+
+            List<XmlDeploymentArtifact>[] pairs = null;
+            try {
+                // ACE-240: do NOT allow local/remote repositories to be empty. If we're 
+                // asking for real artifacts, it means we must have a repository...
+                input = getRepositoryStream(true /* fail */);
+                if (versionFrom == null) {
+                    pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionTo });
                 }
-                catch (IOException e) {
-                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                else {
+                    pairs = getDeploymentArtifactPairs(input, targetId, new String[] { versionFrom, versionTo });
                 }
             }
-        }
-
-        if ((pairs != null) && (pairs.length > 1)) {
-            dataVersionFrom = getAllArtifactData(pairs[0]);
-            dataVersionTo = getAllArtifactData(pairs[1]);
-            Iterator<ArtifactData> it = dataVersionTo.iterator();
-            while (it.hasNext()) {
-                ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
-                // see if there was previously a version of this bundle, and update the 'changed' property accordingly.
-                if (bundleDataVersionTo.isBundle()) {
-                    ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
-                    bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
+            catch (IOException ioe) {
+                m_log.log(LogService.LOG_WARNING, "Problem parsing source version.", ioe);
+                throw ioe;
+            }
+            finally {
+                if (input != null) {
+                    try {
+                        input.close();
+                    }
+                    catch (IOException e) {
+                        m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                    }
                 }
-                else {
-                    ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getUrl(), dataVersionFrom);
-                    bundleDataVersionTo.setChanged(bundleDataVersionFrom == null);
+            }
+
+            if ((pairs != null) && (pairs.length > 1)) {
+                dataVersionFrom = getAllArtifactData(pairs[0]);
+                dataVersionTo = getAllArtifactData(pairs[1]);
+                Iterator<ArtifactData> it = dataVersionTo.iterator();
+                while (it.hasNext()) {
+                    ArtifactDataImpl bundleDataVersionTo = (ArtifactDataImpl) it.next();
+                    // see if there was previously a version of this bundle, and update the 'changed' property accordingly.
+                    if (bundleDataVersionTo.isBundle()) {
+                        ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
+                        bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
+                    }
+                    else {
+                        ArtifactData bundleDataVersionFrom = getArtifactData(bundleDataVersionTo.getUrl(), dataVersionFrom);
+                        bundleDataVersionTo.setChanged(bundleDataVersionFrom == null);
+                    }
                 }
             }
+            else {
+                dataVersionTo = getAllArtifactData(pairs[0]);
+            }
+
+            return dataVersionTo != null ? dataVersionTo : new ArrayList<ArtifactData>();
         }
-        else {
-            dataVersionTo = getAllArtifactData(pairs[0]);
+        finally {
+            m_usageCounter.getAndDecrement();
         }
-
-        return dataVersionTo != null ? dataVersionTo : new ArrayList<ArtifactData>();
     }
 
     @SuppressWarnings("unchecked")
-    public List<String> getVersions(String targetId) throws IllegalArgumentException, IOException {
-    	// check if cache is up to date
-    	if (isCacheUpToDate()) {
-    		List<String> result = m_cachedVersionLists.get(targetId);
-    		if (result != null) {
-    			return result;
-    		}
-    	}
-    	else {
-    		m_cachedVersionLists.clear();
-    	}
-    	
-        List<String> stringVersionList = new ArrayList<String>();
-        InputStream input = null;
-
+    public List<String> getVersions(String targetId) throws OverloadedException, IllegalArgumentException, IOException {
         try {
-            // ACE-240: allow local/remote repositories to be empty; as the target 
-            // might be new & unregistered, it can have no repository yet... 
-            input = getRepositoryStream(false /* fail */);
-            List<Version> versionList;
-            if (input == null) {
-            	versionList = Collections.EMPTY_LIST;
+            int concurrentUsers = m_usageCounter.incrementAndGet();
+            if (m_maximumNumberOfUsers != 0  && m_maximumNumberOfUsers < concurrentUsers) {
+                throw new OverloadedException("Too many users, maximum allowed = " + m_maximumNumberOfUsers + ", current = " + concurrentUsers,  (concurrentUsers - m_maximumNumberOfUsers) * BACKOFF_TIME_PER_USER);
             }
-            else {
-            	versionList = getAvailableVersions(input, targetId);
-            }
-            if (versionList.isEmpty()) {
-                m_log.log(LogService.LOG_DEBUG, "No versions found for target: " + targetId);
+
+            // check if cache is up to date
+            if (isCacheUpToDate()) {
+                List<String> result = m_cachedVersionLists.get(targetId);
+                if (result != null) {
+                    return result;
+                }
             }
             else {
-                // now sort the list of versions and convert all values to strings.
-                Collections.sort(versionList);
-                Iterator<Version> it = versionList.iterator();
-                while (it.hasNext()) {
-                    String version = (it.next()).toString();
-                    stringVersionList.add(version);
-                }
+                m_cachedVersionLists.clear();
             }
-        }
-        catch (IllegalArgumentException iae) {
-            // just move on.
-        }
-        catch (IOException ioe) {
-            m_log.log(LogService.LOG_DEBUG, "Problem parsing DeploymentRepository", ioe);
-            throw ioe;
-        }
-        finally {
-            if (input != null) {
-                try {
-                    input.close();
+
+            List<String> stringVersionList = new ArrayList<String>();
+            InputStream input = null;
+
+            try {
+                // ACE-240: allow local/remote repositories to be empty; as the target 
+                // might be new & unregistered, it can have no repository yet... 
+                input = getRepositoryStream(false /* fail */);
+                List<Version> versionList;
+                if (input == null) {
+                    versionList = Collections.EMPTY_LIST;
+                }
+                else {
+                    versionList = getAvailableVersions(input, targetId);
                 }
-                catch (IOException e) {
-                    m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                if (versionList.isEmpty()) {
+                    m_log.log(LogService.LOG_DEBUG, "No versions found for target: " + targetId);
+                }
+                else {
+                    // now sort the list of versions and convert all values to strings.
+                    Collections.sort(versionList);
+                    Iterator<Version> it = versionList.iterator();
+                    while (it.hasNext()) {
+                        String version = (it.next()).toString();
+                        stringVersionList.add(version);
+                    }
+                }
+            }
+            catch (IllegalArgumentException iae) {
+                // just move on.
+            }
+            catch (IOException ioe) {
+                m_log.log(LogService.LOG_DEBUG, "Problem parsing DeploymentRepository", ioe);
+                throw ioe;
+            }
+            finally {
+                if (input != null) {
+                    try {
+                        input.close();
+                    }
+                    catch (IOException e) {
+                        m_log.log(LogService.LOG_DEBUG, "Error closing stream", e);
+                    }
                 }
             }
-        }
 
-        m_log.log(LogService.LOG_DEBUG, "Cache added for " + targetId);
+            m_log.log(LogService.LOG_DEBUG, "Cache added for " + targetId);
 
-        m_cachedVersionLists.put(targetId, stringVersionList);
-        return stringVersionList;
+            m_cachedVersionLists.put(targetId, stringVersionList);
+            return stringVersionList;
+        }
+        finally {
+            m_usageCounter.getAndDecrement();
+        }
     }
 
     /**
@@ -446,7 +478,17 @@ public class RepositoryBasedProvider imp
             String url = getNotNull(settings, URL, "DeploymentRepository URL not configured.");
             String name = getNotNull(settings, NAME, "RepositoryName not configured.");
             String customer = getNotNull(settings, CUSTOMER, "RepositoryCustomer not configured.");
-
+            String maximumNumberOfUsers = (String) settings.get(MAXIMUM_NUMBER_OF_USERS);
+            
+            if (maximumNumberOfUsers != null) {
+                try {
+                    m_maximumNumberOfUsers = Integer.parseInt(maximumNumberOfUsers);
+                }
+                catch (NumberFormatException nfe) {
+                    throw new ConfigurationException(MAXIMUM_NUMBER_OF_USERS, maximumNumberOfUsers + " is not a valid value for the maximum number of concurrent users.");
+                }
+            }
+            
             // create the remote repository and set it.
             try {
                 BackupRepository backup = new FilebasedBackupRepository(File.createTempFile("currentrepository", null), File.createTempFile("backuprepository", null));

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/Activator.java Tue Oct 22 21:16:47 2013
@@ -18,6 +18,9 @@
  */
 package org.apache.ace.deployment.servlet;
 
+import java.util.Properties;
+
+import javax.servlet.Filter;
 import javax.servlet.Servlet;
 
 import org.apache.ace.connectionfactory.ConnectionFactory;
@@ -51,6 +54,13 @@ public class Activator extends Dependenc
             .add(createServiceDependency().setService(ConnectionFactory.class).setRequired(true))
             .add(createServiceDependency().setService(LogService.class).setRequired(false))
         );
+        
+        Properties props = new Properties();
+        props.put("pattern", "/*");
+        manager.add(createComponent()
+            .setInterface(Filter.class.getName(), null)
+            .setImplementation(OverloadedFilter.class)
+        );
     }
 
     @Override

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/DeploymentServlet.java Tue Oct 22 21:16:47 2013
@@ -38,6 +38,7 @@ import org.apache.ace.authentication.api
 import org.apache.ace.deployment.processor.DeploymentProcessor;
 import org.apache.ace.deployment.provider.ArtifactData;
 import org.apache.ace.deployment.provider.DeploymentProvider;
+import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.streamgenerator.StreamGenerator;
 import org.apache.felix.dm.Component;
 import org.apache.felix.dm.DependencyManager;
@@ -91,7 +92,7 @@ public class DeploymentServlet extends H
      * <code>HttpServletResponse.SC_OK</code> - If all went fine
      */
     @Override
-    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
         try {
             String[] pathElements = verifyAndGetPathElements(request.getPathInfo());
             String targetID = pathElements[1];
@@ -109,6 +110,9 @@ public class DeploymentServlet extends H
             m_log.log(LogService.LOG_WARNING, e.getMessage(), e);
             e.handleAsHttpError(response);
         }
+        catch (OverloadedException oe) {
+            throw new ServletException(oe);
+        }
     }
 
     /**
@@ -156,6 +160,9 @@ public class DeploymentServlet extends H
             m_log.log(LogService.LOG_WARNING, e.getMessage(), e);
             e.handleAsHttpError(response);
         }
+        catch (OverloadedException oe) {
+            throw new ServletException(oe);
+        }
     }
 
     /**
@@ -211,7 +218,7 @@ public class DeploymentServlet extends H
      * @param response
      *            response object.
      */
-    private void handleVersionsRequest(String targetID, HttpServletResponse response) throws AceRestException {
+    private void handleVersionsRequest(String targetID, HttpServletResponse response) throws OverloadedException, AceRestException {
         ServletOutputStream output = null;
 
         List<String> versions = getVersions(targetID);
@@ -232,7 +239,7 @@ public class DeploymentServlet extends H
         }
     }
 
-    private void handlePackageDelivery(String targetID, String version, HttpServletRequest request, HttpServletResponse response) throws AceRestException {
+    private void handlePackageDelivery(String targetID, String version, HttpServletRequest request, HttpServletResponse response) throws OverloadedException, AceRestException {
         ServletOutputStream output = null;
 
         List<String> versions = getVersions(targetID);
@@ -284,7 +291,7 @@ public class DeploymentServlet extends H
         }
     }
 
-    private List<String> getVersions(String targetID) throws AceRestException {
+    private List<String> getVersions(String targetID) throws OverloadedException, AceRestException {
         try {
             return m_provider.getVersions(targetID);
         }

Added: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/OverloadedFilter.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/OverloadedFilter.java?rev=1534794&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/OverloadedFilter.java (added)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/servlet/OverloadedFilter.java Tue Oct 22 21:16:47 2013
@@ -0,0 +1,57 @@
+/*
+ * 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.ace.deployment.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.deployment.provider.OverloadedException;
+
+public class OverloadedFilter implements Filter {
+
+    private static final String HTTP_RETRY_AFTER = "Retry-After";
+
+    @Override
+    public void init(FilterConfig config) throws ServletException {
+    }
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        try {
+            filterChain.doFilter(request, response);
+        }
+        catch (OverloadedException oe) {
+            OverloadedException overloadedException = (OverloadedException) oe;
+            httpResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+            httpResponse.setHeader(HTTP_RETRY_AFTER, "" + overloadedException.getBackoffTime());
+        }
+    }
+
+    @Override
+    public void destroy() {
+    }
+}

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/StreamGenerator.java Tue Oct 22 21:16:47 2013
@@ -21,6 +21,8 @@ package org.apache.ace.deployment.stream
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.apache.ace.deployment.provider.OverloadedException;
+
 public interface StreamGenerator
 {
 
@@ -31,8 +33,9 @@ public interface StreamGenerator
      * @param version the version of the package
      * @return an input stream
      * @throws java.io.IOException when the stream could not be generated
+     * @throws OverloadedException if the streamgenerator is overloaded
      */
-    public InputStream getDeploymentPackage(String id, String version) throws IOException;
+    public InputStream getDeploymentPackage(String id, String version) throws OverloadedException, IOException;
 
     /**
      * Returns an input stream with the requested deployment fix package.
@@ -42,6 +45,7 @@ public interface StreamGenerator
      * @param toVersion the version the target should be in after applying the package.
      * @return an input stream.
      * @throws java.io.IOException when the stream could not be generated.
+     * @throws OverloadedException if the streamgenerator is overloaded
      */
-    public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws IOException;
+    public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws OverloadedException, IOException;
 }
\ No newline at end of file

Modified: ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/StreamGeneratorImpl.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/StreamGeneratorImpl.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/StreamGeneratorImpl.java (original)
+++ ace/trunk/org.apache.ace.deployment/src/org/apache/ace/deployment/streamgenerator/impl/StreamGeneratorImpl.java Tue Oct 22 21:16:47 2013
@@ -33,6 +33,7 @@ import java.util.zip.ZipEntry;
 import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.ace.deployment.provider.ArtifactData;
 import org.apache.ace.deployment.provider.DeploymentProvider;
+import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.streamgenerator.StreamGenerator;
 
 /**
@@ -43,15 +44,8 @@ public class StreamGeneratorImpl impleme
     private volatile DeploymentProvider m_provider;
     private volatile ConnectionFactory m_connectionFactory;
 
-    /**
-     * Returns an input stream with the requested deployment package.
-     *
-     * @param id the ID of the package
-     * @param version the version of the package
-     * @return an input stream
-     * @throws java.io.IOException when the stream could not be generated
-     */
-    public InputStream getDeploymentPackage(String id, String version) throws IOException {
+    @Override
+    public InputStream getDeploymentPackage(String id, String version) throws OverloadedException, IOException {
         List<ArtifactData> data = m_provider.getBundleData(id, version);
         Manifest manifest = new Manifest();
         Attributes main = manifest.getMainAttributes();
@@ -70,16 +64,8 @@ public class StreamGeneratorImpl impleme
         return DeploymentPackageStream.createStreamForThread(m_connectionFactory, manifest, data.iterator(), false);
     }
 
-    /**
-     * Returns an input stream with the requested deployment fix package.
-     *
-     * @param id the ID of the package.
-     * @param fromVersion the version of the target.
-     * @param toVersion the version the target should be in after applying the package.
-     * @return an input stream.
-     * @throws java.io.IOException when the stream could not be generated.
-     */
-    public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws IOException {
+    @Override
+    public InputStream getDeploymentPackage(String id, String fromVersion, String toVersion) throws OverloadedException, IOException {
         //return execute(new WorkerFixPackage(id, fromVersion, toVersion));
         List<ArtifactData> data = m_provider.getBundleData(id, fromVersion, toVersion);
         Manifest manifest = new Manifest();

Modified: ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/MockDeploymentRepository.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/MockDeploymentRepository.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/MockDeploymentRepository.java (original)
+++ ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/MockDeploymentRepository.java Tue Oct 22 21:16:47 2013
@@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.concurrent.Semaphore;
 import java.util.zip.GZIPOutputStream;
 
 import org.apache.ace.range.SortedRangeSet;
@@ -31,10 +32,12 @@ public class MockDeploymentRepository im
 
     private String m_range;
     private String m_xmlRepository;
+    private Semaphore m_semaphore;
 
-    public MockDeploymentRepository(String range, String xmlRepository) {
+    public MockDeploymentRepository(String range, String xmlRepository, Semaphore semaphore) {
         m_range = range;
         m_xmlRepository = xmlRepository;
+        m_semaphore = semaphore;
     }
 
     /* (non-Javadoc)
@@ -42,6 +45,15 @@ public class MockDeploymentRepository im
      * @see org.apache.ace.repository.Repository#checkout(long)
      */
     public InputStream checkout(long version) throws IOException, IllegalArgumentException {
+        if (m_semaphore != null) {
+            m_semaphore.release();
+            try {
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                throw new IOException(e);
+            }
+        }
         if (version == 1) {
             //throw an IOException!
             throw new IOException("Checkout exception.");
@@ -57,11 +69,29 @@ public class MockDeploymentRepository im
     }
 
     public boolean commit(InputStream data, long fromVersion) throws IOException, IllegalArgumentException {
+        if (m_semaphore != null) {
+            m_semaphore.release();
+            try {
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                throw new IOException(e);
+            }
+        }
         // Not used in test
         return false;
     }
 
     public SortedRangeSet getRange() throws IOException {
+        if (m_semaphore != null) {
+            m_semaphore.release();
+            try {
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                throw new IOException(e);
+            }
+        }
         return new SortedRangeSet(m_range);
     }
 }

Added: ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderConcurrencyTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderConcurrencyTest.java?rev=1534794&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderConcurrencyTest.java (added)
+++ ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderConcurrencyTest.java Tue Oct 22 21:16:47 2013
@@ -0,0 +1,118 @@
+/*
+ * 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.ace.deployment.provider.repositorybased;
+
+import static org.apache.ace.test.utils.TestUtils.UNIT;
+
+import java.lang.reflect.Field;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.ace.deployment.provider.OverloadedException;
+import org.apache.ace.repository.Repository;
+import org.apache.ace.test.utils.TestUtils;
+import org.osgi.service.log.LogService;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class RepositoryBasedProviderConcurrencyTest {
+
+    private final String TARGET = "target";
+     
+    private RepositoryBasedProvider m_backend;
+    private Semaphore m_semaphore;
+    private Exception m_exception;
+
+    @BeforeMethod(alwaysRun = true)
+    protected void setUp() throws Exception {
+        // setup mock repository
+        m_semaphore = new Semaphore(0);
+        MockDeploymentRepository repository = new MockDeploymentRepository("", null, m_semaphore);
+        m_backend = new RepositoryBasedProvider();
+        TestUtils.configureObject(m_backend, Repository.class, repository);
+        TestUtils.configureObject(m_backend, LogService.class);
+    }
+    
+    @Test(groups = { UNIT })
+    public void testNoConcurrentUsersAllowed() throws Exception {
+        // -1 number of users makes sure nobody can use the repository
+        setConfigurationForUsers(-1);
+        try {
+            m_backend.getVersions(TARGET);
+            assert false : "Expected an overloaded exception";
+        } catch (OverloadedException oe) {
+            assert true;
+        } catch (Throwable t) {
+            assert false : "Unknown exception";
+        }
+    }
+
+    @Test(groups = { UNIT })
+    public void testConcurrentUsersWithLimit() throws Exception {
+        setConfigurationForUsers(1);
+        new Thread() {
+            public void run() {
+                try {
+                    m_backend.getVersions(TARGET);
+                } catch (Exception e) {
+                    m_exception = e;
+                }
+            }
+        }.start();
+        
+        try {
+            boolean acquire = m_semaphore.tryAcquire(1, 1, TimeUnit.SECONDS);
+            assert acquire : "Could not acquire semaphore, no concurrent threads ?";
+            m_backend.getVersions(TARGET);
+            assert false : "Expected an overloaded exception";
+        } catch (OverloadedException oe) {
+            assert true;
+        }
+        
+        assert m_exception == null : "No Exception expected";
+    }
+    
+    @Test(groups = { UNIT })
+    public void testConcurrentUsersWithoutLimit() throws Exception {
+        // Because it's hard to test for unlimited users we'll just try 2 concurrent threads. 
+        setConfigurationForUsers(0);
+        new Thread() {
+            public void run() {
+                try {
+                    m_backend.getVersions(TARGET);
+                } catch (Exception e) {
+                    m_exception = e;
+                }
+            }
+        }.start();
+        
+        m_semaphore.tryAcquire(1, 1, TimeUnit.SECONDS);
+        m_backend.getVersions(TARGET);
+        assert true;
+        
+        assert m_exception == null : "No Exception expected";
+    }
+    
+    private void setConfigurationForUsers(int numberOfConcurrentUsers) throws Exception {
+        // setting a new configuration on the repository also creates a cache repository etc. This way only the max users is changed.
+        Field field = m_backend.getClass().getDeclaredField("m_maximumNumberOfUsers");
+        field.setAccessible(true);
+        field.set(m_backend, new Integer(numberOfConcurrentUsers));
+    }
+}

Modified: ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java (original)
+++ ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java Tue Oct 22 21:16:47 2013
@@ -99,7 +99,7 @@ public class RepositoryBasedProviderPerf
     public void setUp() throws Exception {
         // setup mock repository
         String range = "1-100000";
-        Repository mock = new MockDeploymentRepository(range, generateHugeTestXml());
+        Repository mock = new MockDeploymentRepository(range, generateHugeTestXml(), null);
         m_backend = new RepositoryBasedProvider();
         TestUtils.configureObject(m_backend, Repository.class, mock);
         TestUtils.configureObject(m_backend, LogService.class);

Modified: ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java?rev=1534794&r1=1534793&r2=1534794&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java (original)
+++ ace/trunk/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java Tue Oct 22 21:16:47 2013
@@ -21,7 +21,6 @@ package org.apache.ace.deployment.provid
 import static org.apache.ace.test.utils.TestUtils.UNIT;
 
 import java.io.File;
-import java.io.IOException;
 import java.io.StringWriter;
 import java.util.Collection;
 import java.util.HashMap;
@@ -146,7 +145,7 @@ public class RepositoryBasedProviderTest
         String range = "1,2,3";
 
         // setup mock repository
-        Repository mock = new MockDeploymentRepository(range, deploymentRepositoryXml);
+        Repository mock = new MockDeploymentRepository(range, deploymentRepositoryXml, null);
         m_backend = new RepositoryBasedProvider();
         TestUtils.configureObject(m_backend, Repository.class, mock);
         TestUtils.configureObject(m_backend, LogService.class);
@@ -359,8 +358,8 @@ public class RepositoryBasedProviderTest
      * @throws java.io.IOException
      */
     @Test(groups = { UNIT })
-    public void testEmptyRepository() throws IOException {
-        Repository mock = new MockDeploymentRepository("", null);
+    public void testEmptyRepository() throws Exception {
+        Repository mock = new MockDeploymentRepository("", null, null);
         TestUtils.configureObject(m_backend, Repository.class, mock);
 
         List<String> versions = m_backend.getVersions(TARGET);
@@ -372,7 +371,7 @@ public class RepositoryBasedProviderTest
      * See if the getVersions() methods normal output works
      */
     @Test(groups = { UNIT })
-    public void testGetVersion() throws IOException {
+    public void testGetVersion() throws Exception {
         List<String> versions = m_backend.getVersions(TARGET);
         assert versions.size() == 1 : "Expected one version to be found, but found " + versions.size();
         assert versions.get(0).equals(VERSION1) : "Expected version " + VERSION1 + " but found " + versions.get(0);
@@ -382,7 +381,7 @@ public class RepositoryBasedProviderTest
      * Test the getVersions method with an illegal version (not in org.osgi.framework.Version format)
      */
     @Test(groups = { UNIT })
-    public void testIllegalVersion() throws IOException {
+    public void testIllegalVersion() throws Exception {
         // an illegal version should be silently ignored
         List<String> versions = m_backend.getVersions(INVALIDVERSIONTARGET);
         assert versions.isEmpty() : "Expected no versions to be found, but found " + versions.size();
@@ -392,7 +391,7 @@ public class RepositoryBasedProviderTest
      * Test with multiple versions. It expects all versions in an ascending order.
      */
     @Test(groups = { UNIT })
-    public void testMultipleVersions() throws IOException {
+    public void testMultipleVersions() throws Exception {
         List<String> versions = m_backend.getVersions(MULTIPLEVERSIONTARGET);
         assert versions.size() == 4 : "Expected three version to be found, but found " + versions.size();
         // all versions should be in ascending order
@@ -406,7 +405,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a single version, returning a single bundle
      */
     @Test(groups = { UNIT })
-    public void testSingleBundleSingleVersionBundleData() throws IOException {
+    public void testSingleBundleSingleVersionBundleData() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(TARGET, VERSION1);
         assert bundleData.size() == 1 : "Expected one bundle to be found, but found " + bundleData.size();
         assert bundleData.contains(BUNDLE1) : "Expected to find bundle " + BUNDLE1.getSymbolicName();
@@ -416,7 +415,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a single version, returning a multiple bundles
      */
     @Test(groups = { UNIT })
-    public void testMultipleBundleSingleVersionBundleData() throws IOException {
+    public void testMultipleBundleSingleVersionBundleData() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1);
         assert bundleData.size() == 2 : "Expected two bundle to be found, but found " + bundleData.size();
         assert bundleData.contains(BUNDLE3) : "Expected to find bundle " + BUNDLE3.getSymbolicName();
@@ -427,7 +426,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData with an illegal version (i.e. a version that doesn't exist)
      */
     @Test(groups = { UNIT })
-    public void testInvalidVersionBundleData() throws IOException {
+    public void testInvalidVersionBundleData() throws Exception {
         try {
             m_backend.getBundleData(TARGET, INVALIDVERSION);
             assert false : "Expected an error because version " + INVALIDVERSION + " doesn't exist for target: "
@@ -442,7 +441,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData with an illegal target (i.e. a target that doesn't exist)
      */
     @Test(groups = { UNIT })
-    public void testInvalidTargetBundleData() throws IOException {
+    public void testInvalidTargetBundleData() throws Exception {
         try {
             m_backend.getBundleData(INVALIDVERSIONTARGET, VERSION1);
             assert false : "Expected an error because version " + VERSION1 + " doesn't exist for target: "
@@ -457,7 +456,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, returning a single bundle that hasn't changed
      */
     @Test(groups = { UNIT })
-    public void testSingleUnchangedBundleMultipleVersions() throws IOException {
+    public void testSingleUnchangedBundleMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(TARGET, VERSION1, VERSION1);
         assert bundleData.size() == 1 : "Expect one bundle, got " + bundleData.size();
         Iterator<ArtifactData> it = bundleData.iterator();
@@ -472,7 +471,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, returning multiple bundles that haven't changed
      */
     @Test(groups = { UNIT })
-    public void testMultipleBundlesMultipleVersions() throws IOException {
+    public void testMultipleBundlesMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1, VERSION1);
         assert bundleData.size() == 2 : "Expected two bundle to be found, but found " + bundleData.size();
         Iterator<ArtifactData> it = bundleData.iterator();
@@ -486,7 +485,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, where in the second version a bundle is removed
      */
     @Test(groups = { UNIT })
-    public void testRemovedBundleMultipleVersions() throws IOException {
+    public void testRemovedBundleMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1, VERSION3);
         assert bundleData.size() == 1 : "Expected one bundle to be found, but found " + bundleData.size();
     }
@@ -495,7 +494,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, where in the second version a bundle is added
      */
     @Test(groups = { UNIT })
-    public void testAddedBundleMultipleVersions() throws IOException {
+    public void testAddedBundleMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION3, VERSION1);
         assert bundleData.size() == 2 : "Expected two bundle to be found, but found " + bundleData.size();
         Iterator<ArtifactData> it = bundleData.iterator();
@@ -514,7 +513,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, where in the second version one bundle has changed and another hasn't
      */
     @Test(groups = { UNIT })
-    public void testSingleChangedBundleMultipleVersions() throws IOException {
+    public void testSingleChangedBundleMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1, VERSION4);
         assert bundleData.size() == 2 : "Expected two bundles to be found, but found " + bundleData.size();
         Iterator<ArtifactData> it = bundleData.iterator();
@@ -536,7 +535,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for a two versions, where two bundles have changed
      */
     @Test(groups = { UNIT })
-    public void testMultipleChangedBundlesMultipleVersions() throws IOException {
+    public void testMultipleChangedBundlesMultipleVersions() throws Exception {
         Collection<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1, VERSION2);
         assert bundleData.size() == 2 : "Expected two bundles to be found, but found " + bundleData.size();
         Iterator<ArtifactData> it = bundleData.iterator();
@@ -558,7 +557,7 @@ public class RepositoryBasedProviderTest
      * See if the getVersions() methods normal output works with literals ' and "
      */
     @Test(groups = { UNIT })
-    public void testGetLiteralTargetVersion() throws IOException {
+    public void testGetLiteralTargetVersion() throws Exception {
         List<String> versions = m_backend.getVersions("'");
         assert versions.size() == 1 : "Expected one version to be found, but found " + versions.size();
         assert versions.get(0).equals(VERSION1) : "Expected version " + VERSION1 + " but found " + versions.get(0);
@@ -580,7 +579,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData for an empty version (no bundle URLS are included)
      */
     @Test(groups = { UNIT })
-    public void testEmptyDeploymentVersion() throws IOException {
+    public void testEmptyDeploymentVersion() throws Exception {
         // get the version number
         List<String> versions = m_backend.getVersions(EMPTYVERSIONTARGET);
         assert versions.size() == 2 : "Expected two version to be found, but found " + versions.size();
@@ -602,7 +601,7 @@ public class RepositoryBasedProviderTest
      * See if a version with a literal is parsed correct and ignored.
      */
     @Test(groups = { UNIT })
-    public void testGetLiteralTargetIllegalVersion() throws IOException {
+    public void testGetLiteralTargetIllegalVersion() throws Exception {
         List<String> versions = m_backend.getVersions("myTarget");
         assert versions.size() == 0 : "Expected no versions to be found, but found " + versions.size();
     }
@@ -611,7 +610,7 @@ public class RepositoryBasedProviderTest
      * Test the getBundleData with some resources.
      */
     @Test(groups = { UNIT })
-    public void testBundleDataWithResources() throws IOException {
+    public void testBundleDataWithResources() throws Exception {
         List<String> versions = m_backend.getVersions(RESOURCETARGET);
         assert versions.size() == 1 : "Expected two version to be found, but found " + versions.size();