blob: 2bd8fe6f8d1cc24c6184c41f4b67daeff9d2610f [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 com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
import static org.apache.jackrabbit.oak.plugins.document.Collection.NODES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class NodeDocumentRevisionCleanerTest {
@Mock
DocumentStore documentStore;
@Mock
DocumentNodeStore documentNodeStore;
@Mock
Checkpoints checkpoints;
NodeDocument workingDocument;
NodeDocumentRevisionCleaner nodeDocumentRevisionCleaner;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
workingDocument = new NodeDocument(documentStore);
Mockito.when(documentStore.find(Mockito.eq(NODES), Mockito.anyString())).thenReturn(workingDocument);
Mockito.when(documentNodeStore.getBranches()).thenReturn(new UnmergedBranches());
nodeDocumentRevisionCleaner = new NodeDocumentRevisionCleaner(documentNodeStore, workingDocument);
}
@After
public void tearDown() {
Mockito.reset(documentStore, documentNodeStore, checkpoints);
}
@Test
public void testMarkCheckpointRevisionsToPreserveOnePropertyOneCluster() throws IOException {
Revision revisionA = Revision.fromString("r100000000-0-1");
Revision revisionB = Revision.fromString("r105000000-0-1");
Revision revisionC = Revision.fromString("r110000000-0-1");
Revision revisionD = Revision.fromString("r115000000-0-1");
Revision revisionE = Revision.fromString("r120000000-0-1");
Revision revisionF = Revision.fromString("r125000000-0-1");
Revision checkpoint1 = Revision.fromString("r109000000-0-1");
Revision checkpoint2 = Revision.fromString("r119000000-0-1");
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3', '" + revisionD + "': 'value4', '" + revisionE + "': 'value5', '" + revisionF + "': 'value6'}, " +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c', '" + revisionC + "': 'c', '" + revisionD + "': 'c', '" + revisionE + "': 'c', '" + revisionF + "': 'c'}" +
"}";
String jsonCheckpoints = "{" +
"'" + checkpoint1 + "': {'expires':'200000000','rv':'r109000000-0-1'}," +
"'" + checkpoint2 + "': {'expires':'200000000','rv':'r119000000-0-1'}" +
"}";
prepareDocumentMock(jsonProperties);
prepareCheckpointsMock(jsonCheckpoints);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
nodeDocumentRevisionCleaner.markCheckpointRevisionsToPreserve();
nodeDocumentRevisionCleaner.removeCandidatesInList();
// The revisions blocked should be:
// - r105000000-0-1 (blocked by checkpoint r109000000-0-1)
// - r115000000-0-1 (blocked by checkpoint r119000000-0-1)
assertEquals(Set.of(revisionB, revisionD), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1));
assertEquals(Set.of(revisionA, revisionC, revisionE, revisionF), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
}
@Test
public void testRecentRevisionsArePreserved() throws IOException {
StringBuilder jsonPropBuilder = new StringBuilder("'prop1': {");
StringBuilder jsonRevisionsBuilder = new StringBuilder("'_revisions': {");
Instant currentTime = Instant.now();
List<Revision> revisions = new ArrayList<>();
for (int i = 29; i >= 0; i--) {
long timestamp = currentTime.minus(i, ChronoUnit.HOURS).toEpochMilli();
Revision revision = new Revision(timestamp, 0, 1);
revisions.add(revision);
jsonPropBuilder.append("'").append(revision).append("': 'value").append(i).append("', ");
jsonRevisionsBuilder.append("'").append(revision).append("': 'c', ");
}
// 23 hours and 59 minutes ago -> Should be preserved
long timestamp = currentTime.minus(23, ChronoUnit.HOURS).minus(59, ChronoUnit.MINUTES).toEpochMilli();
Revision revision = new Revision(timestamp, 0, 1);
revisions.add(revision); // revisions[30]
jsonPropBuilder.append("'").append(revision).append("': 'value").append(30).append("', ");
jsonRevisionsBuilder.append("'").append(revision).append("': 'c', ");
// 24 hours and 1 minute ago -> Should be candidate to clean
timestamp = currentTime.minus(24, ChronoUnit.HOURS).minus(1, ChronoUnit.MINUTES).toEpochMilli();
revision = new Revision(timestamp, 0, 1);
revisions.add(revision); // revisions[31]
jsonPropBuilder.append("'").append(revision).append("': 'value").append(30).append("'");
jsonRevisionsBuilder.append("'").append(revision).append("': 'c'");
jsonPropBuilder.append("}");
jsonRevisionsBuilder.append("}");
StringBuilder jsonBuilder = new StringBuilder("{");
jsonBuilder.append(jsonPropBuilder).append(", ").append(jsonRevisionsBuilder).append("}");
// revisions.forEach(rev -> System.out.println(rev.toReadableString()));
prepareDocumentMock(jsonBuilder.toString());
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
nodeDocumentRevisionCleaner.markRevisionsNewerThanThresholdToPreserve(24, ChronoUnit.HOURS);
nodeDocumentRevisionCleaner.removeCandidatesInList();
// The candidate revisions should be the 6 oldest ones (current time -29, -28, -27, -26, -25, -24)
// and the one created 24 hours and 1 minute ago
Set<Revision> expectedCandidateRevisions = Stream.concat(
revisions.subList(0, 6).stream(), Stream.of(revisions.get(31))
).collect(Collectors.toSet());
// Blocked revisions are all the 24 revisions created in the last 24 hours, and the one created 23 hours and 59 minutes ago
Set<Revision> expectedBlockedRevisions = revisions.subList(6, 31).stream().collect(Collectors.toSet());
assertEquals(expectedCandidateRevisions, nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
assertEquals(expectedBlockedRevisions, nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1));
}
@Test
public void testInitializeCleanupProcessMultipleClusters() throws IOException {
List<Revision> revs = new ArrayList<>();
// Some initial revisions (0-2)
revs.add(Revision.fromString("r100000000-0-1"));
revs.add(Revision.fromString("r100000001-0-2"));
revs.add(Revision.fromString("r100000002-0-3"));
// Blocked by first checkpoint r109000000 (3-5)
revs.add(Revision.fromString("r106000003-0-1"));
revs.add(Revision.fromString("r107000004-0-2"));
revs.add(Revision.fromString("r108000005-0-3"));
// Some unblocked revisions in middle (6-10)
revs.add(Revision.fromString("r110000006-0-3"));
revs.add(Revision.fromString("r110000006-1-3"));
revs.add(Revision.fromString("r114000007-0-2"));
revs.add(Revision.fromString("r115000008-0-1"));
revs.add(Revision.fromString("r118000009-0-1"));
// Blocked by second checkpoint r118000000 (11-13)
revs.add(Revision.fromString("r118000009-1-1"));
revs.add(Revision.fromString("r118000010-0-2"));
revs.add(Revision.fromString("r118000011-0-3"));
// Some more unblocked revisions (14-19)
revs.add(Revision.fromString("r120000012-0-1"));
revs.add(Revision.fromString("r122000013-0-2"));
revs.add(Revision.fromString("r122000013-1-2"));
revs.add(Revision.fromString("r123000014-0-2"));
revs.add(Revision.fromString("r125000015-0-3"));
revs.add(Revision.fromString("r130000016-0-1"));
// Last revision (20-22)
revs.add(Revision.fromString("r130000016-1-1"));
revs.add(Revision.fromString("r130000017-0-2"));
revs.add(Revision.fromString("r130000018-0-3"));
// Checkpoint revisions
Revision checkpoint1 = Revision.fromString("r109000000-0-1");
Revision checkpoint2 = Revision.fromString("r119000000-0-1");
StringBuilder jsonPropBuilder = new StringBuilder("'prop1': {");
StringBuilder jsonRevisionsBuilder = new StringBuilder("'_revisions': {");
for (int i = 0; i < revs.size(); i++) {
jsonPropBuilder.append("'").append(revs.get(i)).append("': 'value").append(i).append("'");
jsonRevisionsBuilder.append("'").append(revs.get(i)).append("': 'c'");
if (i < revs.size() - 1) {
jsonPropBuilder.append(", ");
jsonRevisionsBuilder.append(", ");
}
}
jsonPropBuilder.append("}");
jsonRevisionsBuilder.append("}");
StringBuilder jsonBuilder = new StringBuilder("{");
jsonBuilder.append(jsonPropBuilder).append(", ").append(jsonRevisionsBuilder).append("}");
String jsonCheckpoints = "{" +
"'" + checkpoint1 + "': {'expires':'200000000','rv':'r106500003-0-1,r107500004-0-2,r108500005-0-3'}," +
"'" + checkpoint2 + "': {'expires':'200000000','rv':'r118000009-1-1,r118000010-0-2,r118000011-0-3'}" +
"}";
prepareDocumentMock(jsonBuilder.toString());
prepareCheckpointsMock(jsonCheckpoints);
final UpdateOp op = new UpdateOp(requireNonNull("0:/"), false);
nodeDocumentRevisionCleaner.collectOldRevisions(op);
// The revisions blocked should be:
// - r106000003-0-3, r118000004-0-2, r108000005-0-3 (referenced by checkpoint 1)
// - r118000009-1-1, r118000010-0-2, r118000011-0-3 (referenced by checkpoint 2)
// - r130000016-1-1, r130000017-0-2, r130000018-0-3 (last revision)
assertEquals(Set.of(revs.get(3), revs.get(11), revs.get(20)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1));
assertEquals(Set.of(revs.get(4), revs.get(12), revs.get(21)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(2));
assertEquals(Set.of(revs.get(5), revs.get(13), revs.get(22)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(3));
// Rest of revisions are candidates to clean
assertEquals(Set.of(revs.get(0), revs.get(9), revs.get(10), revs.get(14), revs.get(19)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
assertEquals(Set.of(revs.get(1), revs.get(8), revs.get(15), revs.get(16), revs.get(17)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(2));
assertEquals(Set.of(revs.get(2), revs.get(6), revs.get(7), revs.get(18)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(3));
assertTrue(Collections.disjoint(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1)));
assertTrue(Collections.disjoint(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(2), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(2)));
assertTrue(Collections.disjoint(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(3), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(3)));
}
@Test
public void testCheckpointedRevisionFallback() throws IOException {
List<Revision> revs = new ArrayList<>();
revs.add(Revision.fromString("r100000000-0-1"));
revs.add(Revision.fromString("r100000001-0-2"));
revs.add(Revision.fromString("r100000002-0-3"));
revs.add(Revision.fromString("r101100003-0-1"));
revs.add(Revision.fromString("r101200004-0-2"));
revs.add(Revision.fromString("r101300005-0-3"));
revs.add(Revision.fromString("r102000006-0-1"));
revs.add(Revision.fromString("r102000007-0-3"));
revs.add(Revision.fromString("r103000008-0-1"));
revs.add(Revision.fromString("r103000009-0-2"));
revs.add(Revision.fromString("r103000010-0-3"));
// Checkpoint
Revision checkpoint1 = Revision.fromString("r102000000-0-1");
StringBuilder jsonPropBuilder = new StringBuilder("'prop1': {");
StringBuilder jsonRevisionsBuilder = new StringBuilder("'_revisions': {");
for (int i = 0; i < revs.size(); i++) {
jsonPropBuilder.append("'").append(revs.get(i)).append("': 'value").append(i).append("'");
jsonRevisionsBuilder.append("'").append(revs.get(i)).append("': 'c'");
if (i < revs.size() - 1) {
jsonPropBuilder.append(", ");
jsonRevisionsBuilder.append(", ");
}
}
jsonPropBuilder.append("}");
jsonRevisionsBuilder.append("}");
StringBuilder jsonBuilder = new StringBuilder("{");
jsonBuilder.append(jsonPropBuilder).append(", ").append(jsonRevisionsBuilder).append("}");
String jsonCheckpoints = "{" +
"'" + checkpoint1 + "': {'expires':'200000000','rv':'r102000007-0-1,r102000000-0-2,r102000008-0-3'}" +
"}";
prepareDocumentMock(jsonBuilder.toString());
prepareCheckpointsMock(jsonCheckpoints);
final UpdateOp op = new UpdateOp(requireNonNull("0:/"), false);
nodeDocumentRevisionCleaner.collectOldRevisions(op);
// The revisions blocked should be:
// - r103000008-0-1, r103000009-0-2, r103000010-0-3 (last revisions)
// - r102000006-0-1, r101300005-0-3, r102000007-0-3 (referenced by checkpoint for clusters 1, 2 and 3 respectively)
assertEquals(Set.of(revs.get(6), revs.get(8)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1));
assertEquals(Set.of(revs.get(9)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(2));
assertEquals(Set.of(revs.get(5), revs.get(7), revs.get(10)), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(3));
// Rest of revisions are candidates to clean
assertEquals(Set.of(revs.get(0), revs.get(3)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
assertEquals(Set.of(revs.get(1), revs.get(4)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(2));
assertEquals(Set.of(revs.get(2)), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(3));
}
@Test
public void testLastRevisionIsBlocked() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3'}, " +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
nodeDocumentRevisionCleaner.markLastRevisionForEachProperty();
assertFalse(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1).contains(revisionA));
assertFalse(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1).contains(revisionB));
assertEquals(Set.of(revisionC), nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1));
assertEquals(Set.of(revisionA, revisionB, revisionC), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
}
@Test
public void testFirstDeletedRevisionIsBlocked() throws Exception {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'_deleted': {'" + revisionA + "': 'false', '" + revisionB + "': 'true', '" + revisionC + "': 'false'}," +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
assertTrue(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1).contains(revisionA));
assertEquals(Set.of(revisionA, revisionB, revisionC), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
}
@Test
public void testFirstDeletedRevisionIsBlockedUnordered() throws Exception {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'_deleted': {'" + revisionC + "': 'false', '" + revisionA + "': 'true', '" + revisionB + "': 'false'}," +
"'_revisions': {'" + revisionC + "': 'c', '" + revisionB + "': 'c', '" + revisionA + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
assertTrue(nodeDocumentRevisionCleaner.getBlockedRevisionsToKeep().get(1).contains(revisionA));
assertEquals(Set.of(revisionA, revisionB, revisionC), nodeDocumentRevisionCleaner.getCandidateRevisionsToClean().get(1));
}
@Test
public void testClassifyAndMapRevisionsAndPropertiesWithDeleted() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3'}, " +
"'prop2': {'" + revisionB + "': 'value4'}, " +
"'_deleted': {'" + revisionA + "': 'false'}," +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
SortedMap<Revision, TreeSet<String>> propertiesModifiedByRevision = nodeDocumentRevisionCleaner.getPropertiesModifiedByRevision();
assertEquals(Set.of("prop1", "_deleted"), propertiesModifiedByRevision.get(revisionA));
assertEquals(Set.of("prop1", "prop2"), propertiesModifiedByRevision.get(revisionB));
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionC));
SortedMap<String, SortedMap<Integer, TreeSet<Revision>>> revisionsModifyingProperty = nodeDocumentRevisionCleaner.getRevisionsModifyingPropertyByCluster();
assertEquals(Set.of(revisionA, revisionB, revisionC), revisionsModifyingProperty.get("prop1").get(1));
assertEquals(Set.of(revisionB), revisionsModifyingProperty.get("prop2").get(1));
assertEquals(Set.of(revisionA), revisionsModifyingProperty.get("_deleted").get(1));
}
@Test
public void testClassifyAndMapRevisionsMultipleDeleted() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'_deleted': {'" + revisionA + "': 'false', '" + revisionB + "': 'true', '" + revisionC + "': 'false'}," +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
SortedMap<Revision, TreeSet<String>> propertiesModifiedByRevision = nodeDocumentRevisionCleaner.getPropertiesModifiedByRevision();
assertEquals(Set.of("_deleted"), propertiesModifiedByRevision.get(revisionA));
assertEquals(Set.of("_deleted"), propertiesModifiedByRevision.get(revisionB));
assertEquals(Set.of("_deleted"), propertiesModifiedByRevision.get(revisionC));
SortedMap<String, SortedMap<Integer, TreeSet<Revision>>> revisionsModifyingProperty = nodeDocumentRevisionCleaner.getRevisionsModifyingPropertyByCluster();
assertEquals(Set.of(revisionA, revisionB, revisionC), revisionsModifyingProperty.get("_deleted").get(1));
}
@Test
public void testClassifyAndMapRevisionsAndPropertiesWithoutDeleted() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3'}, " +
"'prop2': {'" + revisionB + "': 'value4'}, " +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
SortedMap<Revision, TreeSet<String>> propertiesModifiedByRevision = nodeDocumentRevisionCleaner.getPropertiesModifiedByRevision();
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionA));
assertEquals(Set.of("prop1", "prop2"), propertiesModifiedByRevision.get(revisionB));
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionC));
SortedMap<String, SortedMap<Integer, TreeSet<Revision>>> revisionsModifyingProperty = nodeDocumentRevisionCleaner.getRevisionsModifyingPropertyByCluster();
assertEquals(Set.of(revisionA, revisionB, revisionC), revisionsModifyingProperty.get("prop1").get(1));
assertEquals(Set.of(revisionB), revisionsModifyingProperty.get("prop2").get(1));
}
@Test
public void testClassifyAndMapRevisionsAndPropertiesNotCommitted() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 1);
Revision revisionC = new Revision(333333333L, 0, 1);
Revision revisionD = new Revision(444444444L, 0, 1);
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3', '" + revisionD + "': 'value5'}, " +
"'prop2': {'" + revisionB + "': 'value4', '" + revisionD + "': 'value5'}, " +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c', '" + revisionD + "': 'nc'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
// Modifications done in revisionD should be ignored, as it is not committed
SortedMap<Revision, TreeSet<String>> propertiesModifiedByRevision = nodeDocumentRevisionCleaner.getPropertiesModifiedByRevision();
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionA));
assertEquals(Set.of("prop1", "prop2"), propertiesModifiedByRevision.get(revisionB));
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionC));
assertNull(propertiesModifiedByRevision.get(revisionD));
SortedMap<String, SortedMap<Integer, TreeSet<Revision>>> revisionsModifyingProperty = nodeDocumentRevisionCleaner.getRevisionsModifyingPropertyByCluster();
assertEquals(Set.of(revisionA, revisionB, revisionC), revisionsModifyingProperty.get("prop1").get(1));
assertEquals(Set.of(revisionB), revisionsModifyingProperty.get("prop2").get(1));
}
@Test
public void testClassifyAndMapRevisionsAndPropertiesDifferentClusters() throws IOException {
Revision revisionA = new Revision(111111111L, 0, 1);
Revision revisionB = new Revision(222222222L, 0, 2);
Revision revisionC = new Revision(333333333L, 0, 3);
Revision revisionD = new Revision(444444444L, 0, 1);
String jsonProperties = "{" +
"'prop1': {'" + revisionA + "': 'value1', '" + revisionB + "': 'value2', '" + revisionC + "': 'value3', '" + revisionD + "': 'value5'}, " +
"'prop2': {'" + revisionB + "': 'value4', '" + revisionD + "': 'value5'}, " +
"'_revisions': {'" + revisionA + "': 'c', '" + revisionB + "': 'c', '" + revisionC + "': 'c', '" + revisionD + "': 'c'}" +
"}";
prepareDocumentMock(jsonProperties);
nodeDocumentRevisionCleaner.classifyRevisionsAndProperties();
SortedMap<Revision, TreeSet<String>> propertiesModifiedByRevision = nodeDocumentRevisionCleaner.getPropertiesModifiedByRevision();
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionA));
assertEquals(Set.of("prop1", "prop2"), propertiesModifiedByRevision.get(revisionB));
assertEquals(Set.of("prop1"), propertiesModifiedByRevision.get(revisionC));
assertEquals(Set.of("prop1", "prop2"), propertiesModifiedByRevision.get(revisionD));
SortedMap<String, SortedMap<Integer, TreeSet<Revision>>> revisionsModifyingProperty = nodeDocumentRevisionCleaner.getRevisionsModifyingPropertyByCluster();
assertEquals(Set.of(revisionA, revisionD), revisionsModifyingProperty.get("prop1").get(1));
assertEquals(Set.of(revisionB), revisionsModifyingProperty.get("prop1").get(2));
assertEquals(Set.of(revisionC), revisionsModifyingProperty.get("prop1").get(3));
assertEquals(Set.of(revisionD), revisionsModifyingProperty.get("prop2").get(1));
assertEquals(Set.of(revisionB), revisionsModifyingProperty.get("prop2").get(2));
assertNull(revisionsModifyingProperty.get("prop2").get(3));
}
private void prepareDocumentMock(String jsonProperties) throws IOException {
String json = jsonProperties.replace("'", "\"");
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Map<String, String>> data = objectMapper.readValue(json, new TypeReference<>() {});
SortedMap<String, Object> entries = new TreeMap<>();
SortedMap<Revision, String> allRevisions = new TreeMap<>(StableRevisionComparator.INSTANCE);
for (Map.Entry<String, Map<String, String>> entry : data.entrySet()) {
String property = entry.getKey();
Map<String, String> revisions = entry.getValue();
SortedMap<Revision, String> sortedRevisions = new TreeMap<>(StableRevisionComparator.INSTANCE);
for (Map.Entry<String, String> revisionEntry : revisions.entrySet()) {
String revisionStr = revisionEntry.getKey();
String value = revisionEntry.getValue();
String[] parts = revisionStr.split("-");
// The timestamp part of the revision string (first part) is parsed as hexadecimal (radix 16)
long timestamp = Long.parseLong(parts[0].substring(1), 16);
int counter = Integer.parseInt(parts[1]);
int clusterId = Integer.parseInt(parts[2]);
Revision revision = new Revision(timestamp, counter, clusterId);
sortedRevisions.put(revision, value);
// Add all revisions to the "_revisions" list
allRevisions.put(revision, value);
}
entries.put(property, sortedRevisions);
}
workingDocument.data = entries;
}
private void prepareCheckpointsMock(String jsonCheckpoints) throws IOException {
String json = jsonCheckpoints.replace("'", "\"");
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Map<String, Object>> data = objectMapper.readValue(json, new TypeReference<>() {});
SortedMap<Revision, Checkpoints.Info> checkpoints = new TreeMap<>(StableRevisionComparator.REVERSE);
for (Map.Entry<String, Map<String, Object>> entry : data.entrySet()) {
String checkpointStr = entry.getKey();
Map<String, Object> checkpointData = entry.getValue();
String[] parts = checkpointStr.split("-");
long timestamp = Long.parseLong(parts[0].substring(1), 16);
int counter = Integer.parseInt(parts[1]);
int clusterId = Integer.parseInt(parts[2]);
Revision checkpoint = new Revision(timestamp, counter, clusterId);
String checkpointDataJson = objectMapper.writeValueAsString(checkpointData);
Checkpoints.Info info = Checkpoints.Info.fromString(checkpointDataJson);
checkpoints.put(checkpoint, info);
}
Mockito.when(documentNodeStore.getCheckpoints()).thenReturn(this.checkpoints);
Mockito.when(documentNodeStore.getCheckpoints().getCheckpoints()).thenReturn(checkpoints);
}
}