blob: 9c5a92878b8ec63a7663f311e8360e49446327a5 [file] [log] [blame]
/*
* 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;
}
}
}