/*
* AbstractAdapter.java
*
* Version: $Revision: 3705 $
*
* Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $
*
* Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
* Institute of Technology. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of the Hewlett-Packard Company nor the name of the
* Massachusetts Institute of Technology nor the names of their
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
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 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 chasses for those unique parts of the document to be
* build upon, there are seven rendering methods that may be overriden 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 unquie mets ids.
*/
private int idSequence = 0;
/**
* The contextPath of this webapplication, used for generateing 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 approprate 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 dicatacte what part of the METS document to render */
List<String> sections = new ArrayList<String>();
List<String> dmdTypes = new ArrayList<String>();
HashMap<String,List> amdTypes = new HashMap<String,List>();
List<String> fileGrpTypes = new ArrayList<String>();
List<String> structTypes = new ArrayList<String>();
/**
* A comma seperated list of METS sections to render. If no value
* is provided then all METS sections are rendered.
*
* @param sections Comma seperated list of METS sections.
*/
public void setSections(String sections)
{
if (sections == null)
return;
for (String section : sections.split(","))
{
this.sections.add(section);
}
}
/**
* A comma seperated list of METS descriptive metadata formats to
* render. If no value is provided then only the DIM format is used.
*
* @param sections Comma seperated list of METS metadata types.
*/
public 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 seperated list of METS metadata types.
*/
public 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 seperated list of METS technical metadata formats to
* render.
*
* @param techMDTypes Comma seperated list of METS metadata types.
*/
public void setTechMDTypes(String techMDTypes)
{
setAmdTypes("techMD", techMDTypes);
}
/**
* A comma seperated list of METS intellectual property rights metadata
* formats to render.
*
* @param rightsMDTypes Comma seperated list of METS metadata types.
*/
public void setRightsMDTypes(String rightsMDTypes)
{
setAmdTypes("rightsMD", rightsMDTypes);
}
/**
* A comma seperated list of METS source metadata
* formats to render.
*
* @param sourceMDTypes Comma seperated list of METS metadata types.
*/
public void setSourceMDTypes(String sourceMDTypes)
{
setAmdTypes("sourceMD", sourceMDTypes);
}
/**
* A comma seperated list of METS digital provenance metadata
* formats to render.
*
* @param digiprovMDTypes Comma seperated list of METS metadata types.
*/
public void setDigiProvMDTypes(String digiprovMDTypes)
{
setAmdTypes("digiprovMD", digiprovMDTypes);
}
/**
* A comma seperated list of METS fileGrps to render. If no value
* is provided then all groups are rendered.
*
* @param sections Comma seperated list of METS file groups.
*/
public void setFileGrpTypes(String fileGrpTypes)
{
if (fileGrpTypes == null)
return;
for (String fileGrpType : fileGrpTypes.split(","))
{
this.fileGrpTypes.add(fileGrpType);
}
}
/**
* A comma seperated list of METS structural types to render. If no
* value is provided then only the DIM format is used.
*
* @param sections Comma seperated list of METS structure types.
*/
public 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 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 specefic 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 provid 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.
* @return The METS file element.
*/
protected 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 provid 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
* @return The METS file element.
*/
protected 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 refrence this bitstream via a handle, however this may
// be null if a handle has not yet been assigned. In this case refrence the
// item its internal id. In the last case where the bitstream is not associated
// with an item (such as a community logo) then refrence 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 consistancy, all prefixs should probably
* end in an underscore, "_".
*
* @param prefix
* Prefix to prepend to the id for readability.
*
* @return A unique METS id.
*/
protected 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 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 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 stardard METS
* defined type. If it is not, use the other string.
*
* @param metadataType type name
* @return True if METS defined
*/
public 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 contentHandler
* (Required) The registered contentHandler where SAX events
* should be routed too.
* @param namespaces
* (Required) SAX Helper class to keep track of namespaces able
* to determine the correct prefix for a given namespace URI.
* @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 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 contentHandler
* (Required) The registered contentHandler where SAX events
* should be routed too.
* @param characters
* (May be null) Characters to send.
*/
protected 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 contentHandler
* (Required) The registered contentHandler where SAX events
* should be routed too.
* @param namespaces
* (Required) SAX Helper class to keep track of namespaces able
* to determine the correct prefix for a given namespace URI.
* @param namespace
* (Required) The namespace of this element.
* @param name
* (Required) The local name of this element.
*/
protected 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 namespaces
* 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)
{
if (!(attributeNamespace.URI.equals(elementNamespace.URI)))
{
diffrentNamespaces = true;
}
}
// copy each one over.
for (String name : attributeMap.keySet())
{
String value = attributeMap.get(name);
if (value == null)
continue;
if (diffrentNamespaces)
attributes.addAttribute(attributeNamespace.URI, name,
qName(attributeNamespace, name), "CDATA", value);
else
attributes.addAttribute("", name, name, "CDATA", value);
}
}
return attributes;
}
/**
* Create the qName for the element with the given localName and namespace
* prefix.
*
* @param prefix
* (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;
}
}