org.dspace.administer.StructBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.administer.StructBuilder.java

Source

/**
 * 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 java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.xpath.XPathAPI;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * This class deals with importing community and collection structures from
 * an XML file.
 * 
 * The XML file structure needs to be:
 * 
 * <import_structure>
 *     <community>
 *         <name>....</name>
 *         <community>...</community>
 *         <collection>
 *             <name>....</name>
 *         </collection>
 *     </community>
 * </import_structure>
 * 
 * it can be arbitrarily deep, and supports all the metadata elements
 * that make up the community and collection metadata.  See the system
 * documentation for more details
 * 
 * @author Richard Jones
 *
 */

public class StructBuilder {
    /** the output xml document which will contain updated information about the 
     * imported structure
     */
    private static org.jdom.Document xmlOutput = new org.jdom.Document(new Element("imported_structure"));

    /** a hashtable to hold metadata for the collection being worked on */
    private static Map<String, String> collectionMap = new HashMap<String, String>();

    /** a hashtable to hold metadata for the community being worked on */
    private static Map<String, String> communityMap = new HashMap<String, String>();

    /**
     * Main method to be run from the command line to import a structure into
     * DSpace
     * 
     * This is of the form:
     * 
     * StructBuilder -f [xml source] -e [administrator email] -o [output file]
     * 
     * 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.
     */
    public static void main(String[] argv) throws Exception {
        CommandLineParser parser = new PosixParser();

        Options options = new Options();

        options.addOption("f", "file", true, "file");
        options.addOption("e", "eperson", true, "eperson");
        options.addOption("o", "output", true, "output");

        CommandLine line = parser.parse(options, argv);

        String file = null;
        String eperson = null;
        String output = null;

        if (line.hasOption('f')) {
            file = line.getOptionValue('f');
        }

        if (line.hasOption('e')) {
            eperson = line.getOptionValue('e');
        }

        if (line.hasOption('o')) {
            output = line.getOptionValue('o');
        }

        if (output == null || eperson == null || file == null) {
            usage();
            System.exit(0);
        }

        // create a context
        Context context = new Context();

        // set the context
        context.setCurrentUser(EPerson.findByEmail(context, eperson));

        // load the XML
        Document document = loadXML(file);

        // run the preliminary validation, to be sure that the the XML document
        // is properly structured
        validate(document);

        // load the mappings into the member variable hashmaps
        communityMap.put("name", "name");
        communityMap.put("description", "short_description");
        communityMap.put("intro", "introductory_text");
        communityMap.put("copyright", "copyright_text");
        communityMap.put("sidebar", "side_bar_text");

        collectionMap.put("name", "name");
        collectionMap.put("description", "short_description");
        collectionMap.put("intro", "introductory_text");
        collectionMap.put("copyright", "copyright_text");
        collectionMap.put("sidebar", "side_bar_text");
        collectionMap.put("license", "license");
        collectionMap.put("provenance", "provenance_description");

        // get the top level community list
        NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");

        // run the import starting with the top level communities
        Element[] elements = handleCommunities(context, first, null);

        // generate the output
        Element root = xmlOutput.getRootElement();
        for (int i = 0; i < elements.length; i++) {
            root.addContent(elements[i]);
        }

        // finally write the string into the output file
        try {
            BufferedWriter out = new BufferedWriter(new FileWriter(output));
            out.write(new XMLOutputter().outputString(xmlOutput));
            out.close();
        } catch (IOException e) {
            System.out.println("Unable to write to output file " + output);
            System.exit(0);
        }

        context.complete();
    }

    /**
     * Output the usage information
     */
    private static void usage() {
        System.out.println("Usage: java StructBuilder -f <source XML file> -o <output file> -e <eperson email>");
        System.out.println(
                "Communities will be created from the top level, and a map of communities to handles will be returned in the output file");
        return;
    }

    /**
     * Validate the XML document.  This method does not return, but if validation
     * fails it generates an error and ceases execution
     * 
     * @param   document   the XML document object
     * @throws TransformerException
     * 
     */
    private static void validate(org.w3c.dom.Document document) throws TransformerException {
        StringBuffer err = new StringBuffer();
        boolean trip = false;

        err.append("The following errors were encountered parsing the source XML\n");
        err.append("No changes have been made to the DSpace instance\n\n");

        NodeList first = XPathAPI.selectNodeList(document, "/import_structure/community");
        if (first.getLength() == 0) {
            err.append("-There are no top level communities in the source document");
            System.out.println(err.toString());
            System.exit(0);
        }

        String errs = validateCommunities(first, 1);
        if (errs != null) {
            err.append(errs);
            trip = true;
        }

        if (trip) {
            System.out.println(err.toString());
            System.exit(0);
        }
    }

    /**
     * Validate the communities section of the XML document.  This returns a string
     * containing any errors encountered, or null if there were no errors
     * 
     * @param communities the NodeList of communities to validate
     * @param level the level in the XML document that we are at, for the purposes
     *          of error reporting
     * 
     * @return the errors that need to be generated by the calling method, or null if
     *          no errors.
     */
    private static String validateCommunities(NodeList communities, int level) throws TransformerException {
        StringBuffer err = new StringBuffer();
        boolean trip = false;
        String errs = null;

        for (int i = 0; i < communities.getLength(); i++) {
            Node n = communities.item(i);
            NodeList name = XPathAPI.selectNodeList(n, "name");
            if (name.getLength() != 1) {
                String pos = Integer.toString(i + 1);
                err.append("-The level " + level + " community in position " + pos);
                err.append(" does not contain exactly one name field\n");
                trip = true;
            }

            // validate sub communities
            NodeList subCommunities = XPathAPI.selectNodeList(n, "community");
            String comErrs = validateCommunities(subCommunities, level + 1);
            if (comErrs != null) {
                err.append(comErrs);
                trip = true;
            }

            // validate collections
            NodeList collections = XPathAPI.selectNodeList(n, "collection");
            String colErrs = validateCollections(collections, level + 1);
            if (colErrs != null) {
                err.append(colErrs);
                trip = true;
            }
        }

        if (trip) {
            errs = err.toString();
        }

        return errs;
    }

    /**
     * validate the collection section of the XML document.  This generates a
     * string containing any errors encountered, or returns null if no errors
     * 
     * @param collections a NodeList of collections to validate
     * @param level the level in the XML document for the purposes of error reporting
     * 
     * @return the errors to be generated by the calling method, or null if none
     */
    private static String validateCollections(NodeList collections, int level) throws TransformerException {
        StringBuffer err = new StringBuffer();
        boolean trip = false;
        String errs = null;

        for (int i = 0; i < collections.getLength(); i++) {
            Node n = collections.item(i);
            NodeList name = XPathAPI.selectNodeList(n, "name");
            if (name.getLength() != 1) {
                String pos = Integer.toString(i + 1);
                err.append("-The level " + level + " collection in position " + pos);
                err.append(" does not contain exactly one name field\n");
                trip = true;
            }
        }

        if (trip) {
            errs = err.toString();
        }

        return errs;
    }

    /**
     * Load in the XML from file.
     * 
     * @param filename
     *            the filename to load from
     * 
     * @return the DOM representation of the XML file
     */
    private static org.w3c.dom.Document loadXML(String filename)
            throws IOException, ParserConfigurationException, SAXException {
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

        org.w3c.dom.Document document = builder.parse(new File(filename));

        return document;
    }

    /**
     * Return the String value of a Node
     * 
     * @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) {
        String value = node.getNodeValue();

        if (node.hasChildNodes()) {
            Node first = node.getFirstChild();

            if (first.getNodeType() == Node.TEXT_NODE) {
                return first.getNodeValue().trim();
            }
        }

        return value;
    }

    /**
     * Take a node list of communities and build the structure from them, delegating
     * to the relevant methods in this class for sub-communities and collections
     * 
     * @param context the context of the request
     * @param communities a nodelist of communities to create along with their sub-structures
     * @param parent the parent community of the nodelist of communities to create
     * 
     * @return an element array containing additional information regarding the 
     *          created communities (e.g. the handles they have been assigned)
     */
    private static Element[] handleCommunities(Context context, NodeList communities, Community parent)
            throws TransformerException, SQLException, Exception {
        Element[] elements = new Element[communities.getLength()];

        for (int i = 0; i < communities.getLength(); i++) {
            Community community;
            Element element = new Element("community");

            // create the community or sub community
            if (parent != null) {
                community = parent.createSubcommunity();
            } else {
                community = Community.create(null, context);
            }

            // default the short description to be an empty string
            community.setMetadata("short_description", " ");

            // now update the metadata
            Node tn = communities.item(i);
            for (Map.Entry<String, String> entry : communityMap.entrySet()) {
                NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
                if (nl.getLength() == 1) {
                    community.setMetadata(entry.getValue(), getStringValue(nl.item(0)));
                }
            }

            // FIXME: at the moment, if the community already exists by name
            // then this will throw a PSQLException on a duplicate key
            // violation
            // Ideally we'd skip this row and continue to create sub
            // communities
            // and so forth where they don't exist, but it's proving
            // difficult
            // to isolate the community that already exists without hitting
            // the database directly.
            community.update();

            // build the element with the handle that identifies the new
            // community
            // along with all the information that we imported here
            // This looks like a lot of repetition of getting information
            // from above
            // but it's here to keep it separate from the create process in
            // case
            // we want to move it or make it switchable later
            element.setAttribute("identifier", community.getHandle());

            Element nameElement = new Element("name");
            nameElement.setText(community.getMetadata("name"));
            element.addContent(nameElement);

            if (community.getMetadata("short_description") != null) {
                Element descriptionElement = new Element("description");
                descriptionElement.setText(community.getMetadata("short_description"));
                element.addContent(descriptionElement);
            }

            if (community.getMetadata("introductory_text") != null) {
                Element introElement = new Element("intro");
                introElement.setText(community.getMetadata("introductory_text"));
                element.addContent(introElement);
            }

            if (community.getMetadata("copyright_text") != null) {
                Element copyrightElement = new Element("copyright");
                copyrightElement.setText(community.getMetadata("copyright_text"));
                element.addContent(copyrightElement);
            }

            if (community.getMetadata("side_bar_text") != null) {
                Element sidebarElement = new Element("sidebar");
                sidebarElement.setText(community.getMetadata("side_bar_text"));
                element.addContent(sidebarElement);
            }

            // handle sub communities
            NodeList subCommunities = XPathAPI.selectNodeList(tn, "community");
            Element[] subCommunityElements = handleCommunities(context, subCommunities, community);

            // handle collections
            NodeList collections = XPathAPI.selectNodeList(tn, "collection");
            Element[] collectionElements = handleCollections(context, collections, community);

            int j;
            for (j = 0; j < subCommunityElements.length; j++) {
                element.addContent(subCommunityElements[j]);
            }
            for (j = 0; j < collectionElements.length; j++) {
                element.addContent(collectionElements[j]);
            }

            elements[i] = element;
        }

        return elements;
    }

    /**
     *  Take a node list of collections and create the structure from them
     * 
     * @param context the context of the request
     * @param collections the node list of collections to be created
     * @param parent the parent community to whom the collections belong
     * 
     * @return an Element array containing additional information about the
     *          created collections (e.g. the handle)
     */
    private static Element[] handleCollections(Context context, NodeList collections, Community parent)
            throws TransformerException, SQLException, AuthorizeException, IOException, Exception {
        Element[] elements = new Element[collections.getLength()];

        for (int i = 0; i < collections.getLength(); i++) {
            Element element = new Element("collection");
            Collection collection = parent.createCollection();

            // default the short description to the empty string
            collection.setMetadata("short_description", " ");

            // import the rest of the metadata
            Node tn = collections.item(i);
            for (Map.Entry<String, String> entry : collectionMap.entrySet()) {
                NodeList nl = XPathAPI.selectNodeList(tn, entry.getKey());
                if (nl.getLength() == 1) {
                    collection.setMetadata(entry.getValue(), getStringValue(nl.item(0)));
                }
            }

            collection.update();

            element.setAttribute("identifier", collection.getHandle());

            Element nameElement = new Element("name");
            nameElement.setText(collection.getMetadata("name"));
            element.addContent(nameElement);

            if (collection.getMetadata("short_description") != null) {
                Element descriptionElement = new Element("description");
                descriptionElement.setText(collection.getMetadata("short_description"));
                element.addContent(descriptionElement);
            }

            if (collection.getMetadata("introductory_text") != null) {
                Element introElement = new Element("intro");
                introElement.setText(collection.getMetadata("introductory_text"));
                element.addContent(introElement);
            }

            if (collection.getMetadata("copyright_text") != null) {
                Element copyrightElement = new Element("copyright");
                copyrightElement.setText(collection.getMetadata("copyright_text"));
                element.addContent(copyrightElement);
            }

            if (collection.getMetadata("side_bar_text") != null) {
                Element sidebarElement = new Element("sidebar");
                sidebarElement.setText(collection.getMetadata("side_bar_text"));
                element.addContent(sidebarElement);
            }

            if (collection.getMetadata("license") != null) {
                Element sidebarElement = new Element("license");
                sidebarElement.setText(collection.getMetadata("license"));
                element.addContent(sidebarElement);
            }

            if (collection.getMetadata("provenance_description") != null) {
                Element sidebarElement = new Element("provenance");
                sidebarElement.setText(collection.getMetadata("provenance_description"));
                element.addContent(sidebarElement);
            }

            elements[i] = element;
        }

        return elements;
    }

}