| /* |
| * 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; |
| } |
| } |
| } |