/*
* 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.core.integration;
import org.apache.jackrabbit.core.query.AbstractQueryTest;
import javax.jcr.RepositoryException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import java.util.Random;
import java.util.Set;
import java.util.HashSet;
import java.util.regex.Pattern;
/**
* <code>AxisQueryTest</code> runs random queries on generated test data. The
* amount of test data can be controled by {@link #NUM_LEVELS} and
* {@link #NODES_PER_LEVEL}. The default values create 363 nodes distributed
* over 5 hierarchy levels.
*/
public class AxisQueryTest extends AbstractQueryTest {
/**
* Number of levels of test data to create.
*/
private static final int NUM_LEVELS = 5;
/**
* Number of nodes per level
*/
private static final int NODES_PER_LEVEL = 3;
/**
* Execute random queries for this amount of time.
*/
private static final int RUN_NUM_SECONDS = 10;
/**
* Controls if query results are checked for their correctness. When the
* number of test nodes increases this becomes expensive and should be
* disabled.
*/
private static final boolean CHECK_RESULT = true;
/**
* Set to a name that is different from {@link #testPath} when number of
* test nodes is increased. This avoids that the test nodes are removed
* after a test run. The number of test nodes can be controlled by
* {@link #NUM_LEVELS} and {@link #NODES_PER_LEVEL}.
*/
private String relTestLocation;
/**
* Absolute path to the test location.
*/
private String absTestLocation;
private Random rand = new Random();
protected void setUp() throws Exception {
super.setUp();
if (relTestLocation == null) {
// use default location
relTestLocation = testPath;
absTestLocation = "/" + relTestLocation;
createNodes(testRootNode, NODES_PER_LEVEL, NUM_LEVELS, 0);
} else {
// customized location
absTestLocation = "/" + relTestLocation;
// only create test data if not yet present
if (!superuser.itemExists(absTestLocation)) {
Node testLocation = superuser.getRootNode().addNode(relTestLocation);
createNodes(testLocation, NODES_PER_LEVEL, NUM_LEVELS, 0);
}
// uncomment to write log messages to system out
//log.setWriter(new PrintWriter(System.out, true));
}
}
public void testExecuteQueries() throws RepositoryException {
Node testLocation = superuser.getRootNode().getNode(relTestLocation);
long end = System.currentTimeMillis() + 1000 * RUN_NUM_SECONDS;
while (end > System.currentTimeMillis()) {
StringBuffer statement = new StringBuffer(relTestLocation);
StringBuffer regexp = new StringBuffer(absTestLocation);
int numSteps = rand.nextInt(NUM_LEVELS) + 1; // at least one step
while (numSteps-- > 0) {
String axis = getRandomAxis();
String name = getRandomName();
statement.append(axis).append(name);
if (axis.equals("//")) {
regexp.append("/(.*/)?");
} else {
regexp.append("/");
}
if (name.equals("*")) {
regexp.append("[^/]*");
} else {
regexp.append(name);
}
}
long time = System.currentTimeMillis();
NodeIterator nodes = executeQuery(statement.toString()).getNodes();
nodes.hasNext();
time = System.currentTimeMillis() - time;
log.print(statement + "\t" + nodes.getSize() + "\t" + time);
if (CHECK_RESULT) {
Set paths = new HashSet();
time = System.currentTimeMillis();
traversalEvaluate(testLocation,
Pattern.compile(regexp.toString()),
paths);
time = System.currentTimeMillis() - time;
log.println("\t" + time);
assertEquals("wrong number of results", paths.size(), nodes.getSize());
while (nodes.hasNext()) {
String path = nodes.nextNode().getPath();
assertTrue(path + " is not part of the result set", paths.contains(path));
}
} else {
log.println();
}
log.flush();
}
}
private String getRandomAxis() {
if (rand.nextBoolean() && rand.nextBoolean()) {
// 25% descendant axis
return "//";
} else {
// 75% child axis
return "/";
}
}
private String getRandomName() {
if (rand.nextBoolean() && rand.nextBoolean()) {
// 25% any name
return "*";
} else {
// 75% name
return "node" + rand.nextInt(NODES_PER_LEVEL);
}
}
private void traversalEvaluate(Node n, Pattern pattern, Set matchingPaths)
throws RepositoryException {
if (pattern.matcher(n.getPath()).matches()) {
matchingPaths.add(n.getPath());
}
for (NodeIterator it = n.getNodes(); it.hasNext(); ) {
traversalEvaluate(it.nextNode(), pattern, matchingPaths);
}
}
private int createNodes(Node n, int nodesPerLevel, int levels, int count)
throws RepositoryException {
levels--;
for (int i = 0; i < nodesPerLevel; i++) {
Node child = n.addNode("node" + i);
child.setProperty("count", count++);
if (count % 1000 == 0) {
superuser.save();
log.println("Created " + (count / 1000) + "k nodes");
}
if (levels > 0) {
count = createNodes(child, nodesPerLevel, levels, count);
}
}
if (levels == 0) {
// final save
superuser.save();
}
return count;
}
}