/* * eXist Open Source Native XML Database * Copyright (C) 2001-2015 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.exist.xmlrpc; import org.apache.xmlrpc.XmlRpcException; import org.apache.xmlrpc.client.XmlRpcClient; import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; import org.exist.TestUtils; import org.exist.test.ExistWebServer; import org.exist.xmldb.XmldbURI; import java.io.*; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.junit.ClassRule; import org.junit.Test; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test for deadlocks when moving resources from one collection to another. Uses * two threads: one stores a document, then moves it to another collection. * Based on XML-RPC. The second thread tries to execute a query via REST. * * Due to the complex move task, threads will deadlock almost immediately if * something's wrong with collection locking. */ public class MoveResourceTest { @ClassRule public static final ExistWebServer existWebServer = new ExistWebServer(true, false, true); private static String getUri() { return "http://localhost:" + existWebServer.getPort() + "/xmlrpc"; } private static String getRestUri() { return "http://localhost:" + existWebServer.getPort(); } @Test public void testMove() throws InterruptedException, ExecutionException { final List<Callable<Void>> tasks = new ArrayList<>(); tasks.add(new MoveThread()); tasks.add(new CheckThread()); tasks.add(new CheckThread()); final ExecutorService service = Executors.newFixedThreadPool(tasks.size()); final CompletionService<Void> cs = new ExecutorCompletionService<>(service); tasks.stream().forEach((task) -> { cs.submit(task); }); //wait for all tasks to complete final int n = tasks.size(); for (int i = 0; i < n; i++) { cs.take().get(); } } private void createCollection(XmlRpcClient client, XmldbURI collection) throws IOException, XmlRpcException { List<Object> params = new ArrayList<>(); params.add(collection.toString()); Boolean result = (Boolean) client.execute("createCollection", params); assertTrue(result); } private String readData() throws IOException { return new String(TestUtils.readRomeoAndJulietSampleXml(), UTF_8); } private class MoveThread implements Callable<Void> { @Override public Void call() throws IOException, XmlRpcException, InterruptedException { for (int i = 0; i < 100; i++) { XmldbURI sourceColl = XmldbURI.ROOT_COLLECTION_URI.append("source" + i); XmldbURI targetColl1 = XmldbURI.ROOT_COLLECTION_URI.append("target"); XmldbURI targetColl2 = targetColl1.append("test" + i); XmldbURI sourceResource = sourceColl.append("source.xml"); XmldbURI targetResource = targetColl2.append("copied.xml"); XmlRpcClient xmlrpc = getClient(); createCollection(xmlrpc, sourceColl); createCollection(xmlrpc, targetColl1); createCollection(xmlrpc, targetColl2); List<Object> params = new ArrayList<>(); params.add(readData()); params.add(sourceResource.toString()); params.add(1); Boolean result = (Boolean) xmlrpc.execute("parse", params); assertTrue(result); params.clear(); params.add(sourceResource.toString()); params.add(targetColl2.toString()); params.add("copied.xml"); xmlrpc.execute("moveResource", params); Map<String, String> options = new HashMap<>(); options.put("indent", "yes"); options.put("encoding", "UTF-8"); options.put("expand-xincludes", "yes"); options.put("process-xsl-pi", "no"); params.clear(); params.add(targetResource.toString()); params.add(options); byte[] data = (byte[]) xmlrpc.execute("getDocument", params); assertTrue(data != null && data.length > 0); synchronized (this) { wait(250); } params.clear(); params.add(sourceColl.toString()); xmlrpc.execute("removeCollection", params); params.set(0, targetColl1.toString()); xmlrpc.execute("removeCollection", params); } return null; } } private class CheckThread implements Callable<Void> { @Override public Void call() throws IOException, InterruptedException { String reqUrl = getRestUri() + "/db?_query=" + URLEncoder.encode("collection('/db')//SPEECH[SPEAKER = 'JULIET']"); for (int i = 0; i < 200; i++) { URL url = new URL(reqUrl); HttpURLConnection connect = (HttpURLConnection) url.openConnection(); connect.setRequestMethod("GET"); connect.connect(); int r = connect.getResponseCode(); assertEquals("Server returned response code " + r, 200, r); try (final InputStream is = connect.getInputStream()) { readResponse(is); } synchronized (this) { wait(250); } } return null; } private String readResponse(final InputStream is) throws IOException { final StringBuilder out = new StringBuilder(); try(final BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) { String line; while ((line = reader.readLine()) != null) { out.append(line); out.append("\r\n"); } } return out.toString(); } } protected static XmlRpcClient getClient() throws MalformedURLException { XmlRpcClient client = new XmlRpcClient(); XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); config.setEnabledForExtensions(true); config.setServerURL(new URL(getUri())); config.setBasicUserName("admin"); config.setBasicPassword(""); client.setConfig(config); return client; } }