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@1795303 13f79535-47bb-0310-9956-ffa450edef68
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.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 @@
/**
* <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 @@
}
/**
+ * <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 @@
* 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 @@
/**
* 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 @@
* @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 @@
}
/**
+ * 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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
}
/**
- * 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;
- }
-
- @Override
- public boolean condition() {
- 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;
- }
- }
- return true;
- }
-
- public ClientException getException() {
- return exception;
+ if (System.currentTimeMillis() > start + timeout) {
+ throw new TimeoutException("Waiting for bundles did not finish in " + timeout + " ms.");
}
}
- StartAllBundlesPoller poller = new StartAllBundlesPoller(symbolicNames, 1000, timeoutSeconds);
- if (!poller.callUntilCondition()) {
- throw new ClientException("Some bundles did not start or timed out", poller.getException());
- }
-
}
+ /**
+ * 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 call() throws Exception {
+ boolean allActive = true;
+ for (String bundle : symbolicNames) {
+ String state = osgiConsoleClient.getBundleState(bundle);
+ if (!state.equalsIgnoreCase(ACTIVE_STATE)) {
+ osgiConsoleClient.startBundle(bundle);
+ allActive = false;
+ }
+ }
+ return allActive;
+ }
+ };
-
-}
\ No newline at end of file
+ p.poll(timeout, 500);
+ }
+}
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.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.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 @@
}
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 @@
* @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 @@
* 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 @@
* @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 @@
}
/**
+ * 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 @@
/**
* Uninstall a bundle
- * @param symbolicName
+ * @param symbolicName bundle symbolic name
* @return the sling response
* @throws ClientException
*/
@@ -344,11 +400,11 @@
/**
* 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 @@
}
/**
+ * 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 @@
* @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 @@
}
/**
- * 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 @@
/**
* 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 @@
/**
* 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 @@
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 @@
}
/**
+ * 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 @@
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 @@
//
/**
- * 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 @@
}
/**
- * 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 @@
}
- 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(long waitInterval, long waitCount, String pid, int... expectedStatus) {
- super(waitInterval, waitCount);
+ public ConfigurationPoller(String pid, int... expectedStatus) {
+ super();
+
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.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 @@
* @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 @@
*
* @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 @@
*
* @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 @@
* 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 @@
* @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 @@
* @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 @@
@Override
- public InstanceConfig save() throws InstanceConfigException {
+ public InstanceConfig save() throws InstanceConfigException, InterruptedException {
for (InstanceConfig ic : configs) {
ic.save();
}
@@ -111,7 +111,7 @@
}
@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 @@
/**
* 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 @@
/**
* 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;