You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 10:17:25 UTC

[sling-org-apache-sling-testing-clients] 04/08: SLING-6853 Improve polling capabilities in o.a.s.testing.clients thanks @volteanu for the contribution!

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.testing.clients-1.1.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git

commit 0883798bbf4fe5a4a7f479f51feffb91534883d3
Author: Andrei Dulvac <du...@apache.org>
AuthorDate: Tue May 16 12:34:17 2017 +0000

    SLING-6853 Improve polling capabilities in o.a.s.testing.clients
    thanks @volteanu for the contribution!
    
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/testing/http/clients@1795303 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   9 +-
 .../apache/sling/testing/clients/SlingClient.java  |  88 ++++++--
 .../sling/testing/clients/html/package-info.java   |   2 +-
 .../testing/clients/osgi/BundlesInstaller.java     | 146 ++++++-------
 .../testing/clients/osgi/OsgiConsoleClient.java    | 241 ++++++++++++++++-----
 .../testing/clients/osgi/OsgiInstanceConfig.java   |  28 ++-
 .../sling/testing/clients/osgi/package-info.java   |   2 +-
 .../apache/sling/testing/clients/package-info.java |   2 +-
 .../testing/clients/util/TimeoutsProvider.java     |   4 +
 .../clients/util/config/InstanceConfig.java        |   4 +-
 .../util/config/impl/InstanceConfigCacheImpl.java  |   4 +-
 .../clients/util/poller/AbstractPoller.java        |   5 +
 .../testing/clients/util/poller/PathPoller.java    |   1 +
 .../sling/testing/clients/util/poller/Poller.java  |   4 +
 .../testing/clients/util/poller/package-info.java  |   2 +-
 15 files changed, 369 insertions(+), 173 deletions(-)

diff --git a/pom.xml b/pom.xml
index ff471a4..be389da 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
     </parent>
 
     <artifactId>org.apache.sling.testing.clients</artifactId>
-    <version>1.0.2-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <packaging>bundle</packaging>
 
     <name>Apache Sling Testing Clients</name>
@@ -149,5 +149,12 @@
             <artifactId>org.apache.sling.hapi.client</artifactId>
             <version>1.0.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.5.3</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/testing/clients/SlingClient.java b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
index ede00b5..bee5b40 100644
--- a/src/main/java/org/apache/sling/testing/clients/SlingClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/SlingClient.java
@@ -30,19 +30,20 @@ import org.apache.http.entity.ContentType;
 import org.apache.http.entity.mime.MultipartEntityBuilder;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClientBuilder;
-
 import org.apache.sling.testing.clients.interceptors.DelayRequestInterceptor;
 import org.apache.sling.testing.clients.interceptors.TestDescriptionInterceptor;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
 import org.apache.sling.testing.clients.util.JsonUtils;
 import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.codehaus.jackson.JsonNode;
 
 import java.io.File;
 import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 import static org.apache.http.HttpStatus.SC_CREATED;
 import static org.apache.http.HttpStatus.SC_OK;
@@ -212,12 +213,16 @@ public class SlingClient extends AbstractSlingClient {
     /**
      * <p>Checks whether a path exists or not by making a GET request to that path with the {@code json extension} </p>
      * <p>It polls the server and waits until the path exists </p>
+     *
+     * @deprecated use {@link #waitExists(String, long, long)} instead.
+     *
      * @param path path to be checked
      * @param waitMillis time to wait between retries
      * @param retryCount number of retries before throwing an exception
      * @throws ClientException if the path was not found
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public void waitUntilExists(final String path, final long waitMillis, int retryCount)
             throws ClientException, InterruptedException {
         AbstractPoller poller =  new AbstractPoller(waitMillis, retryCount) {
@@ -244,6 +249,34 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
+     * <p>Waits until a path exists by making successive GET requests to that path with the {@code json extension} </p>
+     * <p>Polls the server until the path exists or until timeout is reached </p>
+     * @param path path to be checked
+     * @param timeout max total time to wait, in milliseconds
+     * @param delay time to wait between checks, in milliseconds
+     * @throws TimeoutException if the path was not found before timeout
+     * @throws InterruptedException to mark this operation as "waiting", should be rethrown by callers
+     * @since 1.1.0
+     */
+    public void waitExists(final String path, final long timeout, final long delay)
+            throws TimeoutException, InterruptedException {
+
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                return exists(path);
+            }
+
+            @Override
+            protected String message() {
+                return "Path " + path + " does not exist after %1$d ms";
+            }
+        };
+
+        p.poll(timeout, delay);
+    }
+
+    /**
      * Sets String component property on a node.
      *
      * @param nodePath       path to the node to be edited
@@ -303,12 +336,14 @@ public class SlingClient extends AbstractSlingClient {
      * Returns the JSON content of a node already mapped to a {@link org.codehaus.jackson.JsonNode}.<br>
      * Waits max 10 seconds for the node to be created.
      *
+     * @deprecated use {@link #waitExists(String, long, long)} and {@link #doGetJson(String, int, int...)} instead
      * @param path  the path to the content node
      * @param depth the number of levels to go down the tree, -1 for infinity
      * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
      * @throws ClientException if something fails during request/response processing
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public JsonNode getJsonNode(String path, int depth) throws ClientException, InterruptedException {
         return getJsonNode(path, depth, 500, 20);
     }
@@ -316,6 +351,7 @@ public class SlingClient extends AbstractSlingClient {
     /**
      * Returns JSON format of a content node already mapped to a {@link org.codehaus.jackson.JsonNode}.
      *
+     * @deprecated use {@link #waitExists(String, long, long)} and {@link #doGetJson(String, int, int...)} instead
      * @param path                 the path to the content node
      * @param depth                the number of levels to go down the tree, -1 for infinity
      * @param waitMillis           how long it should wait between requests
@@ -326,6 +362,7 @@ public class SlingClient extends AbstractSlingClient {
      * @throws ClientException if something fails during request/response cycle
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public JsonNode getJsonNode(String path, int depth, final long waitMillis, final int retryNumber, int... expectedStatus)
             throws ClientException, InterruptedException {
 
@@ -347,6 +384,31 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
+     * Returns the {@link org.codehaus.jackson.JsonNode} object corresponding to a content node.
+     *
+     * @param path the path to the content node
+     * @param depth the number of levels to go down the tree, -1 for infinity
+     * @param expectedStatus list of allowed HTTP Status to be returned. If not set, 200 (OK) is assumed.
+     *
+     * @return a {@link org.codehaus.jackson.JsonNode} mapping to the requested content node.
+     * @throws ClientException if the path does not exist or something fails during request/response cycle
+     * @since 1.1.0
+     */
+    public JsonNode doGetJson(String path, int depth, int... expectedStatus) throws ClientException {
+
+        // check for infinity
+        if (depth == -1) {
+            path += ".infinity.json";
+        } else {
+            path += "." + depth + ".json";
+        }
+
+        // request the JSON for the node
+        SlingHttpResponse response = this.doGet(path, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return JsonUtils.getJsonNodeFromString(response.getContent());
+    }
+
+    /**
      * Uploads a file to the repository. It creates a leaf node typed {@code nt:file}. The intermediary nodes are created with
      * type "sling:OrderedFolder" if parameter {@code createFolders} is true
      *
@@ -413,33 +475,31 @@ public class SlingClient extends AbstractSlingClient {
     }
 
     /**
-     * Get uuid from any repository path
+     * Get the UUID of a repository path
      *
-     * @param repPath path in repository
-     * @return uuid as String
+     * @param path path in repository
+     * @return uuid as String or null if path does not exist
      * @throws ClientException if something fails during request/response cycle
-     * @throws InterruptedException to mark this operation as "waiting"
      */
-    public String getUUID(String repPath) throws ClientException, InterruptedException {
-        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
-        if (!exists(repPath)) {
+    public String getUUID(String path) throws ClientException {
+        if (!exists(path)) {
             return null;
         }
-        JsonNode jsonNode = getJsonNode(repPath, -1);
+        JsonNode jsonNode = doGetJson(path, -1);
         return getUUId(jsonNode);
     }
 
     /**
-     * Get uuid from any repository path
+     * Get the UUID from a node that was already parsed in a {@link JsonNode}
      *
-     * @param jsonNode {@link JsonNode} in repository
-     * @return uuid as String or null if jsonNode is null or if the uuid was not found
+     * @param jsonNode {@link JsonNode} object of the repository node
+     * @return UUID as String or null if jsonNode is null or if the UUID was not found
      * @throws ClientException if something fails during request/response cycle
      */
+    // TODO make this method static
     public String getUUId(JsonNode jsonNode) throws ClientException {
-        // TODO review if this check is necessary. Maybe rewrite getJsonNode to wait only if requested
         if (jsonNode == null) {
-            return null;  // node does not exist
+            return null;
         }
 
         JsonNode uuidNode = jsonNode.get("jcr:uuid");
diff --git a/src/main/java/org/apache/sling/testing/clients/html/package-info.java b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
index 5f5a9f5..cd55c59 100644
--- a/src/main/java/org/apache/sling/testing/clients/html/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/html/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("2.1.0")
+@Version("2.2.0")
 package org.apache.sling.testing.clients.html;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
index 8a19aa6..fcfd6c5 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
@@ -17,13 +17,14 @@
 package org.apache.sling.testing.clients.osgi;
 
 import org.apache.sling.testing.clients.ClientException;
-import org.apache.sling.testing.clients.util.poller.AbstractPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 
 /**
@@ -40,31 +41,34 @@ public class BundlesInstaller {
 
     /**
      * Checks if a bundle is installed or not. Does not retry.
-     * @param bundleFile
-     * @return
-     * @throws ClientException
-     * @throws IOException
-     * @throws InterruptedException
+     * @param bundleFile bundle file
+     * @return true if the bundle is installed
+     * @throws ClientException if the state of the bundle could not be determined
      */
-    public boolean isInstalled(File bundleFile) throws InterruptedException, IOException {
-        final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
-        log.debug("Checking if installed: " + bundleSymbolicName);
-        boolean installed = osgiConsoleClient.checkBundleInstalled(bundleSymbolicName, 1000, 1);
-        // if this succeeds, then there's no need to install again
-        if (installed) {
+    public boolean isInstalled(File bundleFile) throws ClientException {
+        String bundleSymbolicName = "";
+        try {
+            bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
+            log.debug("Checking if installed: " + bundleSymbolicName);
+
+            osgiConsoleClient.getBundleState(bundleSymbolicName);
             log.debug("Already installed: " + bundleSymbolicName);
             return true;
-        } else {
+        } catch (ClientException e) {
             log.debug("Not yet installed: " + bundleSymbolicName);
             return false;
+        } catch (IOException e) {
+            log.debug("Failed to retrieve bundle symbolic name from file. ", e);
+            throw new ClientException("Failed to retrieve bundle symbolic name from file. ", e);
         }
     }
 
     /**
      * Check if the installed version matches the one of the bundle (file)
-     * @param bundleFile
-     * @return
-     * @throws Exception
+     * @param bundleFile bundle file
+     * @return true if the bundle is installed and has the same version
+     * @throws ClientException if the installed version cannot be retrieved
+     * @throws IOException if the file version cannot be read
      */
     public boolean isInstalledWithSameVersion(File bundleFile) throws ClientException, IOException {
         final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(bundleFile);
@@ -81,11 +85,11 @@ public class BundlesInstaller {
 
     /**
      * Install a list of bundles supplied as Files
-     * @param toInstall
-     * @param startBundles
-     * @throws Exception
+     * @param toInstall list ob bundles to install
+     * @param startBundles whether to start the bundles
+     * @throws ClientException if an error occurs during installation
      */
-    public void installBundles(List<File> toInstall, boolean startBundles) throws ClientException, IOException, InterruptedException {
+    public void installBundles(List<File> toInstall, boolean startBundles) throws ClientException, IOException {
         for(File f : toInstall) {
             final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
@@ -112,12 +116,11 @@ public class BundlesInstaller {
 
     /**
      * Uninstall a list of bundles supplied as Files
-     * @param toUninstall
-     * @throws ClientException
-     * @throws IOException
-     * @throws InterruptedException
+     * @param toUninstall bundles to uninstall
+     * @throws ClientException if one of the requests failed
+     * @throws IOException if the files cannot be read from disk
      */
-    public void uninstallBundles(List<File> toUninstall) throws ClientException, IOException, InterruptedException {
+    public void uninstallBundles(List<File> toUninstall) throws ClientException, IOException {
         for(File f : toUninstall) {
             final String bundleSymbolicName = OsgiConsoleClient.getBundleSymbolicName(f);
             if (isInstalled(f)) {
@@ -137,11 +140,12 @@ public class BundlesInstaller {
 
     /**
      * Wait for all bundles specified in symbolicNames list to be installed in the OSGi web console.
+     * @deprecated use {@link #waitBundlesInstalled(List, long)}
      * @param symbolicNames the list of names for the bundles
      * @param timeoutSeconds how many seconds to wait
-     * @return
-     * @throws Exception
+     * @return true if all the bundles were installed
      */
+    @Deprecated
     public boolean waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
         log.info("Checking that the following bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
         for (String symbolicName : symbolicNames) {
@@ -152,66 +156,50 @@ public class BundlesInstaller {
     }
 
     /**
-     * Start all the bundles in a {{List}}
-     * @param symbolicNames the list of bundles to start
-     * @param timeoutSeconds number of seconds until it times out
-     * @throws ClientException
-     * @throws InterruptedException
+     * Wait for multiple bundles to be installed in the OSGi web console.
+     * @param symbolicNames the list bundles to be checked
+     * @param timeout max total time to wait for all bundles, in ms, before throwing {@code TimeoutException}
+     * @throws TimeoutException if the timeout was reached before all the bundles were installed
+     * @throws InterruptedException to mark this operation as "waiting", callers should rethrow it
      */
-    public void startAllBundles(final List<String> symbolicNames, int timeoutSeconds) throws ClientException, InterruptedException {
-        log.info("Starting bundles (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
-        class StartAllBundlesPoller extends AbstractPoller {
-            private ClientException exception;
-            public StartAllBundlesPoller(List<String> symbolicNames, long waitInterval, long waitCount) {
-                super(waitInterval, waitCount);
-            }
+    public void waitBundlesInstalled(List<String> symbolicNames, long timeout)
+            throws InterruptedException, TimeoutException {
+        log.info("Checking that the following bundles are installed (timeout {} ms): {}", timeout, symbolicNames);
+        long start = System.currentTimeMillis();
+        for (String symbolicName : symbolicNames) {
+            osgiConsoleClient.waitBundleInstalled(symbolicName, timeout, 500);
 
-            @Override
-            public boolean call() {
-                for (String bundle : symbolicNames) {
-                    final String state;
-                    try {
-                        state = osgiConsoleClient.getBundleState(bundle);
-                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
-                            osgiConsoleClient.startBundle(bundle);
-                        }
-                    } catch (ClientException e) {
-                        this.exception = e;
-                        return false;
-                    }
-                }
-                return true;
+            if (System.currentTimeMillis() > start + timeout) {
+                throw new TimeoutException("Waiting for bundles did not finish in " + timeout + " ms.");
             }
+        }
+    }
 
+    /**
+     * Start all the bundles in a {{List}}
+     * @param symbolicNames the list of bundles to start
+     * @param timeout total max time to wait for all the bundles, in ms
+     * @throws TimeoutException if the timeout is reached before all the bundles are started
+     * @throws InterruptedException to mark this operation as "waiting", callers should rethrow it
+     */
+    public void startAllBundles(final List<String> symbolicNames, int timeout) throws InterruptedException, TimeoutException {
+        log.info("Starting bundles (timeout {} seconds): {}", timeout, symbolicNames);
+
+        Polling p = new Polling() {
             @Override
-            public boolean condition() {
+            public Boolean call() throws Exception {
+                boolean allActive = true;
                 for (String bundle : symbolicNames) {
-                    final String state;
-                    try {
-                        state = osgiConsoleClient.getBundleState(bundle);
-                        if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
-                            return false;
-                        }
-                    } catch (ClientException e) {
-                        this.exception = e;
-                        return false;
+                    String state = osgiConsoleClient.getBundleState(bundle);
+                    if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                        osgiConsoleClient.startBundle(bundle);
+                        allActive = false;
                     }
                 }
-                return true;
+                return allActive;
             }
+        };
 
-            public ClientException getException() {
-                return exception;
-            }
-        }
-        StartAllBundlesPoller poller = new StartAllBundlesPoller(symbolicNames, 1000, timeoutSeconds);
-        if (!poller.callUntilCondition()) {
-            throw new ClientException("Some bundles did not start or timed out", poller.getException());
-        }
-
+        p.poll(timeout, 500);
     }
-
-
-
-
-}
\ No newline at end of file
+}
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
index 08cd742..8dd8ca2 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
@@ -24,14 +24,14 @@ import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.testing.clients.ClientException;
-import org.apache.sling.testing.clients.SlingHttpResponse;
-import org.apache.sling.testing.clients.util.JsonUtils;
-import org.apache.sling.testing.clients.util.poller.AbstractPoller;
 import org.apache.sling.testing.clients.SlingClient;
 import org.apache.sling.testing.clients.SlingClientConfig;
+import org.apache.sling.testing.clients.SlingHttpResponse;
 import org.apache.sling.testing.clients.util.FormEntityBuilder;
 import org.apache.sling.testing.clients.util.HttpUtils;
+import org.apache.sling.testing.clients.util.JsonUtils;
 import org.apache.sling.testing.clients.util.poller.PathPoller;
+import org.apache.sling.testing.clients.util.poller.Polling;
 import org.codehaus.jackson.JsonNode;
 import org.osgi.framework.Constants;
 import org.slf4j.Logger;
@@ -41,7 +41,12 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URI;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeoutException;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 
@@ -204,11 +209,12 @@ public class OsgiConsoleClient extends SlingClient {
         }
         return props;
     }
-
     /**
      * Returns a map of all properties set for the config referenced by the PID, where the map keys
      * are the property names. The method waits until the configuration has been set.
      *
+     * @deprecated use {@link #waitGetConfiguration(long, String, int...)}
+     *
      * @param waitCount The number of maximum wait intervals of 500ms.
      *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
      * @param pid pid
@@ -217,11 +223,36 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the response status does not match any of the expectedStatus
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public Map<String, Object> getConfigurationWithWait(long waitCount, String pid, int... expectedStatus)
             throws ClientException, InterruptedException {
-        ConfigurationPoller poller = new ConfigurationPoller(500L, waitCount, pid, expectedStatus);
-        if (!poller.callUntilCondition())
-            return getConfiguration(pid, expectedStatus);
+        ConfigurationPoller poller = new ConfigurationPoller(pid, expectedStatus);
+        try {
+            poller.poll(500L * waitCount, 500);
+        } catch (TimeoutException e) {
+            throw new ClientException("Cannot retrieve configuration.", e);
+        }
+        return poller.getConfig();
+    }
+
+    /**
+     * Returns a map of all properties set for the config referenced by the PID, where the map keys
+     * are the property names. The method waits until the configuration has been set.
+     *
+     * @param timeout Maximum time to wait for the configuration to be available, in ms.
+     * @param pid service pid
+     * @param expectedStatus expected response status
+     * @return the config properties
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     * @throws TimeoutException if the timeout was reached
+     */
+    public Map<String, Object> waitGetConfiguration(long timeout, String pid, int... expectedStatus)
+            throws ClientException, InterruptedException, TimeoutException {
+
+        ConfigurationPoller poller = new ConfigurationPoller(pid, expectedStatus);
+        poller.poll(timeout, 500);
+
         return poller.getConfig();
     }
 
@@ -277,6 +308,8 @@ public class OsgiConsoleClient extends SlingClient {
      * Sets properties of a config referenced by its PID. the properties to be edited are passed as
      * a map of property (name,value) pairs. The method waits until the configuration has been set.
      *
+     * @deprecated use {@link #waitEditConfiguration(long, String, String, Map, int...)}
+     *
      * @param waitCount The number of maximum wait intervals of 500ms.
      *                  Between each wait interval, the method polls the backend to see if the configuration ahs been set.
      * @param PID Persistent identity string
@@ -287,6 +320,7 @@ public class OsgiConsoleClient extends SlingClient {
      * @throws ClientException if the response status does not match any of the expectedStatus
      * @throws InterruptedException to mark this operation as "waiting"
      */
+    @Deprecated
     public String editConfigurationWithWait(int waitCount, String PID, String factoryPID, Map<String, Object> configProperties,
                                             int... expectedStatus) throws ClientException, InterruptedException {
         String pid = editConfiguration(PID, factoryPID, configProperties, expectedStatus);
@@ -295,6 +329,28 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Sets properties of a config referenced by its PID. the properties to be edited are passed as
+     * a map of property (name,value) pairs. The method waits until the configuration has been set.
+     *
+     * @param timeout Max time to wait for the configuration to be set, in ms
+     * @param PID Persistent identity string
+     * @param factoryPID Factory persistent identity string or {@code null}
+     * @param configProperties map of properties
+     * @param expectedStatus expected response status
+     * @return the pid
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     * @throws InterruptedException to mark this operation as "waiting"
+     * @throws TimeoutException if the timeout was reached
+     */
+    public String waitEditConfiguration(long timeout, String PID, String factoryPID, Map<String, Object> configProperties,
+                                        int... expectedStatus)
+            throws ClientException, InterruptedException, TimeoutException {
+        String pid = editConfiguration(PID, factoryPID, configProperties, expectedStatus);
+        waitGetConfiguration(timeout, pid);
+        return pid;
+    }
+
+    /**
      * Delete the config referenced by the PID
      *
      * @param pid pid
@@ -319,7 +375,7 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Uninstall a bundle
-     * @param symbolicName
+     * @param symbolicName bundle symbolic name
      * @return the sling response
      * @throws ClientException
      */
@@ -344,11 +400,11 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Install a bundle using the Felix webconsole HTTP interface, with a specific start level
-     * @param f
-     * @param startBundle
-     * @param startLevel
+     * @param f bundle file
+     * @param startBundle whether to start or just install the bundle
+     * @param startLevel start level
      * @return the sling response
-     * @throws ClientException
+     * @throws ClientException if the request failed
      */
     public SlingHttpResponse installBundle(File f, boolean startBundle, int startLevel) throws ClientException {
         // Setup request for Felix Webconsole bundle install
@@ -370,7 +426,24 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Check that specified bundle is installed and retries every {{waitTime}} milliseconds, until the
+     * bundle is installed or the number of retries was reached
+     * @deprecated does not respect polling practices; use {@link #waitBundleInstalled(String, long, long)} instead
+     * @param symbolicName the name of the bundle
+     * @param waitTime How many milliseconds to wait between retries
+     * @param retries the number of retries
+     * @return true if the bundle was installed until the retries stop, false otherwise
+     * @throws InterruptedException
+     */
+    @Deprecated
+    public boolean checkBundleInstalled(String symbolicName, int waitTime, int retries) throws InterruptedException {
+        final String path = getBundlePath(symbolicName, ".json");
+        return new PathPoller(this, path, waitTime, retries).callAndWait();
+    }
+
+    /**
      * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * @deprecated {@link #waitInstallBundle(File, boolean, int, long, long)}
      * @param f the bundle file
      * @param startBundle whether to start the bundle or not
      * @param startLevel the start level of the bundle. negative values mean default start level
@@ -379,6 +452,7 @@ public class OsgiConsoleClient extends SlingClient {
      * @return true if the bundle was successfully installed, false otherwise
      * @throws ClientException
      */
+    @Deprecated
     public boolean installBundleWithRetry(File f, boolean startBundle, int startLevel, int waitTime, int retries)
             throws ClientException, InterruptedException {
         installBundle(f, startBundle, startLevel);
@@ -390,24 +464,51 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Check that specified bundle is installed and retries every {{waitTime}} milliseconds, until the
-     * bundle is installed or the number of retries was reached
-     * @param symbolicName the name of the bundle
-     * @param waitTime How many milliseconds to wait between retries
-     * @param retries the number of retries
-     * @return true if the bundle was installed until the retries stop, false otherwise
+     * Install a bundle using the Felix webconsole HTTP interface and wait for it to be installed
+     * @param f the bundle file
+     * @param startBundle whether to start the bundle or not
+     * @param startLevel the start level of the bundle. negative values mean default start level
+     * @param timeout how much to wait for the bundle to be installed before throwing a {@code TimeoutException}
+     * @param delay time to wait between checks of the state
+     * @throws ClientException
+     * @throws TimeoutException if the bundle did not install before timeout was reached
      * @throws InterruptedException
      */
-    public boolean checkBundleInstalled(String symbolicName, int waitTime, int retries) throws InterruptedException {
-        final String path = getBundlePath(symbolicName, ".json");
-        return new PathPoller(this, path, waitTime, retries).callAndWait();
+    public void waitInstallBundle(File f, boolean startBundle, int startLevel, long timeout, long delay)
+            throws ClientException, InterruptedException, TimeoutException {
+
+        installBundle(f, startBundle, startLevel);
+        try {
+            waitBundleInstalled(getBundleSymbolicName(f), timeout, delay);
+        } catch (IOException e) {
+            throw new ClientException("Cannot get bundle symbolic name", e);
+        }
+    }
+
+    public void waitBundleInstalled(final String symbolicName, final long timeout, final long delay)
+            throws TimeoutException, InterruptedException {
+
+        final String path = getBundlePath(symbolicName);
+        Polling p = new Polling() {
+            @Override
+            public Boolean call() throws Exception {
+                return exists(path);
+            }
+
+            @Override
+            protected String message() {
+                return "Bundle " + symbolicName + " did not install in %1$ ms";
+            }
+        };
+
+        p.poll(timeout, delay);
     }
 
     /**
      * Get the id of the bundle
-     * @param symbolicName
-     * @return
-     * @throws Exception
+     * @param symbolicName bundle symbolic name
+     * @return the id
+     * @throws ClientException if the id cannot be retrieved
      */
     public long getBundleId(String symbolicName) throws ClientException {
         final JSONObject bundle = getBundleData(symbolicName);
@@ -420,8 +521,8 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Get the version of the bundle
-     * @param symbolicName
-     * @return
+     * @param symbolicName bundle symbolic name
+     * @return bundle version
      * @throws ClientException
      */
     public String getBundleVersion(String symbolicName) throws ClientException {
@@ -435,9 +536,9 @@ public class OsgiConsoleClient extends SlingClient {
 
     /**
      * Get the state of the bundle
-     * @param symbolicName
-     * @return
-     * @throws Exception
+     * @param symbolicName bundle symbolic name
+     * @return the state of the bundle
+     * @throws ClientException if the state cannot be retrieved
      */
     public String getBundleState(String symbolicName) throws ClientException {
         final JSONObject bundle = getBundleData(symbolicName);
@@ -460,13 +561,16 @@ public class OsgiConsoleClient extends SlingClient {
         this.doPost(path, FormEntityBuilder.create().addParameter("action", "start").build(), SC_OK);
     }
 
+
     /**
      * Starts a bundle and waits for it to be started
+     * @deprecated use {@link #waitStartBundle(String, long, long)}
      * @param symbolicName the name of the bundle
      * @param waitTime How many milliseconds to wait between retries
      * @param retries the number of retries
      * @throws ClientException, InterruptedException
      */
+    @Deprecated
     public void startBundlewithWait(String symbolicName, int waitTime, int retries)
             throws ClientException, InterruptedException {
         // start a bundle
@@ -476,6 +580,20 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
+     * Starts a bundle and waits for it to be started
+     * @param symbolicName the name of the bundle
+     * @param timeout max time to wait for the bundle to start, in ms
+     * @param delay time to wait between status checks, in ms
+     * @throws ClientException, InterruptedException, TimeoutException
+     */
+    public void waitStartBundle(String symbolicName, long timeout, long delay)
+            throws ClientException, InterruptedException, TimeoutException {
+        startBundle(symbolicName);
+        // FIXME this should wait for the started state
+        waitBundleInstalled(symbolicName, timeout, delay);
+    }
+
+    /**
      * Calls PackageAdmin.refreshPackages to force re-wiring of all the bundles.
      * @throws ClientException
      */
@@ -499,12 +617,25 @@ public class OsgiConsoleClient extends SlingClient {
         return URL_BUNDLES + "/" + symbolicName;
     }
 
+    /**
+     * Returns a data structure like:
+     *
+     * {
+     *   "status" : "Bundle information: 173 bundles in total - all 173 bundles active.",
+     *   "s" : [173,171,2,0,0],
+     *   "data": [{
+     *     "id":0,
+     *     "name":"System Bundle",
+     *     "fragment":false,
+     *     "stateRaw":32,
+     *     "state":"Active",
+     *     "version":"3.0.7",
+     *     "symbolicName":"org.apache.felix.framework",
+     *     "category":""
+     *   }]
+     * }
+     */
     private JSONObject getBundleData(String symbolicName) throws ClientException {
-        // This returns a data structure like
-        // {"status":"Bundle information: 173 bundles in total - all 173 bundles active.","s":[173,171,2,0,0],"data":
-        //  [
-        //      {"id":0,"name":"System Bundle","fragment":false,"stateRaw":32,"state":"Active","version":"3.0.7","symbolicName":"org.apache.felix.framework","category":""},
-        //  ]}
         final String path = getBundlePath(symbolicName, ".json");
         final String content = this.doGet(path, SC_OK).getContent();
 
@@ -536,9 +667,9 @@ public class OsgiConsoleClient extends SlingClient {
     //
 
     /**
-     * Get the symbolic name from a bundle file
-     * @param bundleFile
-     * @return
+     * Get the symbolic name from a bundle file by looking at the manifest
+     * @param bundleFile bundle file
+     * @return the name extracted from the manifest
      * @throws IOException
      */
     public static String getBundleSymbolicName(File bundleFile) throws IOException {
@@ -557,9 +688,9 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
     /**
-     * Get the version form a bundle file
-     * @param bundleFile
-     * @return
+     * Get the version form a bundle file by looking at the manifest
+     * @param bundleFile bundle file
+     * @return the version
      * @throws IOException
      */
     public static String getBundleVersionFromFile(File bundleFile) throws IOException {
@@ -578,32 +709,24 @@ public class OsgiConsoleClient extends SlingClient {
     }
 
 
-    class ConfigurationPoller extends AbstractPoller {
+    class ConfigurationPoller extends Polling {
 
         private final String pid;
-        int[] expectedStatus;
-        public Map<String, Object> config;
+        private final int[] expectedStatus;
+        private Map<String, Object> config;
+
+        public ConfigurationPoller(String pid, int... expectedStatus) {
+            super();
 
-        public ConfigurationPoller(long waitInterval, long waitCount, String pid, int... expectedStatus) {
-            super(waitInterval, waitCount);
             this.pid = pid;
-            this.config = null;
             this.expectedStatus = expectedStatus;
+            this.config = null;
         }
 
         @Override
-        public boolean call() {
-            try {
-                config = getConfiguration(pid, expectedStatus);
-            } catch (ClientException e) {
-                LOG.warn("Couldn't get config " + pid, e);
-            }
-            return true;
-        }
-
-        @Override
-        public boolean condition() {
-            return null != config;
+        public Boolean call() throws Exception {
+            config = getConfiguration(pid, expectedStatus);
+            return config != null;
         }
 
         public Map<String, Object> getConfig() {
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
index 41ea39c..9f9c4bd 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
@@ -24,23 +24,26 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 
 /**
  * <p>Allows saving and restoring the OSGiConfig to be used before and after altering OSGi configurations for tests</p>
  * <p>See {@link InstanceConfig}</p>
  */
 public class OsgiInstanceConfig implements InstanceConfig {
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
 
     /**
-     * Number of retries for retrieving the current osgi config for save() and restore()
+     * Time im ms to wait for retrieving the current osgi config for save() and restore()
      */
-    protected int waitCount = 20;
+    private static final long WAIT_TIMEOUT = 20000;  // in ms
 
-    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
     private final OsgiConsoleClient osgiClient;
     private final String configPID;
     private Map<String, Object> config;
 
+    @Deprecated
+    protected int waitCount = 20;
 
     /**
      *
@@ -50,7 +53,8 @@ public class OsgiInstanceConfig implements InstanceConfig {
      * @throws ClientException if the client cannot be initialized
      * @throws InstanceConfigException if the config cannot be saved
      */
-    public <T extends SlingClient> OsgiInstanceConfig(T client, String configPID) throws ClientException, InstanceConfigException {
+    public <T extends SlingClient> OsgiInstanceConfig(T client, String configPID)
+            throws ClientException, InstanceConfigException, InterruptedException {
         this.osgiClient = client.adaptTo(OsgiConsoleClient.class);
         this.configPID = configPID;
 
@@ -63,14 +67,14 @@ public class OsgiInstanceConfig implements InstanceConfig {
      *
      * @throws InstanceConfigException if the config cannot be saved
      */
-    public InstanceConfig save() throws InstanceConfigException {
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException {
         try {
-            this.config = osgiClient.getConfigurationWithWait(waitCount, this.configPID);
+            this.config = osgiClient.waitGetConfiguration(WAIT_TIMEOUT, this.configPID);
             LOG.info("Saved OSGi config for {}. It is currently this: {}", this.configPID, this.config);
         } catch (ClientException e) {
             throw new InstanceConfigException("Error getting config", e);
-        } catch (InterruptedException e) {
-            throw new InstanceConfigException("Saving configuration was interrupted ", e);
+        } catch (TimeoutException e) {
+            throw new InstanceConfigException("Timeout of " + WAIT_TIMEOUT + " ms was reached while waiting for the configuration", e);
         }
         return this;
     }
@@ -80,14 +84,14 @@ public class OsgiInstanceConfig implements InstanceConfig {
      *
      * @throws InstanceConfigException if the config cannot be restored
      */
-    public InstanceConfig restore() throws InstanceConfigException {
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException {
         try {
-            osgiClient.editConfigurationWithWait(waitCount, this.configPID, null, config);
+            osgiClient.waitEditConfiguration(WAIT_TIMEOUT, this.configPID, null, config);
             LOG.info("restored OSGi config for {}. It is now this: {}", this.configPID, this.config);
         } catch (ClientException e) {
             throw new InstanceConfigException("Could not edit OSGi configuration", e);
-        } catch (InterruptedException e) {
-            throw new InstanceConfigException("Restoring configuration was interrupted", e);
+        } catch (TimeoutException e) {
+            throw new InstanceConfigException("Timeout of " + WAIT_TIMEOUT + " ms was reached while waiting for the configuration", e);
         }
         return this;
     }
diff --git a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
index 119cb90..83ff1ab 100644
--- a/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
@@ -19,7 +19,7 @@
 /**
  * OSGI testing tools.
  */
-@Version("1.1.0")
+@Version("1.2.0")
 package org.apache.sling.testing.clients.osgi;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/package-info.java b/src/main/java/org/apache/sling/testing/clients/package-info.java
index c7b45c6..991d7e5 100644
--- a/src/main/java/org/apache/sling/testing/clients/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.2.0")
+@Version("1.3.0")
 package org.apache.sling.testing.clients;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
index 4964300..fa6f99a 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
@@ -23,7 +23,11 @@ import org.slf4j.LoggerFactory;
  *  factor. Useful to cope with slower integration testing systems:
  *  use timeout constants in your code that work for usual development
  *  systems, and set a multiplier when running on a slower system.
+ *
+ *  @deprecated duplicate of {@link org.apache.sling.testing.timeouts.TimeoutsProvider}. This will be removed in the future, so switch to
+ *  the other one instead
  */
+@Deprecated
 public class TimeoutsProvider {
     private static final Logger log = LoggerFactory.getLogger(TimeoutsProvider.class);
     public static final String PROP_TIMEOUT_MULTIPLIER = "sling.testing.timeout.multiplier";
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
index 8af55ac..b545a7b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
@@ -28,7 +28,7 @@ public interface InstanceConfig {
      * @return this
      * @throws InstanceConfigException if saving the configuration fails
      */
-    public InstanceConfig save() throws InstanceConfigException;
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException;
 
     /**
      * Restores the saved status of the configuration
@@ -36,5 +36,5 @@ public interface InstanceConfig {
      * @return this
      * @throws InstanceConfigException if restoring the configuration fails
      */
-    public InstanceConfig restore() throws InstanceConfigException;
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException;
 }
diff --git a/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
index 4bae77f..7a82d4b 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
@@ -103,7 +103,7 @@ public class InstanceConfigCacheImpl implements InstanceConfigCache {
 
 
     @Override
-    public InstanceConfig save() throws InstanceConfigException {
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException {
         for (InstanceConfig ic : configs) {
             ic.save();
         }
@@ -111,7 +111,7 @@ public class InstanceConfigCacheImpl implements InstanceConfigCache {
     }
 
     @Override
-    public InstanceConfig restore() throws InstanceConfigException {
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException {
         for (InstanceConfig ic : configs) {
             ic.restore();
         }
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
index 57c34c3..87b42c6 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
@@ -16,6 +16,11 @@
  */
 package org.apache.sling.testing.clients.util.poller;
 
+/**
+ * @deprecated use {@link Polling} instead.
+ * @see Polling for a better way to implement polling
+ */
+@Deprecated
 public abstract class AbstractPoller  implements Poller {
 
     private final long waitInterval;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
index 61a1ee1..cdbbf93 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
@@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
 /**
  * Allows polling for a resource
  */
+@Deprecated
 public class PathPoller extends AbstractPoller {
     private static final Logger LOG = LoggerFactory.getLogger(PathPoller.class);
     private final AbstractSlingClient client;
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
index e772edb..7ac2500 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
@@ -19,7 +19,11 @@ package org.apache.sling.testing.clients.util.poller;
 /**
  * Abstract Poller interface.
  * Provides simple methods to implement custom pollers
+ *
+ * @deprecated use {@link Polling} instead.
+ * @see Polling for a better way to implement polling
  */
+@Deprecated
 public interface Poller {
     boolean call();
     boolean condition();
diff --git a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
index 4ff5bb0..4d723bb 100644
--- a/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
+++ b/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-@Version("1.0.0")
+@Version("1.1.0")
 package org.apache.sling.testing.clients.util.poller;
 
 import org.osgi.annotation.versioning.Version;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.