org.apache.jackrabbit.test.api.TreeComparator Maven / Gradle / Ivy
/*
* 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.test.api;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyIterator;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.Value;
import javax.jcr.PathNotFoundException;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeTypeIterator;
import javax.jcr.nodetype.NodeType;
import java.util.Calendar;
import java.io.ByteArrayInputStream;
import junit.framework.Assert;
/**
* TreeComparator
compares two trees. This allows re-use for
* different tests, and it allows to test a function on any tree, not just a
* simple example node.
*
* TreeComparator also creates an example tree that contains as many features as
* possible.
*/
class TreeComparator extends Assert {
public SerializationContext sc;
public final boolean WORKSPACE = true;
public final boolean SESSION = false;
public final int CHECK_EMPTY = -1;
public final int IGNORE = 0;
public final int CHECK_SAME = 1;
public int check = CHECK_SAME;
private Session session;
private Workspace workspace;
public boolean skipBinary = false, noRecurse = false;
public String sourceFolder, targetFolder;
public String root;
public TreeComparator(SerializationContext sc, Session s) throws Exception {
this.sc = sc;
session = s;
workspace = session.getWorkspace();
init();
}
public void setSession(Session session) {
this.session = session;
}
/**
* Makes sure that the source and target folder exist, and are empty
*/
private void init() throws RepositoryException {
root = sc.testroot;
sourceFolder = root + "/" + sc.sourceFolderName;
targetFolder = root + "/" + sc.targetFolderName;
// make sure the node does not have a and target sub node.
try {
session.getItem(sourceFolder).remove();
session.save();
} catch (PathNotFoundException e) {
// item does not exist
}
try {
Item tgt = session.getItem(targetFolder);
tgt.remove();
session.save();
} catch (PathNotFoundException e) {
// item does not exist
}
// add the source and target nodes
Node rootNode = (Node) session.getItem(root);
rootNode.addNode(sc.sourceFolderName);
rootNode.addNode(sc.targetFolderName);
session.save();
}
/**
* Creates an example tree in the workspace.
*/
public void createExampleTree() {
createExampleTree(true);
}
/**
* Creates a simple example tree. Use this tree for general repository
* functions, such as serialization, namespaces and versioning.
*
* The beauty of this is that the tree contains exactly the features that
* are supported by the repository. Any repository exceptions that occur are
* displayed on "out", but are otherwise ignored.
*
* @param save If true, the example tree is saved to the workspace. If
* false, it remains in the session.
*/
public void createExampleTree(boolean save) {
try {
Node src = (Node) session.getItem(sourceFolder);
Node root = src.addNode(sc.rootNodeName);
root.addNode(sc.nodeName1);
root.addNode(sc.nodeName2, sc.testNodeType);
byte[] byteArray = {(byte) 0, (byte) 255, (byte) 167, (byte) 100, (byte) 21, (byte) 6, (byte) 19, (byte) 71, (byte) 221};
root.setProperty(sc.propertyName1, new ByteArrayInputStream(byteArray));
root.setProperty(sc.nodeName3, "hello");
} catch (Exception e) {
sc.log("Error while creating example tree: " + e.getMessage());
}
if (save) {
try {
session.save();
} catch (RepositoryException e) {
fail("Cannot save the example tree to the repository: " + e);
}
}
}
/**
* Creates a complex example tree in the workspace
*/
public void createComplexTree() {
createComplexTree(true);
}
/**
* Creates a complex example tree that uses as many features of the
* repository as possible
*
* @param save Save the tree to the workspace. If false, the tree remains in
* the session.
*/
public void createComplexTree(boolean save) {
Node rootNode = null;
Node nt = null;
Node pt = null;
Node mvp = null;
Node referenceable = null;
try {
Node src = (Node) session.getItem(sourceFolder);
rootNode = src.addNode(sc.rootNodeName);
nt = rootNode.addNode(sc.nodeTypesTestNode);
rootNode.addNode(sc.mixinTypeTestNode);
pt = rootNode.addNode(sc.propertyTypesTestNode);
rootNode.addNode(sc.sameNameChildrenTestNode);
mvp = rootNode.addNode(sc.multiValuePropertiesTestNode);
referenceable = rootNode.addNode(sc.referenceableNodeTestNode);
rootNode.addNode(sc.orderChildrenTestNode);
rootNode.addNode(sc.namespaceTestNode);
} catch (RepositoryException e) {
sc.log("Error while creating example tree: " + e.getMessage());
}
// Add nodes with mixin types
NodeTypeManager ntmgr = null;
NodeTypeIterator types = null;
try {
ntmgr = workspace.getNodeTypeManager();
types = ntmgr.getMixinNodeTypes();
} catch (RepositoryException e) {
fail("Cannot access NodeType iterator: " + e);
}
while (types.hasNext()) {
NodeType t = (NodeType) types.next();
String name = t.getName();
name = "Node_" + name.replaceAll(":", "_");
Node n = null;
try {
n = nt.addNode(name);
n.addMixin(t.getName());
// try saving, because some exceptions are thrown only at save time
session.save();
} catch (RepositoryException e) {
sc.log("Cannot create node with mixin node type: " + e);
// if saving failed for a node, then remove it again (or else the next save will fail on it)
try {
if (n != null) {
n.remove();
}
} catch (RepositoryException e1) {
sc.log("Could not remove node: " + e);
}
}
}
// Create all property types
try {
// String
pt.setProperty(sc.stringTestProperty, "This is a string.");
// Binary
byte[] byteArray = {(byte) 0, (byte) 255, (byte) 167, (byte) 100, (byte) 21, (byte) 6, (byte) 19, (byte) 71, (byte) 221};
pt.setProperty(sc.binaryTestProperty, new ByteArrayInputStream(byteArray));
// Date
Calendar c = Calendar.getInstance();
c.set(2005, 6, 21, 13, 30, 5);
pt.setProperty(sc.dateTestProperty, c);
// Long
pt.setProperty(sc.longTestProperty, (long) (1 / 3));
// Double
pt.setProperty(sc.doubleTestProperty, (double) Math.PI);
// Boolean
pt.setProperty(sc.booleanTestProperty, true);
// Name
pt.setProperty(sc.nameTestProperty, session.getValueFactory().createValue(sc.jcrPrimaryType, PropertyType.NAME));
// Path
pt.setProperty(sc.pathTestProperty, session.getValueFactory().createValue("paths/dont/have/to/point/anywhere", PropertyType.PATH));
// Reference: Note that I only check if the node exists. We do not specify what happens with
// the UUID during serialization.
sc.ensureMixinType(referenceable, sc.mixReferenceable);
// some implementations may require a save after addMixin()
session.save();
pt.setProperty(sc.referenceTestProperty, referenceable);
// Create a boolean property on the root node, so I can test noRecurse and skipBinary at the same time
rootNode.setProperty(sc.binaryTestProperty, new ByteArrayInputStream(byteArray));
session.save();
} catch (Exception e) {
fail("Could not add property: " + e);
}
// multi value properties
String[] s = {"one", "two", "three"};
try {
mvp.setProperty(sc.multiValueTestProperty, s);
session.save();
} catch (RepositoryException e) {
sc.log("Could not create multi-value property: " + e);
}
// Save to the workspace. Note that export is from session anyway.
if (save) {
try {
session.save();
} catch (RepositoryException e) {
fail("Cannot save the example tree to the repository: " + e);
}
}
}
/**
* Compares the trees in the source and target folder.
*
* @param skipBinary True if skipbinary is set, so binary properties are not
* in the taget tree.
* @param noRecurse True if noRecurse is used, so only the top node and its
* properties are in the target tree.
*/
public void compare(boolean skipBinary, boolean noRecurse) {
this.skipBinary = skipBinary;
this.noRecurse = noRecurse;
compare();
}
/**
* Compares the source and target tree.
*/
public void compare() {
compare(CHECK_SAME);
}
/**
* Compares the source and target tree.
*
* @param check CHECK_SAME checks if the two trees have the same nodes and
* properties. CHECK_EMPTY checks that the target tree is
* empty.
*/
public void compare(int check) {
this.check = check;
compare(sourceFolder + "/" + sc.rootNodeName, 0);
}
/**
* Compares two nodes in the source and target tree
*
* @param sourcePath The path of the node in the source tree
* @param level The level of depth in the tree
*/
public void compare(String sourcePath, int level) {
Node source = null;
Node target = null;
// get the source path
try {
source = (Node) session.getItem(sourcePath);
} catch (RepositoryException e) {
fail("Could not read source node " + sourcePath + ": " + e.getMessage());
}
// get the target path
String targetPath = getTargetPath(sourcePath);
try {
session.getItem(targetFolder);
} catch (RepositoryException e) {
fail("Target folder not found: " + e);
}
// Check noRecurse: After top level, the target tree must be empty
if (noRecurse && level == 1) {
check = CHECK_EMPTY;
}
// compare source and target
if (check == CHECK_SAME) {
try {
target = (Node) session.getItem(targetPath);
} catch (RepositoryException e) {
showTree();
fail("Could not read target node " + targetPath + ": " + e);
}
compareNodes(source, target);
} else if (check == CHECK_EMPTY) {
try {
session.getItem(targetPath);
fail("The item " + targetPath + " must not be available.");
} catch (RepositoryException e) {
}
}
// iterate through all child nodes of the source tree
try {
NodeIterator ni = source.getNodes();
while (ni.hasNext()) {
Node n = (Node) ni.next();
compare(n.getPath(), level + 1);
}
} catch (RepositoryException e) {
fail("Error while iterating through child nodes: " + e);
}
}
/**
* Compares two nodes, a and b
*
* @param a The node in the source tree
* @param b The same node in the target tree
*/
public void compareNodes(Node a, Node b) {
try {
sc.log("Comparing " + a.getPath() + " to " + b.getPath());
} catch (RepositoryException e) {
fail("Nodes not available: " + e.getMessage());
}
// check primary node type
String primaryTypeA = null, primaryTypeB = null;
try {
primaryTypeA = a.getProperty(sc.jcrPrimaryType).getName();
primaryTypeB = b.getProperty(sc.jcrPrimaryType).getName();
} catch (RepositoryException e) {
fail("Primary node type not available: " + e);
}
assertEquals("Primary node type has changed.", primaryTypeA, primaryTypeB);
compareProperties(a, b);
}
/**
* Compares all the properties of the two nodes a and b
*
* @param a The node in the source tree.
* @param b The node in the target tree.
*/
public void compareProperties(Node a, Node b) {
PropertyIterator ai = null;
try {
ai = a.getProperties();
} catch (RepositoryException e) {
fail("Cannot access properties: " + e);
}
while (ai.hasNext()) {
Property pa = (Property) ai.next();
String pName = null;
// todo
String pPath = null;
try {
pPath = pa.getPath();
} catch (RepositoryException e) {
}
int pType = 0;
try {
pName = pa.getName();
if (pa.getDefinition().isMultiple()) {
pType = -9999;
} else {
pType = pa.getValue().getType();
}
} catch (RepositoryException e) {
fail("Cannot access property information: " + e);
}
if (propertyValueMayChange(pName)) {
continue;
}
Property pb = null;
// avoid skipped properties
if (!propertySkipped(pName)) {
try {
pb = b.getProperty(pName);
} catch (RepositoryException e) {
//fail if the property is not there but should
fail("Property '" + pPath + "' not available: " + e);
}
if (!(skipBinary && pType == PropertyType.BINARY)) {
// todo
// compare source and target value
compareProperties(pa, pb);
}
}
}
}
/**
* Compares two properties a and b.
*
* @param a The property in the source tree.
* @param b The property in the target tree.
*/
public void compareProperties(Property a, Property b) {
String nodeName = null, propertyName = null;
boolean isMultiple = false;
try {
nodeName = a.getParent().getName();
propertyName = a.getName();
isMultiple = a.getDefinition().isMultiple();
} catch (RepositoryException e) {
fail("Cannot access property information: " + e);
}
if (!propertyValueMayChange(propertyName)) {
if (isMultiple) {
try {
compareValues(nodeName, propertyName, a.getValues(), b.getValues());
} catch (RepositoryException e) {
fail("Could not access property values: " + e);
}
} else {
try {
compareValue(nodeName, propertyName, a.getValue(), b.getValue());
} catch (RepositoryException e) {
fail("Could not access property value: " + e);
}
}
}
}
/**
* Compares a set of multi-value properties.
*
* @param n Name of the node.
* @param p Name of the property.
* @param a Value (array) of the property in the source tree.
* @param b Value (array) of the property in the target tree.
*/
public void compareValues(String n, String p, Value[] a, Value[] b) {
assertEquals("Multi-value property '" + p + "' of node '" + n + "' has changed length: ", a.length, b.length);
for (int t = 0; t < a.length; t++) {
compareValue(n, p, a[t], b[t]);
}
}
/**
* Compares the value of two properties
*
* @param n Name of the node.
* @param p Name of the property.
* @param a Value of the property in the source tree.
* @param b Value of the property in the target tree.
*/
public void compareValue(String n, String p, Value a, Value b) {
if (!propertyValueMayChange(p)) {
try {
assertEquals("Properties '" + p + "' of node '" + n + "' have different values.", a.getString(), b.getString());
} catch (RepositoryException e) {
fail("Cannot access the content of the property value: " + e);
}
}
}
/**
* Returns whether the value of the property may change in serialization.
* For example, the last changed date of a node may change when the node is
* re-imported. The values that may change are declared in the config file.
*
* @param propertyName The property name you want to check
* @return True or false to indicate whether the value may or may not
* change.
*/
public boolean propertyValueMayChange(String propertyName) {
if (sc.propertyValueMayChange.indexOf(" " + propertyName + " ") < 0) {
return false;
} else {
return true;
}
}
/**
* Returns the path to the source root.
*
* @return The path to the source root.
*/
public String getSourceRootPath() {
return sourceFolder + "/" + sc.rootNodeName;
}
/**
* Returns the path to the target root.
*
* @return The path to the target root.
*/
private String getTargetPath(String sourcePath) {
String targetPath = sourcePath.replaceAll(sourceFolder, targetFolder);
return targetPath;
}
/**
* Displays the current source and target tree for debug/information
* purpose
*/
public void showTree() {
Node n = null;
try {
n = (Node) session.getItem(sc.testroot);
showTree(n, 0);
} catch (RepositoryException e) {
sc.log("Cannot display tree diagnostics: " + e);
}
}
/**
* Recursive display of source and target tree
*/
public void showTree(Node n, int level) throws RepositoryException {
StringBuffer sb = new StringBuffer();
for (int t = 0; t < level; t++) {
sb.append("-");
}
sb.append(n.getName() + " ");
sb.append(n.getPrimaryNodeType().getName() + " [ ");
PropertyIterator pi = n.getProperties();
while (pi.hasNext()) {
Property p = (Property) pi.next();
sb.append(p.getName() + " ");
}
sb.append("]");
sc.log(sb.toString());
NodeIterator ni = n.getNodes();
while (ni.hasNext()) {
showTree((Node) ni.next(), level + 1);
}
}
/**
* Checks if a given property should be skipped during xml import.
*
* @param propertyName
* @return
*/
public boolean propertySkipped(String propertyName) {
if (sc.propertySkipped.indexOf(" " + propertyName + " ") < 0) {
return false;
} else {
return true;
}
}
}