/** * 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.app.xmlui.objectmanager; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.dspace.app.util.Util; import org.dspace.app.xmlui.wing.AttributeMap; import org.dspace.app.xmlui.wing.Namespace; import org.dspace.app.xmlui.wing.WingException; import org.dspace.content.Bitstream; import org.dspace.content.BitstreamFormat; import org.dspace.content.Item; import org.dspace.content.crosswalk.CrosswalkException; import org.dspace.content.crosswalk.DisseminationCrosswalk; import org.dspace.core.PluginManager; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.NamespaceSupport; /** * This is the abstract adapter containing all the common elements between * the three types of adapters: item, container, and repository. Each adapter * translate a given type of DSpace object into a METS document for rendering * into the DRI document. * * This class provides the chassis for those unique parts of the document to be * build upon, there are seven rendering methods that may be overridden for each * section of the METS document. * * Header * Descriptive Section * Administrative Section * File Section * Structure Map * Structural Link * Behavioral Section * * @author Scott Phillips */ public abstract class AbstractAdapter { /** Namespace declaration for METS & XLINK */ public static final String METS_URI = "http://www.loc.gov/METS/"; public static final Namespace METS = new Namespace(METS_URI); public static final String XLINK_URI = "http://www.w3.org/TR/xlink/"; public static final Namespace XLINK = new Namespace(XLINK_URI); public static final String XSI_URI = "http://www.w3.org/2001/XMLSchema-instance"; public static final Namespace XSI = new Namespace(XSI_URI); public static final String DIM_URI = "http://www.dspace.org/xmlns/dspace/dim"; public static final Namespace DIM = new Namespace(DIM_URI); /** * A sequence used to generate unique mets ids. */ private int idSequence = 0; /** * The contextPath of this web application, used for generating URLs. */ protected String contextPath; /** * The SAX handlers for content and lexical events. Also the support * element for namespaces which knows the prefixes for each declared * namespace. */ protected ContentHandler contentHandler; protected LexicalHandler lexicalHandler; protected NamespaceSupport namespaces; /** * Construct a new adapter, implementers must use call this method so * the appropriate internal values are insured to be set correctly. * * @param contextPath * The contextPath of this web application. */ public AbstractAdapter(String contextPath) { this.contextPath = contextPath; } /** The variables that dictate what part of the METS document to render */ List<String> sections = new ArrayList<String>(); List<String> dmdTypes = new ArrayList<String>(); Map<String,List> amdTypes = new HashMap<String,List>(); List<String> fileGrpTypes = new ArrayList<String>(); List<String> structTypes = new ArrayList<String>(); /** * A comma separated list of METS sections to render. If no value * is provided then all METS sections are rendered. * * @param sections Comma separated list of METS sections. */ public final void setSections(String sections) { if (sections == null) { return; } for (String section : sections.split(",")) { this.sections.add(section); } } /** * A comma separated list of METS descriptive metadata formats to * render. If no value is provided then only the DIM format is used. * * @param dmdTypes Comma separated list of METS metadata types. */ public final void setDmdTypes(String dmdTypes) { if (dmdTypes == null) { return; } for (String dmdType : dmdTypes.split(",")) { this.dmdTypes.add(dmdType); } } /** * Store information about what will be rendered in the METS administrative * metadata section. HashMap format: keys = amdSec, value = List of mdTypes * * @param amdSec Section of <amdSec> where this administrative metadata * will be rendered * @param mdTypes Comma separated list of METS metadata types. */ public final void setAmdTypes(String amdSec, String mdTypes) { if (mdTypes == null) { return; } List<String> mdTypeList = new ArrayList<String>(); for (String mdType : mdTypes.split(",")) { mdTypeList.add(mdType); } this.amdTypes.put(amdSec, mdTypeList); } /** * A comma separated list of METS technical metadata formats to * render. * * @param techMDTypes Comma separated list of METS metadata types. */ public final void setTechMDTypes(String techMDTypes) { setAmdTypes("techMD", techMDTypes); } /** * A comma separated list of METS intellectual property rights metadata * formats to render. * * @param rightsMDTypes Comma separated list of METS metadata types. */ public final void setRightsMDTypes(String rightsMDTypes) { setAmdTypes("rightsMD", rightsMDTypes); } /** * A comma separated list of METS source metadata * formats to render. * * @param sourceMDTypes Comma separated list of METS metadata types. */ public final void setSourceMDTypes(String sourceMDTypes) { setAmdTypes("sourceMD", sourceMDTypes); } /** * A comma separated list of METS digital provenance metadata * formats to render. * * @param digiprovMDTypes Comma separated list of METS metadata types. */ public final void setDigiProvMDTypes(String digiprovMDTypes) { setAmdTypes("digiprovMD", digiprovMDTypes); } /** * A comma separated list of METS fileGrps to render. If no value * is provided then all groups are rendered. * * @param fileGrpTypes Comma separated list of METS file groups. */ public final void setFileGrpTypes(String fileGrpTypes) { if (fileGrpTypes == null) { return; } for (String fileGrpType : fileGrpTypes.split(",")) { this.fileGrpTypes.add(fileGrpType); } } /** * A comma separated list of METS structural types to render. If no * value is provided then only the DIM format is used. * * @param structTypes Comma separated list of METS structure types. */ public final void setStructTypes(String structTypes) { if (structTypes == null) { return; } for (String structType : structTypes.split(",")) { this.structTypes.add(structType); } } /** * * * * * * METS methods * * * * * * */ /** * @return the URL for this item in the interface */ protected abstract String getMETSOBJID() throws WingException; /** * @return Return the URL for editing this item */ protected abstract String getMETSOBJEDIT(); /** * @return the METS ID of the mets document. */ protected abstract String getMETSID() throws WingException; /** * @return The Profile this METS document conforms too. */ protected abstract String getMETSProfile() throws WingException; /** * @return The label of this METS document. */ protected abstract String getMETSLabel() throws WingException; /** * Render the complete METS document. */ public final void renderMETS(ContentHandler contentHandler, LexicalHandler lexicalHandler) throws WingException, SAXException, CrosswalkException, IOException, SQLException { this.contentHandler = contentHandler; this.lexicalHandler = lexicalHandler; this.namespaces = new NamespaceSupport(); // Declare our namespaces namespaces.pushContext(); namespaces.declarePrefix("mets", METS.URI); namespaces.declarePrefix("xlink", XLINK.URI); namespaces.declarePrefix("xsi", XSI.URI); namespaces.declarePrefix("dim", DIM.URI); contentHandler.startPrefixMapping("mets", METS.URI); contentHandler.startPrefixMapping("xlink", XLINK.URI); contentHandler.startPrefixMapping("xsi", XSI.URI); contentHandler.startPrefixMapping("dim", DIM.URI); // Send the METS element AttributeMap attributes = new AttributeMap(); attributes.put("ID", getMETSID()); attributes.put("PROFILE", getMETSProfile()); attributes.put("LABEL", getMETSLabel()); String objid = getMETSOBJID(); if (objid != null) { attributes.put("OBJID", objid); } // Include the link for editing the item objid = getMETSOBJEDIT(); if (objid != null) { attributes.put("OBJEDIT", objid); } startElement(METS,"METS",attributes); // If the user requested no specific sections then render them all. boolean all = (sections.size() == 0); if (all || sections.contains("metsHdr")) { renderHeader(); } if (all || sections.contains("dmdSec")) { renderDescriptiveSection(); } if (all || sections.contains("amdSec")) { renderAdministrativeSection(); } if (all || sections.contains("fileSec")) { renderFileSection(); } if (all || sections.contains("structMap")) { renderStructureMap(); } if (all || sections.contains("structLink")) { renderStructuralLink(); } if (all || sections.contains("behaviorSec")) { renderBehavioralSection(); } // FIXME: this is not a met's section, it should be removed if (all || sections.contains("extraSec")) { renderExtraSections(); } endElement(METS,"METS"); contentHandler.endPrefixMapping("mets"); contentHandler.endPrefixMapping("xlink"); contentHandler.endPrefixMapping("dim"); namespaces.popContext(); } /** * Each of the METS sections */ protected void renderHeader() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderDescriptiveSection() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderAdministrativeSection() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderFileSection() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderStructureMap() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderStructuralLink() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderBehavioralSection() throws WingException, SAXException, CrosswalkException, IOException, SQLException {} protected void renderExtraSections() throws WingException, SAXException, CrosswalkException, SQLException, IOException {} /** * Generate a METS file element for a given bitstream. * * @param item * If the bitstream is associated with an item provide the item * otherwise leave null. * @param bitstream * The bitstream to build a file element for. * @param fileID * The unique file id for this file. * @param groupID * The group id for this file, if it is derived from another file * then they should share the same groupID. */ protected final void renderFile(Item item, Bitstream bitstream, String fileID, String groupID) throws SAXException { renderFile(item, bitstream, fileID, groupID, null); } /** * Generate a METS file element for a given bitstream. * * @param item * If the bitstream is associated with an item provide the item * otherwise leave null. * @param bitstream * The bitstream to build a file element for. * @param fileID * The unique file id for this file. * @param groupID * The group id for this file, if it is derived from another file * then they should share the same groupID. * @param admID * The IDs of the administrative metadata sections which pertain * to this file */ protected final void renderFile(Item item, Bitstream bitstream, String fileID, String groupID, String admID) throws SAXException { AttributeMap attributes; // ////////////////////////////// // Determine the file attributes BitstreamFormat format = bitstream.getFormat(); String mimeType = null; if (format != null) { mimeType = format.getMIMEType(); } String checksumType = bitstream.getChecksumAlgorithm(); String checksum = bitstream.getChecksum(); long size = bitstream.getSize(); // //////////////////////////////// // Start the actual file attributes = new AttributeMap(); attributes.put("ID", fileID); attributes.put("GROUPID",groupID); if (admID != null && admID.length()>0) { attributes.put("ADMID", admID); } if (mimeType != null && mimeType.length()>0) { attributes.put("MIMETYPE", mimeType); } if (checksumType != null && checksum != null) { attributes.put("CHECKSUM", checksum); attributes.put("CHECKSUMTYPE", checksumType); } attributes.put("SIZE", String.valueOf(size)); startElement(METS,"file",attributes); // //////////////////////////////////// // Determine the file location attributes String name = bitstream.getName(); String description = bitstream.getDescription(); // If possible reference this bitstream via a handle, however this may // be null if a handle has not yet been assigned. In this case reference the // item its internal id. In the last case where the bitstream is not associated // with an item (such as a community logo) then reference the bitstreamID directly. String identifier = null; if (item != null && item.getHandle() != null) { identifier = "handle/" + item.getHandle(); } else if (item != null) { identifier = "item/" + item.getID(); } else { identifier = "id/" + bitstream.getID(); } String url = contextPath + "/bitstream/"+identifier+"/"; // If we can put the pretty name of the bitstream on the end of the URL try { if (bitstream.getName() != null) { url += Util.encodeBitstreamName(bitstream.getName(), "UTF-8"); } } catch (UnsupportedEncodingException uee) { // just ignore it, we don't have to have a pretty // name on the end of the URL because the sequence id will // locate it. However it means that links in this file might // not work.... } url += "?sequence="+bitstream.getSequenceID(); // ////////////////////// // Start the file location attributes = new AttributeMap(); AttributeMap attributesXLINK = new AttributeMap(); attributesXLINK.setNamespace(XLINK); attributes.put("LOCTYPE", "URL"); attributesXLINK.put("type","locator"); attributesXLINK.put("title", name); if (description != null) { attributesXLINK.put("label", description); } attributesXLINK.put("href", url); startElement(METS,"FLocat",attributes,attributesXLINK); // /////////////////////// // End file location endElement(METS,"FLocate"); // //////////////////////////////// // End the file endElement(METS,"file"); } /** * * Generate a unique METS id. For consistency, all prefixes should probably * end in an underscore, "_". * * @param prefix * Prefix to prepend to the id for readability. * * @return A unique METS id. */ protected final String getGenericID(String prefix) { return prefix + (idSequence++); } /** * Return a dissemination crosswalk for the given name. * * @param crosswalkName * @return The crosswalk or throw an exception if not found. */ public final DisseminationCrosswalk getDisseminationCrosswalk(String crosswalkName) throws WingException { // FIXME add some caching here DisseminationCrosswalk crosswalk = (DisseminationCrosswalk) PluginManager.getNamedPlugin(DisseminationCrosswalk.class, crosswalkName); if (crosswalk == null) { throw new WingException("Unable to find named DisseminationCrosswalk: " + crosswalkName); } return crosswalk; } /** * The METS defined types of Metadata, if a format is not listed here * then it should use the string "OTHER" and provide additional * attributes describing the metadata type */ public static final String[] METS_DEFINED_TYPES = {"MARC","MODS","EAD","DC","NISOIMG","LC-AV","VRA","TEIHDR","DDI","FGDC","PREMIS"/*,"OTHER"*/}; /** * Determine if the provided metadata type is a standard METS * defined type. If it is not, use the other string. * * @param metadataType type name * @return True if METS defined */ public final boolean isDefinedMETStype(String metadataType) { for (String definedType : METS_DEFINED_TYPES) { if (definedType.equals(metadataType)) { return true; } } return false; } /** * * * SAX Helper methods * * * */ /** * Send the SAX events to start this element. * * @param namespace * (Required) The namespace of this element. * @param name * (Required) The local name of this element. * @param attributes * (May be null) Attributes for this element */ protected final void startElement(Namespace namespace, String name, AttributeMap... attributes) throws SAXException { contentHandler.startElement(namespace.URI, name, qName(namespace, name), map2sax(namespace,attributes)); } /** * Send the SAX event for these plain characters, not wrapped in any * elements. * * @param characters * (May be null) Characters to send. */ protected final void sendCharacters(String characters) throws SAXException { if (characters != null) { char[] contentArray = characters.toCharArray(); contentHandler.characters(contentArray, 0, contentArray.length); } } /** * Send the SAX events to end this element. * * @param namespace * (Required) The namespace of this element. * @param name * (Required) The local name of this element. */ protected final void endElement(Namespace namespace, String name) throws SAXException { contentHandler.endElement(namespace.URI, name, qName(namespace, name)); } /** * Build the SAX attributes object based upon Java's String map. This * convenience method will build, or add to an existing attributes object, * the attributes detailed in the AttributeMap. * * @param elementNamespace * SAX Helper class to keep track of namespaces able to determine * the correct prefix for a given namespace URI. * @param attributes * An existing SAX AttributesImpl object to add attributes too. * If the value is null then a new attributes object will be * created to house the attributes. * @param attributeMap * A map of attributes and values. * @return */ private AttributesImpl map2sax(Namespace elementNamespace, AttributeMap ... attributeMaps) { AttributesImpl attributes = new AttributesImpl(); for (AttributeMap attributeMap : attributeMaps) { boolean diffrentNamespaces = false; Namespace attributeNamespace = attributeMap.getNamespace(); if (attributeNamespace != null && !(attributeNamespace.URI.equals(elementNamespace.URI))) { diffrentNamespaces = true; } // copy each one over. for (Map.Entry<String, String> attr : attributeMap.entrySet()) { if (attr.getValue() == null) { continue; } if (diffrentNamespaces) { attributes.addAttribute(attributeNamespace.URI, attr.getKey(), qName(attributeNamespace, attr.getKey()), "CDATA", attr.getValue()); } else { attributes.addAttribute("", attr.getKey(), attr.getKey(), "CDATA", attr.getValue()); } } } return attributes; } /** * Create the qName for the element with the given localName and namespace * prefix. * * @param namespace * (May be null) The namespace prefix. * @param localName * (Required) The element's local name. * @return */ private String qName(Namespace namespace, String localName) { String prefix = namespaces.getPrefix(namespace.URI); if (prefix == null || prefix.equals("")) { return localName; } else { return prefix + ":" + localName; } } }