blob: b4703f15f41067dcc50a9267f37649c0e6aaf641 [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.jackrabbit.oak.plugins.document;
import static java.util.Collections.synchronizedList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import org.apache.jackrabbit.oak.plugins.document.util.Utils;
import org.junit.Test;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
public class MultiDocumentStoreTest extends AbstractMultiDocumentStoreTest {
public MultiDocumentStoreTest(DocumentStoreFixture dsf) {
super(dsf);
}
@Test
public void testInterleavedBatchUpdate() {
int amount = 10;
int halfAmount = amount / 2;
String baseId = this.getClass().getName() + ".testInterleavedBatchUpdate";
// remove if present
for (int i = 0; i < amount; i++) {
String id = baseId + "-" + i;
NodeDocument nd = super.ds1.find(Collection.NODES, id);
if (nd != null) {
super.ds1.remove(Collection.NODES, id);
}
removeMe.add(id);
}
{
// create half of the entries in ds1
List<UpdateOp> ops = new ArrayList<UpdateOp>();
for (int i = 0; i < halfAmount; i++) {
String id = baseId + "-" + i;
UpdateOp up = new UpdateOp(id, true);
up.set("_createdby", "ds1");
ops.add(up);
}
List<NodeDocument> result = super.ds1.createOrUpdate(Collection.NODES, ops);
assertEquals(halfAmount, result.size());
for (NodeDocument doc : result) {
assertNull(doc);
}
}
{
// create all of the entries in ds2
List<UpdateOp> ops = new ArrayList<UpdateOp>();
for (int i = 0; i < amount; i++) {
String id = baseId + "-" + i;
UpdateOp up = new UpdateOp(id, true);
up.set("_createdby", "ds2");
ops.add(up);
}
List<NodeDocument> result = super.ds2.createOrUpdate(Collection.NODES, ops);
assertEquals(amount, result.size());
for (NodeDocument doc : result) {
// documents are either new or have been created by ds1
if (doc != null) {
assertEquals("ds1", doc.get("_createdby"));
}
}
}
// final check: does DS1 see all documents including the changes made by DS2?
for (int i = 0; i < amount; i++) {
String id = baseId + "-" + i;
NodeDocument doc = super.ds1.find(Collection.NODES, id, 0);
assertNotNull(doc);
assertEquals("ds2", doc.get("_createdby"));
}
}
@Test
public void concurrentBatchUpdate() throws Exception {
final CountDownLatch ready = new CountDownLatch(2);
final CountDownLatch go = new CountDownLatch(1);
final List<String> ids = Lists.newArrayList();
for (int i = 0; i < 100; i++) {
ids.add(Utils.getIdFromPath("/node-" + i));
}
removeMe.addAll(ids);
// make sure not present before test run
ds1.remove(Collection.NODES, ids);
final List<Exception> exceptions = synchronizedList(new ArrayList<Exception>());
final Map<String, NodeDocument> result1 = Maps.newHashMap();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
List<UpdateOp> ops = Lists.newArrayList();
for (String id : ids) {
UpdateOp op = new UpdateOp(id, true);
op.set("_t1", "value");
ops.add(op);
}
Collections.shuffle(ops);
ready.countDown();
go.await();
List<NodeDocument> docs = ds1.createOrUpdate(Collection.NODES, ops);
for (int i = 0; i < ops.size(); i++) {
UpdateOp op = ops.get(i);
result1.put(op.getId(), docs.get(i));
}
} catch (Exception e) {
exceptions.add(e);
}
}
}, "t1");
final Map<String, NodeDocument> result2 = Maps.newHashMap();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
List<UpdateOp> ops = Lists.newArrayList();
for (String id : ids) {
UpdateOp op = new UpdateOp(id, true);
op.set("_t2", "value");
ops.add(op);
}
Collections.shuffle(ops);
ready.countDown();
go.await();
List<NodeDocument> docs = ds2.createOrUpdate(Collection.NODES, ops);
for (int i = 0; i < ops.size(); i++) {
UpdateOp op = ops.get(i);
result2.put(op.getId(), docs.get(i));
}
} catch (Exception e) {
exceptions.add(e);
}
}
}, "t2");
t1.start();
t2.start();
ready.await();
go.countDown();
t1.join();
t2.join();
for (Exception e : exceptions) {
fail(e.toString());
}
for (String id : ids) {
NodeDocument d1 = result1.get(id);
NodeDocument d2 = result2.get(id);
if (d1 != null) {
if (d2 != null) {
fail("found " + id + " in both result sets, modcounts are: " + d1.getModCount() + "/" + d2.getModCount());
}
} else {
assertNotNull("id " + id + " is in neither result set", d2);
}
}
}
@Test
public void batchUpdateCachedDocument() throws Exception {
String id = Utils.getIdFromPath("/foo");
removeMe.add(id);
UpdateOp op = new UpdateOp(id, true);
op.set("_ds1", 1);
assertNull(ds1.createOrUpdate(Collection.NODES, op));
// force ds2 to populate the cache with doc
assertNotNull(ds2.find(Collection.NODES, id));
// modify doc via ds1
op = new UpdateOp(id, false);
op.set("_ds1", 2);
assertNotNull(ds1.createOrUpdate(Collection.NODES, op));
// modify doc via ds2 with batch createOrUpdate
op = new UpdateOp(id, false);
op.set("_ds2", 1);
List<UpdateOp> ops = Lists.newArrayList();
ops.add(op);
for (int i = 0; i < 10; i++) {
// add more ops to make sure a batch
// update call is triggered
String docId = Utils.getIdFromPath("/node-" + i);
UpdateOp update = new UpdateOp(docId, true);
update.set("_ds2", 1);
removeMe.add(docId);
ops.add(update);
}
List<NodeDocument> old = ds2.createOrUpdate(Collection.NODES, ops);
assertEquals(11, old.size());
assertNotNull(old.get(0));
assertEquals(2L, old.get(0).get("_ds1"));
NodeDocument foo = ds2.find(Collection.NODES, id);
assertNotNull(foo);
assertEquals(2L, foo.get("_ds1"));
assertEquals(1L, foo.get("_ds2"));
}
@Test
public void testUpdateOrCreateDeletedDocument() {
String id = Utils.getIdFromPath("/foo");
removeMe.add(id);
UpdateOp op1 = new UpdateOp(id, true);
assertNull(ds1.createOrUpdate(Collection.NODES, op1));
NodeDocument d = ds1.find(Collection.NODES, id);
assertNotNull(d);
Long mc = d.getModCount();
ds2.remove(Collection.NODES, id);
UpdateOp op2 = new UpdateOp(id, true);
NodeDocument prev = ds1.createOrUpdate(Collection.NODES, op2);
assertNull(prev);
// make sure created
NodeDocument created = ds1.find(Collection.NODES, id);
assertNotNull(created);
assertEquals(mc, created.getModCount());
}
@Test
public void testUpdateNoCreateDeletedDocument() {
String id = Utils.getIdFromPath("/foo");
removeMe.add(id);
UpdateOp op1 = new UpdateOp(id, true);
assertNull(ds1.createOrUpdate(Collection.NODES, op1));
NodeDocument d = ds1.find(Collection.NODES, id);
assertNotNull(d);
ds2.remove(Collection.NODES, id);
UpdateOp op2 = new UpdateOp(id, false);
NodeDocument prev = ds1.createOrUpdate(Collection.NODES, op2);
assertNull(prev);
// make sure not created
assertNull(ds1.find(Collection.NODES, id));
}
@Test
public void batchUpdateNoCreateDeletedDocument() {
batchUpdateNoCreateDeletedDocument(2);
}
@Test
public void batchUpdateNoCreateDeletedDocumentMany() {
batchUpdateNoCreateDeletedDocument(10);
}
private void batchUpdateNoCreateDeletedDocument(int numUpdates) {
String id1 = this.getClass().getName() + ".batchUpdateNoCreateDeletedDocument";
List<String> ids = new ArrayList<>();
for (int i = 1; i < numUpdates; i++) {
ids.add(id1 + "b" + i);
}
removeMe.add(id1);
removeMe.addAll(ids);
List<String> allIds = new ArrayList<>(ids);
allIds.add(id1);
// create docs
for (String id : allIds) {
UpdateOp up = new UpdateOp(id, true);
assertNull(ds1.createOrUpdate(Collection.NODES, up));
NodeDocument doc = ds1.find(Collection.NODES, id);
assertNotNull(doc);
}
// remove id1 on ds2
ds2.remove(Collection.NODES, id1);
// bulk update
List<UpdateOp> ops = new ArrayList<>();
ops.add(new UpdateOp(id1, false));
for (String id : ids) {
ops.add(new UpdateOp(id, false));
}
List<NodeDocument> result = ds1.createOrUpdate(Collection.NODES, ops);
assertEquals(numUpdates, result.size());
// id1 result should be reported as null and not be created
assertNull(result.get(0));
assertNull(ds1.find(Collection.NODES, id1));
// for other ids result should be reported with previous doc
for (int i = 1; i < numUpdates; i++) {
NodeDocument prev = result.get(i);
assertNotNull(prev);
String id = ids.get(i - 1);
assertEquals(id, prev.getId());
NodeDocument updated = ds1.find(Collection.NODES, id);
assertNotNull(updated);
if (prev.getModCount() != null) {
assertNotEquals(updated.getModCount(), prev.getModCount());
}
}
}
}