/*
* 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.exist.indexing.lucene;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.exist.TestUtils;
import org.exist.test.ExistXmldbEmbeddedServer;
import org.exist.util.FileUtils;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.xmldb.IndexQueryService;
import org.exist.xmldb.XQueryService;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;
import org.xmldb.api.modules.XUpdateQueryService;
public class ConcurrencyTest {
@ClassRule
public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer();
private static int CONCURRENT_THREADS = Runtime.getRuntime().availableProcessors() * 3;
private static Collection test;
private static final String COLLECTION_CONFIG1 =
"<collection xmlns=\"http://exist-db.org/collection-config/1.0\">" +
" <index>" +
" <lucene>" +
" <text qname=\"LINE\"/>" +
" <text qname=\"SPEAKER\"/>" +
" </lucene>" +
" </index>" +
"</collection>";
@Test
public void store() {
final ExecutorService executor = newFixedThreadPool(CONCURRENT_THREADS, "store");
for (int i = 0; i < CONCURRENT_THREADS; i++) {
final String name = "store-thread-" + i;
final Runnable run = () -> {
try {
storeRemoveDocs(name);
} catch(final XMLDBException | IOException e) {
e.printStackTrace();;
fail(e.getMessage());
}
};
executor.submit(run);
}
executor.shutdown();
boolean terminated = false;
try {
terminated = executor.awaitTermination(60 * 60, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
//Nothing to do
}
assertTrue(terminated);
}
@Test
public void update() {
final ExecutorService executor = newFixedThreadPool(CONCURRENT_THREADS, "update");
for (int i = 0; i < CONCURRENT_THREADS; i++) {
final String name = "update-thread" + i;
Runnable run = () -> {
try {
xupdateDocs(name);
} catch (final XMLDBException | IOException e) {
e.printStackTrace();
fail(e.getMessage());
}
};
executor.submit(run);
}
executor.shutdown();
boolean terminated = false;
try {
terminated = executor.awaitTermination(60 * 60, TimeUnit.SECONDS);
} catch (final InterruptedException e) {
//Nothing to do
}
assertTrue(terminated);
}
private ExecutorService newFixedThreadPool(final int nThreads, final String threadsBaseName) {
return Executors.newFixedThreadPool(nThreads, new ThreadFactory() {
private final AtomicInteger counter = new AtomicInteger();
@Override
public Thread newThread(final Runnable r) {
return new Thread(r, threadsBaseName + "-" + counter.getAndIncrement());
}
});
}
private void storeRemoveDocs(final String collectionName) throws XMLDBException, IOException {
storeDocs(collectionName);
final XQueryService xqs = (XQueryService) test.getService("XQueryService", "1.0");
ResourceSet result = xqs.query("//SPEECH[ft:query(LINE, 'king')]");
assertEquals(98, result.getSize());
result = xqs.query("//SPEECH[ft:query(SPEAKER, 'juliet')]");
assertEquals(118, result.getSize());
final String[] resources = test.listResources();
for (int i = 0; i < resources.length; i++) {
final Resource resource = test.getResource(resources[i]);
test.removeResource(resource);
}
result = xqs.query("//SPEECH[ft:query(LINE, 'king')]");
assertEquals(0, result.getSize());
result = xqs.query("//SPEECH[ft:query(SPEAKER, 'juliet')]");
assertEquals(0, result.getSize());
}
private void xupdateDocs(final String collectionName) throws XMLDBException, IOException {
storeDocs(collectionName);
final XQueryService xqs = (XQueryService) test.getService("XQueryService", "1.0");
ResourceSet result = xqs.query("//SPEECH[ft:query(SPEAKER, 'juliet')]");
assertEquals(118, result.getSize());
final String xupdate =
LuceneIndexTest.XUPDATE_START +
" <xu:remove select=\"//SPEECH[ft:query(SPEAKER, 'juliet')]\"/>" +
LuceneIndexTest.XUPDATE_END;
final XUpdateQueryService xuqs = (XUpdateQueryService) test.getService("XUpdateQueryService", "1.0");
xuqs.update(xupdate);
result = xqs.query("//SPEECH[ft:query(SPEAKER, 'juliet')]");
assertEquals(0, result.getSize());
result = xqs.query("//SPEECH[ft:query(LINE, 'king')]");
assertEquals(98, result.getSize());
}
private void storeDocs(final String collectionName) throws XMLDBException, IOException {
Collection collection = null;
try {
collection = existEmbeddedServer.createCollection(test, collectionName);
final IndexQueryService iqs = (IndexQueryService) collection.getService("IndexQueryService", "1.0");
iqs.configureCollection(COLLECTION_CONFIG1);
final Path samples = TestUtils.shakespeareSamples();
final List<Path> files = FileUtils.list(samples);
final MimeTable mimeTab = MimeTable.getInstance();
for (final Path file : files) {
final MimeType mime = mimeTab.getContentTypeFor(FileUtils.fileName(file));
if (mime != null && mime.isXMLType()) {
final Resource resource = collection.createResource(FileUtils.fileName(file), XMLResource.RESOURCE_TYPE);
resource.setContent(file);
collection.storeResource(resource);
}
}
} finally {
if(collection != null) {
collection.close();
}
}
}
@BeforeClass
public static void initDB() throws ClassNotFoundException, IllegalAccessException, InstantiationException, XMLDBException {
test = existEmbeddedServer.createCollection(existEmbeddedServer.getRoot(), "test");
}
@AfterClass
public static void closeDB() throws XMLDBException {
test.close();
TestUtils.cleanupDB();
}
}