[DS-1443] Recreate this patch atop current (7x) master.

This commit is contained in:
Mark H. Wood
2019-05-06 16:18:53 -04:00
parent afc6682bfb
commit 2a914e1499
4 changed files with 605 additions and 60 deletions

View File

@@ -757,6 +757,13 @@
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -7,12 +7,16 @@
*/
package org.dspace.administer;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -29,6 +33,9 @@ import org.apache.xpath.XPathAPI;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.content.MetadataSchema;
import org.dspace.content.MetadataValue;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
@@ -36,6 +43,7 @@ import org.dspace.core.Context;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@@ -47,8 +55,7 @@ import org.xml.sax.SAXException;
* an XML file.
*
* The XML file structure needs to be:
* <p>
* {@code
* <pre>{@code
* <import_structure>
* <community>
* <name>....</name>
@@ -58,7 +65,7 @@ import org.xml.sax.SAXException;
* </collection>
* </community>
* </import_structure>
* }
* }</pre>
* <p>
* It can be arbitrarily deep, and supports all the metadata elements
* that make up the community and collection metadata. See the system
@@ -68,26 +75,31 @@ import org.xml.sax.SAXException;
*/
public class StructBuilder {
/**
* The output XML document which will contain updated information about the
* imported structure.
/** Name of the root element for the document to be imported. */
static final String INPUT_ROOT = "import_structure";
/*
* Name of the root element for the document produced by importing.
* Community and collection elements are annotated with their identifiers.
*/
private static final org.jdom.Document xmlOutput
= new org.jdom.Document(new Element("imported_structure"));
static final String RESULT_ROOT = "imported_structure";
/**
* A hash table to hold metadata for the collection being worked on.
* A table to hold metadata for the collection being worked on.
*/
private static final Map<String, String> collectionMap = new HashMap<>();
/**
* A hash table to hold metadata for the community being worked on.
* A table to hold metadata for the community being worked on.
*/
private static final Map<String, String> communityMap = new HashMap<>();
protected static CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
protected static CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
protected static EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
protected static CommunityService communityService
= ContentServiceFactory.getInstance().getCommunityService();
protected static CollectionService collectionService
= ContentServiceFactory.getInstance().getCollectionService();
protected static EPersonService ePersonService
= EPersonServiceFactory.getInstance().getEPersonService();
/**
* Default constructor
@@ -96,30 +108,38 @@ public class StructBuilder {
/**
* Main method to be run from the command line to import a structure into
* DSpace
* DSpacee or export existing structure to a file.The command is of the form:
*
* This is of the form:
* <p>{@code StructBuilder -f [XML source] -e [administrator email] -o [output file]}
*
* {@code StructBuilder -f [xml source] -e [administrator email] -o [output file]}
* <p>to import, or
*
* The output file will contain exactly the same as the source xml document, but
* with the handle for each imported item added as an attribute.
* <p>{@code StructBuilder -x -e [administrator email] -o [output file]}</p>
*
* @param argv the command line arguments given
* <p>to export. The output will contain exactly the same as the source XML
* document, but with the Handle for each imported item added as an attribute.
*
*
* @param argv command line arguments.
* @throws ParserConfigurationException passed through.
* @throws SQLException passed through.
* @throws FileNotFoundException if input or output could not be opened.
* @throws TransformerException if the input document is invalid.
*/
public static void main(String[] argv)
throws ParserConfigurationException, SQLException {
throws ParserConfigurationException, SQLException,
FileNotFoundException, IOException, TransformerException {
// Parse the command line.
CommandLineParser parser = new DefaultParser();
Options options = new Options();
options.addOption("h", "help", false, "help");
options.addOption("h", "help", false, "Print this help message.");
options.addOption("?", "help");
options.addOption("f", "file", true, "input structure document");
options.addOption("e", "eperson", true, "eperson");
options.addOption("o", "output", true, "output structure document");
options.addOption("f", "file", true, "File of new structure information.");
options.addOption("e", "eperson", true, "User who is manipulating the repository's structure.");
options.addOption("o", "output", true, "File to receive the structure map.");
options.addOption("x", "export", false, "Export the current structure as XML.");
CommandLine line = null;
try {
@@ -130,32 +150,49 @@ public class StructBuilder {
System.exit(1);
}
// If the user asked for help, give it and exit.
if (line.hasOption('h') || line.hasOption('?')) {
usage(options);
giveHelp(options);
System.exit(0);
}
String file = null;
// Otherwise, analyze the command.
// Must be import or export.
if (!(line.hasOption('f') || line.hasOption('x'))) {
giveHelp(options);
System.exit(1);
}
String input = null;
String eperson = null;
String output = null;
if (line.hasOption('f')) {
file = line.getOptionValue('f');
input = line.getOptionValue('f');
}
if (line.hasOption('e')) {
eperson = line.getOptionValue('e');
} else { // EPerson is required
usage(options);
System.exit(1);
}
if (line.hasOption('o')) {
output = line.getOptionValue('o');
}
if (output == null || eperson == null || file == null) {
} else { // output is required
usage(options);
System.exit(1);
}
// Open the output stream.
OutputStream outputStream;
if ("-".equals(output)) {
outputStream = System.out;
} else {
outputStream = new FileOutputStream(output);
}
// create a context
Context context = new Context();
@@ -167,10 +204,40 @@ public class StructBuilder {
System.exit(1);
}
// Export? Import?
if (line.hasOption('x')) { // export
exportStructure(context, outputStream);
} else { // Must be import
InputStream inputStream;
if ("-".equals(input)) {
inputStream = System.in;
} else {
inputStream = new FileInputStream(input);
}
importStructure(context, inputStream, outputStream);
}
System.exit(0);
}
/**
* Import new Community/Collection structure.
*
* @param context
* @param input XML which describes the new communities and collections.
* @param output input, annotated with the new objects' identifiers.
* @throws IOException
* @throws ParserConfigurationException
* @throws SAXException
* @throws TransformerException
* @throws SQLException
*/
static void importStructure(Context context, InputStream input, OutputStream output)
throws IOException, ParserConfigurationException, SQLException, TransformerException {
// load the XML
Document document = null;
try {
document = loadXML(file);
document = loadXML(input);
} catch (IOException ex) {
System.err.format("The input document could not be read: %s%n", ex.getMessage());
System.exit(1);
@@ -180,7 +247,7 @@ public class StructBuilder {
}
// run the preliminary validation, to be sure that the the XML document
// is properly structured
// is properly structured.
try {
validate(document);
} catch (TransformerException ex) {
@@ -188,6 +255,12 @@ public class StructBuilder {
System.exit(1);
}
// Check for 'identifier' attributes -- possibly output by this class.
NodeList identifierNodes = XPathAPI.selectNodeList(document, "//*[@identifier]");
if (identifierNodes.getLength() > 0) {
System.err.println("The input document has 'identifier' attributes, which will be ignored.");
}
// load the mappings into the member variable hashmaps
communityMap.put("name", "name");
communityMap.put("description", "short_description");
@@ -219,33 +292,152 @@ public class StructBuilder {
}
// generate the output
Element root = xmlOutput.getRootElement();
final Element root = new Element(RESULT_ROOT);
for (Element element : elements) {
root.addContent(element);
}
// finally write the string into the output file
try (BufferedWriter out = new BufferedWriter(new FileWriter(output));) {
out.write(new XMLOutputter().outputString(xmlOutput));
// finally write the string into the output file.
final org.jdom.Document xmlOutput = new org.jdom.Document(root);
try {
new XMLOutputter().output(xmlOutput, output);
} catch (IOException e) {
System.out.println("Unable to write to output file " + output);
System.out.printf("Unable to write to output file %s: %s%n",
output, e.getMessage());
System.exit(1);
}
context.complete();
}
/**
* Add a single community, and its children, to the Document.
*
* @param community
* @return a fragment representing this Community.
*/
private static Element exportACommunity(Community community) {
// Export this Community.
Element element = new Element("community");
element.setAttribute("identifier", community.getHandle());
element.addContent(new Element("name").setText(community.getName()));
element.addContent(new Element("description")
.setText(communityService.getMetadataFirstValue(community,
MetadataSchema.DC_SCHEMA, "description", "abstract", Item.ANY)));
element.addContent(new Element("intro")
.setText(communityService.getMetadataFirstValue(community,
MetadataSchema.DC_SCHEMA, "description", null, Item.ANY)));
element.addContent(new Element("copyright")
.setText(communityService.getMetadataFirstValue(community,
MetadataSchema.DC_SCHEMA, "rights", null, Item.ANY)));
element.addContent(new Element("sidebar")
.setText(communityService.getMetadataFirstValue(community,
MetadataSchema.DC_SCHEMA, "description", "tableofcontents", Item.ANY)));
// Export this Community's Community children.
for (Community subCommunity : community.getSubcommunities()) {
element.addContent(exportACommunity(subCommunity));
}
// Export this Community's Collection children.
for (Collection collection : community.getCollections()) {
element.addContent(exportACollection(collection));
}
return element;
}
/**
* Add a single Collection to the Document.
*
* @param collection
* @return a fragment representing this Collection.
*/
private static Element exportACollection(Collection collection) {
// Export this Collection.
Element element = new Element("collection");
element.setAttribute("identifier", collection.getHandle());
element.addContent(new Element("name").setText(collection.getName()));
element.addContent(new Element("description")
.setText(collectionService.getMetadataFirstValue(collection,
MetadataSchema.DC_SCHEMA, "description", "abstract", Item.ANY)));
element.addContent(new Element("intro")
.setText(collectionService.getMetadataFirstValue(collection,
MetadataSchema.DC_SCHEMA, "description", null, Item.ANY)));
element.addContent(new Element("copyright")
.setText(collectionService.getMetadataFirstValue(collection,
MetadataSchema.DC_SCHEMA, "rights", null, Item.ANY)));
element.addContent(new Element("sidebar")
.setText(collectionService.getMetadataFirstValue(collection,
MetadataSchema.DC_SCHEMA, "description", "tableofcontents", Item.ANY)));
element.addContent(new Element("license")
.setText(collectionService.getMetadataFirstValue(collection,
MetadataSchema.DC_SCHEMA, "rights", "license", Item.ANY)));
// Provenance is special: multivalued
for (MetadataValue value : collectionService.getMetadata(collection,
MetadataSchema.DC_SCHEMA, "provenance", null, Item.ANY)) {
element.addContent(new Element("provenance")
.setText(value.getValue()));
}
return element;
}
/**
* Write out the existing Community/Collection structure.
*/
static void exportStructure(Context context, OutputStream output) {
// Build a document from the Community/Collection hierarchy.
Element rootElement = new Element(INPUT_ROOT); // To be read by importStructure, perhaps
List<Community> communities = null;
try {
communities = communityService.findAllTop(context);
} catch (SQLException ex) {
System.out.printf("Unable to get the list of top-level communities: %s%n",
ex.getMessage());
System.exit(1);
}
for (Community community : communities) {
rootElement.addContent(exportACommunity(community));
}
// Now write the structure out.
org.jdom.Document xmlOutput = new org.jdom.Document(rootElement);
try {
XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
outputter.output(xmlOutput, output);
} catch (IOException e) {
System.out.printf("Unable to write to output file %s: %s%n",
output, e.getMessage());
System.exit(1);
}
}
/**
* Output the usage information.
*/
private static void usage(Options options) {
HelpFormatter helper = new HelpFormatter();
helper.printHelp("java StructBuilder -f <source XML file> -o <output file> -e <eperson email>",
"Load community/collection structure from a file.",
helper.printUsage(new PrintWriter(System.out), 80/* FIXME Magic */,
"structure-builder", options);
}
/**
* Help the user more.
*/
private static void giveHelp(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("struct-builder",
"Import or export Community/Collection structure.",
options,
"Communities will be created from the top level,"
+ " and a map of communities to handles will be returned"
+ " in the output file.");
"When importing (-f), communities will be created from the "
+ "top level, and a map of communities to handles will "
+ "be returned in the output file. When exporting (-x),"
+ "the current structure will be written to the map file.",
true);
}
/**
@@ -267,7 +459,7 @@ public class StructBuilder {
if (first.getLength() == 0) {
err.append("-There are no top level communities in the source document.");
System.out.println(err.toString());
System.exit(0);
System.exit(1);
}
String errs = validateCommunities(first, 1);
@@ -278,7 +470,7 @@ public class StructBuilder {
if (trip) {
System.out.println(err.toString());
System.exit(0);
System.exit(1);
}
}
@@ -367,17 +559,17 @@ public class StructBuilder {
}
/**
* Load in the XML from file.
* Load the XML document from input.
*
* @param filename the filename to load from
* @return the DOM representation of the XML file
* @param input the filename to load from.
* @return the DOM representation of the XML input.
*/
private static org.w3c.dom.Document loadXML(String filename)
private static org.w3c.dom.Document loadXML(InputStream input)
throws IOException, ParserConfigurationException, SAXException {
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
.newDocumentBuilder();
org.w3c.dom.Document document = builder.parse(new File(filename));
org.w3c.dom.Document document = builder.parse(input);
return document;
}
@@ -388,7 +580,7 @@ public class StructBuilder {
* @param node the node from which we want to extract the string value
* @return the string value of the node
*/
public static String getStringValue(Node node) {
private static String getStringValue(Node node) {
String value = node.getNodeValue();
if (node.hasChildNodes()) {

View File

@@ -0,0 +1,334 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.administer;
import static org.junit.Assert.assertFalse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import org.dspace.AbstractIntegrationTest;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.MetadataSchema;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.content.service.CollectionService;
import org.dspace.content.service.CommunityService;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.Comparison;
import org.xmlunit.diff.ComparisonFormatter;
import org.xmlunit.diff.DefaultComparisonFormatter;
import org.xmlunit.diff.Diff;
import org.xmlunit.diff.Difference;
import org.xmlunit.util.Predicate;
/**
* Tests of {@link StructBuilder}.
*
* @author Mark H. Wood <mwood@iupui.edu>
*/
public class StructBuilderIT
extends AbstractIntegrationTest {
private static final Logger log = LoggerFactory.getLogger(StructBuilderIT.class);
private static final CommunityService communityService
= ContentServiceFactory.getInstance().getCommunityService();
private static final CollectionService collectionService
= ContentServiceFactory.getInstance().getCollectionService();
public StructBuilderIT() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
/**
* Ensure that there is no left-over structure to confuse a test.
*
* @throws SQLException passed through.
* @throws AuthorizeException passed through.
* @throws IOException passed through.
*/
@Before
public void setUp() throws SQLException, AuthorizeException, IOException {
// Clear out all communities and collections.
context.turnOffAuthorisationSystem();
for (Community community : communityService.findAllTop(context)) {
deleteSubCommunities(community);
communityService.delete(context, community);
}
context.restoreAuthSystemState();
}
@After
public void tearDown() {
}
/** Test structure document. */
private static final String IMPORT_DOCUMENT =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<import_structure>\n" +
" <community>\n" +
" <name>Top Community 0</name>\n" +
" <description/><intro/><copyright/><sidebar/>" +
" <community>\n" +
" <name>Sub Community 0.0</name>\n" +
" <description/><intro/><copyright/><sidebar/>" +
" <collection>\n" +
" <name>Collection 0.0.0</name>\n" +
" <description/><intro/><copyright/><sidebar/><license/><provenance/>" +
" </collection>\n" +
" </community>\n" +
" <collection>\n" +
" <name>Collection 0.1</name>\n" +
" <description/><intro/><copyright/><sidebar/><license/><provenance/>" +
" </collection>\n" +
" </community>\n" +
"</import_structure>\n";
private static final String EXPORT_DOCUMENT =
"<?xml version='1.0' encoding='UTF-8'?>\n" +
"<import_structure>\n" +
" <community>\n" +
" <name>Top Community 0</name>" +
" <description/><intro/><copyright/><sidebar/>" +
" <collection>\n" +
" <name>Collection 0.0</name>" +
" <description/><intro/><copyright/><sidebar/><license/>" +
" </collection>\n" +
" </community>\n" +
"</import_structure>\n";
/**
* Test of main method, of class StructBuilder.
* @throws java.lang.Exception
/*
@Test
public void testMain()
throws Exception {
System.out.println("main");
String[] argv = null;
StructBuilder.main(argv);
// TODO review the generated test code and remove the default call to fail.
fail("The test case is a prototype.");
}
*/
/**
* Test of importStructure method, of class StructBuilder.
*
* @throws java.lang.Exception passed through.
*/
@Test
public void testImportStructure()
throws Exception {
System.out.println("importStructure");
// Run the method under test and collect its output.
ByteArrayOutputStream outputDocument
= new ByteArrayOutputStream(IMPORT_DOCUMENT.length() * 2 * 2);
byte[] inputBytes = IMPORT_DOCUMENT.getBytes(StandardCharsets.UTF_8);
context.turnOffAuthorisationSystem();
try (InputStream input = new ByteArrayInputStream(inputBytes);) {
StructBuilder.importStructure(context, input, outputDocument);
} catch (IOException | SQLException
| ParserConfigurationException | TransformerException ex) {
System.err.println(ex.getMessage());
System.exit(1);
} finally {
context.restoreAuthSystemState();
}
// Compare import's output with its input.
// N.B. here we rely on StructBuilder to emit communities and
// collections in the same order as the input document. If that changes,
// we will need a smarter NodeMatcher, probably based on <name> children.
Source output = new StreamSource(
new ByteArrayInputStream(outputDocument.toByteArray()));
Source reference = new StreamSource(
new ByteArrayInputStream(
IMPORT_DOCUMENT.getBytes(StandardCharsets.UTF_8)));
Diff myDiff = DiffBuilder.compare(reference).withTest(output)
.normalizeWhitespace()
// .withNodeFilter(new MyNodeFilter())
.withAttributeFilter((Attr attr) ->
!attr.getName().equals("identifier"))
.checkForIdentical()
.build();
// Was there a difference?
// Always output differences -- one is expected.
ComparisonFormatter formatter = new DefaultComparisonFormatter();
for (Difference difference : myDiff.getDifferences()) {
System.err.println(difference.toString(formatter));
}
// Test for *significant* differences.
assertFalse("Output does not match input.", isDifferent(myDiff));
// TODO spot-check some objects.
}
/**
* Test of exportStructure method, of class StructBuilder.
* @throws ParserConfigurationException passed through.
* @throws org.xml.sax.SAXException passed through.
* @throws java.io.IOException passed through.
* @throws java.sql.SQLException passed through.
* @throws org.dspace.authorize.AuthorizeException passed through.
*/
@Test
public void testExportStructure()
throws ParserConfigurationException, SAXException, IOException,
SQLException, AuthorizeException {
// Create some structure to test.
context.turnOffAuthorisationSystem();
Community community0 = communityService.create(null, context);
communityService.setMetadataSingleValue(context, community0,
MetadataSchema.DC_SCHEMA, "title", null,
null, "Top Community 0");
Collection collection0_0 = collectionService.create(context, community0);
collectionService.setMetadataSingleValue(context, collection0_0,
MetadataSchema.DC_SCHEMA, "title", null,
null, "Collection 0.0");
// Export the current structure.
System.out.println("exportStructure");
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
StructBuilder.exportStructure(context, outputStream);
context.restoreAuthSystemState();
// Compare the output to the expected output.
Source output = new StreamSource(
new ByteArrayInputStream(outputStream.toByteArray()));
Source reference = new StreamSource(
new ByteArrayInputStream(
EXPORT_DOCUMENT.getBytes(StandardCharsets.UTF_8)));
Diff myDiff = DiffBuilder.compare(reference).withTest(output)
.normalizeWhitespace()
// .withNodeFilter(new MyNodeFilter())
.withAttributeFilter((Attr attr) ->
!attr.getName().equals("identifier"))
.checkForIdentical()
.build();
// Was there a difference?
// Always output differences -- one is expected.
ComparisonFormatter formatter = new DefaultComparisonFormatter();
for (Difference difference : myDiff.getDifferences()) {
System.err.println(difference.toString(formatter));
}
// Test for *significant* differences.
assertFalse("Output does not match input.", myDiff.hasDifferences());
}
/**
* Delete all child communities and collections of a given community.
* All descendant collections must be empty of Items.
*
* @param c the Community to be pruned of all descendants.
* @throws SQLException passed through.
* @throws AuthorizeException passed through.
* @throws IOException passed through.
*/
private void deleteSubCommunities(Community c)
throws SQLException, AuthorizeException, IOException {
for (Community subCommunity : c.getSubcommunities()) {
deleteSubCommunities(subCommunity);
communityService.delete(context, subCommunity);
}
for (Collection collection : c.getCollections()) {
collectionService.delete(context, collection);
}
}
/**
* Test that the documents are not different, except that their root
* elements have specific different names.
*
* @param diff
* @return true if these are otherwise-identical "import_structure" and
* "imported_structure" documents.
*/
private boolean isDifferent(Diff diff) {
Iterator<Difference> diffIterator = diff.getDifferences().iterator();
// There must be at least one difference.
if (!diffIterator.hasNext()) {
log.error("Not enough differences.");
return true;
}
// The difference must be that the root nodes are named "import_structure"
// and "imported_structure".
Comparison comparison = diffIterator.next().getComparison();
Node controlNode = comparison.getControlDetails().getTarget();
Node testNode = comparison.getTestDetails().getTarget();
if (!controlNode.getNodeName().equals("import_structure")
|| !testNode.getNodeName().equals("imported_structure")) {
log.error("controlNode name: {}", controlNode.getNodeName());
log.error("test node name: {}", testNode.getNodeName());
return true;
}
if ((controlNode.getParentNode().getNodeType() != Node.DOCUMENT_NODE)
|| (testNode.getParentNode().getNodeType() != Node.DOCUMENT_NODE)) {
log.error("control node's parent type is {}", controlNode.getParentNode().getNodeType());
log.error("test node's parent type is {}", testNode.getParentNode().getNodeType());
return true;
}
// There must be at most one difference.
return diffIterator.hasNext();
}
/**
* Reject uninteresting nodes.
*/
private static class MyNodeFilter implements Predicate<Node> {
private static final List<String> dontCare = Arrays.asList(
"description",
"intro",
"copyright",
"sidebar",
"license",
"provenance");
@Override
public boolean test(Node node) {
String type = node.getLocalName();
return ! dontCare.contains(type);
}
}
}

View File

@@ -0,0 +1,12 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.administer;
/**
* Test administrative tools.
*/