/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.jcr;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.nodetype.NodeType;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.jcr.security.AccessControlList;
import javax.jcr.security.AccessControlManager;
import javax.jcr.security.AccessControlPolicyIterator;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.modeshape.common.junit.SkipTestRule;
import org.modeshape.common.statistic.Stopwatch;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.api.JcrTools;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.Path;
import org.modeshape.jcr.value.Path.Segment;
public abstract class AbstractJcrRepositoryTest {
@Rule
public TestRule skipTestRule = new SkipTestRule();
protected boolean print;
@Before
public void beforeEach() throws Exception {
print = false;
}
protected abstract JcrRepository repository();
protected abstract JcrSession session();
protected Path path( String path ) {
return session().context().getValueFactories().getPathFactory().create(path);
}
protected Name name( String name ) {
return session().context().getValueFactories().getNameFactory().create(name);
}
protected String relativePath( String path ) {
return !path.startsWith("/") ? path : path.substring(1);
}
protected String asString( Object value ) {
return session().context().getValueFactories().getStringFactory().create(value);
}
protected AccessControlList acl( String path ) throws Exception {
AccessControlManager acm = session().getAccessControlManager();
AccessControlPolicyIterator it = acm.getApplicablePolicies(path);
if (it.hasNext()) {
return (AccessControlList)it.nextAccessControlPolicy();
}
return (AccessControlList)acm.getPolicies(path)[0];
}
protected void assertNoNode( String path ) throws RepositoryException {
// Verify that the parent node does exist now ...
assertThat("Did not expect to find '" + path + "'", session().getRootNode().hasNode(relativePath(path)), is(false));
try {
session().getNode(path);
fail("Did not expect to find node at \"" + path + "\"");
} catch (PathNotFoundException e) {
// expected
}
}
protected Node assertNode( String path ) throws RepositoryException {
if (print && !session().getRootNode().hasNode(path)) {
// We won't find the node, so print out the information ...
Node parent = session().getRootNode();
for (Segment segment : path(path)) {
if (!parent.hasNode(asString(segment))) {
System.out.println("Unable to find '" + path + "'; lowest node is '" + parent.getPath() + "'");
break;
}
parent = parent.getNode(asString(segment));
}
}
Node node = session().getNode(path);
assertThat(node, is(notNullValue()));
// Verify that the path can be found via navigating ...
if (path.trim().length() == 0) {
// This is the root path, so of course it exists ...
assertThat(session().getRootNode(), is(notNullValue()));
} else {
}
return node;
}
protected Node assertNode( String path,
String primaryType ) throws RepositoryException {
Node node = assertNode(path);
assertEquals(primaryType, node.getPrimaryNodeType().getName());
return node;
}
protected void assertSameProperties( Node node1,
Node node2,
String... excludedPropertyNames ) throws RepositoryException {
Set<String> excludedNames = new HashSet<String>(Arrays.asList(excludedPropertyNames));
Set<String> node2Names = new HashSet<String>();
// Find the names of all (non-excluded) proeprties in node 2 ...
PropertyIterator iter = node2.getProperties();
while (iter.hasNext()) {
Property prop2 = iter.nextProperty();
node2Names.add(prop2.getName());
}
node2Names.removeAll(excludedNames);
iter = node1.getProperties();
while (iter.hasNext()) {
Property prop1 = iter.nextProperty();
String name = prop1.getName();
if (excludedNames.contains(name)) continue;
Property prop2 = node2.getProperty(prop1.getName());
assertThat(prop1.isMultiple(), is(prop2.isMultiple()));
if (prop1.isMultiple()) {
Value[] values1 = prop1.getValues();
Value[] values2 = prop2.getValues();
assertThat(values1, is(values2));
} else {
assertThat(prop1.getValue().getString(), is(prop2.getValue().getString()));
}
node2Names.remove(name);
}
// There should be no more properties left ...
if (!node2Names.isEmpty()) {
fail("Found extra properties in node2: " + node2Names);
}
}
protected void addMixinRecursively( String path,
String... nodeTypes ) throws RepositoryException {
Node node = session().getRootNode().getNode(relativePath(path));
addMixin(node, true, nodeTypes);
}
protected Node addMixin( String path,
String... nodeTypes ) throws RepositoryException {
Node node = session().getRootNode().getNode(relativePath(path));
return addMixin(node, false, nodeTypes);
}
protected Node addMixin( Node node,
boolean recursive,
String... nodeTypes ) throws RepositoryException {
assertThat(node, is(notNullValue()));
for (String nodeType : nodeTypes) {
if (!hasMixin(node, nodeType)) {
node.addMixin(nodeType);
}
}
if (recursive) {
NodeIterator children = node.getNodes();
while (children.hasNext()) {
addMixin(children.nextNode(), true, nodeTypes);
}
}
return node;
}
protected boolean hasMixin( Node node,
String mixinNodeType ) throws RepositoryException {
for (NodeType mixin : node.getMixinNodeTypes()) {
if (mixin.getName().equals(mixinNodeType)) return true;
}
return false;
}
protected QueryResult assertJcrSql2Query( String sql,
long expectedRowCount ) throws RepositoryException {
Query query = session().getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2);
QueryResult results = query.execute();
printMessage(query.getStatement());
ValidateQuery.validateQuery().printDetail(print).rowCount(expectedRowCount).validate(query, results);
return results;
}
protected void printMessage( Object message ) {
if (print) {
System.out.println(message);
}
}
protected void printResults( QueryResult results ) {
printResults(null, results);
}
protected void printResults( String msg,
QueryResult results ) {
if (print) {
if (msg != null) System.out.println(msg);
System.out.println(results);
}
}
protected void printDetails( Node node ) throws RepositoryException {
if (print) {
new JcrTools(true).printNode(node);
}
}
protected void print() throws RepositoryException {
print(session().getRootNode(), true);
}
private Node nodeFor( String path ) throws RepositoryException {
if (path.equals("/")) {
return session().getRootNode();
}
return session().getRootNode().getNode(relativePath(path));
}
protected void print( String path ) throws RepositoryException {
print(nodeFor(path), true);
}
protected void print( String path,
boolean includeSystem ) throws RepositoryException {
print(nodeFor(path), includeSystem, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
protected void print( String path,
boolean includeSystem,
int maxNumberOfChildren ) throws RepositoryException {
print(nodeFor(path), includeSystem, maxNumberOfChildren, Integer.MAX_VALUE);
}
protected void print( Node node ) throws RepositoryException {
print(node, true);
}
protected void print( Node node,
boolean includeSystem ) throws RepositoryException {
print(node, includeSystem, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
protected void print( Node node,
boolean includeSystem,
int maxNumberOfChildren ) throws RepositoryException {
print(node, includeSystem, maxNumberOfChildren, Integer.MAX_VALUE);
}
protected void print( Node node,
boolean includeSystem,
int maxNumberOfChildren,
int depth ) throws RepositoryException {
if (print && depth > 0) {
if (!includeSystem && node.getPath().equals("/jcr:system")) return;
if (node.getDepth() != 0) {
int snsIndex = node.getIndex();
String segment = node.getName() + (snsIndex > 1 ? ("[" + snsIndex + "]") : "");
System.out.println(StringUtil.createString(' ', 2 * node.getDepth()) + '/' + segment);
}
int nextDepth = depth - 1;
if (nextDepth <= 0) return;
NodeIterator children = node.getNodes();
int count = 0;
while (children.hasNext()) {
if (count >= maxNumberOfChildren) {
System.out.println(StringUtil.createString(' ', 2 * (node.getDepth() + 1)) + "...");
break;
}
print(children.nextNode(), includeSystem, maxNumberOfChildren, nextDepth);
++count;
}
}
}
protected void navigate( String path ) throws RepositoryException {
Node node = session().getRootNode().getNode(relativePath(path));
navigate(node, true);
}
protected void navigate( Node node ) throws RepositoryException {
navigate(node, true);
}
protected void navigate( Node node,
boolean includeSystem ) throws RepositoryException {
navigate(node, includeSystem, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
protected void navigate( Node node,
boolean includeSystem,
int maxNumberOfChildren ) throws RepositoryException {
navigate(node, includeSystem, maxNumberOfChildren, Integer.MAX_VALUE);
}
protected void navigate( Node node,
boolean includeSystem,
int maxNumberOfChildren,
int depth ) throws RepositoryException {
if (depth > 0) {
if (!includeSystem && node.getPath().equals("/jcr:system")) return;
if (node.getDepth() != 0) {
int snsIndex = node.getIndex();
String segment = node.getName() + (snsIndex > 1 ? ("[" + snsIndex + "]") : "");
if (print) System.out.println(StringUtil.createString(' ', 2 * node.getDepth()) + '/' + segment);
}
int nextDepth = depth - 1;
if (nextDepth <= 0) return;
NodeIterator children = node.getNodes();
int count = 0;
while (children.hasNext()) {
if (count >= maxNumberOfChildren) {
if (print) System.out.println(StringUtil.createString(' ', 2 * (node.getDepth() + 1)) + "...");
break;
}
navigate(children.nextNode(), includeSystem, maxNumberOfChildren, nextDepth);
++count;
}
}
}
private static String getRandomString( int length ) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
sb.append((char)((Math.random() * 26) + 'a'));
}
return sb.toString();
}
private static int createChildren( Node parent,
int numProperties,
int width,
int depth ) throws Exception {
if (depth < 1) {
return 0;
}
int count = width;
for (int i = 0; i < width; i++) {
Node newNode = parent.addNode(getRandomString(9), "nt:unstructured");
for (int j = 0; j < numProperties; j++) {
newNode.setProperty(getRandomString(8), getRandomString(16));
}
count += createChildren(newNode, numProperties, width, depth - 1);
}
return count;
}
protected static int createSubgraph( JcrSession session,
String initialPath,
int depth,
int numberOfChildrenPerNode,
int numberOfPropertiesPerNode,
boolean oneBatch,
Stopwatch stopwatch,
PrintStream output,
String description ) throws Exception {
// Calculate the number of nodes that we'll created, but subtract 1 since it doesn't create the root
long totalNumber = calculateTotalNumberOfNodesInTree(numberOfChildrenPerNode, depth, false);
if (initialPath == null) initialPath = "";
if (description == null) {
description = "" + numberOfChildrenPerNode + "x" + depth + " tree with " + numberOfPropertiesPerNode
+ " properties per node";
}
if (output != null) output.println(description + " (" + totalNumber + " nodes):");
long totalNumberCreated = 0;
Node parentNode = session.getNode(initialPath);
if (stopwatch != null) stopwatch.start();
totalNumberCreated += createChildren(parentNode, numberOfPropertiesPerNode, numberOfChildrenPerNode, depth);
assertThat(totalNumberCreated, is(totalNumber));
session.save();
if (stopwatch != null) {
stopwatch.stop();
if (output != null) {
output.println(" " + getTotalAndAverageDuration(stopwatch, totalNumberCreated));
}
}
return (int)totalNumberCreated;
}
protected static int traverseSubgraph( JcrSession session,
String initialPath,
int depth,
int numberOfChildrenPerNode,
int numberOfPropertiesPerNode,
boolean oneBatch,
Stopwatch stopwatch,
PrintStream output,
String description ) throws Exception {
// Calculate the number of nodes that we'll created, but subtract 1 since it doesn't create the root
long totalNumber = calculateTotalNumberOfNodesInTree(numberOfChildrenPerNode, depth, false);
if (initialPath == null) initialPath = "";
if (description == null) {
description = "" + numberOfChildrenPerNode + "x" + depth + " tree with " + numberOfPropertiesPerNode
+ " properties per node";
}
if (output != null) output.println(description + " (" + totalNumber + " nodes):");
long totalNumberTraversed = 0;
Node parentNode = session.getNode(initialPath);
if (stopwatch != null) stopwatch.start();
totalNumberTraversed += traverseChildren(parentNode);
assertThat(totalNumberTraversed, is(totalNumber));
session.save();
if (stopwatch != null) {
stopwatch.stop();
if (output != null) {
output.println(" " + getTotalAndAverageDuration(stopwatch, totalNumberTraversed));
}
}
return (int)totalNumberTraversed;
}
protected static int traverseChildren( Node parentNode ) throws Exception {
int childCount = 0;
NodeIterator children = parentNode.getNodes();
while (children.hasNext()) {
childCount++;
childCount += traverseChildren(children.nextNode());
}
return childCount;
}
protected static String getTotalAndAverageDuration( Stopwatch stopwatch,
long numNodes ) {
long totalDurationInMilliseconds = TimeUnit.NANOSECONDS.toMillis(stopwatch.getTotalDuration().longValue());
if (numNodes == 0) numNodes = 1;
long avgDuration = totalDurationInMilliseconds / numNodes;
String units = " millisecond(s)";
if (avgDuration < 1L) {
long totalDurationInMicroseconds = TimeUnit.NANOSECONDS.toMicros(stopwatch.getTotalDuration().longValue());
avgDuration = totalDurationInMicroseconds / numNodes;
units = " microsecond(s)";
}
return "total = " + stopwatch.getTotalDuration() + "; avg = " + avgDuration + units;
}
protected static int calculateTotalNumberOfNodesInTree( int numberOfChildrenPerNode,
int depth,
boolean countRoot ) {
assert numberOfChildrenPerNode > 0;
int totalNumber = 0;
for (int i = 0; i <= depth; ++i) {
totalNumber += (int)Math.pow(numberOfChildrenPerNode, i);
}
return countRoot ? totalNumber : totalNumber - 1;
}
protected void assertChildrenInclude( Node parentNode,
String... minimalChildNamesWithSns ) throws RepositoryException {
assertChildrenInclude(null, parentNode, minimalChildNamesWithSns);
}
protected void assertHasMixins( Node node,
String... mixinNodeTypes ) throws RepositoryException {
List<String> mixins = new ArrayList<String>();
for (NodeType nodeType : node.getMixinNodeTypes()) {
mixins.add(nodeType.getName());
}
for (String expectedMixin : mixinNodeTypes) {
assertThat(expectedMixin + "mixin not found", mixins.contains(expectedMixin), is(true));
}
}
protected void assertChildrenInclude( String errorMessage,
Node parentNode,
String... minimalChildNamesWithSns ) throws RepositoryException {
Set<String> childNames = new HashSet<String>();
childNames.addAll(Arrays.asList(minimalChildNamesWithSns));
NodeIterator iter = parentNode.getNodes();
while (iter.hasNext()) {
Node child = iter.nextNode();
String name = child.getName();
if (child.getIndex() > 1) {
name = name + "[" + child.getIndex() + "]";
}
childNames.remove(name);
// If we've found all of the expected child names, then return immediately
// (in case there are a very large number of children) ...
if (childNames.isEmpty()) {
return;
}
}
String message = "Names of children not found under node: " + childNames;
if (!StringUtil.isBlank(errorMessage)) {
message += ". " + errorMessage;
}
assertThat(message, childNames.isEmpty(), is(true));
}
}