You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by du...@apache.org on 2020/04/09 13:50:43 UTC

svn commit: r38870 [5/6] - in /release/sling: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/testing/ src/main/java/org/apache/sling/testing/clients/ src/main...

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.JsonProcessingException;
+import org.codehaus.jackson.map.ObjectMapper;
+
+import java.io.IOException;
+
+public class JsonUtils {
+    /**
+     * Get {@link JsonNode} from a a String containing JSON.
+     *
+     * @param jsonString A string containing JSON
+     * @return A {@link JsonNode} that is the root node of the JSON structure.
+     * @throws ClientException if error occurs while reading json string
+     */
+    public static JsonNode getJsonNodeFromString(String jsonString) throws ClientException {
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            return mapper.readTree(jsonString);
+        } catch (JsonProcessingException e) {
+            throw new ClientException("Could not read json file.", e);
+        } catch (IOException e) {
+            throw new ClientException("Could not read json node.", e);
+        }
+    }
+}
\ No newline at end of file

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import java.net.ServerSocket;
+import java.util.HashSet;
+import java.util.Set;
+
+public class PortAllocator {
+
+    private static Set<Integer> allocatedPorts;
+
+    static {
+        allocatedPorts = new HashSet<Integer>();
+    }
+
+    public Integer allocatePort() {
+        while (true) {
+            int port = tryAllocation();
+
+            boolean portAdded = checkAndAddPort(port);
+
+            if (portAdded) {
+                return port;
+            }
+        }
+    }
+
+    private int tryAllocation() {
+        try {
+            ServerSocket serverSocket = new ServerSocket(0);
+            int port = serverSocket.getLocalPort();
+            serverSocket.close();
+            return port;
+        } catch (Exception e) {
+            throw new RuntimeException("Can't allocate a port");
+        }
+    }
+
+    private synchronized boolean checkAndAddPort(int port) {
+        return allocatedPorts.add(port);
+    }
+
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+public class ResourceUtil {
+
+    /**
+     * We must get the Resource as a stream from the ContextClassLoader and not from the normal classLoader
+     * acquired by using getClass.getClassLoader, since we must be able to load resources from different threads
+     * e.g. running in ant.
+     *
+     * @param resourcePath path to the resource
+     * @return resource as InputStream
+     */
+    public static InputStream getResourceAsStream(final String resourcePath) {
+        final String path = resourcePath.startsWith("/") ? resourcePath.substring(1) : resourcePath;
+        return Thread.currentThread().getContextClassLoader().getResourceAsStream(path);
+    }
+
+    /**
+     * Helper method to read a resource from class using {@link Class#getResourceAsStream(String)}
+     * and convert into a String.
+     *
+     * @param resource The resource to read.
+     * @return The requested resource as String, resolved using
+     *         {@link Class#getResourceAsStream(String)}, or {@code null}
+     *         if the requested resource cannot be resolved for some reason
+     * @throws IOException if the Resource Stream cannot be read
+     */
+    public static String readResourceAsString(final String resource) throws IOException {
+        InputStream resourceAsStream = ResourceUtil.getResourceAsStream(resource);
+        if (resourceAsStream != null) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            try {
+                BufferedReader reader = new BufferedReader(new InputStreamReader(resourceAsStream, UTF_8.name()));
+                while ((line = reader.readLine()) != null) {
+                    if (sb.length() > 0) {
+                        sb.append("\n");
+                    }
+                    sb.append(line);
+                }
+            } finally {
+                resourceAsStream.close();
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/ServerErrorRetryStrategy.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ServiceUnavailableRetryStrategy;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.apache.sling.testing.clients.SystemPropertiesConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.apache.http.HttpStatus.*;
+
+/**
+ * {code ServiceUnavailableRetryStrategy} strategy for retrying request in case of a 5XX response code
+ */
+public class ServerErrorRetryStrategy implements ServiceUnavailableRetryStrategy {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ServerErrorRetryStrategy.class);
+
+    public ServerErrorRetryStrategy() {
+        super();
+    }
+
+    @Override
+    public boolean retryRequest(final HttpResponse response, final int executionCount, final HttpContext context) {
+        boolean needsRetry = executionCount <= SystemPropertiesConfig.getHttpRetries() && responseRetryCondition(response);
+
+        if (SystemPropertiesConfig.isHttpLogRetries() && needsRetry && LOG.isWarnEnabled()) {
+            LOG.warn("Request retry needed due to service unavailable response");
+            LOG.warn("Response headers contained:");
+            Arrays.stream(response.getAllHeaders()).forEach(h -> LOG.warn("Header {}:{}", h.getName(), h.getValue()));
+            try {
+                String content = EntityUtils.toString(response.getEntity());
+                LOG.warn("Response content: {}", content);
+            } catch (IOException exc) {
+                LOG.warn("Response as no content");
+            }
+        }
+        return needsRetry;
+    }
+
+    @Override
+    public long getRetryInterval() {
+        return SystemPropertiesConfig.getHttpRetriesDelay();
+    }
+
+    private boolean responseRetryCondition(final HttpResponse response) {
+        final Integer statusCode = response.getStatusLine().getStatusCode();
+        final Collection<Integer> errorCodes = SystemPropertiesConfig.getHttpRetriesErrorCodes();
+        if (errorCodes != null && !errorCodes.isEmpty()) {
+            return errorCodes.contains(statusCode);
+        } else {
+            return statusCode >= SC_INTERNAL_SERVER_ERROR &&
+                    statusCode < SC_INTERNAL_SERVER_ERROR + 100;
+        }
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ */
+public class SlingParameter {
+
+    private String typeHint = null;
+    private boolean delete = false;
+
+    String parameterName;
+    private String[] values = null;
+    private boolean multiple = false;
+
+    public SlingParameter(String parameterName) {
+        if (parameterName == null || parameterName.length() == 0) {
+            throw new IllegalArgumentException("parameterName must not be null or empty");
+        }
+        this.parameterName = parameterName;
+    }
+
+    public SlingParameter value(String value) {
+        if (value != null) {
+            this.values(new String[]{value});
+        } else {
+            this.values(new String[]{});
+        }
+        return this;
+    }
+
+    public SlingParameter values(String[] values) {
+        if (values == null) {
+            this.values = new String[]{};
+        } else {
+            this.values = values;
+        }
+        return this;
+    }
+
+    public SlingParameter typeHint(String typeHint) {
+        this.typeHint = typeHint;
+        return this;
+    }
+
+    public SlingParameter delete() {
+        this.delete = true;
+        return this;
+    }
+
+    public SlingParameter multiple() {
+        this.multiple = true;
+        return this;
+    }
+
+    public List<NameValuePair> toNameValuePairs() {
+        List<NameValuePair> parameters = new ArrayList<NameValuePair>();
+
+        if (multiple) {
+            for (String value : values) {
+                parameters.add(new BasicNameValuePair(parameterName, value));
+            }
+        } else if (values != null && values.length == 1) {
+            parameters.add(new BasicNameValuePair(parameterName, values[0]));
+        } else if (values != null && values.length > 1) {
+            // TODO not sure about the proper format of the values in this case?
+            // For now, only take the first one.
+            parameters.add(new BasicNameValuePair(parameterName, values[0]));
+        } else {
+            parameters.add(new BasicNameValuePair(parameterName, null));
+        }
+
+        // add @TypeHint suffix
+        if (typeHint != null) {
+            String parameter = parameterName + "@TypeHint";
+            parameters.add(new BasicNameValuePair(parameter, typeHint));
+        }
+
+        // add @Delete suffix
+        if (delete) {
+            String parameter = parameterName + "@Delete";
+            parameters.add(new BasicNameValuePair(parameter, "true"));
+        }
+
+        return parameters;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/TimeoutsProvider.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Return timeout values that can be multiplied by a configurable
+ *  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";
+    private static float timeoutFactor = -1;
+    private static TimeoutsProvider INSTANCE;
+    
+    private TimeoutsProvider() {
+        if(timeoutFactor < 0) {
+            timeoutFactor = 1;
+            final String str = System.getProperty(PROP_TIMEOUT_MULTIPLIER);
+            if(str != null) {
+                try {
+                    timeoutFactor = Float.valueOf(str.trim());
+                    log.info("Timeout factor set to {} from system property {}", 
+                            timeoutFactor, PROP_TIMEOUT_MULTIPLIER);
+                } catch(NumberFormatException nfe) {
+                    throw new IllegalStateException("Invalid timeout factor: " + PROP_TIMEOUT_MULTIPLIER + "=" + str);
+                }
+            }
+        }
+    }
+    
+    public static TimeoutsProvider getInstance() {
+        if(INSTANCE == null) {
+            synchronized (TimeoutsProvider.class) {
+                INSTANCE = new TimeoutsProvider();
+            }
+        }
+        return INSTANCE;
+    }
+    
+    public long getTimeout(long nomimalValue) {
+        final long result = (long)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    public int getTimeout(int nomimalValue) {
+        final int result = (int)(nomimalValue * timeoutFactor);
+        return result;
+    }
+
+    /**
+     * Get timeout from a system property, with default value
+     * @param systemPropertyName Name of the system property that holds the timeout multiplier
+     * @param defaultNominalValue The default timeout multiplier
+     * @return the total timeout value
+     */
+    public int getTimeout(String systemPropertyName, int defaultNominalValue) {
+        int result = defaultNominalValue;
+        final String str = System.getProperty(systemPropertyName);
+        if(str != null) {
+            result = Integer.parseInt(str);
+        }
+        return getTimeout(result);
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class URLParameterBuilder {
+
+    public final static String DEFAULT_ENCODING = "UTF-8";
+
+    private List<NameValuePair> params;
+    private String encoding;
+
+    public static URLParameterBuilder create() {
+        return new URLParameterBuilder();
+    }
+
+    URLParameterBuilder() {
+        params = new ArrayList<NameValuePair>();
+        encoding = DEFAULT_ENCODING;
+    }
+
+    public URLParameterBuilder add(String name, String value) {
+        params.add(new BasicNameValuePair(name, value));
+        return this;
+    }
+
+    public URLParameterBuilder add(NameValuePair pair) {
+        params.add(pair);
+        return this;
+    }
+
+    public URLParameterBuilder add(List<NameValuePair> list) {
+        params.addAll(list);
+        return this;
+    }
+
+    public URLParameterBuilder add(String name, String[] values) {
+        for (String value : values) this.add(name, value);
+        return this;
+    }
+
+    public URLParameterBuilder setEncoding(String encoding) {
+        this.encoding = encoding;
+        return this;
+    }
+
+    /**
+     * Build the URL parameters
+     *
+     * @return The URL parameters string without the leading question mark.
+     */
+    public String getURLParameters() {
+        return URLEncodedUtils.format(params, encoding);
+    }
+
+    public List<NameValuePair> getList() {
+        return params;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.sling.testing.clients.util;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/** Generate unique paths, for tests isolation */
+public class UniquePaths {
+
+    private static long startTime = System.currentTimeMillis();
+    private static AtomicLong counter = new AtomicLong();
+    public final static String SEP = "_";
+    public final static String U_PATTERN = "_UNIQ_";
+    
+    /**
+     * Return a unique path based on basePath
+     * @param nameReference The simple class name of that object is used as part of the 
+     *                      generated unique ID
+     * @param basePath All occurrences of {@link UniquePaths#U_PATTERN} in basePath are replaced by the generated
+     *                 unique ID. If $U$ is not found in basePath, unique ID is added at its end.
+     * @return path with a unique value for each call.
+     */
+    public static String get(Object nameReference, String basePath) {
+        if(basePath == null) {
+            basePath = "";
+        }
+        
+        final StringBuilder sb = new StringBuilder();
+        sb.append(nameReference.getClass().getSimpleName());
+        sb.append(SEP);
+        sb.append(startTime);
+        sb.append(SEP);
+        sb.append(counter.incrementAndGet());
+        
+        if(basePath.contains(U_PATTERN)) {
+            return basePath.replaceAll(U_PATTERN, sb.toString());
+        } else {
+            return basePath + sb.toString();
+        }
+    }
+    
+    /**
+     * Get a unique ID with no base path
+     *
+     * @param nameReference The simple class name of that object is used as part of the
+     *                      generated unique ID
+     * @return path with a unique value for each call
+     */
+    public static String get(Object nameReference) {
+        return get(nameReference, null);
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.sling.xss.XSSAPI;
+import org.apache.sling.xss.impl.XSSAPIImpl;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+/**
+ * Basic class for XSS Testing
+ * The reliability of these methods are not critical
+ */
+public class XSSUtils {
+
+    /**
+     * Use to ensure that HTTP query strings are in proper form, by escaping
+     * special characters such as spaces.
+     *
+     * @param urlString the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeUrl(String urlString) {
+        try {
+            return URLEncoder.encode(urlString, "UTF-8");
+        } catch (UnsupportedEncodingException ex) {
+            throw new RuntimeException("UTF-8 not supported", ex);
+        }
+    }
+
+    /**
+     * Use to encapsulate old-style escaping of HTML (using StringEscapeUtils).
+     * NB: newer code uses XSSAPI (based on OWASP's ESAPI).
+     *
+     * @param htmlString the string to be escaped
+     * @return the escaped string
+     */
+    public static String escapeHtml(String htmlString) {
+        return StringEscapeUtils.escapeHtml4(htmlString);
+    }
+
+    /**
+     * Use to encapsulate old-style escaping of XML (with JSTL encoding rules).
+     * NB: newer code uses XSSAPI (based on OWASP's ESAPI).
+     *
+     * @param xmlString the string to be escaped
+     * @return the escaped string
+     */
+    public static String escapeXml(String xmlString) {
+        String xssString = xmlString;
+        if (xmlString != null) {
+            xssString = xssString.replace(";", "&#x3b;");
+            xssString = xssString.replace(" ", "&#x20;");
+            xssString = xssString.replace("'", "&#x27;");
+            xssString = xssString.replace("\"", "&quot;");
+            xssString = xssString.replace(">", "&gt;");
+            xssString = xssString.replace("<", "&lt;");
+            xssString = xssString.replace("/", "&#x2f;");
+            xssString = xssString.replace("(", "&#x28;");
+            xssString = xssString.replace(")", "&#x29;");
+            xssString = xssString.replace(":", "&#x3a;");
+        }
+        return xssString;
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for HTML element content.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForHTML(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForHTML(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for HTML attribute values.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForHTMLAttr(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForHTMLAttr(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for XML element content.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForXML(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForXML(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for XML attribute values.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForXMLAttr(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForXMLAttr(source);
+    }
+
+    /**
+     * Use to encapsulate new-style (XSSAPI-based) encoding for JavaScript strings.
+     *
+     * @param source the string to be encoded
+     * @return the encoded string
+     */
+    public static String encodeForJSString(String source) {
+        XSSAPI xssAPI = new XSSAPIImpl();
+        return xssAPI.encodeForJSString(source);
+    }
+
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfig.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config;
+
+/**
+ * Allows saving and restoring an instance configuration.
+ * Implementations define the behaviour of save() and restore()
+ */
+public interface InstanceConfig {
+
+    /**
+     * Saves the current status of the configuration
+     *
+     * @return this
+     * @throws InstanceConfigException if saving the configuration fails
+     * @throws InterruptedException if interrupted
+     */
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException;
+
+    /**
+     * Restores the saved status of the configuration
+     *
+     * @return this
+     * @throws InstanceConfigException if restoring the configuration fails
+     * @throws InterruptedException if interrupted
+     */
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException;
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigCache.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config;
+
+import java.util.Collection;
+
+/**
+ * A cache for different {@link InstanceConfig} objects
+ */
+public interface InstanceConfigCache  extends InstanceConfig, Collection<InstanceConfig> {
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/InstanceConfigException.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config;
+
+public class InstanceConfigException extends Exception {
+
+    public InstanceConfigException(Exception e) {
+        super(e);
+    }
+
+    public InstanceConfigException() {
+    }
+
+    public InstanceConfigException(String message) {
+        super(message);
+    }
+
+    public InstanceConfigException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InstanceConfigException(Throwable cause) {
+        super(cause);
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/EmptyInstanceConfig.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config.impl;
+
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EmptyInstanceConfig implements InstanceConfig {
+    private static final Logger LOG = LoggerFactory.getLogger(EmptyInstanceConfig.class);
+
+    public InstanceConfig save() throws InstanceConfigException {
+        LOG.debug("Saved nothing");
+        return this;
+    }
+
+    public InstanceConfig restore() throws InstanceConfigException {
+        LOG.debug("Restored nothing");
+        return this;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/InstanceConfigCacheImpl.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.config.impl;
+
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigCache;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public class InstanceConfigCacheImpl implements InstanceConfigCache {
+    List<InstanceConfig> configs;
+
+    public InstanceConfigCacheImpl(List<InstanceConfig> configs) {
+        this.configs = configs;
+    }
+
+    public InstanceConfigCacheImpl() {
+        this.configs = new ArrayList<InstanceConfig>();
+    }
+
+    @Override
+    public int size() {
+        return configs.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return configs.isEmpty();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+        return configs.contains(o);
+    }
+
+    @Override
+    public Iterator<InstanceConfig> iterator() {
+        return configs.iterator();
+    }
+
+    @Override
+    public Object[] toArray() {
+        return configs.toArray();
+    }
+
+    @Override
+    public <T> T[] toArray(T[] a) {
+        return configs.toArray(a);
+    }
+
+    @Override
+    public boolean add(InstanceConfig instanceConfig) {
+        return configs.add(instanceConfig);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+        return configs.remove(o);
+    }
+
+    @Override
+    public boolean containsAll(Collection<?> c) {
+        return configs.containsAll(c);
+    }
+
+    @Override
+    public boolean addAll(Collection<? extends InstanceConfig> c) {
+        return configs.addAll(c);
+    }
+
+    @Override
+    public boolean removeAll(Collection<?> c) {
+        return configs.removeAll(c);
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> c) {
+        return configs.retainAll(c);
+    }
+
+    @Override
+    public void clear() {
+       configs.clear();
+    }
+
+
+    @Override
+    public InstanceConfig save() throws InstanceConfigException, InterruptedException {
+        for (InstanceConfig ic : configs) {
+            ic.save();
+        }
+        return this;
+    }
+
+    @Override
+    public InstanceConfig restore() throws InstanceConfigException, InterruptedException {
+        for (InstanceConfig ic : configs) {
+            ic.restore();
+        }
+        return this;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/impl/package-info.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util.config.impl;
+
+import org.osgi.annotation.versioning.Version;

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/config/package-info.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@Version("1.0.0")
+package org.apache.sling.testing.clients.util.config;
+
+import org.osgi.annotation.versioning.Version;

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/package-info.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/package-info.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/package-info.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@Version("2.0.0")
+package org.apache.sling.testing.clients.util;
+
+import org.osgi.annotation.versioning.Version;

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/AbstractPoller.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.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;
+    private final long waitCount;
+
+    /**
+     * Convenience method to execute a generic call and do polling until a condition is met
+     * The user must implement the {@link Poller#call()} and {@link Poller#condition()} methods
+     * @param waitInterval Number of milliseconds to wait between polls
+     * @param waitCount Number of wait intervals
+     */
+    public AbstractPoller(long waitInterval, long waitCount) {
+        this.waitInterval = waitInterval;
+        this.waitCount = waitCount;
+    }
+
+    /**
+     * Calls the {@link Poller#call()} once and then calls {@link Poller#condition()} until it returns true
+     * The method waits AbstractPoller#waitInterval milliseconds between calls to {@link Poller#condition()}
+     * A maximum of AbstractPoller#waitCount intervals are checked
+     * @return true if the condition is met after waiting a maximum of AbstractPoller#waitCount intervals, false otherwise
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public boolean callAndWait() throws InterruptedException {
+        if (!call()) return false;
+        for (int i=0; i<waitCount; i++) {
+            if (condition()) return true;
+            Thread.sleep(waitInterval);
+        }
+        return false;
+    }
+
+    /**
+     * Calls the @see: Poller#call() and then calls {@link Poller#condition()} until it returns true
+     * The Poller#call() method is called in each wait interval, before the Poller#condition().
+     * The method waits AbstractPoller#waitInterval milliseconds between calls to {@link Poller#condition()}
+     * A maximum of AbstractPoller#waitCount intervals are checked
+     * @return true if the condition is met after waiting a maximum of AbstractPoller#waitCount intervals, false otherwise
+     * @throws InterruptedException to mark this operation as "waiting"
+     */
+    public boolean callUntilCondition() throws InterruptedException {
+        if (!call()) return false;
+        if (condition()) return true;
+        for (int i = 0; i < waitCount; i++) {
+            Thread.sleep(waitInterval);
+            if (!call()) return false;
+            if (condition()) return true;
+        }
+        return false;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/PathPoller.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.testing.clients.util.poller;
+
+import org.apache.sling.testing.clients.AbstractSlingClient;
+import org.apache.sling.testing.clients.ClientException;
+import org.slf4j.Logger;
+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;
+    private final String path;
+    private final int[] expectedStatus;
+    private Exception exception;
+
+    public PathPoller(AbstractSlingClient client, String path, long waitInterval, long waitCount, int... expectedStatus) {
+        super(waitInterval, waitCount);
+        this.client = client;
+        this.path = path;
+        if (null == expectedStatus || expectedStatus.length == 0) {
+            this.expectedStatus = new int[]{200};
+        } else {
+            this.expectedStatus = expectedStatus;
+        }
+    }
+
+
+    @Override
+    public boolean call() {
+        return true;
+    }
+
+    @Override
+    public boolean condition() {
+        try {
+            client.doGet(path, expectedStatus);
+        } catch (ClientException e) {
+            LOG.warn("Get on {} failed: {}", path, e);
+            this.exception = e;
+            return false;
+        }
+        return true;
+    }
+
+    public Exception getException() {
+        return exception;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Poller.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.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();
+    boolean callAndWait() throws InterruptedException;
+    boolean callUntilCondition() throws InterruptedException;
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/Polling.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing.clients.util.poller;
+
+import org.apache.sling.testing.timeouts.TimeoutsProvider;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Helper for repeating a call until it returns true, with timeout capabilities.
+ * Subclasses should override the {@link #call()} method.
+ * Can be used with lambda expressions, using the constructor {@link #Polling(Callable c)}.
+ *
+ * @since 1.1.0
+ */
+public class Polling implements Callable<Boolean> {
+
+    /**
+     * Optional object to be used by the default implementation of call()
+     */
+    protected final Callable<Boolean> c;
+
+    /**
+     * Holder for the last exception thrown by call(), to be used for logging
+     */
+    protected Exception lastException;
+
+    /**
+     * Counter for total waiting time
+     */
+    protected long waited;
+
+    /**
+     * Default constructor to be used in subclasses that override the {@link #call()} method.
+     * Should not be used directly on {@code Polling} instances, but only on extended classes.
+     * If used directly to get a {@code Polling} instance, executing {@link #poll(long timeout, long delay)}
+     * will be equivalent to {@code Thread.sleep(timeout)}
+     */
+    public Polling() {
+        this.c = null;
+        this.lastException = null;
+        this.waited = 0;
+    }
+
+    /**
+     * Creates a new instance that uses the {@code Callable} parameter for polling
+     *
+     * @param c object whose {@code call()} method will be polled
+     */
+    public Polling(Callable<Boolean> c) {
+        this.c = c;
+        this.lastException = null;
+        this.waited = 0;
+    }
+
+    /**
+     * <p>Method to be called by {@link #poll(long timeout, long delay)}, potentially multiple times,
+     * until it returns true or timeout is reached.<br>
+     * Subclasses can override it to change the check accordingly. The method should return true
+     * only when the call was successful.<br>
+     * It can return false or throw any {@code Exception} to make the poller try again later.</p>
+     *
+     * <p>The default implementation delegates the call to the {@code Callable c} instance.</p>
+     *
+     * @return {@code true} to end polling
+     * @throws Exception if unable to compute a result
+     */
+    @Override
+    public Boolean call() throws Exception {
+        if (c != null) {
+            return c.call();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * <p>Tries to execute {@link #call()} until it returns true or until {@code timeout} is reached.
+     * Between retries, it waits using {@code Thread.sleep(delay)}. It means the retry is not at a fixed pace,
+     * but depends on the execution time of the call itself.</p>
+     * <p>The method guarantees that the call() will be executed at least once. If the timeout is 0 or less, then
+     * call() will be executed exactly once.</p>
+     * <p>The timeout is adjusted using {@link TimeoutsProvider} so the final value can be changed using the
+     * system property: {@value org.apache.sling.testing.timeouts.TimeoutsProvider#PROP_TIMEOUT_MULTIPLIER}</p>
+     *
+     * @param timeout max total execution time, in milliseconds
+     * @param delay time to wait between calls, in milliseconds
+     *
+     * @throws TimeoutException if {@code timeout} was reached
+     * @throws InterruptedException if the thread was interrupted while sleeping; caller should throw it further
+     */
+    public void poll(long timeout, long delay) throws TimeoutException, InterruptedException {
+        long start = System.currentTimeMillis();
+        long effectiveTimeout = TimeoutsProvider.getInstance().getTimeout(timeout);
+
+        do {
+            try {
+                boolean success = call();
+                if (success) {
+                    waited = System.currentTimeMillis() - start;
+                    return;
+                }
+            } catch (InterruptedException e) {
+                throw e; // Never inhibit InterruptedException
+            } catch (Exception e) {
+                lastException = e;
+            }
+            Thread.sleep(delay);
+        } while (System.currentTimeMillis() < start + effectiveTimeout);
+
+        waited = System.currentTimeMillis() - start;
+        throw new TimeoutException(String.format(message(), effectiveTimeout, delay) +
+                " Last exception was: " + lastException);
+    }
+
+    public long getWaited() {
+        return waited;
+    }
+
+    /**
+     * Returns the string to be used in the {@code TimeoutException}, if needed.
+     * The string is passed to {@code String.format(message(), timeout, delay)}, so it can be a format
+     * including {@code %1$d} and {@code %2$d}. The field {@code lastException} is also available for logging
+     *
+     * @return the format string
+     */
+    protected String message() {
+        return "Call failed to return true in %1$d ms.";
+    }
+
+    /**
+     * Return the last exception while polling or {null}
+     * @return
+     */
+    public Exception getLastException() {
+        return lastException;
+    }
+}

Added: release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/clients/util/poller/package-info.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@Version("1.3.0")
+package org.apache.sling.testing.clients.util.poller;
+
+import org.osgi.annotation.versioning.Version;

Added: release/sling/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java
==============================================================================
--- release/sling/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java (added)
+++ release/sling/src/main/java/org/apache/sling/testing/timeouts/TimeoutsProvider.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.testing.timeouts;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Return timeout values that can be multiplied by a configurable
+ *  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.
+ */
+public class TimeoutsProvider {
+    private static final Logger log = LoggerFactory.getLogger(TimeoutsProvider.class);
+    public static final String PROP_TIMEOUT_MULTIPLIER = "sling.testing.timeout.multiplier";
+    private static float timeoutFactor = -1;
+    private static TimeoutsProvider INSTANCE;
+    
+    private TimeoutsProvider() {
+        if(timeoutFactor < 0) {
+            timeoutFactor = 1;
+            final String str = System.getProperty(PROP_TIMEOUT_MULTIPLIER);
+            if(str != null) {
+                try {
+                    timeoutFactor = Float.valueOf(str.trim());
+                    log.info("Timeout factor set to {} from system property {}", 
+                            timeoutFactor, PROP_TIMEOUT_MULTIPLIER);
+                } catch(NumberFormatException nfe) {
+                    throw new IllegalStateException("Invalid timeout factor: " + PROP_TIMEOUT_MULTIPLIER + "=" + str);
+                }
+            }
+        }
+    }
+
+    /**
+     *
+     * @return the instance of the singleton
+     */
+    public static TimeoutsProvider getInstance() {
+        if(INSTANCE == null) {
+            synchronized (TimeoutsProvider.class) {
+                INSTANCE = new TimeoutsProvider();
+            }
+        }
+        return INSTANCE;
+    }
+
+    /**
+     *
+     * @param nomimalValue base number to be multiplied internally with the factor
+     * @return the new timeout
+     */
+    public long getTimeout(long nomimalValue) {
+        final long result = (long)(nomimalValue * timeoutFactor);
+        return result;
+    }
+
+    /**
+     *
+     * @param nomimalValue base number to be multiplied internally with the factor
+     * @return the new timeout
+     */
+    public int getTimeout(int nomimalValue) {
+        final int result = (int)(nomimalValue * timeoutFactor);
+        return result;
+    }
+    
+    /**
+     * Get timeout from a system property, with default value
+     * @param systemPropertyName the name of the system prop from which to get the timeout
+     * @param defaultNominalValue default value in case the property does not exist
+     * @return the timeout
+     */
+    public int getTimeout(String systemPropertyName, int defaultNominalValue) {
+        int result = defaultNominalValue;
+        final String str = System.getProperty(systemPropertyName);
+        if(str != null) {
+            result = Integer.parseInt(str);
+        }
+        return getTimeout(result);
+    }
+}

Added: release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java
==============================================================================
--- release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java (added)
+++ release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetPathTest.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetPathTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, input: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                {"http://HOST",              "http://HOST/page.html",             "/page.html"},
+                {"http://HOST",              "http://HOST/my/page.html",          "/my/page.html"},
+                {"http://HOST",              "http://HOST/my/",                   "/my/"},
+                {"http://HOST",              "http://HOST/my",                    "/my"},
+                {"http://HOST",              "http://HOST/",                      "/"},
+                {"http://HOST",              "http://HOST",                       "/"},
+                {"http://HOST",              "/page.html",                        "/page.html"},
+                {"http://HOST",              "/my/page.html",                     "/my/page.html"},
+                {"http://HOST",              "/my/",                              "/my/"},
+                {"http://HOST",              "/",                                 "/"},
+                {"http://HOST",              "page.html",                         "/page.html"},
+                {"http://HOST",              "my/page.html",                      "/my/page.html"},
+                {"http://HOST",              "my",                                "/my"},
+                {"http://HOST",              "",                                  "/"},
+
+                {"http://HOST:4502",         "http://HOST:4502/page.html",        "/page.html"},
+                {"http://HOST:4502",         "http://HOST:4502/my/page.html",     "/my/page.html"},
+                {"http://HOST:4502",         "http://HOST:4502/my/",              "/my/"},
+                {"http://HOST:4502",         "http://HOST:4502/my",               "/my"},
+                {"http://HOST:4502",         "http://HOST:4502/",                 "/"},
+                {"http://HOST:4502",         "http://HOST:4502",                  "/"},
+                {"http://HOST:4502",         "/page.html",                        "/page.html"},
+                {"http://HOST:4502",         "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502",         "/my/",                              "/my/"},
+                {"http://HOST:4502",         "/my",                               "/my"},
+                {"http://HOST:4502",         "/",                                 "/"},
+                {"http://HOST:4502",         "page.html",                         "/page.html"},
+                {"http://HOST:4502",         "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502",         "my/",                               "/my/"},
+                {"http://HOST:4502",         "my",                                "/my"},
+                {"http://HOST:4502",         "",                                  "/"},
+
+                {"http://HOST:4502/",        "http://HOST:4502/page.html",        "/page.html"},
+                {"http://HOST:4502/",        "http://HOST:4502/my/page.html",     "/my/page.html"},
+                {"http://HOST:4502/",        "http://HOST:4502/my/",              "/my/"},
+                {"http://HOST:4502/",        "http://HOST:4502/my",               "/my"},
+                {"http://HOST:4502/",        "http://HOST:4502/",                 "/"},
+                {"http://HOST:4502/",        "http://HOST:4502",                  "/"},
+                {"http://HOST:4502/",        "/page.html",                        "/page.html"},
+                {"http://HOST:4502/",        "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/",        "/my/",                              "/my/"},
+                {"http://HOST:4502/",        "/my",                               "/my"},
+                {"http://HOST:4502/",        "/",                                 "/"},
+                {"http://HOST:4502/",        "page.html",                         "/page.html"},
+                {"http://HOST:4502/",        "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/",        "my/",                               "/my/"},
+                {"http://HOST:4502/",        "my",                                "/my"},
+                {"http://HOST:4502/",        "",                                  "/"},
+
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/page.html",    "/page.html"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my/page.html", "/my/page.html"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my/",          "/my/"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/my",           "/my"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX/",             "/"},
+                {"http://HOST:4502/CTX",     "http://HOST:4502/CTX",              "/"},
+                {"http://HOST:4502/CTX",     "/CTX",                              "/"},
+                {"http://HOST:4502/CTX",     "/CTX/",                             "/"},
+                {"http://HOST:4502/CTX",     "/CTX/page.html",                    "/page.html"},
+                {"http://HOST:4502/CTX",     "/page.html",                        "/page.html"},
+                {"http://HOST:4502/CTX",     "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/CTX",     "/my/",                              "/my/"},
+                {"http://HOST:4502/CTX",     "/my",                               "/my"},
+                {"http://HOST:4502/CTX",     "/",                                 "/"},
+                {"http://HOST:4502/CTX",     "CTX",                               "/"},
+                {"http://HOST:4502/CTX",     "CTX/",                              "/"},
+                {"http://HOST:4502/CTX",     "CTX/page.html",                     "/page.html"},
+                {"http://HOST:4502/CTX",     "page.html",                         "/page.html"},
+                {"http://HOST:4502/CTX",     "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/CTX",     "my/",                               "/my/"},
+                {"http://HOST:4502/CTX",     "my",                                "/my"},
+                {"http://HOST:4502/CTX",     "",                                  "/"},
+
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/page.html",    "/page.html"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/page.html", "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/",          "/my/"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my",           "/my"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/",             "/"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX",              "/"},
+
+                {"http://HOST:4502/CTX/",    "/CTX",                              "/"},
+                {"http://HOST:4502/CTX/",    "/CTX/",                             "/"},
+                {"http://HOST:4502/CTX/",    "/CTX/page.html",                    "/page.html"},
+                {"http://HOST:4502/CTX/",    "/page.html",                        "/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/page.html",                     "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/",                              "/my/"},
+                {"http://HOST:4502/CTX/",    "/my",                               "/my"},
+                {"http://HOST:4502/CTX/",    "/",                                 "/"},
+                {"http://HOST:4502/CTX/",    "CTX",                               "/"},
+                {"http://HOST:4502/CTX/",    "CTX/",                              "/"},
+                {"http://HOST:4502/CTX/",    "CTX/page.html",                     "/page.html"},
+                {"http://HOST:4502/CTX/",    "page.html",                         "/page.html"},
+                {"http://HOST:4502/CTX/",    "my/page.html",                      "/my/page.html"},
+                {"http://HOST:4502/CTX/",    "my/",                               "/my/"},
+                {"http://HOST:4502/CTX/",    "my",                                "/my"},
+                {"http://HOST:4502/CTX/",    "",                                  "/"},
+
+                {"http://HOST:4502/CTX/",    "/CTX/?param=value",                   "/?param=value"},
+                {"http://HOST:4502/CTX/",    "/CTX/page.html?param=value",          "/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "/page.html?param=value",              "/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "/my/page.html?param=value",           "/my/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "/my/?param=value",                    "/my/?param=value"},
+                {"http://HOST:4502/CTX/",    "/my?param=value",                     "/my?param=value"},
+                {"http://HOST:4502/CTX/",    "CTX/?param=value",                    "/?param=value"},
+                {"http://HOST:4502/CTX/",    "CTX/page.html?param=value",           "/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "page.html?param=value",               "/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "my/page.html?param=value",            "/my/page.html?param=value"},
+                {"http://HOST:4502/CTX/",    "my/?param=value",                     "/my/?param=value"},
+                {"http://HOST:4502/CTX/",    "my?param=value",                      "/my?param=value"},
+
+                {"http://HOST:4502/CTX/",    "http://www.google.com",             "http://www.google.com"},
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String inputUri;
+
+    @Parameterized.Parameter(value = 2)
+    public String expectedPath;
+
+    @Test
+    public void testGetPath() throws ClientException, URISyntaxException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals(URI.create(expectedPath), c.getPath(inputUri));
+    }
+}
+
+
+
+
+
+
+
+
+
+
+

Added: release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java
==============================================================================
--- release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java (added)
+++ release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetServerUrlTest.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetServerUrlTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, path: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                {"http://HOST",             "http://HOST/"},
+                {"http://HOST:4502",        "http://HOST:4502/"},
+                {"http://HOST:4502/",       "http://HOST:4502/"},
+                {"http://HOST:4502/CTX",    "http://HOST:4502/CTX/"},
+                {"http://HOST:4502/CTX/",   "http://HOST:4502/CTX/"},
+        });
+    }
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String expectedUrl;
+
+    @Test
+    public void testGetUrl() throws ClientException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals("", URI.create(expectedUrl), c.getUrl());
+    }
+}

Added: release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java
==============================================================================
--- release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java (added)
+++ release/sling/src/test/java/org/apache/sling/testing/AbstractSlingClientGetUrlTest.java Thu Apr  9 13:50:42 2020
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.sling.testing;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class AbstractSlingClientGetUrlTest {
+
+    @Parameterized.Parameters(name = "{index} - serverUrl: {0}, path: {1}, expected: {2}")
+    public static Collection<String[]> data() {
+        return Arrays.asList(new String[][] {
+                // Server URL with no port
+                {"http://HOST",              "/page.html",            "http://HOST/page.html"},
+                {"http://HOST",              "/my/page.html",         "http://HOST/my/page.html"},
+                {"http://HOST",              "/my/",                  "http://HOST/my/"},
+                {"http://HOST",              "/my",                   "http://HOST/my"},
+                {"http://HOST",              "/",                     "http://HOST/"},
+
+                {"http://HOST",              "page.html",             "http://HOST/page.html"},
+                {"http://HOST",              "my/page.html",          "http://HOST/my/page.html"},
+                {"http://HOST",              "my/",                   "http://HOST/my/"},
+                {"http://HOST",              "my",                    "http://HOST/my"},
+                {"http://HOST",              "",                      "http://HOST/"},
+
+                // Server URL with with port
+                {"http://HOST:4502",         "/page.html",            "http://HOST:4502/page.html"},
+                {"http://HOST:4502",         "/my/page.html",         "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502",         "/my/",                  "http://HOST:4502/my/"},
+                {"http://HOST:4502",         "/my",                   "http://HOST:4502/my"},
+                {"http://HOST:4502",         "/",                     "http://HOST:4502/"},
+
+                {"http://HOST:4502",         "page.html",             "http://HOST:4502/page.html"},
+                {"http://HOST:4502",         "my/page.html",          "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502",         "my/",                   "http://HOST:4502/my/"},
+                {"http://HOST:4502",         "my",                    "http://HOST:4502/my"},
+                {"http://HOST:4502",         "",                      "http://HOST:4502/"},
+
+                // Server URL with with port and trailing slash
+                {"http://HOST:4502/",        "/page.html",            "http://HOST:4502/page.html"},
+                {"http://HOST:4502/",        "/my/page.html",         "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502/",        "/my/",                  "http://HOST:4502/my/"},
+                {"http://HOST:4502/",        "/my",                   "http://HOST:4502/my"},
+                {"http://HOST:4502/",        "/",                     "http://HOST:4502/"},
+
+                {"http://HOST:4502/",        "page.html",             "http://HOST:4502/page.html"},
+                {"http://HOST:4502/",        "my/page.html",          "http://HOST:4502/my/page.html"},
+                {"http://HOST:4502/",        "my/",                   "http://HOST:4502/my/"},
+                {"http://HOST:4502/",        "my",                    "http://HOST:4502/my"},
+                {"http://HOST:4502/",        "",                      "http://HOST:4502/"},
+
+                // Server URL with with port and context path (no trailing slash)
+                {"http://HOST:4502/CTX",     "/page.html",            "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX",     "/my/page.html",         "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX",     "/my/",                  "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX",     "/my",                   "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX",     "/",                     "http://HOST:4502/CTX/"},
+
+                {"http://HOST:4502/CTX",     "page.html",             "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX",     "my/page.html",          "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX",     "my/",                   "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX",     "my",                    "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX",     "",                      "http://HOST:4502/CTX/"},
+
+                // Server URL with with port and context path and trailing slash
+                {"http://HOST:4502/CTX/",    "/page.html",            "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/page.html",         "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX/",    "/my/",                  "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX/",    "/my",                   "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX/",    "/",                     "http://HOST:4502/CTX/"},
+
+                {"http://HOST:4502/CTX/",    "page.html",             "http://HOST:4502/CTX/page.html"},
+                {"http://HOST:4502/CTX/",    "my/page.html",          "http://HOST:4502/CTX/my/page.html"},
+                {"http://HOST:4502/CTX/",    "my/",                   "http://HOST:4502/CTX/my/"},
+                {"http://HOST:4502/CTX/",    "my",                    "http://HOST:4502/CTX/my"},
+                {"http://HOST:4502/CTX/",    "",                      "http://HOST:4502/CTX/"},
+
+                // External URLs
+                {"http://HOST:4502/CTX/",    "http://www.google.com", "http://www.google.com"},
+                {"http://HOST:4502/CTX/",    "http://HOST:4502/CTX/my/page.html", "http://HOST:4502/CTX/my/page.html"},
+
+                // URL encoding of the path
+                {"http://HOST:4502/CTX/",    "!@*()'~ #$%^&{}[]|\\<>?\"`", "http://host:4502/CTX/!@*()'~%20%23$%25%5E&%7B%7D%5B%5D%7C%5C%3C%3E%3F%22%60"},
+        });
+    }
+
+
+    private static final List<NameValuePair> TEST_PARAMETERS = Arrays.<NameValuePair>asList(new BasicNameValuePair("key", "value"));
+    private static final String STRING_TEST_PARAMETERS = "key=value";
+
+    @Parameterized.Parameter(value = 0)
+    public String serverUrl;
+
+    @Parameterized.Parameter(value = 1)
+    public String inputPath;
+
+    @Parameterized.Parameter(value = 2)
+    public String expectedUrl;
+
+    @Test
+    public void testGetUrlWithParam() throws ClientException {
+        SlingClient c = new SlingClient(URI.create(serverUrl), "USER", "PWD");
+        assertEquals("", URI.create(expectedUrl), c.getUrl(inputPath));
+        assertEquals(URI.create(expectedUrl), c.getUrl(inputPath, null));
+        assertEquals(URI.create(expectedUrl + "?"), c.getUrl(inputPath, new ArrayList<NameValuePair>()));
+        assertEquals(URI.create(expectedUrl + "?" + STRING_TEST_PARAMETERS), c.getUrl(inputPath, TEST_PARAMETERS));
+    }
+}