org.apache.jackrabbit.test.api.ExportDocViewTest 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 org.apache.jackrabbit.test.AbstractJCRTest;
import org.apache.jackrabbit.test.XMLChar;
import org.xml.sax.SAXException;
import org.xml.sax.ContentHandler;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Attr;
import org.w3c.dom.NamedNodeMap;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.NamespaceRegistry;
import javax.jcr.RepositoryException;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.PropertyIterator;
import javax.jcr.Value;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.ArrayList;
import java.io.File;
import java.io.IOException;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.io.StringWriter;
/**
* ExportDocViewTest
tests the two Session methods :
* {@link Session#exportDocumentView(String, ContentHandler, boolean, boolean)}
* and {@link Session#exportDocumentView(String, java.io.OutputStream, boolean, boolean)}
* against the required behaviours according the document view xml mapping
* defined in the JSR 170 specification in chapter 6.4.2, 6.4.3 and 6.4.4 .
*
*/
public class ExportDocViewTest extends AbstractJCRTest {
private final boolean CONTENTHANDLER = true, STREAM = false;
private final boolean SKIPBINARY = true, SAVEBINARY = false;
private final boolean NORECURSE = true, RECURSE = false;
/**
* Resolved Name for jcr:xmltext
*/
private String JCR_XMLTEXT;
/**
* Resolved Name for jcr:xmlcharacters
*/
private String JCR_XMLDATA;
/**
* the stack of the text node values to check
*/
private Stack textValuesStack;
private class StackEntry {
// the list of text node values of the text nodes of an xml element
List textValues;
// the current position in the ArrayList
int position = 0;
}
/**
* indicates if the tested repository exports multivalued properties.
*/
private boolean exportMultivalProps = false;
/**
* indicates if the tested repository escapes (xml)invalid jcr names.
*/
private boolean exportInvalidXmlNames = false;
private boolean skipBinary;
private boolean noRecurse;
private boolean withHandler;
private File file;
private Session session;
private Workspace workspace;
private NamespaceRegistry nsr;
private String testPath;
private Document doc;
protected void setUp() throws Exception {
isReadOnly = true;
session = getHelper().getReadOnlySession();
workspace = session.getWorkspace();
nsr = workspace.getNamespaceRegistry();
file = File.createTempFile("docViewExportTest", ".xml");
super.setUp();
JCR_XMLTEXT = session.getNamespacePrefix(NS_JCR_URI) + ":xmltext";
JCR_XMLDATA = session.getNamespacePrefix(NS_JCR_URI) + ":xmlcharacters";
testPath = testRoot;
}
protected void tearDown() throws Exception {
file.delete();
if (session != null) {
session.logout();
session = null;
}
workspace = null;
nsr = null;
super.tearDown();
}
public void testExportDocView_handler_session_skipBinary_noRecurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(CONTENTHANDLER, SKIPBINARY, NORECURSE);
}
public void testExportDocView_handler_session_skipBinary_recurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(CONTENTHANDLER, SKIPBINARY, RECURSE);
}
public void testExportDocView_handler_session_saveBinary_noRecurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(CONTENTHANDLER, SAVEBINARY, NORECURSE);
}
public void testExportDocView_handler_session_saveBinary_recurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(CONTENTHANDLER, SAVEBINARY, RECURSE);
}
public void testExportDocView_stream_session_skipBinary_recurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(STREAM, SKIPBINARY, RECURSE);
}
public void testExportDocView_stream_session_skipBinary_noRecurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(STREAM, SKIPBINARY, NORECURSE);
}
public void testExportDocView_stream_session_saveBinary_noRecurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(STREAM, SAVEBINARY, NORECURSE);
}
public void testExportDocView_stream_session_saveBinary_recurse()
throws IOException, RepositoryException, SAXException, TransformerException {
doTestExportDocView(STREAM, SAVEBINARY, RECURSE);
}
/**
* Tests session.exportDocView with the different argument possibilities.
* The flag withHandler decides if the method requiring a ContentHandler as
* argument is called. The class org.apache.xml.serialize.XMLSerializer is
* taken as ContentHandler in this case. In both cases ( export with a
* ContentHandler and export with Stream) the test node is exported to the
* file defined in the setUp. This exported file is parsed using
* javax.xml.transform package and the receiving document is compared with
* the test node and its properties and child nodes in the repository.
*
* @param withHandler boolean, decides to call method requiring a
* ContentHandler as argument
* @param skipBinary
* @param noRecurse
*/
public void doTestExportDocView(boolean withHandler, boolean skipBinary, boolean noRecurse)
throws RepositoryException, IOException, SAXException, TransformerException {
this.skipBinary = skipBinary;
this.noRecurse = noRecurse;
this.withHandler = withHandler;
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
try {
if (withHandler) {
SAXTransformerFactory stf =
(SAXTransformerFactory) SAXTransformerFactory.newInstance();
TransformerHandler th = stf.newTransformerHandler();
th.setResult(new StreamResult(os));
session.exportDocumentView(testPath, th, skipBinary, noRecurse);
} else {
session.exportDocumentView(testPath, os, skipBinary, noRecurse);
}
} finally {
os.close();
}
// build the DOM tree
InputStream in = new BufferedInputStream(new FileInputStream(file));
doc = readDocument(in);
compareTree();
}
/**
* Compares the test node with the document's root element. In case also the
* child nodes are exported (noRecurse = false) the child nodes of the test
* node are compared with the child elements of the root element too.
*
* @throws RepositoryException
*/
private void compareTree() throws RepositoryException, IOException {
Element root = doc.getDocumentElement();
textValuesStack = new Stack();
// we assume the path is valid
Item item = session.getItem(testPath);
// only an absolute path to a node is allowed
if (!item.isNode()) {
fail("Item at the given root path " + testPath + " is not a node.");
}
Node node = (Node) item;
// multival props exported?
setExportMultivalProps(node, root, false);
// items with invalid xml names exported?
setExportInvalidXmlNames(node, root, false);
// check the test root node
checkRootElement(node, root);
// check the namespaces
compareNamespaces(root);
// check the exported data against the node which is exported.
compareNode(node, root);
// check the whole tree
if (!noRecurse) {
checkChildNodes(node, root);
}
}
/**
* Assures that root element exists and has correct jcr:root name if it is
* the root node of the repository. (chapter 6.4.2.2 of the JCR
* specification.) Also checks if multivalued properties are exported
* (chapter 6.4.2.5 of the JCR specification.) Also tries to find out if
* items with an invalid xml name are exported or not. (chapter 6.4.2.4 of
* the JCR specification.)
*
* @param node
* @param root
* @throws RepositoryException
*/
private void checkRootElement(Node node, Element root) throws RepositoryException {
boolean isValidName = XMLChar.isValidName(node.getName());
if (root != null) {
// check correct element name if the root node of the repository is exported.
if (node.getDepth() == 0) {
assertEquals("Exported root node has not correct name jcr:root.",
"jcr:root", root.getTagName());
}
} else {
if (exportInvalidXmlNames || isValidName) {
fail("Node " + node.getPath() + " is not exported.");
}
}
}
/**
* Checks the child nodes of the given node against the child nodes of the
* given xml element. The found text nodes of the xml element are hold in an
* ArrayList and put on a stack for further checking if another child
* element is between them.
*
* @param node
* @param elem
* @throws RepositoryException
*/
private void checkChildNodes(Node node, Element elem)
throws RepositoryException, IOException {
NodeIterator nodeIter = node.getNodes();
if (getSize(node.getNodes()) == 0) {
assertTrue("Exported node " + node.getPath() + " has child elements " +
"although it has no child nodes ", 0 == countChildElems(elem));
} else {
// create a stack entry for the text child nodes
// of the current xml element
StackEntry entry = new StackEntry();
entry.textValues = getChildTextNodeValues(elem);
textValuesStack.push(entry);
// xmltext nodes directly following each other
// are serialized together as xml text
List jcrTextNodes = new ArrayList();
while (nodeIter.hasNext()) {
Node childNode = nodeIter.nextNode();
if (isXMLTextNode(childNode)) {
jcrTextNodes.add(childNode);
} else {
if (jcrTextNodes.size() > 0) {
compareXmltextNodes(jcrTextNodes, elem);
// reset the Array
jcrTextNodes.clear();
}
compareChildTree(childNode, elem);
}
}
// finally we are through the child nodes
// so we delete the stackEntry
textValuesStack.pop();
}
}
/**
* Compares the child tree of a given node against the child elements of a
* given element. (chapter 6.4.2.1 points 2,3,4 of the JCR specification).
*
* Considered are the export constraints regarding nodes named jcr:xmldata
* (chapter 6.4.2.3 of the JCR specification).
*
* Also the numbers of exported child elements is compared with the number
* of child nodes.
*
* @param node
* @param parentElem
* @throws RepositoryException
*/
private void compareChildTree(Node node, Element parentElem)
throws RepositoryException, IOException {
Element nodeElem;
// find a childElem belonging to the node and check it.
nodeElem = findElem(node, parentElem);
if (nodeElem != null) {
compareNode(node, nodeElem);
// go deep
checkChildNodes(node, nodeElem);
}
}
/**
* Checks the given Element if it has a child element with the same (or
* eventually escaped) name as the given node. (chapter 6.4.2.1 point 3 of
* the JCR specification).
*
* @param node
* @param parentElem
* @return Child Element of parentElem. Null if no corresponidng element is
* found.
* @throws RepositoryException
*/
private Element findElem(Node node, Element parentElem) throws RepositoryException {
String name = node.getName();
Element nodeElem = null;
// valid xml name?
boolean isValidName = XMLChar.isValidName(name);
name = !isValidName ? escapeNames(name) : name;
// same name sibs
List children = getChildElems(parentElem, name);
if (children.size() > 0) {
// xmltext nodes are not exported as elements
if (isXMLTextNode(node)) {
fail("Xml text node " + node.getPath() +
" is wronlgy exported as element.");
} else {
// order of same name siblings is preserved during export
int index = node.getIndex();
try {
nodeElem = children.get(index - 1);
} catch (IndexOutOfBoundsException iobe) {
fail("Node " + node.getPath() + " is not exported."
+ iobe.toString());
}
}
} else {
// need to be exported
if (!isXMLTextNode(node) && (isValidName || exportInvalidXmlNames)) {
fail("Node " + node.getPath() + " is not exported.");
}
}
return nodeElem;
}
/**
* Check if a property of a node is exported. This is true if a
* corresponding attribute is found in the element the node is exported to.
* An attribute is corresponding when it has the same name as the given
* property (or it is equal to its escaped name). (chapter 6.4.2.1 point 5
* of the JCR specification).
*
* @param prop
* @param elem
* @return
* @throws RepositoryException
*/
private Attr findAttribute(Property prop, Element elem)
throws RepositoryException {
String name = prop.getName();
boolean isValidName = XMLChar.isValidName(name);
name = !isValidName ? escapeNames(name) : name;
Attr attribute = elem.getAttributeNode(name);
return attribute;
}
/**
* Check if a property should be exported according the three choices
* skipBinary, exportMultivalProps and exportInvalidXmlNames.
*
* @param prop
* @param attribute
* @throws RepositoryException
*/
private void checkAttribute(Property prop, Attr attribute) throws RepositoryException {
boolean isBinary = (prop.getType() == PropertyType.BINARY);
boolean isMultiple = prop.getDefinition().isMultiple();
if (skipBinary) {
if (isBinary && !(isMultiple && !exportMultivalProps)) {
assertEquals("Value of binary property " + prop.getPath() +
" exported although skipBinary is true",
attribute.getValue().length(), 0);
}
// check the flags
else {
checkExportFlags(prop, attribute);
}
}
// saveBinary
else {
if (isBinary && !(isMultiple && !exportMultivalProps)) {
assertTrue("Binary property " + prop.getPath() +
" not exported although skipBinary is false", attribute != null);
}
// check anyway the flags
checkExportFlags(prop, attribute);
}
}
/**
* Checks attribute export regarding the two flags and without considering
* skipBinary.
*
* @param prop
* @param attribute
* @throws RepositoryException
*/
private void checkExportFlags(Property prop, Attr attribute)
throws RepositoryException {
String name = prop.getName();
boolean isMultiple = prop.getDefinition().isMultiple();
boolean isValidName = XMLChar.isValidName(name);
if (isMultiple) {
if (exportMultivalProps) {
assertTrue("Not all multivalued properties are exported: "
+ prop.getPath() + " is not exported.", attribute != null);
} else {
// skipping multi-valued properties entirely is legal
// according to "6.4.2.5 Multi-value Properties" of the
// jsr-170 specification
return;
}
}
// check anyway the other flag
if (exportInvalidXmlNames && !isValidName) {
assertTrue("Not all properties with invalid xml name are exported: " +
prop.getPath() + " is not exported.", attribute != null);
} else {
assertTrue("Property " + prop.getPath() + " is not exported.",
attribute != null);
}
}
/**
* Compares the given node with the given element. Comparison is succesful
* if the number of exported child nodes and exported properties match the
* found child elements and attributes considering the possible exceptions
* and if the comparison of the properties of the node with the attributes
* of the element is successful too.
*
* @param node
* @param elem
* @throws RepositoryException
*/
private void compareNode(Node node, Element elem)
throws RepositoryException, IOException {
// count the child nodes and compare with the exported child elements
compareChildNumber(node, elem);
// count the properties and compare with attributes exported
comparePropNumber(node, elem);
PropertyIterator propIter = node.getProperties();
while (propIter.hasNext()) {
Property prop = propIter.nextProperty();
Attr attr = findAttribute(prop, elem);
checkAttribute(prop, attr);
if (attr != null) {
compareProperty(prop, attr);
}
}
}
/**
* Compare the given property with the given attribute. Comparison is
* successful if their values can be matched considering binary type,
* multivalue export. (chapter 6.4.2.1 point 6 of the JCR specification).
*
* @param prop
* @param attr
* @throws RepositoryException
*/
private void compareProperty(Property prop, Attr attr)
throws RepositoryException, IOException {
boolean isMultiple = prop.getDefinition().isMultiple();
boolean isBinary = (prop.getType() == PropertyType.BINARY);
String attrVal = attr.getValue();
String val = null;
if (isMultiple) {
val = exportValues(prop, isBinary);
} else {
if (isBinary) {
try {
attrVal = decodeBase64(attrVal);
val = prop.getString();
} catch (IOException ioe) {
fail("Could not decode value of binary attribute " +
attr.getName() + " of element " +
attr.getOwnerElement().getTagName());
}
} else {
val = prop.getString();
}
}
if (isBinary && skipBinary) {
assertEquals("Value of binary property " + prop.getPath() +
" is not exported correctly: ", "", attrVal);
assertEquals("Value of binary property " + prop.getPath() +
" exported although skipBinary is true",
"", attrVal);
} else {
assertTrue("Value of property " + prop.getPath() +
" is not exported correctly: " + attrVal,
val.equals(attrVal) || escapeValues(val).equals(attrVal));
}
}
/**
* Checks if all registered namespaces are exported into the root element.
* (chapter 6.4.2.1 point 1 of the JCR specification).
*
* @param root
* @throws RepositoryException
*/
private void compareNamespaces(Element root) throws RepositoryException {
Map nameSpaces = new AttributeSeparator(root).getNsAttrs();
// check if all namespaces exist that were exported
for (Iterator e = nameSpaces.keySet().iterator(); e.hasNext(); ) {
String prefix = e.next();
String URI = nameSpaces.get(prefix);
assertEquals("Prefix of uri" + URI + "is not exported correctly.",
nsr.getPrefix(URI), prefix);
assertEquals("Uri of prefix " + prefix + "is not exported correctly.",
nsr.getURI(prefix), URI);
}
String[] registeredNamespaces = nsr.getURIs();
// check if all required namespaces are exported
for (int i = 0; i < registeredNamespaces.length; i++) {
String prefix = nsr.getPrefix(registeredNamespaces[i]);
// skip default namespace and xml namespaces
if (prefix.length() == 0 || prefix.startsWith("xml")) {
continue;
} else {
assertTrue("namespace: " + registeredNamespaces[i] + " not exported", nameSpaces.keySet().contains(prefix));
}
}
}
/**
* Count the number of child nodes of a node which are exported and compare
* with the number expected.
*
* @param node
* @param elem
* @throws RepositoryException
*/
private void compareChildNumber(Node node, Element elem) throws RepositoryException {
NodeIterator iter = node.getNodes();
long size = 0;
long exported = countChildElems(elem);
// child tree is exported too
if (!noRecurse) {
size = getSize(node.getNodes());
while (iter.hasNext()) {
Node n = iter.nextNode();
String name = n.getName();
// xmltext node ?
if (isXMLTextNode(n)) {
size--;
}
if (!exportInvalidXmlNames && !XMLChar.isValidName(name)) {
size--;
}
}
}
assertEquals("The number of child nodes of node " + node.getPath() +
" which are exported is not correct: ", size, exported);
}
/**
* Count the number of exported properties of a given node and compare with
* the number of the properties expected to be exported.
*
* @param node
* @param elem
* @throws RepositoryException
*/
private void comparePropNumber(Node node, Element elem)
throws RepositoryException {
PropertyIterator iter = node.getProperties();
long size = getSize(node.getProperties());
long exported = new AttributeSeparator(elem).getNonNsAttrs().size();
while (iter.hasNext()) {
Property prop = iter.nextProperty();
String name = prop.getName();
boolean isMultiple = prop.getDefinition().isMultiple();
// props not exported so we decrease the expected size.
if (!exportInvalidXmlNames && !XMLChar.isValidName(name)) {
size--;
} else if (!exportMultivalProps && isMultiple) {
size--;
}
}
assertEquals("The number of properties exported of node " + node.getPath() +
" is not correct.", size, exported);
}
/**
* Compares the text of a given XML element with the values of the
* jcr:xmlcharacters properties of the given jcr:xmltext nodes sequel. If
* the sequel has more than one node the serialized values are concatenated
* with a space. We only check the case withHandler is true.
*
* @param nodes
* @param parentElem
* @throws RepositoryException
*/
private void compareXmltextNodes(List nodes, Element parentElem)
throws RepositoryException {
// only this case
if (withHandler) {
String value = "";
String exportedVal = "";
StackEntry currentEntry = (StackEntry) textValuesStack.pop();
try {
exportedVal = (String) currentEntry.textValues.get(currentEntry.position);
currentEntry.position++;
textValuesStack.push(currentEntry);
} catch (IndexOutOfBoundsException iobe) {
fail("Xmltext nodes not correctly exported: " + iobe.getMessage());
}
int size = nodes.size();
if (size == 1) {
Node node = nodes.get(0);
Property prop = node.getProperty(JCR_XMLDATA);
value = prop.getString();
assertEquals("The " + JCR_XMLTEXT + " node " + node.getPath() +
" is not exported correctly.",
value, exportedVal);
} else {
// check the concatenated values sequenceally
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
Property prop = node.getProperty(JCR_XMLDATA);
value = prop.getString();
// the first one
if (i == 0) {
if (exportedVal.regionMatches(0, value, 0, value.length())) {
// test ok, remove the checked part of the text
exportedVal = exportedVal.substring(0, value.length());
} else {
fail("The " + JCR_XMLTEXT + " node " + node.getPath() +
" is not exported correctly: expected: " +
value + " found: " + exportedVal);
}
}
// we assume at the moment that any white space char is possible
// between two adjacent xmltext nodesso we try to match as long
// as space characters are at the beginning of the
// remaining exported string
// todo once this will be specified in the spec more exactly
else {
// the last one
if (exportedVal.regionMatches(0, value, 0, value.length())) {
// test ok
exportedVal = exportedVal.substring(0, value.length());
} else {
boolean match = false;
int j = 0;
char c = exportedVal.charAt(j);
while (c == ' ' || c == '\n' || c == '\r'
|| c == '\t' || c == '\u000B') {
if (exportedVal.regionMatches(j, value, 0, value.length())) {
exportedVal = exportedVal.substring(j, value.length() + j);
match = true;
break;
} else {
j++;
c = exportedVal.charAt(j);
}
}
assertTrue("The " + JCR_XMLTEXT + " node " + node.getPath() +
" is not exported correctly: expected: "
+ value + " found: " + exportedVal, match);
}
}
}
}
}
}
/**
* Loops through all child items of a given node to test if items with
* invalid xml name are exported. (chapter 6.4.2.4 of the JCR
* specification).
*
* @param node the root node of the tree to search
* @param elem the parent element of the element to which the parent node of
* the given node is exported.
* @throws RepositoryException
*/
private boolean setExportInvalidXmlNames(Node node, Element elem, boolean isSet)
throws RepositoryException {
if (!XMLChar.isValidName(node.getName())) {
if (elem != null) {
exportInvalidXmlNames = true;
isSet = true;
} else {
exportInvalidXmlNames = false;
isSet = true;
}
}
// try properties
if (!isSet) {
PropertyIterator iter = node.getProperties();
while (iter.hasNext()) {
Property prop = iter.nextProperty();
if (!exportMultivalProps && prop.getDefinition().isMultiple()) {
continue;
}
if (!XMLChar.isValidName(prop.getName())) {
// property exported?
exportInvalidXmlNames = isExportedProp(prop, elem);
isSet = true;
}
}
}
// try child nodes
if (!isSet && !noRecurse) {
// search again
NodeIterator iter = node.getNodes();
while (iter.hasNext()) {
Node n = iter.nextNode();
Element el = findElem(n, elem);
isSet = setExportInvalidXmlNames(n, el, isSet);
}
}
return isSet;
}
/**
* Set the exportMultivalProps flag. Traverses the tree given by the node
* and searches a multivalue property which is exported to an attribute of a
* element of an element tree. (chapter 6.4.2.5 of the JCR specification).
*
* @param node
* @param elem
* @throws RepositoryException
*/
private boolean setExportMultivalProps(Node node, Element elem, boolean isSet)
throws RepositoryException {
Property[] props = searchMultivalProps(node);
// multivalued property with valid xml name
if (props[0] != null) {
exportMultivalProps = isExportedProp(props[0], elem);
isSet = true;
} else {
// invalid xml named multivalue property exported
if (props[1] != null) {
exportMultivalProps = isExportedProp(props[1], elem);
if (!exportMultivalProps && exportInvalidXmlNames) {
isSet = true;
}
}
}
if (!isSet && !noRecurse) {
// search again
NodeIterator iter = node.getNodes();
while (iter.hasNext()) {
Node n = iter.nextNode();
Element el = findElem(n, elem);
if (el != null) {
isSet = setExportMultivalProps(n, el, isSet);
} else {
isSet = false;
}
}
}
return isSet;
}
//-----------------------------------< helper methods >-----------------------------
/**
* Search a given node if it contains a multivalue property. As invalid xml
* names may be exported or not we want to find a multivalue property with
* valid xml name and also one with an invalid xml name. Returned is a pair
* of multivalued properties, the first has a valid xml name, the second an
* invalid one. In case one of these is not found it is replaced by null in
* the pair.
*
* @param node the node to start the search.
* @return A pair of multivalued properties.
* @throws RepositoryException
*/
private Property[] searchMultivalProps(Node node) throws RepositoryException {
Property[] properties = {null, null};
for (PropertyIterator props = node.getProperties(); props.hasNext();) {
Property property = props.nextProperty();
if (property.getDefinition().isMultiple()) {
if (XMLChar.isValidName(property.getName())) {
properties[0] = property;
break;
} else {
properties[1] = property;
}
}
}
return properties;
}
/**
* Tests if a property is exported or not.
*
* @param prop
* @param elem
* @return
* @throws RepositoryException
*/
private boolean isExportedProp(Property prop, Element elem) throws RepositoryException {
String name = prop.getName();
name = XMLChar.isValidName(prop.getName()) ? name : escapeNames(name);
Attr attr = elem.getAttributeNode(name);
return (attr != null);
}
/**
* Checks if a given node is a jcr:xmltext named node and fulfills the
* condition that the property's value is exported as text.
*
* @param node The node to check.
* @return boolean indicating if the given node fulfills the required
* conditions.
* @throws RepositoryException
*/
private boolean isXMLTextNode(Node node) throws RepositoryException {
boolean isTrue = node.getName().equals(JCR_XMLTEXT);
if (node.hasProperty(JCR_XMLDATA)) {
Property prop = node.getProperty(JCR_XMLDATA);
isTrue = !prop.getDefinition().isMultiple()
&& prop.getType() == PropertyType.STRING
// only one property beneath the required jcr:primaryType
&& getSize(node.getProperties()) == 2
&& getSize(node.getNodes()) == 0;
} else {
isTrue = false;
}
return isTrue;
}
//-----------------------------------< static helper methods >-----------------------------
/**
* Decodes a given base 64 encoded string.
*
* @param str
* @return
* @throws IOException
*/
private static String decodeBase64(String str) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Base64.decode(str, bos);
String decoded = bos.toString("UTF-8");
return decoded;
}
/**
* Encodes a given stream to base64.
*
* @param in the stream to encode.
* @return the encoded string in base64.
* @throws IOException if an error occurs.
*/
private static String encodeBase64(InputStream in) throws IOException {
StringWriter writer = new StringWriter();
Base64.encode(in, writer);
return writer.getBuffer().toString();
}
/**
* Exports values of a multivalue property and concatenate the values
* separated by a space. (chapter 6.4.4 of the JCR specification).
*
* @param prop
* @param isBinary
* @return
* @throws RepositoryException
*/
private static String exportValues(Property prop, boolean isBinary)
throws RepositoryException, IOException {
Value[] vals = prop.getValues();
// order of multi values is preserved.
// multival with empty array is exported as empty string
StringBuffer exportedVal = new StringBuffer();
String space = "";
if (isBinary) {
for (int i = 0; i < vals.length; i++) {
exportedVal.append(space);
InputStream in = vals[i].getStream();
try {
exportedVal.append(encodeBase64(in));
} finally {
in.close();
}
space = " ";
}
} else {
for (int i = 0; i < vals.length; i++) {
exportedVal.append(space);
exportedVal.append(escapeValues(vals[i].getString()));
space = " ";
}
}
return exportedVal.toString();
}
/**
* Escapes the characters of a given String representing a Name of an item.
* The escaping scheme is according the requirements of the JSR 170
* Specification chapter 6.4.3 . No check performed if the given string is
* indeed a Name or not.
*
* @param name
* @return
*/
private static String escapeNames(String name) {
return EscapeJCRUtil.escapeJCRNames(name);
}
/**
* Escapes the characters of a given string value according the requirements
* of chapter 6.4.4 of JSR 170 Specification.
*
* @param value The string to escape its characters.
* @return
*/
private static String escapeValues(String value) {
return EscapeJCRUtil.escapeJCRValues(value);
}
//----------------< helpers to retrieve data from an xml document >-------------------
/**
* Returns all child elements of the given xml element which have the given
* name.
*
* @param elem
* @param name
* @return
*/
private List getChildElems(Element elem, String name) {
List children = new ArrayList();
org.w3c.dom.Node child = elem.getFirstChild();
while (child != null) {
if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
if (name.equals("*") || name.equals(child.getNodeName())) {
children.add((Element)child);
}
}
child = child.getNextSibling();
}
return children;
}
/**
* Counts the number of child elements of the given xml element.
*
* @param elem
* @return
*/
private long countChildElems(Element elem) {
long length = 0;
org.w3c.dom.Node child = elem.getFirstChild();
while (child != null) {
if (child.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
length++;
}
child = child.getNextSibling();
}
return length;
}
/**
* Collects the characters of successive text nodes of the given xml element
* into an ArrayList.
*
* @param elem
* @return
*/
private List getChildTextNodeValues(Element elem) {
List textValues = new ArrayList();
StringBuffer buf = new StringBuffer();
org.w3c.dom.Node child = elem.getFirstChild();
// collect the characters of successive text nodes
while (child != null) {
if (child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
while (child != null
&& child.getNodeType() == org.w3c.dom.Node.TEXT_NODE) {
buf.append(child.getNodeValue());
child = child.getNextSibling();
}
textValues.add(buf.toString());
buf = new StringBuffer();
} else {
child = child.getNextSibling();
}
}
return textValues;
}
/**
* Reads a DOM document from the given XML stream.
*
* @param xml XML stream
* @return DOM document
* @throws RepositoryException if the document could not be read
*/
private Document readDocument(InputStream xml) throws RepositoryException {
try {
StreamSource source = new StreamSource(xml);
DOMResult result = new DOMResult();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
transformer.transform(source, result);
return (Document) result.getNode();
} catch (TransformerException e) {
throw new RepositoryException("Unable to read xml file", e);
}
}
/**
* Helper class to separate the attributes with xmlns namespace from the
* attributes without xmlns namspace. Solely used for the root element of an
* xml document.
*/
private class AttributeSeparator {
private static final String xmlnsURI = "http://www.w3.org/2000/xmlns/";
private static final String xmlnsPrefix = "xmlns";
NamedNodeMap attrs;
Map nsAttrs;
Map nonNsAttrs;
AttributeSeparator(Element elem) {
nsAttrs = new HashMap();
nonNsAttrs = new HashMap();
attrs = elem.getAttributes();
separateAttrs();
}
public Map getNsAttrs() {
return nsAttrs;
}
public Map getNonNsAttrs() {
return nonNsAttrs;
}
private void separateAttrs() {
for (int i = 0; i < attrs.getLength(); i++) {
Attr attribute = (Attr) attrs.item(i);
if (xmlnsURI.equals(attribute.getNamespaceURI())) {
String localName = attribute.getLocalName();
// ignore setting default namespace
if (xmlnsPrefix.equals(localName)) {
continue;
}
nsAttrs.put(localName, attribute.getValue());
} else {
nonNsAttrs.put(attribute.getName(), attribute.getValue());
}
}
}
}
}