All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.cq.testing.client.TopologyClient Maven / Gradle / Ivy

There is a newer version: 1.2.9
Show newest version
/*
 * Copyright 2017 Adobe Systems Incorporated
 *
 * Licensed 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 com.adobe.cq.testing.client;

import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.sling.testing.clients.ClientException;
import org.apache.sling.testing.clients.SlingClientConfig;
import org.apache.sling.testing.clients.SlingHttpResponse;
import org.apache.sling.testing.clients.util.FormEntityBuilder;
import org.apache.sling.testing.clients.util.HttpUtils;
import org.apache.sling.testing.clients.util.JsonUtils;
import org.apache.sling.testing.clients.util.poller.Polling;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeoutException;

import static org.apache.http.HttpStatus.SC_OK;

public class TopologyClient extends CQClient {

    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(TopologyClient.class);

    public static final String DEFAULT_CONNECTOR_PATH = "/libs/sling/topology/connector";
    public static final String QE_TOPOLOGY_SERVLET_PATH = "/libs/granite/qe/topology";
    public static final String CONNECTOR_URLS_PATH = "connectorurls";
    public static final String CLUSTER_VIEW_PATH = "clusterview";

    public static final String UTF_8 = "UTF-8";
    public static final String QE_SLINGSETTINGS_SERVLET_PATH = "/libs/granite/qe/slingsettings";

    public TopologyClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException {
        super(http, config);
    }

    public TopologyClient(URI serverUrl, String user, String password) throws ClientException {
        super(serverUrl, user, password);
    }

    /**
     * Join a topology by adding the connector url to the discovery configuration
     * @param baseUrl The base url of the instance about to be joined; uses the {@link TopologyClient#DEFAULT_CONNECTOR_PATH}
     *               to build the full connector URL
     * @return true if the topology was joined, false otherwise
     * @throws ClientException if the request failed
     */
    public boolean joinTopology(String baseUrl) throws ClientException {
        return joinTopology(baseUrl, DEFAULT_CONNECTOR_PATH);
    }

    /**
     * Join a topology by adding the connector url to the discovery configuration
     * @param baseUrl The full connector URL to be added to the discovery configuration
     * @param connectorPath path to the connector
     * @return true if the topology was joined, false otherwise
     * @throws ClientException if the request failed
     */
    public boolean joinTopology(String baseUrl, String connectorPath) throws ClientException {
        Set newConnectorUrlsList = AddConnUrl(baseUrl, connectorPath);

        // Write the configuration with the new values
        setConnectorUrls(newConnectorUrlsList);

        return true;
    }

    private Set AddConnUrl(String baseUrl, String connectorPath) throws ClientException {
        URL bUrl, connectorUrl;

        try {
            bUrl = new URL(baseUrl);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("baseUrl is not a valid URL", e);
        }

        try {
            connectorUrl = new URL(bUrl, connectorPath);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Could not create connector URL from server URL and connector path", e);
        }

        String connectorUrlString = connectorUrl.toString();
        Set newConnectorUrlsList = getConnectorUrls();

        // Add the new connector url if it doesn't exist
        newConnectorUrlsList.add(connectorUrlString);
        return newConnectorUrlsList;
    }

    /**
     * Join a topology by adding the connector url to the discovery configuration.
     * This method waits and retries until the topology is current
     * @param baseUrl base url
     * @param connectorPath connector path
     * @param timeout time (in milliseconds) to wait before throwing an exception
     * @return true
     * @throws ClientException if the topology could not be joined
     * @throws InterruptedException if interrupted while waiting for the server
     */
    public boolean joinTopologyWithWait(String baseUrl, String connectorPath, long timeout) throws ClientException, InterruptedException {
        Set newConnectorUrlsList = AddConnUrl(baseUrl, connectorPath);
        // Write the configuration with the new values
        setConnectorUrlsWithWait(newConnectorUrlsList, timeout);

        return true;
    }

    /**
     * Join a topology by adding the connector url to the discovery configuration.
     * This method waits and retries until the topology is current
     * @param baseUrl base url
     * @param timeout time (in milliseconds) to wait before throwing an exception
     * @return true
     * @throws ClientException if the topology could not be joined
     * @throws InterruptedException if interrupted while waiting for the server
     */
    public boolean joinTopologyWithWait(String baseUrl, long timeout) throws ClientException, InterruptedException {
        return joinTopologyWithWait(baseUrl, DEFAULT_CONNECTOR_PATH, timeout);
    }

    /**
     * Leave a topology by removing a connector URL from the discovery configuration
     * @param baseUrl the base URL of the instance about to be left; uses the {@link TopologyClient#DEFAULT_CONNECTOR_PATH}
     *               to build the full connector URL
     * @return true if topology was left, false otherwise
     * @throws ClientException in case of error
     */
    public boolean leaveTopology(String baseUrl) throws ClientException {
        return leaveTopology(baseUrl, DEFAULT_CONNECTOR_PATH);
    }

    /**
     * Leave a topology by removing a connector URL from the discovery configuration
     * @param baseUrl the full connector URL to be removed
     * @param connectorPath path to the connector
     * @return true if topology was left, false otherwise
     * @throws ClientException if the request failed
     */
    public boolean leaveTopology(String baseUrl, String connectorPath) throws ClientException {
        Set newConnectorUrlsList = removeConnUrl(baseUrl, connectorPath);

        // Write the configuration with the new values
        setConnectorUrls(newConnectorUrlsList);

        return true;
    }

    private Set removeConnUrl(String baseUrl, String connectorPath) throws ClientException {
        URL bUrl, connectorUrl;

        try {
            bUrl = new URL(baseUrl);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("baseUrl is not a valid URL", e);
        }

        try {
            connectorUrl = new URL(bUrl, connectorPath);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Could not create connector URL from server URL and connector path", e);
        }

        Set newConnectorUrlsList = getConnectorUrls();
        // remove the connector url if it exists;
        newConnectorUrlsList.remove(connectorUrl.toString());
        return newConnectorUrlsList;
    }

    /**
     * Leave a topology by removing a connector URL from the discovery configuration
     * This method waits and retries until the topology is current
     *
     * @param baseUrl base url
     * @param connectorPath connector path
     * @param timeout time (in milliseconds) to wait before throwing an exception
     * @return true
     * @throws ClientException if it could not leave the topology
     * @throws InterruptedException if interrupted while waiting for the server
     */
    public boolean leaveTopologyWithWait(String baseUrl, String connectorPath, long timeout) throws ClientException, InterruptedException {
        Set newConnectorUrlsList = removeConnUrl(baseUrl, connectorPath);

        // Write the configuration with the new values
        setConnectorUrlsWithWait(newConnectorUrlsList, timeout);
        return true;
    }

    /**
     * Leave a topology by removing a connector URL from the discovery configuration
     * This method waits and retries until the topology is current
     *
     * @param baseUrl base url
     * @param timeout time (in milliseconds) to wait before throwing an exception
     * @return true
     * @throws ClientException if it could not leave the topology
     * @throws InterruptedException if interrupted while waiting for the server
     */
    public boolean leaveTopologyWithWait(String baseUrl, long timeout) throws ClientException, InterruptedException {
        return leaveTopologyWithWait(baseUrl, DEFAULT_CONNECTOR_PATH, timeout);
    }


    /**
     * Get the set of connector URLs configured for the discovery service
     *
     * @return the set of URLs
     * @throws ClientException if the request failed
     */
    public Set getConnectorUrls() throws ClientException {
        JsonNode json = getConnectorUrlsJson();
        return parseConnectorUrls(json);
    }

    /**
     * Get the set of connector URLs configured for the discovery service.
     * The method retries to get the list until the configuration is saved and the topology is current
     *
     * @param timeout time to wait (in milliseconds) before giving up
     * @return set of connector URLs
     * @throws ClientException if the topology could not be joined
     * @throws InterruptedException if interrupted while waiting for the server
     */
    public Set getConnectorUrlsWithWait(long timeout) throws ClientException, InterruptedException {
        class TopologyPoller extends Polling {
            public JsonNode json = null;
            public Set connectorUrls = null;

            @Override
            public Boolean call() throws Exception {
                json = getConnectorUrlsJson();
                if (null != json && "ok".equals(json.get("status").textValue())
                        && json.get("is_current").booleanValue()) {
                    connectorUrls = parseConnectorUrls(json);
                    return true;
                }
                return false;
            }
        }

        TopologyPoller poller = new TopologyPoller();
        try {
            poller.poll(timeout, 1000);
        } catch (TimeoutException e) {
            throw new ClientException("Failed to retrieve connector urls in " + poller.getWaited() + " ms", e);
        }

        return poller.connectorUrls;
    }

    private JsonNode getConnectorUrlsJson(int... statusCode) throws ClientException {
        // Get the current connectors urls list
        SlingHttpResponse response = doGet(QE_TOPOLOGY_SERVLET_PATH + "." + CONNECTOR_URLS_PATH + ".json",
                HttpUtils.getExpectedStatus(SC_OK, statusCode));
        return JsonUtils.getJsonNodeFromString(response.getContent());
    }

    private Set parseConnectorUrls(JsonNode json) throws ClientException {
        Set connectorUrls = new HashSet<>(5);
        Iterator connectorListIterator = json.get("data").elements();
        while (connectorListIterator.hasNext()){
            String connUrl;
            try {
                connUrl = URLDecoder.decode(connectorListIterator.next().textValue(), UTF_8);
            } catch (UnsupportedEncodingException e) {
                throw new ClientException("Could not decode URL", e);
            }
            // skip empty values
            if (null == connUrl || connUrl.isEmpty())
                continue;
            connectorUrls.add(connUrl);
        }

        return connectorUrls;
    }


    /**
     * Write the discovery service configuration with a set of connector URLs
     * @param connectorUrlsList the set of connector URLs to be written
     * @throws ClientException if the request failed
     */
    public void setConnectorUrls(Set connectorUrlsList) throws ClientException {
        String connectorUrls = StringUtils.join(connectorUrlsList.toArray(), ",");
        FormEntityBuilder feb = FormEntityBuilder.create().addParameter("connectorUrls", connectorUrls);
        doPost(QE_TOPOLOGY_SERVLET_PATH + "." + CONNECTOR_URLS_PATH + ".json", feb.build(), SC_OK);
    }

    /**
     * Write the discovery service configuration with a set of connector URLs
     * The method retries until the configuration was saved and the topology is current
     *
     * @param connectorUrlsList list of connector urls
     * @param timeout time (in milliseconds) to wait before throwing an exception
     * @throws ClientException if the request failed
     * @throws InterruptedException to mark this method as "waiting"
     */
    public void setConnectorUrlsWithWait(Set connectorUrlsList, long timeout)
            throws ClientException, InterruptedException {
        setConnectorUrls(connectorUrlsList);
        getConnectorUrlsWithWait(timeout);
    }

    /**
     *
     * @return The Sling ID of this instance
     * @throws ClientException if call to instance fails
     */
    public String getSlingId() throws ClientException {
        // Get the current connectors urls list
        SlingHttpResponse response = doGet(QE_SLINGSETTINGS_SERVLET_PATH + ".sling_id.json", SC_OK);
        JsonNode json = JsonUtils.getJsonNodeFromString(response.getContent());
        // this is a String and not a UUID because the sling method in SlingSettingsService returns String

        return json.get("data").get("sling_id").textValue();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy