mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-16 14:33:09 +00:00
[DS-1443] Recreate this patch atop current (7x) master.
This commit is contained in:
@@ -757,6 +757,13 @@
|
|||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
<version>20180130</version>
|
<version>20180130</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xmlunit</groupId>
|
||||||
|
<artifactId>xmlunit-matchers</artifactId>
|
||||||
|
<version>2.6.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@@ -7,12 +7,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.dspace.administer;
|
package org.dspace.administer;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.FileInputStream;
|
||||||
import java.io.File;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileWriter;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
@@ -29,6 +33,9 @@ import org.apache.xpath.XPathAPI;
|
|||||||
import org.dspace.authorize.AuthorizeException;
|
import org.dspace.authorize.AuthorizeException;
|
||||||
import org.dspace.content.Collection;
|
import org.dspace.content.Collection;
|
||||||
import org.dspace.content.Community;
|
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.factory.ContentServiceFactory;
|
||||||
import org.dspace.content.service.CollectionService;
|
import org.dspace.content.service.CollectionService;
|
||||||
import org.dspace.content.service.CommunityService;
|
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.factory.EPersonServiceFactory;
|
||||||
import org.dspace.eperson.service.EPersonService;
|
import org.dspace.eperson.service.EPersonService;
|
||||||
import org.jdom.Element;
|
import org.jdom.Element;
|
||||||
|
import org.jdom.output.Format;
|
||||||
import org.jdom.output.XMLOutputter;
|
import org.jdom.output.XMLOutputter;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
@@ -47,18 +55,17 @@ import org.xml.sax.SAXException;
|
|||||||
* an XML file.
|
* an XML file.
|
||||||
*
|
*
|
||||||
* The XML file structure needs to be:
|
* The XML file structure needs to be:
|
||||||
* <p>
|
* <pre>{@code
|
||||||
* {@code
|
|
||||||
* <import_structure>
|
* <import_structure>
|
||||||
* <community>
|
* <community>
|
||||||
* <name>....</name>
|
* <name>....</name>
|
||||||
* <community>...</community>
|
* <community>...</community>
|
||||||
* <collection>
|
* <collection>
|
||||||
* <name>....</name>
|
* <name>....</name>
|
||||||
* </collection>
|
* </collection>
|
||||||
* </community>
|
* </community>
|
||||||
* </import_structure>
|
* </import_structure>
|
||||||
* }
|
* }</pre>
|
||||||
* <p>
|
* <p>
|
||||||
* It can be arbitrarily deep, and supports all the metadata elements
|
* It can be arbitrarily deep, and supports all the metadata elements
|
||||||
* that make up the community and collection metadata. See the system
|
* that make up the community and collection metadata. See the system
|
||||||
@@ -68,26 +75,31 @@ import org.xml.sax.SAXException;
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class StructBuilder {
|
public class StructBuilder {
|
||||||
/**
|
/** Name of the root element for the document to be imported. */
|
||||||
* The output XML document which will contain updated information about the
|
static final String INPUT_ROOT = "import_structure";
|
||||||
* imported 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
|
static final String RESULT_ROOT = "imported_structure";
|
||||||
= new org.jdom.Document(new Element("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<>();
|
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<>();
|
private static final Map<String, String> communityMap = new HashMap<>();
|
||||||
|
|
||||||
protected static CommunityService communityService = ContentServiceFactory.getInstance().getCommunityService();
|
protected static CommunityService communityService
|
||||||
protected static CollectionService collectionService = ContentServiceFactory.getInstance().getCollectionService();
|
= ContentServiceFactory.getInstance().getCommunityService();
|
||||||
protected static EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService();
|
protected static CollectionService collectionService
|
||||||
|
= ContentServiceFactory.getInstance().getCollectionService();
|
||||||
|
protected static EPersonService ePersonService
|
||||||
|
= EPersonServiceFactory.getInstance().getEPersonService();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor
|
* Default constructor
|
||||||
@@ -96,30 +108,38 @@ public class StructBuilder {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Main method to be run from the command line to import a structure into
|
* 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
|
* <p>{@code StructBuilder -x -e [administrator email] -o [output file]}</p>
|
||||||
* with the handle for each imported item added as an attribute.
|
|
||||||
*
|
*
|
||||||
* @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 ParserConfigurationException passed through.
|
||||||
* @throws SQLException 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)
|
public static void main(String[] argv)
|
||||||
throws ParserConfigurationException, SQLException {
|
throws ParserConfigurationException, SQLException,
|
||||||
|
FileNotFoundException, IOException, TransformerException {
|
||||||
|
// Parse the command line.
|
||||||
CommandLineParser parser = new DefaultParser();
|
CommandLineParser parser = new DefaultParser();
|
||||||
|
|
||||||
Options options = new Options();
|
Options options = new Options();
|
||||||
|
|
||||||
options.addOption("h", "help", false, "help");
|
options.addOption("h", "help", false, "Print this help message.");
|
||||||
options.addOption("?", "help");
|
options.addOption("?", "help");
|
||||||
options.addOption("f", "file", true, "input structure document");
|
options.addOption("f", "file", true, "File of new structure information.");
|
||||||
options.addOption("e", "eperson", true, "eperson");
|
options.addOption("e", "eperson", true, "User who is manipulating the repository's structure.");
|
||||||
options.addOption("o", "output", true, "output structure document");
|
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;
|
CommandLine line = null;
|
||||||
try {
|
try {
|
||||||
@@ -130,32 +150,49 @@ public class StructBuilder {
|
|||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user asked for help, give it and exit.
|
||||||
if (line.hasOption('h') || line.hasOption('?')) {
|
if (line.hasOption('h') || line.hasOption('?')) {
|
||||||
usage(options);
|
giveHelp(options);
|
||||||
System.exit(0);
|
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 eperson = null;
|
||||||
String output = null;
|
String output = null;
|
||||||
|
|
||||||
if (line.hasOption('f')) {
|
if (line.hasOption('f')) {
|
||||||
file = line.getOptionValue('f');
|
input = line.getOptionValue('f');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.hasOption('e')) {
|
if (line.hasOption('e')) {
|
||||||
eperson = line.getOptionValue('e');
|
eperson = line.getOptionValue('e');
|
||||||
|
} else { // EPerson is required
|
||||||
|
usage(options);
|
||||||
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (line.hasOption('o')) {
|
if (line.hasOption('o')) {
|
||||||
output = line.getOptionValue('o');
|
output = line.getOptionValue('o');
|
||||||
}
|
} else { // output is required
|
||||||
|
|
||||||
if (output == null || eperson == null || file == null) {
|
|
||||||
usage(options);
|
usage(options);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open the output stream.
|
||||||
|
OutputStream outputStream;
|
||||||
|
if ("-".equals(output)) {
|
||||||
|
outputStream = System.out;
|
||||||
|
} else {
|
||||||
|
outputStream = new FileOutputStream(output);
|
||||||
|
}
|
||||||
|
|
||||||
// create a context
|
// create a context
|
||||||
Context context = new Context();
|
Context context = new Context();
|
||||||
|
|
||||||
@@ -167,10 +204,40 @@ public class StructBuilder {
|
|||||||
System.exit(1);
|
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
|
// load the XML
|
||||||
Document document = null;
|
Document document = null;
|
||||||
try {
|
try {
|
||||||
document = loadXML(file);
|
document = loadXML(input);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
System.err.format("The input document could not be read: %s%n", ex.getMessage());
|
System.err.format("The input document could not be read: %s%n", ex.getMessage());
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@@ -180,7 +247,7 @@ public class StructBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run the preliminary validation, to be sure that the the XML document
|
// run the preliminary validation, to be sure that the the XML document
|
||||||
// is properly structured
|
// is properly structured.
|
||||||
try {
|
try {
|
||||||
validate(document);
|
validate(document);
|
||||||
} catch (TransformerException ex) {
|
} catch (TransformerException ex) {
|
||||||
@@ -188,6 +255,12 @@ public class StructBuilder {
|
|||||||
System.exit(1);
|
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
|
// load the mappings into the member variable hashmaps
|
||||||
communityMap.put("name", "name");
|
communityMap.put("name", "name");
|
||||||
communityMap.put("description", "short_description");
|
communityMap.put("description", "short_description");
|
||||||
@@ -219,33 +292,152 @@ public class StructBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate the output
|
// generate the output
|
||||||
Element root = xmlOutput.getRootElement();
|
final Element root = new Element(RESULT_ROOT);
|
||||||
|
|
||||||
for (Element element : elements) {
|
for (Element element : elements) {
|
||||||
root.addContent(element);
|
root.addContent(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally write the string into the output file
|
// finally write the string into the output file.
|
||||||
try (BufferedWriter out = new BufferedWriter(new FileWriter(output));) {
|
final org.jdom.Document xmlOutput = new org.jdom.Document(root);
|
||||||
out.write(new XMLOutputter().outputString(xmlOutput));
|
try {
|
||||||
|
new XMLOutputter().output(xmlOutput, output);
|
||||||
} catch (IOException e) {
|
} 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);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
context.complete();
|
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.
|
* Output the usage information.
|
||||||
*/
|
*/
|
||||||
private static void usage(Options options) {
|
private static void usage(Options options) {
|
||||||
HelpFormatter helper = new HelpFormatter();
|
HelpFormatter helper = new HelpFormatter();
|
||||||
helper.printHelp("java StructBuilder -f <source XML file> -o <output file> -e <eperson email>",
|
helper.printUsage(new PrintWriter(System.out), 80/* FIXME Magic */,
|
||||||
"Load community/collection structure from a file.",
|
"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,
|
options,
|
||||||
"Communities will be created from the top level,"
|
"When importing (-f), communities will be created from the "
|
||||||
+ " and a map of communities to handles will be returned"
|
+ "top level, and a map of communities to handles will "
|
||||||
+ " in the output file.");
|
+ "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) {
|
if (first.getLength() == 0) {
|
||||||
err.append("-There are no top level communities in the source document.");
|
err.append("-There are no top level communities in the source document.");
|
||||||
System.out.println(err.toString());
|
System.out.println(err.toString());
|
||||||
System.exit(0);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
String errs = validateCommunities(first, 1);
|
String errs = validateCommunities(first, 1);
|
||||||
@@ -278,7 +470,7 @@ public class StructBuilder {
|
|||||||
|
|
||||||
if (trip) {
|
if (trip) {
|
||||||
System.out.println(err.toString());
|
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
|
* @param input the filename to load from.
|
||||||
* @return the DOM representation of the XML file
|
* @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 {
|
throws IOException, ParserConfigurationException, SAXException {
|
||||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||||
.newDocumentBuilder();
|
.newDocumentBuilder();
|
||||||
|
|
||||||
org.w3c.dom.Document document = builder.parse(new File(filename));
|
org.w3c.dom.Document document = builder.parse(input);
|
||||||
|
|
||||||
return document;
|
return document;
|
||||||
}
|
}
|
||||||
@@ -388,7 +580,7 @@ public class StructBuilder {
|
|||||||
* @param node the node from which we want to extract the string value
|
* @param node the node from which we want to extract the string value
|
||||||
* @return the string value of the node
|
* @return the string value of the node
|
||||||
*/
|
*/
|
||||||
public static String getStringValue(Node node) {
|
private static String getStringValue(Node node) {
|
||||||
String value = node.getNodeValue();
|
String value = node.getNodeValue();
|
||||||
|
|
||||||
if (node.hasChildNodes()) {
|
if (node.hasChildNodes()) {
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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.
|
||||||
|
*/
|
Reference in New Issue
Block a user