ACE-627 allow ordering of artifact data to be defined

- introduced an ArtifactDataHelper service that DeploymentProviders can use to
  define a particular order for the artifacts that are being returned by it.



git-svn-id: https://svn.apache.org/repos/asf/ace/trunk@1802921 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/Activator.java b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/Activator.java
index 27552ca..036c079 100644
--- a/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/Activator.java
+++ b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/Activator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.ace.it.deployment.provider.filebased;
 
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.DeploymentProvider;
 import org.apache.felix.dm.DependencyActivatorBase;
 import org.apache.felix.dm.DependencyManager;
@@ -33,15 +34,18 @@
             .setInterface(DeploymentProvider.class.getName(), null)
             .setImplementation(FileBasedProvider.class)
             .add(createConfigurationDependency()
-                .setPid(PID)
-             )
-             .add(createServiceDependency()
-                 .setService(LogService.class)
-                 .setRequired(false)));
+                .setPid(PID))
+            .add(createServiceDependency()
+                .setService(ArtifactDataHelper.class)
+                .setDefaultImplementation(NoOpArtifactDataHelper.class)
+                .setRequired(false))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
     }
 
     @Override
     public void destroy(BundleContext arg0, DependencyManager arg1) throws Exception {
         // TODO Auto-generated method stub
     }
-}
\ No newline at end of file
+}
diff --git a/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/FileBasedProvider.java b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/FileBasedProvider.java
index cc6824e..989e7a2 100644
--- a/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/FileBasedProvider.java
+++ b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/FileBasedProvider.java
@@ -40,6 +40,7 @@
 import java.util.jar.Manifest;
 
 import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.DeploymentProvider;
 import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
@@ -66,14 +67,17 @@
     private static final int OSGI_R4_MANIFEST_VERSION = 2;
     private volatile File m_baseDirectory;
     private volatile File m_defaultDirectory;
+
+    private volatile ArtifactDataHelper m_artifactDataHelper;
     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; 
+    private static final int BACKOFF_TIME_PER_USER = 5;
 
     /**
      * Get the bundle data from the bundles in the <data dir>/<target>/<version> directory It reads the manifest from all the
@@ -85,7 +89,8 @@
             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);
+
+            return m_artifactDataHelper.process(internalGetBundleData(targetId, version), targetId, null, version);
         } finally {
             m_usageCounter.getAndDecrement();
         }
@@ -147,7 +152,7 @@
             }
             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();
@@ -155,7 +160,7 @@
                 ArtifactData bundleDataVersionFrom = getBundleData(bundleDataVersionTo.getSymbolicName(), dataVersionFrom);
                 bundleDataVersionTo.setChanged(!bundleDataVersionTo.equals(bundleDataVersionFrom));
             }
-            return dataVersionTo;
+            return m_artifactDataHelper.process(dataVersionTo, targetId, versionFrom, versionTo);
         } finally {
             m_usageCounter.getAndDecrement();
         }
@@ -198,7 +203,7 @@
                 // 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<>();
@@ -341,7 +346,7 @@
 
         return bundleData;
     }
-    
+
     /**
      *
      * @param targetId ID that requested versions
@@ -381,7 +386,7 @@
             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()) {
diff --git a/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/NoOpArtifactDataHelper.java b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/NoOpArtifactDataHelper.java
new file mode 100644
index 0000000..a1d4b0f
--- /dev/null
+++ b/org.apache.ace.deployment.itest/src/org/apache/ace/it/deployment/provider/filebased/NoOpArtifactDataHelper.java
@@ -0,0 +1,33 @@
+/*
+ * 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.it.deployment.provider.filebased;
+
+import java.util.List;
+
+import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
+
+public class NoOpArtifactDataHelper implements ArtifactDataHelper {
+
+    @Override
+    public List<ArtifactData> process(List<ArtifactData> artifacts, String targetId, String versionFrom, String versionTo) {
+        return artifacts;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.ace.deployment.itest/test/org/apache/ace/it/deployment/provider/filebased/FileBasedProviderTest.java b/org.apache.ace.deployment.itest/test/org/apache/ace/it/deployment/provider/filebased/FileBasedProviderTest.java
index d5ac6d4..9cfdc11 100644
--- a/org.apache.ace.deployment.itest/test/org/apache/ace/it/deployment/provider/filebased/FileBasedProviderTest.java
+++ b/org.apache.ace.deployment.itest/test/org/apache/ace/it/deployment/provider/filebased/FileBasedProviderTest.java
@@ -18,13 +18,16 @@
  */
 package org.apache.ace.it.deployment.provider.filebased;
 
+import static org.testng.Assert.*;
 import java.io.File;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
 
 import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
 import org.apache.ace.deployment.util.test.BundleStreamGenerator;
 import org.apache.ace.test.utils.FileUtils;
@@ -61,7 +64,6 @@
     private ArtifactData BUNDLE3_2;
     private ArtifactData BUNDLE4_2;
 
-    @SuppressWarnings("serial")
     @BeforeTest(alwaysRun = true)
     protected void setUp() throws Exception {
 
@@ -73,6 +75,13 @@
 
         m_backend = new FileBasedProvider();
         TestUtils.configureObject(m_backend, LogService.class);
+        TestUtils.configureObject(m_backend, ArtifactDataHelper.class, new ArtifactDataHelper() {
+            @Override
+            public List<ArtifactData> process(List<ArtifactData> artifacts, String targetId, String versionFrom, String versionTo) {
+                return artifacts;
+            }
+        });
+
         m_backend.updated(new Hashtable<String, String>() {
             {
                 put("BaseDirectoryName", m_tempDirectory.getAbsolutePath());
@@ -159,6 +168,26 @@
     }
 
     /**
+     * Tests that a {@link ArtifactDataHelper} instance can be used to mangle the returned artifact data.
+     */
+    @Test()
+    public void testArtifactDataHelperIsUsed() {
+        TestUtils.configureObject(m_backend, ArtifactDataHelper.class, new ArtifactDataHelper() {
+            @Override
+            public List<ArtifactData> process(List<ArtifactData> artifacts, String targetId, String versionFrom, String versionTo) {
+                Collections.sort(artifacts, (a, b) -> b.getSymbolicName().compareTo(a.getSymbolicName()));
+                return artifacts;
+            }
+        });
+
+        // XXX
+        List<ArtifactData> bundleData = m_backend.getBundleData(MULTIPLEVERSIONTARGET, VERSION1);
+        assertEquals(2, bundleData.size(), "Expected two bundle to be found, but found " + bundleData.size());
+        assertEquals(BUNDLE4, bundleData.get(0), "Expected to find bundle " + BUNDLE4.getSymbolicName());
+        assertEquals(BUNDLE3, bundleData.get(1), "Expected to find bundle " + BUNDLE3.getSymbolicName());
+    }
+
+    /**
      * Test the getBundleData for a single version, returning a single bundle
      */
     @Test()
diff --git a/org.apache.ace.deployment/provider.api.bnd b/org.apache.ace.deployment/provider.api.bnd
index 2efae6d..4bdfe71 100644
--- a/org.apache.ace.deployment/provider.api.bnd
+++ b/org.apache.ace.deployment/provider.api.bnd
@@ -1,6 +1,6 @@
 # Licensed to the Apache Software Foundation (ASF) under the terms of ASLv2 (http://www.apache.org/licenses/LICENSE-2.0).
 
 Export-Package: org.apache.ace.deployment.provider
-Bundle-Version: 1.1.2
+Bundle-Version: 1.2.0
 Bundle-Name: Apache ACE Deployment Provider API
 Bundle-Description: Provides the Deployment Provider API packages
\ No newline at end of file
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/ArtifactDataHelper.java b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/ArtifactDataHelper.java
new file mode 100644
index 0000000..616ecb0
--- /dev/null
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/ArtifactDataHelper.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.util.List;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Provides an additional hook for processing {@link ArtifactData}s as returned by a {@link DeploymentProvider}.
+ * <p>
+ * {@link DeploymentProvider}s can use this service to define a particular order in which they should be included in the
+ * deployment package.
+ * </p>
+ */
+@ConsumerType
+public interface ArtifactDataHelper {
+
+    /**
+     * @param artifacts
+     *            the list of artifacts that should be processed, cannot be <code>null</code>;
+     * @param targetId
+     *            the identifier of the target these artifacts are intended for, cannot be <code>null</code>;
+     * @param versionFrom
+     *            the optional from version, can be <code>null</code>;
+     * @param versionTo
+     *            the to version, cannot be <code>null</code>.
+     * @return the list of processed artifacts, in the order they should appear in the deployment package.
+     */
+    List<ArtifactData> process(List<ArtifactData> artifacts, String targetId, String versionFrom, String versionTo);
+
+}
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java
index 970485e..1d85df7 100644
--- a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/DeploymentProvider.java
@@ -24,8 +24,7 @@
 import org.osgi.annotation.versioning.ProviderType;
 
 /**
- * An interface that provides the meta information for the bundles
- * in a certain version number.
+ * An interface that provides the meta information for the bundles in a certain version number.
  */
 @ProviderType
 public interface DeploymentProvider {
@@ -34,43 +33,52 @@
      * Get the collection of bundleData for a specific version. This data can be used to generate a deployment package.
      * The ArtifactData.hasChanged method will return true for all bundles in this collection
      *
-     * @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.
+     * @return a list of artifacts, in the order in which they should appear in the deployment package. In case no
+     *         artifacts are present in this version, an empty list is to be returned.
+     * @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 OverloadedException, IllegalArgumentException, IOException;
+    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.
      *
      * Changes between versions are indicated by ArtifactData.hasChanged:
      * <ol>
-     * <li> If a bundle was present in versionFrom and not in VersionTo, it will not be in the collection</li>
-     * <li> If a bundle existed in versionFrom and exists unchanged in VersionTo, hasChanged will return false</li>
-     * <li> If a bundle existed in versionFrom and exists changed (i.e. other version) in versionTo, hasChanged will return true</li>
-     * <li> If a bundle did not exist in versionFrom and exists in VersionTo, hasChanged will return true</li>
+     * <li>If a bundle was present in versionFrom and not in VersionTo, it will not be in the collection</li>
+     * <li>If a bundle existed in versionFrom and exists unchanged in VersionTo, hasChanged will return false</li>
+     * <li>If a bundle existed in versionFrom and exists changed (i.e. other version) in versionTo, hasChanged will
+     * return true</li>
+     * <li>If a bundle did not exist in versionFrom and exists in VersionTo, hasChanged will return true</li>
      * </ol>
      *
-     * @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.
+     * @return a list of artifacts, in the order in which they should appear in the deployment package. In case no
+     *         artifacts are present in this version, an empty list is to be returned.
+     * @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 OverloadedException, IllegalArgumentException, IOException;
+    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
-     * ascending order, so the latest version is the last one in the list.
+     * Returns a list of versions for a specific target. The list is sorted in ascending order, so the latest version is
+     * the last one in the list.
      *
-     * @param targetId  The id of the target for which all available deployment package
-     *                   versions are being retrieved.
-     * @return All available deployment package versions for a specific target. If none available,
-     *         return an empty List.
+     * @param targetId
+     *            The id of the target for which all available deployment package versions are being retrieved.
+     * @return All available deployment package versions for a specific target. If none available, 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
+     * @throws java.io.IOException
+     *             If an IOException occurs.
+     * @throws OverloadedException
+     *             if the provider is overloaded
      */
-    public List<String> getVersions(String targetId) throws OverloadedException, IllegalArgumentException, IOException;
-}
\ No newline at end of file
+    List<String> getVersions(String targetId) throws OverloadedException, IllegalArgumentException, IOException;
+}
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/packageinfo b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/packageinfo
index 3ef1862..d96c0b8 100644
--- a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/packageinfo
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/packageinfo
@@ -1 +1 @@
-version 1.1.1
\ No newline at end of file
+version 1.2.0
\ No newline at end of file
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/Activator.java b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/Activator.java
index f01c9d6..fe05093 100644
--- a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/Activator.java
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/Activator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.ace.deployment.provider.repositorybased;
 
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.DeploymentProvider;
 import org.apache.felix.dm.DependencyActivatorBase;
 import org.apache.felix.dm.DependencyManager;
@@ -33,15 +34,13 @@
             .setInterface(DeploymentProvider.class.getName(), null)
             .setImplementation(RepositoryBasedProvider.class)
             .add(createConfigurationDependency()
-                .setPid(PID)
-             )
-             .add(createServiceDependency()
-                 .setService(LogService.class)
-                 .setRequired(false)));
+                .setPid(PID))
+            .add(createServiceDependency()
+                .setService(ArtifactDataHelper.class)
+                .setDefaultImplementation(NoOpArtifactDataHelper.class)
+                .setRequired(false))
+            .add(createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)));
     }
-
-    @Override
-    public void destroy(BundleContext arg0, DependencyManager arg1) throws Exception {
-        // TODO Auto-generated method stub
-    }
-}
\ No newline at end of file
+}
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/NoOpArtifactDataHelper.java b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/NoOpArtifactDataHelper.java
new file mode 100644
index 0000000..290107a
--- /dev/null
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/NoOpArtifactDataHelper.java
@@ -0,0 +1,33 @@
+/*
+ * 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 java.util.List;
+
+import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
+
+public class NoOpArtifactDataHelper implements ArtifactDataHelper {
+
+    @Override
+    public List<ArtifactData> process(List<ArtifactData> artifacts, String targetId, String versionFrom, String versionTo) {
+        return artifacts;
+    }
+
+}
\ No newline at end of file
diff --git a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java
index 60d72e3..d32b779 100644
--- a/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java
+++ b/org.apache.ace.deployment/src/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProvider.java
@@ -39,6 +39,7 @@
 
 import org.apache.ace.connectionfactory.ConnectionFactory;
 import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.DeploymentProvider;
 import org.apache.ace.deployment.provider.OverloadedException;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
@@ -65,7 +66,7 @@
     private static final String URL = "url";
     private static final String NAME = "name";
     private static final String CUSTOMER = "customer";
-    
+
     /**
      * Key, intended to be used for artifacts which are bundles and will publish
      * a resource processor (see OSGi compendium section 114.10).
@@ -99,12 +100,13 @@
     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 ArtifactDataHelper m_artifactDataHelper;
     private volatile LogService m_log;
 
     /** This variable is volatile since it can be changed by the Updated() method. */
@@ -116,7 +118,7 @@
      */
     private volatile Repository m_directRepository;
     private volatile DependencyManager m_manager;
-    
+
     private final SAXParserFactory m_saxParserFactory;
     private final Map<String,List<String>> m_cachedVersionLists;
 
@@ -124,7 +126,7 @@
     /** 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; 
+    private static final int BACKOFF_TIME_PER_USER = 5;
 
     public RepositoryBasedProvider() {
         m_saxParserFactory = SAXParserFactory.newInstance();
@@ -158,7 +160,7 @@
 
             List<XmlDeploymentArtifact>[] pairs = null;
             try {
-                // ACE-240: do NOT allow local/remote repositories to be empty. If we're 
+                // 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) {
@@ -204,7 +206,12 @@
                 dataVersionTo = getAllArtifactData(pairs[0]);
             }
 
-            return dataVersionTo != null ? dataVersionTo : new ArrayList<ArtifactData>();
+            if (dataVersionTo == null) {
+                return new ArrayList<>();
+            }
+
+            // ACE-627: allow a custom ordering to be specified for the artifacts to appear in our DP...
+            return m_artifactDataHelper.process(dataVersionTo, targetId, versionFrom, versionTo);
         }
         finally {
             m_usageCounter.getAndDecrement();
@@ -233,8 +240,8 @@
             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... 
+                // 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) {
@@ -296,7 +303,7 @@
         // get the bundledata for each URL
         for (XmlDeploymentArtifact pair : deploymentArtifacts) {
             long artifactSize = pair.getSize();
-            
+
             Map<String, String> directives = pair.getDirective();
             if (directives.get(DIRECTIVE_KEY_PROCESSORID) == null) {
                 // this is a bundle.
@@ -317,8 +324,8 @@
                 String filename = directives.get(DIRECTIVE_KEY_RESOURCE_ID);
                 result.add(new ArtifactDataImpl(pair.getUrl(), directives, filename, artifactSize, true /* hasChanged */));
             }
-
         }
+
         return result;
     }
 
@@ -383,10 +390,10 @@
      */
     private List<Version> getAvailableVersions(InputStream input, String targetId) throws IllegalArgumentException {
         DeploymentPackageVersionCollector collector = new DeploymentPackageVersionCollector(targetId);
-        
+
         try {
             m_saxParserFactory.newSAXParser().parse(input, collector);
-            
+
             return collector.getVersions();
         }
         catch (Exception e) {
@@ -408,7 +415,7 @@
      */
     private List<XmlDeploymentArtifact>[] getDeploymentArtifactPairs(InputStream input, String targetId, String[] versions) throws IllegalArgumentException {
         final DeploymentArtifactCollector collector = new DeploymentArtifactCollector(targetId, versions);
-        
+
         try {
             m_saxParserFactory.newSAXParser().parse(input, collector);
 
@@ -460,7 +467,7 @@
         	return new GZIPInputStream(result);
         }
     }
-    
+
     private boolean isCacheUpToDate() {
         CachedRepository cachedRepository = m_cachedRepository;
         try {
@@ -478,7 +485,7 @@
             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);
@@ -487,7 +494,7 @@
                     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));
diff --git a/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java b/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java
index c65d834..92901d4 100644
--- a/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java
+++ b/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderPerformanceTest.java
@@ -30,6 +30,7 @@
 import javax.xml.transform.stream.StreamResult;
 
 import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.repository.Repository;
 import org.apache.ace.test.utils.TestUtils;
 import org.osgi.framework.Constants;
@@ -101,8 +102,9 @@
         m_backend = new RepositoryBasedProvider();
         TestUtils.configureObject(m_backend, Repository.class, mock);
         TestUtils.configureObject(m_backend, LogService.class);
+        TestUtils.configureObject(m_backend, ArtifactDataHelper.class, new NoOpArtifactDataHelper());
     }
-    
+
     /**
      * Test the getBundleData for a single version, returning a single bundle, for a huge XML.
      */
@@ -181,7 +183,7 @@
 
     /**
      * Helper method to create the description of a deploymentpacakge with given data.
-     * 
+     *
      * @param doc The document to add the version to.
      * @param targetText The targetId in the deploymentversion.
      * @param versionText The version in the deploymentversion.
diff --git a/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java b/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java
index 74fe04d..d1396fc 100644
--- a/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java
+++ b/org.apache.ace.deployment/test/org/apache/ace/deployment/provider/repositorybased/RepositoryBasedProviderTest.java
@@ -39,6 +39,7 @@
 import javax.xml.transform.stream.StreamResult;
 
 import org.apache.ace.deployment.provider.ArtifactData;
+import org.apache.ace.deployment.provider.ArtifactDataHelper;
 import org.apache.ace.deployment.provider.impl.ArtifactDataImpl;
 import org.apache.ace.deployment.util.test.BundleStreamGenerator;
 import org.apache.ace.repository.Repository;
@@ -144,6 +145,7 @@
         m_backend = new RepositoryBasedProvider();
         TestUtils.configureObject(m_backend, Repository.class, mock);
         TestUtils.configureObject(m_backend, LogService.class);
+        TestUtils.configureObject(m_backend, ArtifactDataHelper.class, new NoOpArtifactDataHelper());
     }
 
     /**
@@ -294,7 +296,7 @@
 
     /**
      * Helper method to create the description of a deploymentpacakge with given data.
-     * 
+     *
      * @param doc
      *            The document to add the version to.
      * @param targetText
@@ -354,7 +356,7 @@
 
     /**
      * Without any checked in data, we should just get back no version, but the provider should not crash.
-     * 
+     *
      * @throws java.io.IOException
      */
     @Test()