You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2014/08/18 16:51:13 UTC
svn commit: r1618624 - in /jackrabbit/oak/trunk:
oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/
oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/
oak-jcr/src/test/java/org/apache/jackra...
Author: davide
Date: Mon Aug 18 14:51:13 2014
New Revision: 1618624
URL: http://svn.apache.org/r1618624
Log:
OAK-2035 - OrderedIndex fails to keep a correct skipList on TarMK
fixed the predicate that was double-encoding the values and added IT for covering the use case.
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OrderedIndexIT.java
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/ValuePathTuple.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java?rev=1618624&r1=1618623&r2=1618624&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java Mon Aug 18 14:51:13 2014
@@ -114,8 +114,20 @@ public class OrderedContentMirrorStoreSt
this.direction = direction;
}
+ private static void printWalkedLanes(final String msg, final String[] walked) {
+ String m = (msg == null) ? "" : msg;
+ if (walked == null) {
+ LOG.debug(m + " walked: null");
+ } else {
+ for (int i = 0; i < walked.length; i++) {
+ LOG.debug("{}walked[{}]: {}", new Object[] { m, i, walked[i] });
+ }
+ }
+ }
+
@Override
NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) {
+ LOG.debug("fetchKeyNode() - new item '{}' -----------------------------------------", key);
// this is where the actual adding and maintenance of index's keys happen
NodeBuilder node = null;
NodeBuilder start = index.child(START);
@@ -132,8 +144,14 @@ public class OrderedContentMirrorStoreSt
// we use the seek for seeking the right spot. The walkedLanes will have all our
// predecessors
String entry = seek(index, condition, walked);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("fetchKeyNode() - entry: {} ", entry);
+ printWalkedLanes("fetchKeyNode() - ", walked);
+ }
+
if (entry != null && entry.equals(key)) {
// it's an existing node. We should not need to update anything around pointers
+ LOG.debug("fetchKeyNode() - node already there.");
node = index.getChildNode(key);
} else {
// the entry does not exits yet
@@ -141,6 +159,7 @@ public class OrderedContentMirrorStoreSt
// it's a brand new node. let's start setting an empty next
setPropertyNext(node, EMPTY_NEXT_ARRAY);
int lane = getLane();
+ LOG.debug("fetchKeyNode() - extracted lane: {}", lane);
String next;
NodeBuilder predecessor;
for (int l = lane; l >= 0; l--) {
@@ -149,6 +168,12 @@ public class OrderedContentMirrorStoreSt
next = getPropertyNext(predecessor, l);
setPropertyNext(predecessor, key, l);
setPropertyNext(node, next, l);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("fetchKeyNode() - on lane: {}", l);
+ LOG.debug("fetchKeyNode() - next from previous: {}", next);
+ LOG.debug("fetchKeyNode() - new status of predecessor name: {} - {} ", walked[l], predecessor.getProperty(NEXT));
+ LOG.debug("fetchKeyNode() - new node name: {} - {}", key, node.getProperty(NEXT));
+ }
}
}
return node;
@@ -268,21 +293,33 @@ public class OrderedContentMirrorStoreSt
public Iterable<String> query(final Filter filter, final String indexName,
final NodeState indexMeta, final String indexStorageNodeName,
final PropertyRestriction pr) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("query() - filter: {}", filter);
+ LOG.debug("query() - indexName: {}", indexName);
+ LOG.debug("query() - indexMeta: {}", indexMeta);
+ LOG.debug("query() - indexStorageNodeName: {}", indexStorageNodeName);
+ LOG.debug("query() - pr: {}", pr);
+ }
final NodeState indexState = indexMeta.getChildNode(indexStorageNodeName);
final NodeBuilder index = new ReadOnlyBuilder(indexState);
-
- if (pr.first != null && !pr.first.equals(pr.last)) {
+ final String firstEncoded = (pr.first == null) ? null
+ : encode(pr.first.getValue(Type.STRING));
+ final String lastEncoded = (pr.last == null) ? null
+ : encode(pr.last.getValue(Type.STRING));
+
+ if (firstEncoded != null && !firstEncoded.equals(lastEncoded)) {
// '>' & '>=' and between use case
+ LOG.debug("'>' & '>=' and between use case");
ChildNodeEntry firstValueableItem;
String firstValuableItemKey;
Iterable<String> it = Collections.emptyList();
Iterable<ChildNodeEntry> childrenIterable;
- if (pr.last == null) {
+ if (lastEncoded == null) {
LOG.debug("> & >= case.");
firstValuableItemKey = seek(index,
- new PredicateGreaterThan(pr.first.getValue(Type.STRING), pr.firstIncluding));
+ new PredicateGreaterThan(firstEncoded, pr.firstIncluding));
if (firstValuableItemKey != null) {
firstValueableItem = new OrderedChildNodeEntry(firstValuableItemKey,
indexState.getChildNode(firstValuableItemKey));
@@ -291,15 +328,15 @@ public class OrderedContentMirrorStoreSt
it = new QueryResultsWrapper(filter, indexName, childrenIterable);
} else {
it = new QueryResultsWrapper(filter, indexName, new BetweenIterable(
- indexState, firstValueableItem, pr.first.getValue(Type.STRING),
+ indexState, firstValueableItem, firstEncoded,
pr.firstIncluding, direction));
}
}
} else {
String first, last;
boolean includeFirst, includeLast;
- first = pr.first.getValue(Type.STRING);
- last = pr.last.getValue(Type.STRING);
+ first = firstEncoded;
+ last = lastEncoded;
includeFirst = pr.firstIncluding;
includeLast = pr.lastIncluding;
@@ -330,9 +367,10 @@ public class OrderedContentMirrorStoreSt
}
return it;
- } else if (pr.last != null && !pr.last.equals(pr.first)) {
+ } else if (lastEncoded != null && !lastEncoded.equals(firstEncoded)) {
// '<' & '<=' use case
- final String searchfor = pr.last.getValue(Type.STRING);
+ LOG.debug("'<' & '<=' use case");
+ final String searchfor = lastEncoded;
final boolean include = pr.lastIncluding;
Predicate<String> predicate = new PredicateLessThan(searchfor, include);
@@ -359,8 +397,8 @@ public class OrderedContentMirrorStoreSt
return it;
} else {
// property is not null. AKA "open query"
- Iterable<String> values = null;
- return query(filter, indexName, indexMeta, values);
+ LOG.debug("property is not null. AKA 'open query'. FullIterable");
+ return new QueryResultsWrapper(filter, indexName, new FullIterable(indexState, false));
}
}
@@ -602,8 +640,10 @@ public class OrderedContentMirrorStoreSt
@Override
public boolean hasNext() {
- return (includeStart && start.equals(current))
- || (!includeStart && !Strings.isNullOrEmpty(getPropertyNext(current)));
+ boolean hasNext = (includeStart && start.equals(current))
+ || (!includeStart && !Strings.isNullOrEmpty(getPropertyNext(current)));
+
+ return hasNext;
}
@Override
@@ -622,6 +662,7 @@ public class OrderedContentMirrorStoreSt
throw new NoSuchElementException();
}
}
+
return entry;
}
@@ -751,7 +792,10 @@ public class OrderedContentMirrorStoreSt
@Nullable final String[] walkedLanes) {
boolean keepWalked = false;
String searchfor = condition.getSearchFor();
- LOG.debug("seek() - Searching for: {}", condition.getSearchFor());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("seek() - Searching for: {}", condition.getSearchFor());
+ LOG.debug("seek() - condition: {}", condition);
+ }
Predicate<String> walkingPredicate = direction.isAscending()
? new PredicateLessThan(searchfor, true)
: new PredicateGreaterThan(searchfor, true);
@@ -781,6 +825,8 @@ public class OrderedContentMirrorStoreSt
// we're asking for a <, <= query from ascending index or >, >= from descending
// we have to walk the lanes from bottom to up rather than up to bottom.
+ LOG.debug("seek() - cross case");
+
lane = 0;
do {
stillLaning = lane < OrderedIndex.LANES;
@@ -801,6 +847,8 @@ public class OrderedContentMirrorStoreSt
}
} while (((!Strings.isNullOrEmpty(nextkey) && walkingPredicate.apply(nextkey)) || stillLaning) && (found == null));
} else {
+ LOG.debug("seek() - plain case");
+
lane = OrderedIndex.LANES - 1;
do {
@@ -854,7 +902,6 @@ public class OrderedContentMirrorStoreSt
* {@code searchfor}
*/
static class PredicateGreaterThan implements Predicate<String> {
- private String searchforEncoded;
private String searchforDecoded;
private boolean include;
@@ -863,7 +910,6 @@ public class OrderedContentMirrorStoreSt
}
public PredicateGreaterThan(@Nonnull String searchfor, boolean include) {
- this.searchforEncoded = encode(searchfor);
this.searchforDecoded = searchfor;
this.include = include;
}
@@ -873,8 +919,8 @@ public class OrderedContentMirrorStoreSt
boolean b = false;
if (!Strings.isNullOrEmpty(arg0)) {
String name = arg0;
- b = searchforEncoded.compareTo(name) < 0 || (include && searchforEncoded
- .equals(name));
+ b = searchforDecoded.compareTo(name) < 0 ||
+ (include && searchforDecoded.equals(name));
}
return b;
@@ -890,7 +936,6 @@ public class OrderedContentMirrorStoreSt
* evaluates when the current element is less than (<) and less than equal {@code searchfor}
*/
static class PredicateLessThan implements Predicate<String> {
- private String searchforEncoded;
private String searchforOriginal;
private boolean include;
@@ -899,7 +944,6 @@ public class OrderedContentMirrorStoreSt
}
public PredicateLessThan(@Nonnull String searchfor, boolean include) {
- this.searchforEncoded = encode(searchfor);
this.searchforOriginal = searchfor;
this.include = include;
}
@@ -909,10 +953,10 @@ public class OrderedContentMirrorStoreSt
boolean b = false;
if (!Strings.isNullOrEmpty(arg0)) {
String name = arg0;
- b = searchforEncoded.compareTo(name) > 0
- || (include && searchforEncoded.equals(name));
+ b = searchforOriginal.compareTo(name) > 0
+ || (include && searchforOriginal.equals(name));
}
-
+
return b;
}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java?rev=1618624&r1=1618623&r2=1618624&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java Mon Aug 18 14:51:13 2014
@@ -3252,7 +3252,6 @@ public class OrderedContentMirrorStorage
// testing the exception in case of wrong parameters
String searchFor = "wedontcareaswetesttheexception";
- NodeState node = index.getChildNode(searchFor);
String entry = searchFor;
String[] wl = new String[0];
String item = null;
@@ -3389,6 +3388,12 @@ public class OrderedContentMirrorStorage
predicate = new PredicateLessThan(searchfor, true);
entry = null;
assertFalse(predicate.apply(entry));
+
+ // equality matching
+ searchfor = "2012-11-25T21:00:45.967-07:00";
+ entry = "2012-11-25T21%3A00%3A45.967-07%3A00";
+ predicate = new PredicateLessThan(searchfor, true);
+ assertTrue("this should have matched the equality flag", predicate.apply(entry));
}
/**
Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OrderedIndexIT.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OrderedIndexIT.java?rev=1618624&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OrderedIndexIT.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OrderedIndexIT.java Mon Aug 18 14:51:13 2014
@@ -0,0 +1,323 @@
+/*
+ * 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.jcr;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.collect.ImmutableList.of;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.util.Calendar.DAY_OF_MONTH;
+import static java.util.Calendar.HOUR_OF_DAY;
+import static java.util.Collections.reverseOrder;
+import static java.util.Collections.sort;
+import static javax.jcr.PropertyType.DATE;
+import static javax.jcr.PropertyType.NAME;
+import static javax.jcr.PropertyType.STRING;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.PROPERTY_NAMES;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.DIRECTION;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection.DESC;
+import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.NT_OAK_UNSTRUCTURED;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Random;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.annotation.Nonnull;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+import javax.jcr.query.QueryResult;
+import javax.jcr.query.Row;
+import javax.jcr.query.RowIterator;
+
+import org.apache.jackrabbit.oak.jcr.util.ValuePathTuple;
+import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection;
+import org.apache.jackrabbit.test.RepositoryStub;
+import org.apache.jackrabbit.test.RepositoryStubException;
+import org.apache.jackrabbit.util.ISO8601;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableSet;
+
+public class OrderedIndexIT {
+ private static final String INDEX_DEF_NODE = "indexdef";
+ private static final String ORDERED_PROPERTY = "foo";
+ private static final String CONTENT = "content";
+ private static final String NODE_TYPE = NT_UNSTRUCTURED;
+
+ private static final Logger LOG = LoggerFactory.getLogger(OrderedIndexIT.class);
+
+ private static final List<TimeZone> TZS = of(
+ TimeZone.getTimeZone("GMT+01:00"),
+ TimeZone.getTimeZone("GMT+02:00"),
+ TimeZone.getTimeZone("GMT+03:00"),
+ TimeZone.getTimeZone("GMT+05:00"),
+ TimeZone.getTimeZone("GMT-02:00"),
+ TimeZone.getTimeZone("GMT-04:00"),
+ TimeZone.getTimeZone("GMT-05:00"),
+ TimeZone.getTimeZone("GMT-07:00"),
+ TimeZone.getTimeZone("GMT-08:00"),
+ TimeZone.getTimeZone("GMT")
+ );
+
+ /**
+ * define the index
+ *
+ * @param session
+ * @throws RepositoryException
+ */
+ private void createIndexDefinition(@Nonnull final Session session) throws RepositoryException {
+ checkNotNull(session);
+
+ Node oakIndex = session.getRootNode().getNode(INDEX_DEFINITIONS_NAME);
+ Node indexDef = oakIndex.addNode(INDEX_DEF_NODE, INDEX_DEFINITIONS_NODE_TYPE);
+ indexDef.setProperty(TYPE_PROPERTY_NAME, TYPE, STRING);
+ indexDef.setProperty(PROPERTY_NAMES, new String[]{ORDERED_PROPERTY} , NAME);
+ indexDef.setProperty(DIRECTION, DESC.getDirection(), STRING);
+ indexDef.setProperty(REINDEX_PROPERTY_NAME, true);
+ session.save();
+ }
+
+ /**
+ * add a bunch of sequential nodes with the provided property values pick-up randomly and start
+ * numerating nodes from {@code startFrom}.
+ *
+ * The caller will have to perform the {@link Session#save()} after the method call.
+ *
+ * @param values the property values to set
+ * @param father under whom we should add the nodes
+ * @param propertyType the type of the property to be stored
+ * @return
+ * @throws RepositoryException
+ * @throws LockException
+ * @throws ConstraintViolationException
+ * @throws VersionException
+ * @throws PathNotFoundException
+ * @throws ItemExistsException
+ */
+ private List<ValuePathTuple> addNodes(@Nonnull final List<String> values,
+ @Nonnull final Node father,
+ final int propertyType,
+ final int startFrom) throws RepositoryException {
+
+ checkNotNull(father);
+ checkArgument(startFrom >= 0, "startFrom must be >= 0");
+
+ List<String> working = newArrayList(checkNotNull(values));
+ Random rnd = new Random(1);
+ int counter = startFrom;
+ Node n;
+ List<ValuePathTuple> vpt = newArrayList();
+
+ while (!working.isEmpty()) {
+ String v = working.remove(rnd.nextInt(working.size()));
+ n = father.addNode("n" + counter++, NODE_TYPE);
+ n.setProperty(ORDERED_PROPERTY, v, propertyType);
+ vpt.add(new ValuePathTuple(v, n.getPath()));
+ }
+
+ return vpt;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void oak2035() throws IOException, RepositoryException, RepositoryStubException {
+ final int numberOfNodes = 1500;
+ final String statement = String.format(
+ "/jcr:root/content//element(*, %s) order by @%s descending", NODE_TYPE,
+ ORDERED_PROPERTY);
+
+ Properties env = new Properties();
+ env.load(getClass().getResourceAsStream("/repositoryStubImpl.properties"));
+ RepositoryStub stub = RepositoryStub.getInstance(env);
+ Repository repo = stub.getRepository();
+ Session session = null;
+ Node root, content;
+
+ try {
+ session = repo.login(stub.getSuperuserCredentials());
+
+ createIndexDefinition(session);
+
+ root = session.getRootNode();
+ if (!root.hasNode(CONTENT)) {
+ root.addNode(CONTENT, NT_OAK_UNSTRUCTURED);
+ }
+ session.save();
+ content = root.getNode(CONTENT);
+
+ Calendar start = midnightFirstJan2013();
+ List<String> dates = generateOrderedDates(String.class, numberOfNodes, DESC, start,
+ DAY_OF_MONTH, 1, TZS, true);
+
+ List<ValuePathTuple> nodes = addNodes(dates, content, DATE, 0);
+ session.save();
+
+ // ensuring the correct order for checks
+ sort(nodes, reverseOrder());
+
+ if (LOG.isDebugEnabled()) {
+ for (ValuePathTuple node : nodes) {
+ LOG.debug(node.toString());
+ }
+ }
+
+ QueryManager qm = session.getWorkspace().getQueryManager();
+ Query query = qm.createQuery(statement, Query.XPATH);
+ QueryResult result = query.execute();
+
+ assertRightOrder(nodes, result.getRows());
+
+ } finally {
+ if (session != null) {
+ session.logout();
+ }
+ }
+ }
+
+ private void assertRightOrder(final List<ValuePathTuple> expected,
+ final RowIterator obtained) throws RepositoryException {
+ checkNotNull(expected);
+ checkNotNull(obtained);
+
+ assertTrue("the obtained result is empty", obtained.hasNext());
+
+ Iterator<ValuePathTuple> exp = expected.iterator();
+
+ while (exp.hasNext() && obtained.hasNext()) {
+ ValuePathTuple vpt = exp.next();
+ Row row = obtained.nextRow();
+
+ // check manually about paths and dates
+ // if paths don't match maybe the date does. It's the date we care, in case of multiple
+ // paths under the same date the order of them is non-deterministic dependent on
+ // persistence rules
+ if (!vpt.getPath().equals(row.getPath())) {
+ String property = row.getNode().getProperty(ORDERED_PROPERTY).getString();
+ if (!vpt.getValue().equals(property)) {
+ fail(String.format(
+ "both path and date failed to match. Expected: %s - %s. Obtained: %s, %s",
+ vpt.getPath(),
+ vpt.getValue(),
+ row.getPath(),
+ property
+ ));
+ }
+ }
+ }
+
+ assertFalse("we should have processed all the expected", exp.hasNext());
+ assertFalse("we should have processed all the obtained", obtained.hasNext());
+ }
+
+ // ------------------------------------- < copied over from BasicOrderedPropertyIndexQueryTest>
+ // TODO should we have anything in commons?
+
+ /**
+ * generates a list of sorted dates as ISO8601 formatted strings.
+ *
+ * @param returnType Allowed values: {@link String} and {@link Long}. When String is specified
+ * it will return ISO8601 formatted dates, otherwise the milliseconds.
+ * @param amount the amount of dates to be generated
+ * @param direction the direction of the sorting for the dates
+ * @param start the dates from where to start generating.
+ * @param increaseOf the {@link Calendar} field to increase while generating
+ * @param increaseBy the amount of increase to be used.
+ * @param timezones available timezones to be used in a random manner
+ * @param generateDuplicates if true it will generate a duplicate with a probability of 10%
+ * @return
+ */
+ public static List<String> generateOrderedDates(Class<?> returnType,
+ int amount,
+ @Nonnull OrderDirection direction,
+ @Nonnull final Calendar start,
+ int increaseOf,
+ int increaseBy,
+ @Nonnull List<TimeZone> timezones,
+ boolean generateDuplicates) {
+ final Set<Integer> allowedIncrease = ImmutableSet.of(HOUR_OF_DAY, DAY_OF_MONTH);
+
+ checkArgument(amount > 0, "the amount must be > 0");
+ checkNotNull(direction);
+ checkNotNull(start);
+ checkArgument(allowedIncrease.contains(increaseOf), "Wrong increaseOf. Allowed values: "
+ + allowedIncrease);
+ checkArgument(increaseBy > 0, "increaseBy must be a positive number");
+ checkNotNull(timezones);
+ checkArgument(returnType.equals(String.class) || returnType.equals(Long.class),
+ "only String and Long accepted as return type");
+
+ final int tzsSize = timezones.size();
+ final boolean tzsExtract = tzsSize > 0;
+ final Random rnd = new Random(1);
+ final Random duplicate = new Random(2);
+
+ List<String> values = new ArrayList<String>(amount);
+ Calendar lstart = (Calendar) start.clone();
+ int hours = (OrderDirection.DESC.equals(direction)) ? -increaseBy : increaseBy;
+
+ for (int i = 0; i < amount; i++) {
+ if (tzsExtract) {
+ lstart.setTimeZone(timezones.get(rnd.nextInt(tzsSize)));
+ }
+ if (returnType.equals(String.class)) {
+ values.add(ISO8601.format(lstart));
+ } else if (returnType.equals(Long.class)) {
+ values.add(String.valueOf(lstart.getTimeInMillis()));
+ }
+ if (generateDuplicates && duplicate.nextDouble() < 0.1) {
+ // let's not increase the date
+ } else {
+ lstart.add(increaseOf, hours);
+ }
+
+ }
+
+ return values;
+ }
+
+ /**
+ * @return a Calendar set for midnight of 1st January 2013
+ */
+ public static Calendar midnightFirstJan2013() {
+ Calendar c = Calendar.getInstance();
+ c.set(2013, Calendar.JANUARY, 1, 0, 0, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ return c;
+ }
+
+}
Added: jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/ValuePathTuple.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/ValuePathTuple.java?rev=1618624&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/ValuePathTuple.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/util/ValuePathTuple.java Mon Aug 18 14:51:13 2014
@@ -0,0 +1,194 @@
+/*
+ * 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.jcr.util;
+
+import com.google.common.base.Predicate;
+
+// TODO copied over from oak-core. Should we have something common?
+
+/**
+ * convenience orderable object that represents a tuple of values and paths
+ *
+ * where the values are the indexed keys from the index and the paths are the path which hold the
+ * key
+ */
+public class ValuePathTuple implements Comparable<ValuePathTuple> {
+ private String value;
+ private String path;
+
+ /**
+ * convenience Predicate for easing the testing
+ */
+ public static class GreaterThanPredicate implements Predicate<ValuePathTuple> {
+ /**
+ * the value for comparison
+ */
+ private String value;
+
+ /**
+ * whether we should include the value in the result
+ */
+ private boolean include;
+
+ public GreaterThanPredicate(String value) {
+ this.value = value;
+ }
+
+ public GreaterThanPredicate(String value, boolean include) {
+ this.value = value;
+ this.include = include;
+ }
+
+ @Override
+ public boolean apply(ValuePathTuple arg0) {
+ return (value.compareTo(arg0.getValue()) < 0)
+ || (include && value.equals(arg0.getValue()));
+ }
+ };
+
+ public static class LessThanPredicate implements Predicate<ValuePathTuple> {
+ /**
+ * the value for comparison
+ */
+ private String value;
+
+ /**
+ * whether we should include the value in the result
+ */
+ private boolean include;
+
+ public LessThanPredicate(String value) {
+ this.value = value;
+ }
+
+ public LessThanPredicate(String value, boolean include) {
+ this.value = value;
+ this.include = include;
+ }
+
+ @Override
+ public boolean apply(ValuePathTuple arg0) {
+ return (value.compareTo(arg0.getValue()) > 0)
+ || (include && value.equals(arg0.getValue()));
+ }
+
+ }
+
+ public static class BetweenPredicate implements Predicate<ValuePathTuple> {
+ private String start;
+ private String end;
+ private boolean includeStart;
+ private boolean includeEnd;
+
+ public BetweenPredicate(String start, String end, boolean includeStart, boolean includeEnd) {
+ this.start = start;
+ this.end = end;
+ this.includeStart = includeStart;
+ this.includeEnd = includeEnd;
+ }
+
+ @Override
+ public boolean apply(ValuePathTuple arg0) {
+ String other = arg0.getValue();
+ return
+ (start.compareTo(other) < 0 || (includeStart && start.equals(other)))
+ && (end.compareTo(other) > 0 || (includeEnd && end.equals(other)));
+ }
+ }
+
+ public ValuePathTuple(String value, String path) {
+ this.value = value;
+ this.path = path;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + this.getClass().hashCode();
+ result = prime * result + ((path == null) ? 0 : path.hashCode());
+ result = prime * result + ((value == null) ? 0 : value.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ValuePathTuple other = (ValuePathTuple) obj;
+ if (path == null) {
+ if (other.getPath() != null) {
+ return false;
+ }
+ } else if (!path.equals(other.getPath())) {
+ return false;
+ }
+ if (value == null) {
+ if (other.getValue() != null) {
+ return false;
+ }
+ } else if (!value.equals(other.getValue())) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int compareTo(ValuePathTuple o) {
+ if (this.equals(o)) {
+ return 0;
+ }
+ if (this.value.compareTo(o.getValue()) < 0) {
+ return -1;
+ }
+ if (this.value.compareTo(o.getValue()) > 0) {
+ return 1;
+ }
+ if (this.path.compareTo(o.getPath()) < 0) {
+ return -1;
+ }
+ if (this.path.compareTo(o.getPath()) > 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "value: %s - path: %s - hash: %s",
+ value,
+ path,
+ super.toString()
+ );
+ }
+}