You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2016/04/29 16:03:33 UTC

svn commit: r1741632 [3/4] - in /sling/trunk/testing/http: ./ clients/ clients/src/ clients/src/main/ clients/src/main/java/ clients/src/main/java/org/ clients/src/main/java/org/apache/ clients/src/main/java/org/apache/sling/ clients/src/main/java/org/...

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/BundlesInstaller.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,186 @@
+/*
+ * 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.osgi;
+
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.jar.JarInputStream;
+import java.util.jar.Manifest;
+
+
+/** Utility that installs and starts additional bundles for testing */ 
+public class BundlesInstaller {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final WebconsoleClient webconsoleClient;
+    public static final String ACTIVE_STATE = "active";
+    
+    public BundlesInstaller(WebconsoleClient wcc) {
+        webconsoleClient = wcc;
+    }
+   
+    public boolean isInstalled(File bundleFile) throws Exception {
+        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
+        try{
+            log.debug("Checking if installed: "+bundleSymbolicName);
+            webconsoleClient.checkBundleInstalled(bundleSymbolicName, 1);
+            // if this succeeds, then there's no need to install again
+            log.debug("Already installed: "+bundleSymbolicName);
+            return true;
+        } catch(AssertionError e) {
+            log.debug("Not yet installed: "+bundleSymbolicName);
+            return false;
+        }
+
+    }
+    
+    /** Check if the installed version matches the one of the bundle (file) **/
+    public boolean isInstalledWithSameVersion(File bundleFile) throws Exception {
+        final String bundleSymbolicName = getBundleSymbolicName(bundleFile);
+        final String versionOnServer = webconsoleClient.getBundleVersion(bundleSymbolicName);
+        final String versionInBundle = getBundleVersion(bundleFile);
+        if (versionOnServer.equals(versionInBundle)) {
+            return true;
+        } else {
+            log.info("Bundle installed doesn't match: "+bundleSymbolicName+
+                    ", versionOnServer="+versionOnServer+", versionInBundle="+versionInBundle);
+            return false;
+        }
+    }
+    
+    /** Install a list of bundles supplied as Files */
+    public void installBundles(List<File> toInstall, boolean startBundles) throws Exception {
+        for(File f : toInstall) {
+            final String bundleSymbolicName = getBundleSymbolicName(f);
+            if (isInstalled(f)) {
+                if (f.getName().contains("SNAPSHOT")) {
+                    log.info("Reinstalling (due to SNAPSHOT version): {}", bundleSymbolicName);
+                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                } else if (!isInstalledWithSameVersion(f)) {
+                    log.info("Reinstalling (due to version mismatch): {}", bundleSymbolicName);
+                    webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+                } else {
+                    log.info("Not reinstalling: {}", bundleSymbolicName);
+                    continue;
+                }
+            }
+            webconsoleClient.installBundle(f, startBundles);
+            log.info("Installed: {}", bundleSymbolicName);
+        }
+        
+        // ensure that bundles are re-wired esp. if an existing bundle was updated
+        webconsoleClient.refreshPackages();
+
+        log.info("{} additional bundles installed", toInstall.size());
+    }
+    
+    /** Uninstall a list of bundles supplied as Files */
+    public void uninstallBundles(List<File> toUninstall) throws Exception {
+        for(File f : toUninstall) {
+            final String bundleSymbolicName = getBundleSymbolicName(f);
+            if (isInstalled(f)) {
+                log.info("Uninstalling bundle: {}", bundleSymbolicName);
+                webconsoleClient.uninstallBundle(bundleSymbolicName, f);
+            } else {
+                log.info("Could not uninstall: {} as it never was installed", bundleSymbolicName);
+            }
+        }
+        
+        // ensure that bundles are re-wired esp. if an existing bundle was updated
+        webconsoleClient.refreshPackages();
+
+        log.info("{} additional bundles uninstalled", toUninstall.size());
+    }
+    
+    /** Wait for all bundles specified in symbolicNames list to be installed in the
+     *  remote web console.
+     */
+    public void waitForBundlesInstalled(List<String> symbolicNames, int timeoutSeconds) throws Exception {
+        log.info("Checking that bundles are installed (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
+        for(String symbolicName : symbolicNames) {
+            webconsoleClient.checkBundleInstalled(symbolicName, timeoutSeconds);
+        }
+    }
+    
+    public void startAllBundles(List<String> symbolicNames, int timeoutSeconds) throws Exception {
+        log.info("Starting bundles (timeout {} seconds): {}", timeoutSeconds, symbolicNames);
+        
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000L;
+        final List<String> toStart = new LinkedList<String>();
+        while(System.currentTimeMillis() < timeout) {
+            toStart.clear();
+            for(String name : symbolicNames) {
+                final String state = webconsoleClient.getBundleState(name);
+                if(!state.equalsIgnoreCase(ACTIVE_STATE)) {
+                    toStart.add(name);
+                    break;
+                }
+            }
+            
+            if(toStart.isEmpty()) {
+                log.info("Ok - all bundles are in the {} state", ACTIVE_STATE);
+                break;
+            }
+            
+            for(String name : toStart) {
+                webconsoleClient.startBundle(name);
+            }
+            
+            Thread.sleep(500L);
+        }
+        
+        if(!toStart.isEmpty()) {
+            throw new Exception("Some bundles did not start: " + toStart);
+        }
+    }
+    
+    public String getBundleSymbolicName(File bundleFile) throws IOException {
+        String name = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if (m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            name = m.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME);
+        } finally {
+            jis.close();
+        }
+        return name;
+    }
+    
+    public String getBundleVersion(File bundleFile) throws IOException {
+        String version = null;
+        final JarInputStream jis = new JarInputStream(new FileInputStream(bundleFile));
+        try {
+            final Manifest m = jis.getManifest();
+            if(m == null) {
+                throw new IOException("Manifest is null in " + bundleFile.getAbsolutePath());
+            }
+            version = m.getMainAttributes().getValue(Constants.BUNDLE_VERSION);
+        } finally {
+            jis.close();
+        }
+        return version;
+    }
+}
\ No newline at end of file

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Component.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Component.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Component.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/Component.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,51 @@
+/*
+ * 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.osgi;
+
+public class Component {
+
+    public enum Status {
+
+        ACTIVE("active"),
+
+        REGISTERED("registered"),
+
+        UNSATISFIED("unsatisfied");
+
+        String value;
+
+        Status(String value) {
+            this.value = value;
+        }
+
+        public static Status value(String o) {
+            for(Status s : values()) {
+                if(s.value.equalsIgnoreCase(o)) {
+                    return s;
+                }
+            }
+            return null;
+        }
+
+        public String toString() {
+            return value;
+        }
+
+    }
+
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentInfo.java Fri Apr 29 14:03:32 2016
@@ -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
+ *
+ * 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.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+public class ComponentInfo {
+
+    private JsonNode component;
+
+    public ComponentInfo(JsonNode root) throws ClientException {
+        if(root.get("id") != null) {
+            if(root.get("id") == null) {
+                throw new ClientException("No Component Info returned");
+            }
+            component = root;
+        } else {
+            if(root.get("data") == null && root.get("data").size() < 1) {
+                throw new ClientException("No Component Info returned");
+            }
+            component = root.get("data").get(0);
+        }
+    }
+
+    /**
+     * @return the component identifier
+     */
+    public int getId() {
+        return component.get("id").getIntValue();
+    }
+
+    /**
+     * @return the component name
+     */
+    public String getName() {
+        return component.get("name").getTextValue();
+    }
+
+    /**
+     * @return the component status
+     */
+    public Component.Status getStatus() {
+        return Component.Status.value(component.get("state").getTextValue());
+    }
+
+    /**
+     * @return the component persistent identifier
+     */
+    public String getPid() {
+        return component.get("pid").getTextValue();
+    }
+
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/ComponentsInfo.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,95 @@
+/*
+ * 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.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.codehaus.jackson.JsonNode;
+
+import java.util.Iterator;
+
+/**
+ * Thin wrapper around the list of components
+ */
+public class ComponentsInfo {
+
+    private JsonNode root = null;
+
+    /**
+     * The only constructor.
+     * 
+     * @param rootNode the root JSON node of the components info.
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentsInfo(JsonNode rootNode) throws ClientException {
+        this.root = rootNode;
+    }
+
+    /**
+     * @return the number of installed components
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public int getNumberOfInstalledComponents() throws ClientException {
+        if(root.get("status") == null)
+            throw new ClientException("Number of installed Components not defined!");
+        return Integer.parseInt(root.get("status").getValueAsText());
+    }
+
+    /**
+     * @param id the id of the component
+     * @return the ComponentInfo for a component with the identifier {@code id}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forId(String id) throws ClientException {
+        JsonNode component = findBy("id", id);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    /**
+     * @param name the name of the component
+     * @return the ComponentInfo for a component with the name {@code name}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forName(String name) throws ClientException {
+        JsonNode component = findBy("name", name);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    /**
+     * @param pid the pid of the component
+     * @return the ComponentInfo for a component with the pid {@code pid}
+     * @throws ClientException if the info cannot be retrieved
+     */
+    public ComponentInfo forPid(String pid) throws ClientException {
+        JsonNode component = findBy("pid", pid);
+        return (component != null) ? new ComponentInfo(component) : null;
+    }
+
+    private JsonNode findBy(String key, String value) {
+        Iterator<JsonNode> nodes = root.get("data").getElements();
+        while(nodes.hasNext()) {
+            JsonNode node = nodes.next();
+            if(node.get(key) != null) {
+                if(node.get(key).isValueNode()) {
+                    return node;
+                }
+            }
+        }
+        return null;
+    }
+
+}
\ No newline at end of file

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiConsoleClient.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,324 @@
+/*
+ * 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.osgi;
+
+import org.apache.http.Header;
+import org.apache.http.impl.client.CloseableHttpClient;
+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.util.FormEntityBuilder;
+import org.apache.sling.testing.clients.util.HttpUtils;
+import org.codehaus.jackson.JsonNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.*;
+
+import static org.apache.http.HttpStatus.SC_MOVED_TEMPORARILY;
+import static org.apache.http.HttpStatus.SC_OK;
+
+/**
+ * A client that wraps the Felix OSGi Web Console REST API calls.
+ */
+public class OsgiConsoleClient extends SlingClient {
+
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiConsoleClient.class);
+    /**
+     * All System Console REST API calls go to /system/console and below
+     */
+    private final String CONSOLE_ROOT_URL = "/system/console";
+
+    /**
+     * The URL for configuration requests
+     */
+    private final String URL_CONFIGURATION = CONSOLE_ROOT_URL + "/configMgr";
+
+    /**
+     * The URL for bundle requests
+     */
+    private final String URL_BUNDLES = CONSOLE_ROOT_URL + "/bundles";
+
+    /**
+     * The URL for components requests
+     */
+    private final String URL_COMPONENTS = CONSOLE_ROOT_URL + "/components";
+
+    /**
+     * Default constructor. Simply calls {@link SlingClient#SlingClient(URI, String, String)}
+     *
+     * @param serverUrl the URL to the server under test
+     * @param userName the user name used for authentication
+     * @param password the password for this user
+     * @throws ClientException if the client cannot be instantiated
+     */
+    public OsgiConsoleClient(URI serverUrl, String userName, String password) throws ClientException {
+        super(serverUrl, userName, password);
+    }
+
+    /**
+     * Constructor used by adaptTo() and InternalBuilder classes. Should not be called directly in the code
+     *
+     * @param http http client to be used for requests
+     * @param config sling specific configs
+     * @throws ClientException if the client cannot be instantiated
+     */
+    public OsgiConsoleClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
+        super(http, config);
+    }
+
+    /**
+     * Returns the wrapper for the bundles info json
+     *
+     * @param expectedStatus list of accepted statuses of the response
+     * @return all the bundles info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public BundlesInfo getBundlesInfo(int... expectedStatus) throws ClientException {
+        // request the bundles information
+        SlingHttpResponse resp = this.doGet(URL_BUNDLES + ".json", HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        // return the wrapper
+        return new BundlesInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the bundle info json
+     *
+     * @param id the id of the bundle
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the bundle info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public BundleInfo getBundleInfo(String id, int... expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_BUNDLES + "/" + id + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new BundleInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the components info json
+     *
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the components info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public ComponentsInfo getComponentsInfo(int... expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_COMPONENTS + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new ComponentsInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns the wrapper for the component info json
+     *
+     * @param id the id of the component
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the component info
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public ComponentInfo getComponentInfo(String id, int expectedStatus) throws ClientException {
+        SlingHttpResponse resp = this.doGet(URL_COMPONENTS + "/" + id + ".json");
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        return new ComponentInfo(JsonUtils.getJsonNodeFromString(resp.getContent()));
+    }
+
+    /**
+     * Returns a map of all properties set for the config referenced by the PID, where the map keys
+     * are the property names.
+     *
+     * @param pid the pid of the configuration
+     * @param expectedStatus list of accepted statuses of the response
+     * @return the properties as a map
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public Map<String, Object> getConfiguration(String pid, int... expectedStatus) throws ClientException {
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + pid, null);
+        // check the returned status
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_OK, expectedStatus));
+        // get the JSON node
+        JsonNode rootNode = JsonUtils.getJsonNodeFromString(resp.getContent());
+        // go through the params
+        Map<String, Object> props = new HashMap<String, Object>();
+        if(rootNode.get("properties") == null)
+            return props;
+        JsonNode properties = rootNode.get("properties");
+        for(Iterator<String> it = properties.getFieldNames(); it.hasNext();) {
+            String propName = it.next();
+            JsonNode value = properties.get(propName).get("value");
+            if(value != null) {
+                props.put(propName, value.getValueAsText());
+                continue;
+            }
+            value = properties.get(propName).get("values");
+            if(value != null) {
+                Iterator<JsonNode> iter = value.getElements();
+                List<String> list = new ArrayList<String>();
+                while(iter.hasNext()) {
+                    list.add(iter.next().getValueAsText());
+                }
+                props.put(propName, list.toArray(new String[list.size()]));
+            }
+        }
+        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.
+     *
+     * @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
+     * @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"
+     */
+    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);
+        return poller.getConfig();
+    }
+
+    /**
+     * Sets properties of a config referenced by its PID. the properties to be edited are passed as
+     * a map of property name,value pairs.
+     *
+     * @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 location of the config
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public String editConfiguration(String PID, String factoryPID, Map<String, Object> configProperties, int... expectedStatus)
+            throws ClientException {
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("apply", "true");
+        builder.addParameter("action", "ajaxConfigManager");
+        // send factory PID if set
+        if (factoryPID != null) {
+            builder.addParameter("factoryPid", factoryPID);
+        }
+        // add properties to edit
+        StringBuilder propertyList = new StringBuilder("");
+        for (String propName : configProperties.keySet()) {
+            Object o = configProperties.get(propName);
+            if (o instanceof String) {
+                builder.addParameter(propName, (String)o);
+            } else if (o instanceof String[]) {
+                for (String s : (String[])o) {
+                    builder.addParameter(propName, s);
+                }
+            }
+            propertyList.append(propName).append(",");
+        }
+        // cut off the last comma
+        builder.addParameter("propertylist", propertyList.substring(0, propertyList.length() - 1));
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + PID, builder.build());
+        // check the returned status
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(SC_MOVED_TEMPORARILY, expectedStatus));
+
+        Header[] locationHeader = resp.getHeaders("Location");
+        if (locationHeader!=null && locationHeader.length==1) {
+        	return locationHeader[0].getValue().substring(URL_CONFIGURATION.length()+1);
+        } else {
+        	return null;
+        }
+    }
+
+    /**
+     * 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 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
+     * @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"
+     */
+    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);
+        getConfigurationWithWait(waitCount, pid);
+        return pid;
+    }
+
+    /**
+     * Delete the config referenced by the PID
+     *
+     * @param pid pid
+     * @param expectedStatus expected response status
+     * @throws ClientException if the response status does not match any of the expectedStatus
+     */
+    public void deleteConfiguration(String pid, int... expectedStatus) throws ClientException {
+        FormEntityBuilder builder = FormEntityBuilder.create();
+        builder.addParameter("apply", "1");
+        builder.addParameter("delete", "1");
+        // make the request
+        SlingHttpResponse resp = this.doPost(URL_CONFIGURATION + "/" + pid, builder.build());
+        // check the returned status         
+        HttpUtils.verifyHttpStatus(resp, HttpUtils.getExpectedStatus(200, expectedStatus));
+    }
+
+
+    class ConfigurationPoller extends AbstractPoller {
+
+        private final String pid;
+        int[] expectedStatus;
+        public Map<String, Object> config;
+
+        public ConfigurationPoller(long waitInterval, long waitCount, String pid, int... expectedStatus) {
+            super(waitInterval, waitCount);
+            this.pid = pid;
+            this.config = null;
+            this.expectedStatus = expectedStatus;
+        }
+
+        @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 Map<String, Object> getConfig() {
+            return config;
+        }
+    }
+}
\ No newline at end of file

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/OsgiInstanceConfig.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,94 @@
+/*
+ * 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.osgi;
+
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.util.config.InstanceConfig;
+import org.apache.sling.testing.clients.util.config.InstanceConfigException;
+import org.apache.sling.testing.clients.SlingClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+/**
+ * <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 {
+
+    /**
+     * Number of retries for retrieving the current osgi config for save() and restore()
+     */
+    protected int waitCount = 20;
+
+    private static final Logger LOG = LoggerFactory.getLogger(OsgiInstanceConfig.class);
+    private final OsgiConsoleClient osgiClient;
+    private final String configPID;
+    private Map<String, Object> config;
+
+
+    /**
+     *
+     * @param client The Granite Client to be used internally
+     * @param configPID The PID for the OSGi configuration
+     * @param <T> The type of the Granite Client
+     * @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 {
+        this.osgiClient = client.adaptTo(OsgiConsoleClient.class);
+        this.configPID = configPID;
+
+        // Save the configuration
+        save();
+    }
+
+    /**
+     * Save the current OSGi configuration for the PID defined in the constructor
+     *
+     * @throws InstanceConfigException if the config cannot be saved
+     */
+    public InstanceConfig save() throws InstanceConfigException {
+        try {
+            this.config = osgiClient.getConfigurationWithWait(waitCount, 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);
+        }
+        return this;
+    }
+
+    /**
+     * Restore the current OSGi configuration for the PID defined in the constructor
+     *
+     * @throws InstanceConfigException if the config cannot be restored
+     */
+    public InstanceConfig restore() throws InstanceConfigException {
+        try {
+            osgiClient.editConfigurationWithWait(waitCount, 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);
+        }
+        return this;
+    }
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/WebconsoleClient.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,190 @@
+/*
+ * 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.osgi;
+
+
+import java.io.File;
+
+import org.apache.http.entity.mime.MultipartEntity;
+import org.apache.http.entity.mime.content.FileBody;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.testing.tools.http.RequestBuilder;
+import org.apache.sling.testing.tools.http.RequestExecutor;
+import org.apache.sling.testing.tools.http.RetryingContentChecker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** HTTP Client for the Felix webconsole - simplistic for now */
+public class WebconsoleClient {
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final RequestExecutor executor;
+    private final RequestBuilder builder;
+    private final String username;
+    private final String password;
+    
+    public static final String JSON_KEY_ID = "id";
+    public static final String JSON_KEY_VERSION = "version";
+    public static final String JSON_KEY_DATA = "data";
+    public static final String JSON_KEY_STATE = "state";
+    public static final String CONSOLE_BUNDLES_PATH = "/system/console/bundles";
+    
+    public WebconsoleClient(String slingServerUrl, String username, String password) {
+        this.builder = new RequestBuilder(slingServerUrl);
+        this.executor = new RequestExecutor(new DefaultHttpClient());
+        this.username = username;
+        this.password = password;
+    }
+    
+    public void uninstallBundle(String symbolicName, File f) throws Exception {
+        final long bundleId = getBundleId(symbolicName);
+        
+        log.info("Uninstalling bundle {} with bundleId {}", symbolicName, bundleId);
+
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("uninstall"));
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH+"/"+bundleId)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
+    public void installBundle(File f, boolean startBundle) throws Exception {
+        installBundle(f, startBundle, 0);
+    }
+    
+    /** Install a bundle using the Felix webconsole HTTP interface, with a specific start level */
+    public void installBundle(File f, boolean startBundle, int startLevel) throws Exception {
+        
+        // Setup request for Felix Webconsole bundle install
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("install"));
+        if(startBundle) {
+            entity.addPart("bundlestart", new StringBody("true"));
+        }
+        entity.addPart("bundlefile", new FileBody(f));
+        
+        if(startLevel > 0) {
+            entity.addPart("bundlestartlevel", new StringBody(String.valueOf(startLevel)));
+            log.info("Installing bundle {} at start level {}", f.getName(), startLevel);
+        } else {
+            log.info("Installing bundle {} at default start level", f.getName());
+        }
+        
+        // Console returns a 302 on success (and in a POST this
+        // is not handled automatically as per HTTP spec)
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(302);
+    }
+    
+    /** Check that specified bundle is installed - must be called
+     *  before other methods that take a symbolicName parameter, 
+     *  in case installBundle was just called and the actual 
+     *  installation hasn't happened yet. */
+    public void checkBundleInstalled(String symbolicName, int timeoutSeconds) {
+        final String path = getBundlePath(symbolicName, ".json");
+        new RetryingContentChecker(executor, builder, username, password).check(path, 200, timeoutSeconds, 500);
+    }
+    
+    private JSONObject getBundleData(String symbolicName) throws Exception {
+        // 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 = executor.execute(
+                builder.buildGetRequest(path)
+                .withCredentials(username, password)
+        ).assertStatus(200)
+        .getContent();
+        
+        final JSONObject root = new JSONObject(content);
+        if(!root.has(JSON_KEY_DATA)) {
+            throw new Exception(path + " does not provide '" + JSON_KEY_DATA + "' element, JSON content=" + content);
+        }
+        final JSONArray data = root.getJSONArray(JSON_KEY_DATA);
+        if(data.length() < 1) {
+            throw new Exception(path + "." + JSON_KEY_DATA + " is empty, JSON content=" + content);
+        }
+        final JSONObject bundle = data.getJSONObject(0);
+        if(!bundle.has(JSON_KEY_STATE)) {
+            throw new Exception(path + ".data[0].state missing, JSON content=" + content);
+        }
+        return bundle;
+    }
+
+    /** Get bundle id */
+    public long getBundleId(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getLong(JSON_KEY_ID);
+    }
+    
+    /** Get bundle version **/
+    public String getBundleVersion(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getString(JSON_KEY_VERSION);
+    }
+    
+    /** Get specified bundle state */
+    public String getBundleState(String symbolicName) throws Exception {
+        final JSONObject bundle = getBundleData(symbolicName);
+        return bundle.getString(JSON_KEY_STATE);
+    }
+    
+    /** Start specified bundle */
+    public void startBundle(String symbolicName) throws Exception {
+        // To start the bundle we POST action=start to its URL
+        final String path = getBundlePath(symbolicName, null);
+        log.info("Starting bundle {} via {}", symbolicName, path);
+        
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action",new StringBody("start"));
+        executor.execute(
+                builder.buildPostRequest(path)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+    private String getBundlePath(String symbolicName, String extension) {
+        return CONSOLE_BUNDLES_PATH + "/" + symbolicName 
+        + (extension == null ? "" : extension);
+    }
+
+    /** Calls PackageAdmin.refreshPackages to enforce re-wiring of all bundles. */
+    public void refreshPackages() throws Exception {
+        log.info("Refresh packages.");
+
+        final MultipartEntity entity = new MultipartEntity();
+        entity.addPart("action", new StringBody("refreshPackages"));
+
+        executor.execute(
+                builder.buildPostRequest(CONSOLE_BUNDLES_PATH)
+                .withCredentials(username, password)
+                .withEntity(entity)
+        ).assertStatus(200);
+    }
+    
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/osgi/package-info.java Fri Apr 29 14:03:32 2016
@@ -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.
+ */
+/**
+ * OSGI testing tools.
+ */
+@aQute.bnd.annotation.Version("1.0.0")
+package org.apache.sling.testing.clients.osgi;

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/package-info.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/package-info.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/package-info.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/package-info.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import aQute.bnd.annotation.Version;
+

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/FormEntityBuilder.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,81 @@
+/*
+ * 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.entity.UrlEncodedFormEntity;
+import org.apache.http.message.BasicNameValuePair;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper for creating Entity objects for POST requests.
+ */
+public class FormEntityBuilder {
+    public final static String DEFAULT_ENCODING = "UTF-8";
+
+    private final List<NameValuePair> params;
+    private String encoding;
+
+    public static FormEntityBuilder create() {
+        return new FormEntityBuilder();
+    }
+
+    FormEntityBuilder() {
+        params = new ArrayList<NameValuePair>();
+        encoding = DEFAULT_ENCODING;
+    }
+
+    public FormEntityBuilder addAllParameters(Map<String, String> parameters) {
+        if (parameters != null) {
+            for (String key : parameters.keySet()) {
+                addParameter(key, parameters.get(key));
+            }
+        }
+
+        return this;
+    }
+
+    public FormEntityBuilder addAllParameters(List<NameValuePair> parameters) {
+        if (parameters != null) {
+            params.addAll(parameters);
+        }
+
+        return this;
+    }
+
+    public FormEntityBuilder addParameter(String name, String value) {
+        params.add(new BasicNameValuePair(name, value));
+        return this;
+    }
+
+    public FormEntityBuilder setEncoding(String encoding) {
+        this.encoding = encoding;
+        return this;
+    }
+
+    public UrlEncodedFormEntity build() {
+        try {
+            return new UrlEncodedFormEntity(params, encoding);
+        } catch (UnsupportedEncodingException ue) {
+            throw new Error("Unexpected UnsupportedEncodingException", ue);
+        }
+    }
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/HttpUtils.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,183 @@
+/*
+ * 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.Header;
+import org.apache.http.HttpResponse;
+import org.apache.sling.testing.clients.ClientException;
+import org.apache.sling.testing.clients.SlingHttpResponse;
+
+
+import java.net.URI;
+
+
+public class HttpUtils {
+
+    /**
+     * Verify expected status and dump response in case expected status is not returned.
+     * Warning! It will try to consume the entity in case of error
+     *
+     * @param response       The Sling HTTP response
+     * @param expectedStatus List of acceptable HTTP Statuses
+     * @throws ClientException if status is not expected
+     */
+    public static void verifyHttpStatus(SlingHttpResponse response, int... expectedStatus) throws ClientException {
+        if (!checkStatus(response, expectedStatus)) {
+            throwError(response, buildDefaultErrorMessage(response), expectedStatus);
+        }
+    }
+
+    /**
+     * Verify expected status and show error message in case expected status is not returned.
+     *
+     * @param response       The SlingHttpResponse of an executed request.
+     * @param errorMessage   error message; if {@code null}, errorMessage is extracted from response
+     * @param expectedStatus List of acceptable HTTP Statuses
+     * @throws ClientException if status is not expected
+     */
+    public static void verifyHttpStatus(HttpResponse response, String errorMessage, int... expectedStatus)
+            throws ClientException {
+        if (!checkStatus(response, expectedStatus)) {
+            throwError(response, errorMessage, expectedStatus);
+        }
+    }
+
+    private static boolean checkStatus(HttpResponse response, int... expectedStatus)
+            throws ClientException {
+
+        // if no HttpResponse was given
+        if (response == null) {
+            throw new NullPointerException("The response is null!");
+        }
+
+        // if no expected statuses are given
+        if (expectedStatus == null || expectedStatus.length == 0) {
+            throw new IllegalArgumentException("At least one expected HTTP Status must be set!");
+        }
+
+        // get the returned HTTP Status
+        int givenStatus = getHttpStatus(response);
+
+        // check if it matches with an expected one
+        for (int expected : expectedStatus) {
+            if (givenStatus == expected) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private static boolean throwError(HttpResponse response, String errorMessage, int... expectedStatus)
+            throws ClientException {
+        // build error message
+        String errorMsg = "Expected HTTP Status: ";
+        for (int expected : expectedStatus) {
+            errorMsg += expected + " ";
+        }
+
+        // get the returned HTTP Status
+        int givenStatus = getHttpStatus(response);
+
+        errorMsg += ". Instead " + givenStatus + " was returned!\n";
+        if (errorMessage != null) {
+            errorMsg += errorMessage;
+        }
+
+        // throw the exception
+        throw new ClientException(errorMsg);
+    }
+
+
+    /**
+     * Build default error message
+     *
+     * @param resp The response of a sling request
+     * @return default error message
+     */
+    public static String buildDefaultErrorMessage(SlingHttpResponse resp) {
+
+        String content = resp.getContent();
+
+        // if no response content is available
+        if (content == null) return "";
+        String errorMsg = resp.getSlingMessage();
+
+        errorMsg = (errorMsg == null || errorMsg.length() == 0)
+                // any other returned content
+                ? " Response Content:\n" + content
+                // response message from sling response
+                : "Error Message: \n" + errorMsg;
+
+        return errorMsg;
+    }
+
+    /**
+     * Get HTTP Status of the response.
+     *
+     * @param response The RequestExecutor of an executed request.
+     * @return The HTTP Status of the response
+     * @throws ClientException never (kept for uniformity)
+     */
+    public static int getHttpStatus(HttpResponse response) throws ClientException {
+        return response.getStatusLine().getStatusCode();
+    }
+
+    /**
+     * Get the first 'Location' header and verify it's a valid URI.
+     *
+     * @param response HttpResponse the http response
+     * @return the location path
+     * @throws ClientException never (kept for uniformity)
+     */
+    public static String getLocationHeader(HttpResponse response) throws ClientException {
+        if (response == null) throw new ClientException("Response must not be null!");
+
+        String locationPath = null;
+        Header locationHeader = response.getFirstHeader("Location");
+        if (locationHeader != null) {
+            String location = locationHeader.getValue();
+            URI locationURI = URI.create(location);
+            locationPath = locationURI.getPath();
+        }
+
+        if (locationPath == null) {
+            throw new ClientException("not able to determine location path");
+        }
+        return locationPath;
+    }
+
+    /**
+     * Check if expected status is in range
+     *
+     * @param response the http response
+     * @param range    the http status range
+     * @return true if response is in range
+     */
+    public static boolean isInHttpStatusRange(HttpResponse response, int range) {
+        return range == response.getStatusLine().getStatusCode() / 100 * 100;
+    }
+
+    public static int[] getExpectedStatus(int defaultStatus, int... expectedStatus) {
+        if (expectedStatus == null || expectedStatus.length == 0) {
+            expectedStatus = new int[]{defaultStatus};
+        }
+        return expectedStatus;
+    }
+
+
+}
\ No newline at end of file

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/InputStreamBodyWithLength.java Fri Apr 29 14:03:32 2016
@@ -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
+ *
+ * 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.entity.ContentType;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.sling.testing.clients.ClientException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * If we want to upload a file that is a resource in a jar file, the http client expects a content length.
+ */
+public class InputStreamBodyWithLength extends InputStreamBody {
+    private long streamLength;
+
+    public InputStreamBodyWithLength(String resourcePath, String contentType, String fileName) throws ClientException {
+        super(ResourceUtil.getResourceAsStream(resourcePath), ContentType.create(contentType), fileName);
+        this.streamLength = getResourceStreamLength(resourcePath);
+    }
+
+    @Override
+    public long getContentLength() {
+        return streamLength;
+    }
+
+    /**
+     * Returns the length of a resource (which is needed for the InputStreamBody
+     * to work. Can't currently think of a better solution than going through
+     * the resource stream and count.
+     *
+     * @param resourcePath path to the file
+     * @return the size of the resource
+     */
+    private static long getResourceStreamLength(String resourcePath) throws ClientException {
+        int streamLength = 0;
+        InputStream stream = ResourceUtil.getResourceAsStream(resourcePath);
+        try {
+            for (int avail = stream.available(); avail > 0; avail = stream.available()) {
+                streamLength += avail;
+                stream.skip(avail);
+            }
+        } catch (IOException e) {
+            throw new ClientException("Could not read " + resourcePath + "!", e);
+        } finally {
+            try {
+                stream.close();
+            } catch (IOException e) {
+                throw new ClientException("Could not close Inputstream for " + resourcePath + "!", e);
+            }
+        }
+        return streamLength;
+    }
+}
\ No newline at end of file

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/JsonUtils.java Fri Apr 29 14:03:32 2016
@@ -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: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/PortAllocator.java Fri Apr 29 14:03:32 2016
@@ -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: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/ResourceUtil.java Fri Apr 29 14:03:32 2016
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+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(String resourcePath) {
+        return Thread.currentThread().getContextClassLoader().getClass().getResourceAsStream(resourcePath);
+    }
+
+    /**
+     * 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(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"));
+                while ((line = reader.readLine()) != null) {
+                    sb.append(line).append("\n");
+                }
+            } finally {
+                resourceAsStream.close();
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+}

Added: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/SlingParameter.java Fri Apr 29 14:03:32 2016
@@ -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: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/URLParameterBuilder.java Fri Apr 29 14:03:32 2016
@@ -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: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/UniquePaths.java Fri Apr 29 14:03:32 2016
@@ -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: sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java
URL: http://svn.apache.org/viewvc/sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java?rev=1741632&view=auto
==============================================================================
--- sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java (added)
+++ sling/trunk/testing/http/clients/src/main/java/org/apache/sling/testing/clients/util/XSSUtils.java Fri Apr 29 14:03:32 2016
@@ -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);
+    }
+
+}