This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-testing-clients.git
commit 42e5ba4b33261a09190f875a31058a966649c1c2 Author: Valentin Olteanu <[email protected]> AuthorDate: Tue Mar 6 10:56:41 2018 +0100 SLING-7169 - Add IndexingClient with waitForAsyncIndexing --- .../testing/clients/indexing/IndexingClient.java | 424 +++++++++++++++++++++ .../testing/clients/indexing/package-info.java | 23 ++ .../clients/indexing/IndexingClientTest.java | 196 ++++++++++ 3 files changed, 643 insertions(+) diff --git a/src/main/java/org/apache/sling/testing/clients/indexing/IndexingClient.java b/src/main/java/org/apache/sling/testing/clients/indexing/IndexingClient.java new file mode 100644 index 0000000..7808891 --- /dev/null +++ b/src/main/java/org/apache/sling/testing/clients/indexing/IndexingClient.java @@ -0,0 +1,424 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +package org.apache.sling.testing.clients.indexing; + +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.SlingClient; +import org.apache.sling.testing.clients.SlingClientConfig; +import org.apache.sling.testing.clients.osgi.OsgiConsoleClient; +import org.apache.sling.testing.clients.query.QueryClient; +import org.apache.sling.testing.clients.util.poller.Polling; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; + +import static java.util.UUID.randomUUID; +import static org.apache.http.HttpStatus.SC_OK; + +/** + * <p>Interface to the oak indexing mechanism</p> + * + * <p>Exposes {@link #waitForAsyncIndexing(long, long)} for waiting all the indexing lanes to finish + * indexing and to guarantee all the indices are up to date</p> + * + * <p>For using {@link #waitForAsyncIndexing(long, long)}, the user must have access rights to: + * <ul> + * <li>read/write in {@code /tmp}</li> + * <li>install bundles via {@link org.apache.sling.testing.clients.osgi.OsgiConsoleClient} + * (if the query servlet was not previously installed)</li> + * </ul> + * In short, it requires administrative rights. + * </p> + */ +public class IndexingClient extends SlingClient { + private static final Logger LOG = LoggerFactory.getLogger(IndexingClient.class); + + /** Root of all the data created by this tool. Its presence marks that it was already installed */ + private static final String WAIT_FOR_ASYNC_INDEXING_ROOT = "/tmp/testing/waitForAsyncIndexing"; + + /** Where new index definitions are added */ + private static final String INDEX_PATH = WAIT_FOR_ASYNC_INDEXING_ROOT + "/oak:index"; + + /** Where the content to be indexed is created */ + private static final String CONTENT_PATH = WAIT_FOR_ASYNC_INDEXING_ROOT + "/content"; + + /** Prefix to be added to all the index names */ + private static final String INDEX_PREFIX = "testIndexingLane-"; + + /** Prefix to be added to all the properties */ + private static final String PROPERTY_PREFIX = "testProp-"; + + /** Prefix to be added to all the property values */ + private static final String VALUE_PREFIX = "testasyncval-"; + + /** Prefix to be added to all the tags */ + private static final String TAG_PREFIX = "testTag"; + + /** Placeholder for index name */ + private static final String INDEX_NAME_PLACEHOLDER = "<<INDEXNAME>>"; + + /** Placeholder for random, unique parts in content and queries */ + private static final String PROPERTY_PLACEHOLDER = "<<PROPNAME>>"; + + /** Placeholder for random, unique parts in content and queries */ + private static final String VALUE_PLACEHOLDER = "<<RANDVAL>>"; + + /** Placeholder for identifying the lane to which the index and the content belongs to */ + private static final String LANE_PLACEHOLDER = "<<LANE>>"; + + /** Placeholder for identifying the tag to which the index and the queries belongs to */ + private static final String TAG_PLACEHOLDER = "<<TAG>>"; + + /** Template for index definitions to be installed */ + private static final String INDEX_DEFINITION = "{" + + " '" + INDEX_NAME_PLACEHOLDER + "': {\n" + + " 'jcr:primaryType': 'oak:QueryIndexDefinition',\n" + + " 'type': 'lucene',\n" + + " 'async': '" + LANE_PLACEHOLDER + "',\n" + + " 'tags': '" + TAG_PLACEHOLDER + "',\n" + + " 'indexRules': {\n" + + " 'jcr:primaryType': 'nt:unstructured',\n" + + " 'nt:base': {\n" + + " 'jcr:primaryType': 'nt:unstructured',\n" + + " 'properties': {\n" + + " 'jcr:primaryType': 'nt:unstructured',\n" + + " '" + PROPERTY_PLACEHOLDER + "': {\n" + + " 'jcr:primaryType': 'nt:unstructured',\n" + + " 'name': '" + PROPERTY_PLACEHOLDER + "',\n" + + " 'analyzed': true\n" + + " }\n" + + " }\n" + + " }\n" + + " }\n" + + " }" + + "}"; + + /** Template for the content to be created and searched */ + private static final String CONTENT_DEFINITION = "{" + + "'testContent-" + LANE_PLACEHOLDER + "-" + VALUE_PLACEHOLDER + "': {" + + " 'jcr:primaryType': 'nt:unstructured', " + + " '" + PROPERTY_PLACEHOLDER +"': '" + VALUE_PLACEHOLDER + "'" + + "}}"; + + + /** Templates for queries to be executed against each index, in order of priority */ + private static final List<String> QUERIES = Arrays.asList( + // for Oak versions that support option(index tag testTag) + "/jcr:root" + WAIT_FOR_ASYNC_INDEXING_ROOT + "//*" + + "[jcr:contains(@" + PROPERTY_PLACEHOLDER + ", '" + VALUE_PLACEHOLDER +"')] " + + "option(traversal ok, index tag " + TAG_PLACEHOLDER + ")", + // for older Oak versions + "/jcr:root" + WAIT_FOR_ASYNC_INDEXING_ROOT + "//*" + + "[jcr:contains(@" + PROPERTY_PLACEHOLDER + ", '" + VALUE_PLACEHOLDER +"')] " + + "option(traversal ok)" + ); + + /** Global counter for how much time was spent in total waiting for async indexing */ + private static final AtomicLong totalWaited = new AtomicLong(); + + /** + * Constructor used by Builders and adaptTo(). <b>Should never be called directly from the code.</b> + * + * @param http the underlying HttpClient to be used + * @param config sling specific configs + * @throws ClientException if the client could not be created + */ + public IndexingClient(CloseableHttpClient http, SlingClientConfig config) throws ClientException { + super(http, config); + } + + /** + * <p>Handy constructor easy to use in simple tests. Creates a client that uses basic authentication.</p> + * + * <p>For constructing clients with complex configurations, use a {@link InternalBuilder}</p> + * + * <p>For constructing clients with the same configuration, but a different class, use {@link #adaptTo(Class)}</p> + * + * @param url url of the server (including context path) + * @param user username for basic authentication + * @param password password for basic authentication + * @throws ClientException never, kept for uniformity with the other constructors + */ + public IndexingClient(URI url, String user, String password) throws ClientException { + super(url, user, password); + } + + /** + * Retrieves the list of indexing lanes configured on the instance + * + * @return list of lane names + * @throws ClientException if the request fails + */ + public List<String> getLaneNames() throws ClientException { + try { + Object asyncConfigs = adaptTo(OsgiConsoleClient.class) + .getConfiguration("org.apache.jackrabbit.oak.plugins.index.AsyncIndexerService") + .get("asyncConfigs"); + + if (asyncConfigs instanceof String[]) { + String[] configs = (String[]) asyncConfigs; // ugly, we should refactor OsgiConsoleClient + + List<String> lanes = new ArrayList<>(configs.length); + for (String asyncConfig : configs) { + lanes.add(asyncConfig.split(":")[0]); + } + return lanes; + } else { + throw new ClientException("Cannot retrieve config from AsyncIndexerService, asyncConfigs is not a String[]"); + } + } catch (Exception e) { + throw new ClientException("Failed to retrieve lanes", e); + } + } + + /** + * <p>Blocks until all the async indices are up to date, to guarantee that the susequent queries return + * all the results.</p> + * + * <p>Works by creating a custom index for each lane, adding specific content to + * be indexed by these indices and then repeatedly searching this content until everything is found (indexed). + * All the content is created under {@value #WAIT_FOR_ASYNC_INDEXING_ROOT}</p> + * + * <p>Indices are automatically created, but only if not already present. + * This method does not delete the indices at the end to avoid generating too much noise on the instance. + * To completely clean any traces, the user must call {@link #uninstall()}</p> + * + * <p>Requires administrative rights to install bundles and to create nodes under + * {@value #WAIT_FOR_ASYNC_INDEXING_ROOT}</p> + * + * @param timeout max time to wait, in milliseconds, before throwing {@code TimeoutException} + * @param delay time to sleep between retries + * @throws TimeoutException if the {@code timeout} was reached before all the indices were updated + * @throws InterruptedException to mark this method as waiting + * @throws ClientException if an error occurs during http requests/responses + */ + public void waitForAsyncIndexing(final long timeout, final long delay) + throws TimeoutException, InterruptedException, ClientException { + + install(); // will install only if needed + + final String uniqueValue = randomUUID().toString(); // to be added in all the content nodes + final List<String> lanes = getLaneNames(); // dynamically detect which lanes to wait for + + Polling p = new Polling(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return searchContent(lanes, uniqueValue); + } + }); + + try { + createContent(lanes, uniqueValue); + p.poll(timeout, delay); + } finally { + long total = totalWaited.addAndGet(p.getWaited()); // count waited in all the cases (timeout) + LOG.info("Waited for async index {} ms (overall: {} ms)", p.getWaited(), total); + try { + deleteContent(uniqueValue); + } catch (ClientException e) { + LOG.warn("Failed to delete temporary content", e); + } + } + } + + /** + * Same as {@link #waitForAsyncIndexing(long timeout, long delay)}, + * but with default values for {@code timeout=1min} and {@code delay=500ms}. + * + * @see #waitForAsyncIndexing(long, long) + * + * @throws TimeoutException if the {@code timeout} was reached before all the indices were updated + * @throws InterruptedException to mark this method as waiting + * @throws ClientException if an error occurs during http requests/responses + */ + public void waitForAsyncIndexing() throws InterruptedException, ClientException, TimeoutException { + waitForAsyncIndexing(TimeUnit.MINUTES.toMillis(1), 500); + } + + /** + * <p>Creates the necessary custom indices in the repository, if not already present.</p> + * + * <p>It is automatically called in each wait, there's no need to + * explicitly invoke it from the test.</p> + * + * @throws ClientException if the installation fails + */ + public void install() throws ClientException { + if (exists(WAIT_FOR_ASYNC_INDEXING_ROOT)) { + LOG.debug("Skipping install since {} already exists", WAIT_FOR_ASYNC_INDEXING_ROOT); + return; + } + + createNodeRecursive(WAIT_FOR_ASYNC_INDEXING_ROOT, "sling:Folder"); + createNode(INDEX_PATH, "nt:unstructured"); + createNode(CONTENT_PATH, "sling:Folder"); + + final List<String> lanes = getLaneNames(); + for (String lane : lanes) { + String indexName = getIndexName(lane); + String indexDefinition = replacePlaceholders(INDEX_DEFINITION, lane, null); + LOG.info("Creating index {} in {}", indexName, INDEX_PATH); + LOG.debug(indexDefinition); + importContent(INDEX_PATH, "json", indexDefinition); + // Trigger reindex to make sure the complete index definition is used + setPropertyString(INDEX_PATH + "/" + indexName, "reindex", "true"); + } + } + + /** + * <p>Cleans all the data generated by {@link #install()} and {@link #waitForAsyncIndexing(long, long)}.</p> + * + * <p>User must manually call this if needed, as opposed to {@link #install()}, which is called + * automatically.</p> + * + * @throws ClientException if the cleanup failed + */ + public void uninstall() throws ClientException { + deletePath(WAIT_FOR_ASYNC_INDEXING_ROOT, SC_OK); + } + + /** + * Creates all the content structures to be indexed, one for each lane, + * with the given {@code uniqueValue}, to make them easily identifiable + * + * @param lanes list of lanes for which to create the content + * @param uniqueValue the unique value to be added + * @throws ClientException if the content creation fails + */ + private void createContent(final List<String> lanes, final String uniqueValue) throws ClientException { + // All the content is grouped under the same node + String contentHolder = CONTENT_PATH + "/" + uniqueValue; + LOG.debug("creating content in {}", contentHolder); + createNode(contentHolder, "sling:Folder"); + + for (String lane : lanes) { + String contentNode = replacePlaceholders(CONTENT_DEFINITION, lane, uniqueValue); + LOG.debug("creating: {}", contentNode); + importContent(contentHolder, "json", contentNode); + } + } + + /** + * Deletes the temporary nodes created in {@link #createContent(List, String)} + * + * @throws ClientException if the content cannot be deleted + */ + private void deleteContent(String uniqueValue) throws ClientException { + if (uniqueValue != null) { + String contentHolder = CONTENT_PATH + "/" + uniqueValue; + LOG.debug("deleting {}", contentHolder); + deletePath(contentHolder, SC_OK); + } + } + + /** + * Performs queries for each of the created content and checks that all return results + * + * @param lanes list of lanes for which to run queries + * @param uniqueValue the unique value to be used in queries + * @return true if all the queries returned at least one result (all indices are up to date) + * @throws ClientException if the http request failed + * @throws InterruptedException to mark this method as waiting + */ + private boolean searchContent(final List<String> lanes, final String uniqueValue) + throws ClientException, InterruptedException { + for (String lane : lanes) { + if (!searchContentForIndex(lane, uniqueValue)) { + return false; + } + } + // Queries returned at least one result for each index + return true; + } + + /** + * Tries all the known queries for a specific index lane, + * until one of them returns at least one result. + * + * @param lane the indexing lane to query + * @param uniqueValue the unique value to be used in queries + * @return true if at least one query returned results + * @throws ClientException if the http request fails + * @throws InterruptedException to mark this method as waiting + */ + private boolean searchContentForIndex(final String lane, final String uniqueValue) + throws ClientException, InterruptedException { + QueryClient queryClient = adaptTo(QueryClient.class); + + for (String query : QUERIES) { + // prepare the query with the final values + String indexName = getIndexName(lane); + String effectiveQuery = replacePlaceholders(query, lane, uniqueValue); + + try { + // Check query plan to make sure we use the good index + String plan = queryClient.getPlan(effectiveQuery, QueryClient.QueryType.XPATH); + if (plan.contains(indexName)) { + // The proper index is used, we can check the results + long results = queryClient.doCount(effectiveQuery, QueryClient.QueryType.XPATH); + if (results > 0) { + LOG.debug("Found {} results using query {}", results, effectiveQuery); + return true; + } + } else { + LOG.debug("Did not find index {} in plan: {}", indexName, plan); + LOG.debug("Will try the next query, if available"); + } + } catch (ClientException e) { + if (e.getHttpStatusCode() == 400) { + LOG.debug("Unsupported query: {}", effectiveQuery); + LOG.debug("Will try the next query, if available"); + } else { + // We don't continue if there's another problem + throw e; + } + } + } + // No query returned results + return false; + } + + private String replacePlaceholders(String original, String lane, String value) { + // Tags must be alphanumeric + String tag = StringUtils.capitalize(lane.replaceAll("[^A-Za-z0-9]", "")); + + String result = original; + result = StringUtils.replace(result, LANE_PLACEHOLDER, lane); + result = StringUtils.replace(result, INDEX_NAME_PLACEHOLDER, INDEX_PREFIX + lane); + result = StringUtils.replace(result, PROPERTY_PLACEHOLDER, PROPERTY_PREFIX + lane); + result = StringUtils.replace(result, VALUE_PLACEHOLDER, VALUE_PREFIX + value); + result = StringUtils.replace(result, TAG_PLACEHOLDER, TAG_PREFIX + tag); + + return result; + } + + private String getIndexName(final String lane) { + return INDEX_PREFIX + lane; + } +} diff --git a/src/main/java/org/apache/sling/testing/clients/indexing/package-info.java b/src/main/java/org/apache/sling/testing/clients/indexing/package-info.java new file mode 100644 index 0000000..385a674 --- /dev/null +++ b/src/main/java/org/apache/sling/testing/clients/indexing/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +@Version("0.1.0") +package org.apache.sling.testing.clients.indexing; + +import org.osgi.annotation.versioning.Version; diff --git a/src/test/java/org/apache/sling/testing/clients/indexing/IndexingClientTest.java b/src/test/java/org/apache/sling/testing/clients/indexing/IndexingClientTest.java new file mode 100644 index 0000000..a509a21 --- /dev/null +++ b/src/test/java/org/apache/sling/testing/clients/indexing/IndexingClientTest.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +package org.apache.sling.testing.clients.indexing; + +import org.apache.http.*; +import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.http.entity.StringEntity; +import org.apache.http.message.BasicHttpEntityEnclosingRequest; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.HttpRequestHandler; +import org.apache.sling.testing.clients.ClientException; +import org.apache.sling.testing.clients.HttpServerRule; +import org.apache.sling.testing.clients.query.servlet.QueryServlet; +import org.junit.ClassRule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeoutException; + +public class IndexingClientTest { + private static final Logger LOG = LoggerFactory.getLogger(IndexingClientTest.class); + + private static final String EXPLAIN_RESPONSE = "{\"plan\": \"random plan with testIndexingLane-async and testIndexingLane-fulltext-async\",\"time\": 1}"; + private static final String QUERY_RESPONSE = "{\"total\": 1234,\"time\": 1}"; + + @ClassRule + public static HttpServerRule httpServer = new HttpServerRule() { + HttpRequestHandler okHandler = new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setStatusCode(200); + response.setEntity(new StringEntity("Everything's fine")); + } + }; + + HttpRequestHandler createdHandler = new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setStatusCode(201); + response.setEntity(new StringEntity("Created")); + } + }; + + @Override + protected void registerHandlers() throws IOException { + // Normal query request + serverBootstrap.registerHandler(QueryServlet.SERVLET_PATH, new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + List<NameValuePair> parameters = URLEncodedUtils.parse( + request.getRequestLine().getUri(), Charset.defaultCharset()); + + for (NameValuePair parameter : parameters) { + if (parameter.getName().equals("explain") && !parameter.getValue().equals("false")) { + response.setEntity(new StringEntity(EXPLAIN_RESPONSE)); + return; + } + } + + response.setEntity(new StringEntity(QUERY_RESPONSE)); + } + }); + + // Install servlet + serverBootstrap.registerHandler("/system/console/bundles", new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + // is install (post) or checking status (get) + if (request instanceof BasicHttpEntityEnclosingRequest) { + response.setStatusCode(302); + } else { + response.setStatusCode(200); + } + } + }); + + // Check bundle status + serverBootstrap.registerHandler("BUNDLE_PATH" + ".json", new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setEntity(new StringEntity("JSON_BUNDLE")); + } + }); + + // Uninstall bundle + serverBootstrap.registerHandler("BUNDLE_PATH", new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setStatusCode(200); + } + }); + + // Uninstall bundle + serverBootstrap.registerHandler( + "/system/console/configMgr/org.apache.jackrabbit.oak.plugins.index.AsyncIndexerService", + new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + response.setStatusCode(200); + response.setEntity(new StringEntity("{\"properties\":{" + + "\"asyncConfigs\":{\"values\":[\"async:5\",\"fulltext-async:5\"]}}}")); + } + } + ); + + serverBootstrap.registerHandler("/tmp/testing/waitForAsyncIndexing/content/*", new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) + throws HttpException, IOException { + List<NameValuePair> params = extractParameters(request); + + for (NameValuePair param : params) { + if (param.getName().equals(":operation") && (param.getValue().equals("delete"))) { + response.setStatusCode(200); + return; + } + } + + response.setStatusCode(201); + response.setEntity(new StringEntity("Created!")); + } + }); + + // unimportant requests + serverBootstrap.registerHandler("/tmp.json", okHandler); + serverBootstrap.registerHandler("/tmp/testing.json", okHandler); + serverBootstrap.registerHandler("/tmp/testing/waitForAsyncIndexing", okHandler); + serverBootstrap.registerHandler("/tmp/testing", okHandler); + serverBootstrap.registerHandler("/tmp/testing/waitForAsyncIndexing/oak:index", createdHandler); + serverBootstrap.registerHandler("/tmp/testing/waitForAsyncIndexing/content", createdHandler); + } + }; + + private IndexingClient client; + + public IndexingClientTest() throws ClientException { + client = new IndexingClient(httpServer.getURI(), "admin", "admin"); + client = new IndexingClient(java.net.URI.create("http://localhost:4502"), "admin", "admin"); + } + + @Test + public void testInstall() throws ClientException { + client.install(); + } + + @Test + public void testUninstall() throws ClientException { + client.uninstall(); + } + + @Test + public void testWaitForAsyncIndexing() throws ClientException, TimeoutException, InterruptedException { + client.waitForAsyncIndexing(); + } + + private static List<NameValuePair> extractParameters(HttpRequest request) { + if (request instanceof HttpEntityEnclosingRequest) { + HttpEntity entity = ((HttpEntityEnclosingRequest) request).getEntity(); + try { + return URLEncodedUtils.parse(entity); + } catch (IOException e) { + LOG.error("Failed to parse entity", e); + } + } + + return new ArrayList<>(); + } +} -- To stop receiving notification emails like this one, please contact [email protected].
