diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index 1a7326fa75..329255b438 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -757,6 +757,13 @@ json 20180130 + + + org.xmlunit + xmlunit-matchers + 2.6.2 + test + diff --git a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java index f39803c2b5..2e71a6e3fb 100644 --- a/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java +++ b/dspace-api/src/main/java/org/dspace/administer/StructBuilder.java @@ -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,18 +55,17 @@ import org.xml.sax.SAXException; * an XML file. * * The XML file structure needs to be: - *

- * {@code + *

{@code
  * 
- * 
- * ....
- * ...
- * 
- * ....
- * 
- * 
+ *   
+ *     ....
+ *     ...
+ *     
+ *       ....
+ *     
+ *   
  * 
- * }
+ * }
*

* 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 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 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: + *

{@code StructBuilder -f [XML source] -e [administrator email] -o [output file]} * - * {@code StructBuilder -f [xml source] -e [administrator email] -o [output file]} + *

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. + *

{@code StructBuilder -x -e [administrator email] -o [output file]}

* - * @param argv the command line arguments given + *

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 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 -o -e ", - "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()) { diff --git a/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java new file mode 100644 index 0000000000..1136008c06 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/administer/StructBuilderIT.java @@ -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 + */ +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 = + "\n" + + "\n" + + " \n" + + " Top Community 0\n" + + " " + + " \n" + + " Sub Community 0.0\n" + + " " + + " \n" + + " Collection 0.0.0\n" + + " " + + " \n" + + " \n" + + " \n" + + " Collection 0.1\n" + + " " + + " \n" + + " \n" + + "\n"; + + private static final String EXPORT_DOCUMENT = + "\n" + + "\n" + + " \n" + + " Top Community 0" + + " " + + " \n" + + " Collection 0.0" + + " " + + " \n" + + " \n" + + "\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 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 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 { + private static final List dontCare = Arrays.asList( + "description", + "intro", + "copyright", + "sidebar", + "license", + "provenance"); + + @Override + public boolean test(Node node) { + String type = node.getLocalName(); + return ! dontCare.contains(type); + } + } +} diff --git a/dspace-api/src/test/java/org/dspace/administer/package-info.java b/dspace-api/src/test/java/org/dspace/administer/package-info.java new file mode 100644 index 0000000000..e8e1bf68f0 --- /dev/null +++ b/dspace-api/src/test/java/org/dspace/administer/package-info.java @@ -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. + */