001: /*
002: * AbstractAdapter.java
003: *
004: * Version: $Revision: 1.11 $
005: *
006: * Date: $Date: 2006/06/07 22:13:39 $
007: *
008: * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040:
041: package org.dspace.app.xmlui.objectmanager;
042:
043: import java.io.IOException;
044: import java.io.UnsupportedEncodingException;
045: import java.sql.SQLException;
046: import java.util.ArrayList;
047: import java.util.List;
048:
049: import org.dspace.app.util.Util;
050: import org.dspace.app.xmlui.wing.AttributeMap;
051: import org.dspace.app.xmlui.wing.Namespace;
052: import org.dspace.app.xmlui.wing.WingException;
053: import org.dspace.content.Bitstream;
054: import org.dspace.content.BitstreamFormat;
055: import org.dspace.content.Item;
056: import org.dspace.content.crosswalk.CrosswalkException;
057: import org.dspace.content.crosswalk.DisseminationCrosswalk;
058: import org.dspace.core.PluginManager;
059: import org.xml.sax.ContentHandler;
060: import org.xml.sax.SAXException;
061: import org.xml.sax.ext.LexicalHandler;
062: import org.xml.sax.helpers.AttributesImpl;
063: import org.xml.sax.helpers.NamespaceSupport;
064:
065: /**
066: * This is the abstract adapter containing all the common elements between
067: * the three types of adapters: item, container, and repository. Each adapter
068: * translate a given type of DSpace object into a METS document for rendering
069: * into the DRI document.
070: *
071: * This class provides the chasses for those unique parts of the document to be
072: * build upon, there are seven rendering methods that may be overriden for each
073: * section of the METS document.
074: *
075: * Header
076: * Descriptive Section
077: * Administrative Section
078: * File Section
079: * Structure Map
080: * Structural Link
081: * Behavioral Section
082: *
083: * @author Scott Phillips
084: */
085:
086: public abstract class AbstractAdapter {
087: /** Namespace declaration for METS & XLINK */
088: public static final String METS_URI = "http://www.loc.gov/METS/";
089: public static final Namespace METS = new Namespace(METS_URI);
090: public static final String XLINK_URI = "http://www.w3.org/TR/xlink/";
091: public static final Namespace XLINK = new Namespace(XLINK_URI);
092: public static final String XSI_URI = "http://www.w3.org/2001/XMLSchema-instance";
093: public static final Namespace XSI = new Namespace(XSI_URI);
094: public static final String DIM_URI = "http://www.dspace.org/xmlns/dspace/dim";
095: public static final Namespace DIM = new Namespace(DIM_URI);
096:
097: /**
098: * A sequence used to generate unquie mets ids.
099: */
100: private int idSequence = 0;
101:
102: /**
103: * The contextPath of this webapplication, used for generateing urls.
104: */
105: protected String contextPath;
106:
107: /**
108: * The SAX handlers for content and lexical events. Also the support
109: * element for namespaces which knows the prefixes for each declared
110: * namespace.
111: */
112: protected ContentHandler contentHandler;
113: protected LexicalHandler lexicalHandler;
114: protected NamespaceSupport namespaces;
115:
116: /**
117: * Construct a new adapter, implementers must use call this method so
118: * the approprate internal values are insured to be set correctly.
119: *
120: * @param contextPath
121: * The contextPath of this web application.
122: */
123: public AbstractAdapter(String contextPath) {
124: this .contextPath = contextPath;
125: }
126:
127: /** The variables that dicatacte what part of the METS document to render */
128: List<String> sections = new ArrayList<String>();
129: List<String> dmdTypes = new ArrayList<String>();
130: List<String> amdTypes = new ArrayList<String>();
131: List<String> fileGrpTypes = new ArrayList<String>();
132: List<String> structTypes = new ArrayList<String>();
133:
134: /**
135: * A comma seperated list of METS sections to render. If no value
136: * is provided then all METS sections are rendered.
137: *
138: * @param sections Comma seperated list of METS sections.
139: */
140: public void setSections(String sections) {
141: if (sections == null)
142: return;
143:
144: for (String section : sections.split(",")) {
145: this .sections.add(section);
146: }
147: }
148:
149: /**
150: * A comma seperated list of METS descriptive metadata formats to
151: * render. If no value is provided then only the DIM format is used.
152: *
153: * @param sections Comma seperated list of METS metadata types.
154: */
155: public void setDmdTypes(String dmdTypes) {
156: if (dmdTypes == null)
157: return;
158:
159: for (String dmdType : dmdTypes.split(",")) {
160: this .dmdTypes.add(dmdType);
161: }
162: }
163:
164: /**
165: * A comma seperated list of METS administrative metadata formats to
166: * render.
167: *
168: * @param sections Comma seperated list of METS metadata types.
169: */
170: public void setAmdTypes(String amdTypes) {
171: if (amdTypes == null)
172: return;
173:
174: for (String amdType : amdTypes.split(",")) {
175: this .amdTypes.add(amdType);
176: }
177: }
178:
179: /**
180: * A comma seperated list of METS fileGrps to render. If no value
181: * is provided then all groups are rendered.
182: *
183: * @param sections Comma seperated list of METS file groups.
184: */
185: public void setFileGrpTypes(String fileGrpTypes) {
186: if (fileGrpTypes == null)
187: return;
188:
189: for (String fileGrpType : fileGrpTypes.split(",")) {
190: this .fileGrpTypes.add(fileGrpType);
191: }
192: }
193:
194: /**
195: * A comma seperated list of METS structural types to render. If no
196: * value is provided then only the DIM format is used.
197: *
198: * @param sections Comma seperated list of METS structure types.
199: */
200: public void setStructTypes(String structTypes) {
201: if (structTypes == null)
202: return;
203:
204: for (String structType : structTypes.split(",")) {
205: this .structTypes.add(structType);
206: }
207: }
208:
209: /**
210: *
211: *
212: *
213: *
214: *
215: * METS methods
216: *
217: *
218: *
219: *
220: *
221: *
222: */
223:
224: /**
225: * @return the URL for this item in the interface
226: */
227: protected abstract String getMETSOBJID() throws WingException;
228:
229: /**
230: * @return Return the URL for editing this item
231: */
232: protected abstract String getMETSOBJEDIT();
233:
234: /**
235: * @return the METS ID of the mets document.
236: */
237: protected abstract String getMETSID() throws WingException;
238:
239: /**
240: * @return The Profile this METS document conforms too.
241: */
242: protected abstract String getMETSProfile() throws WingException;
243:
244: /**
245: * @return The label of this METS document.
246: */
247: protected abstract String getMETSLabel() throws WingException;
248:
249: /**
250: * Render the complete METS document.
251: */
252: public void renderMETS(ContentHandler contentHandler,
253: LexicalHandler lexicalHandler) throws WingException,
254: SAXException, CrosswalkException, IOException, SQLException {
255: this .contentHandler = contentHandler;
256: this .lexicalHandler = lexicalHandler;
257: this .namespaces = new NamespaceSupport();
258:
259: // Declare our namespaces
260: namespaces.pushContext();
261: namespaces.declarePrefix("mets", METS.URI);
262: namespaces.declarePrefix("xlink", XLINK.URI);
263: namespaces.declarePrefix("xsi", XSI.URI);
264: namespaces.declarePrefix("dim", DIM.URI);
265: contentHandler.startPrefixMapping("mets", METS.URI);
266: contentHandler.startPrefixMapping("xlink", XLINK.URI);
267: contentHandler.startPrefixMapping("xsi", XSI.URI);
268: contentHandler.startPrefixMapping("dim", DIM.URI);
269:
270: // Send the METS element
271: AttributeMap attributes = new AttributeMap();
272: attributes.put("ID", getMETSID());
273: attributes.put("PROFILE", getMETSProfile());
274: attributes.put("LABEL", getMETSLabel());
275: String objid = getMETSOBJID();
276: if (objid != null)
277: attributes.put("OBJID", objid);
278:
279: // Include the link for editing the item
280: objid = getMETSOBJEDIT();
281: if (objid != null)
282: attributes.put("OBJEDIT", objid);
283:
284: startElement(METS, "METS", attributes);
285:
286: // If the user requested no specefic sections then render them all.
287: boolean all = (sections.size() == 0);
288:
289: if (all || sections.contains("metsHdr"))
290: renderHeader();
291: if (all || sections.contains("dmdSec"))
292: renderDescriptiveSection();
293: if (all || sections.contains("amdSec"))
294: renderAdministrativeSection();
295: if (all || sections.contains("fileSec"))
296: renderFileSection();
297: if (all || sections.contains("structMap"))
298: renderStructureMap();
299: if (all || sections.contains("structLink"))
300: renderStructuralLink();
301: if (all || sections.contains("behaviorSec"))
302: renderBehavioralSection();
303:
304: // FIXME: this is not a met's section, it should be removed
305: if (all || sections.contains("extraSec"))
306: renderExtraSections();
307:
308: endElement(METS, "METS");
309: contentHandler.endPrefixMapping("mets");
310: contentHandler.endPrefixMapping("xlink");
311: contentHandler.endPrefixMapping("dim");
312: namespaces.popContext();
313:
314: }
315:
316: /**
317: * Each of the METS sections
318: */
319: protected void renderHeader() throws WingException, SAXException,
320: CrosswalkException, IOException, SQLException {
321: }
322:
323: protected void renderDescriptiveSection() throws WingException,
324: SAXException, CrosswalkException, IOException, SQLException {
325: }
326:
327: protected void renderAdministrativeSection() throws WingException,
328: SAXException, CrosswalkException, IOException, SQLException {
329: }
330:
331: protected void renderFileSection() throws WingException,
332: SAXException, CrosswalkException, IOException, SQLException {
333: }
334:
335: protected void renderStructureMap() throws WingException,
336: SAXException, CrosswalkException, IOException, SQLException {
337: }
338:
339: protected void renderStructuralLink() throws WingException,
340: SAXException, CrosswalkException, IOException, SQLException {
341: }
342:
343: protected void renderBehavioralSection() throws WingException,
344: SAXException, CrosswalkException, IOException, SQLException {
345: }
346:
347: protected void renderExtraSections() throws WingException,
348: SAXException, CrosswalkException, SQLException, IOException {
349: }
350:
351: /**
352: * Generate a METS file element for a given bitstream.
353: *
354: * @param item
355: * If the bitstream is associated with an item provid the item
356: * otherwise leave null.
357: * @param bitstream
358: * The bitstream to build a file element for.
359: * @param fileID
360: * The unique file id for this file.
361: * @param groupID
362: * The group id for this file, if it is derived from another file
363: * then they should share the same groupID.
364: * @return The METS file element.
365: */
366: protected void renderFile(Item item, Bitstream bitstream,
367: String fileID, String groupID) throws SAXException {
368: AttributeMap attributes;
369:
370: // //////////////////////////////
371: // Determine the file attributes
372: BitstreamFormat format = bitstream.getFormat();
373: String mimeType = null;
374: if (format != null)
375: mimeType = format.getMIMEType();
376: String checksumType = bitstream.getChecksumAlgorithm();
377: String checksum = bitstream.getChecksum();
378: long size = bitstream.getSize();
379:
380: // ////////////////////////////////
381: // Start the actual file
382: attributes = new AttributeMap();
383: attributes.put("ID", fileID);
384: attributes.put("GROUP_ID", groupID);
385: if (mimeType != null)
386: attributes.put("MIMETYPE", mimeType);
387: if (checksumType != null && checksumType != null) {
388: attributes.put("CHECKSUM", checksum);
389: attributes.put("CHECKSUMTYPE", checksumType);
390: }
391: attributes.put("SIZE", String.valueOf(size));
392: startElement(METS, "file", attributes);
393:
394: // ////////////////////////////////////
395: // Determine the file location attributes
396: String name = bitstream.getName();
397: String description = bitstream.getDescription();
398:
399: // If possible refrence this bitstream via a handle, however this may
400: // be null if a handle has not yet been assigned. In this case refrence the
401: // item its internal id. In the last case where the bitstream is not associated
402: // with an item (such as a community logo) then refrence the bitstreamID directly.
403: String identifier = null;
404: if (item != null && item.getHandle() != null)
405: identifier = "handle/" + item.getHandle();
406: else if (item != null)
407: identifier = "item/" + item.getID();
408: else
409: identifier = "id/" + bitstream.getID();
410:
411: String url = contextPath + "/bitstream/" + identifier + "/";
412:
413: // If we can put the pretty name of the bitstream on the end of the URL
414: try {
415: if (bitstream.getName() != null)
416: url += Util.encodeBitstreamName(bitstream.getName(),
417: "UTF-8");
418: } catch (UnsupportedEncodingException uee) {
419: // just ignore it, we don't have to have a pretty
420: // name on the end of the url because the sequence id will
421: // locate it. However it means that links in this file might
422: // not work....
423: }
424:
425: url += "?sequence=" + bitstream.getSequenceID();
426:
427: // //////////////////////
428: // Start the file location
429: attributes = new AttributeMap();
430: AttributeMap attributesXLINK = new AttributeMap();
431: attributesXLINK.setNamespace(XLINK);
432: attributes.put("LOCTYPE", "URL");
433: attributesXLINK.put("type", "locator");
434: attributesXLINK.put("title", name);
435: if (description != null)
436: attributesXLINK.put("label", description);
437: attributesXLINK.put("href", url);
438: startElement(METS, "FLocat", attributes, attributesXLINK);
439:
440: // ///////////////////////
441: // End file location
442: endElement(METS, "FLocate");
443:
444: // ////////////////////////////////
445: // End the file
446: endElement(METS, "file");
447: }
448:
449: /**
450: *
451: * Generate a unique METS id. For consistancy, all prefixs should probably
452: * end in an underscore, "_".
453: *
454: * @param prefix
455: * Prefix to prepend to the id for readability.
456: *
457: * @return A unique METS id.
458: */
459: protected String getGenericID(String prefix) {
460: return prefix + (idSequence++);
461: }
462:
463: /**
464: * Return a dissemination crosswalk for the given name.
465: *
466: * @param crosswalkName
467: * @return The crosswalk or throw an exception if not found.
468: */
469: public DisseminationCrosswalk getDisseminationCrosswalk(
470: String crosswalkName) throws WingException {
471: // Fixme add some caching here
472: DisseminationCrosswalk crosswalk = (DisseminationCrosswalk) PluginManager
473: .getNamedPlugin(DisseminationCrosswalk.class,
474: crosswalkName);
475:
476: if (crosswalk == null)
477: throw new WingException(
478: "Unable to find named DisseminationCrosswalk: "
479: + crosswalkName);
480:
481: return crosswalk;
482: }
483:
484: /**
485: * The METS defined types of Metadata, if a format is not listed here
486: * then it should use the string "OTHER" and provide additional
487: * attributes describing the metadata type
488: */
489: public static String[] METS_DEFINED_TYPES = { "MARC", "MODS",
490: "EAD", "DC", "NISOIMG", "LC-AV", "VRA", "TEIHDR", "DDI",
491: "FGDC"/*,"OTHER"*/};
492:
493: /**
494: * Determine if the provided metadata type is a stardard METS
495: * defined type. If it is not, use the other string.
496: *
497: * @param metadataType type name
498: * @return True if METS defined
499: */
500: public boolean isDefinedMETStype(String metadataType) {
501: for (String definedType : METS_DEFINED_TYPES) {
502: if (definedType.equals(metadataType))
503: return true;
504: }
505: return false;
506: }
507:
508: /**
509: *
510: *
511: * SAX Helper methods
512: *
513: *
514: *
515: */
516:
517: /**
518: * Send the SAX events to start this element.
519: *
520: * @param contentHandler
521: * (Required) The registered contentHandler where SAX events
522: * should be routed too.
523: * @param namespaces
524: * (Required) SAX Helper class to keep track of namespaces able
525: * to determine the correct prefix for a given namespace URI.
526: * @param namespace
527: * (Required) The namespace of this element.
528: * @param name
529: * (Required) The local name of this element.
530: * @param attributes
531: * (May be null) Attributes for this element
532: */
533: protected void startElement(Namespace namespace, String name,
534: AttributeMap... attributes) throws SAXException {
535: contentHandler.startElement(namespace.URI, name, qName(
536: namespace, name), map2sax(namespace, attributes));
537: }
538:
539: /**
540: * Send the SAX event for these plain characters, not wrapped in any
541: * elements.
542: *
543: * @param contentHandler
544: * (Required) The registered contentHandler where SAX events
545: * should be routed too.
546: * @param characters
547: * (May be null) Characters to send.
548: */
549: protected void sendCharacters(String characters)
550: throws SAXException {
551: if (characters != null) {
552: char[] contentArray = characters.toCharArray();
553: contentHandler.characters(contentArray, 0,
554: contentArray.length);
555: }
556: }
557:
558: /**
559: * Send the SAX events to end this element.
560: *
561: * @param contentHandler
562: * (Required) The registered contentHandler where SAX events
563: * should be routed too.
564: * @param namespaces
565: * (Required) SAX Helper class to keep track of namespaces able
566: * to determine the correct prefix for a given namespace URI.
567: * @param namespace
568: * (Required) The namespace of this element.
569: * @param name
570: * (Required) The local name of this element.
571: */
572: protected void endElement(Namespace namespace, String name)
573: throws SAXException {
574: contentHandler.endElement(namespace.URI, name, qName(namespace,
575: name));
576: }
577:
578: /**
579: * Build the SAX attributes object based upon Java's String map. This
580: * convenience method will build, or add to an existing attributes object,
581: * the attributes detailed in the AttributeMap.
582: *
583: * @param namespaces
584: * SAX Helper class to keep track of namespaces able to determine
585: * the correct prefix for a given namespace URI.
586: * @param attributes
587: * An existing SAX AttributesImpl object to add attributes too.
588: * If the value is null then a new attributes object will be
589: * created to house the attributes.
590: * @param attributeMap
591: * A map of attributes and values.
592: * @return
593: */
594: private AttributesImpl map2sax(Namespace elementNamespace,
595: AttributeMap... attributeMaps) {
596:
597: AttributesImpl attributes = new AttributesImpl();
598: for (AttributeMap attributeMap : attributeMaps) {
599: boolean diffrentNamespaces = false;
600: Namespace attributeNamespace = attributeMap.getNamespace();
601: if (attributeNamespace != null) {
602: if (!(attributeNamespace.URI
603: .equals(elementNamespace.URI))) {
604: diffrentNamespaces = true;
605: }
606: }
607:
608: // copy each one over.
609: for (String name : attributeMap.keySet()) {
610: String value = attributeMap.get(name);
611: if (value == null)
612: continue;
613:
614: if (diffrentNamespaces)
615: attributes.addAttribute(attributeNamespace.URI,
616: name, qName(attributeNamespace, name),
617: "CDATA", value);
618: else
619: attributes.addAttribute("", name, name, "CDATA",
620: value);
621:
622: }
623: }
624: return attributes;
625: }
626:
627: /**
628: * Create the qName for the element with the given localName and namespace
629: * prefix.
630: *
631: * @param prefix
632: * (May be null) The namespace prefix.
633: * @param localName
634: * (Required) The element's local name.
635: * @return
636: */
637: private String qName(Namespace namespace, String localName) {
638: String prefix = namespaces.getPrefix(namespace.URI);
639: if (prefix == null || prefix.equals(""))
640: return localName;
641: else
642: return prefix + ":" + localName;
643: }
644:
645: }
|