/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.storage.translation;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Iterator;
import org.fcrepo.common.Constants;
import org.fcrepo.common.xml.format.XMLFormat;
import org.fcrepo.server.errors.ObjectIntegrityException;
import org.fcrepo.server.errors.StreamIOException;
import org.fcrepo.server.storage.types.DSBinding;
import org.fcrepo.server.storage.types.Datastream;
import org.fcrepo.server.storage.types.DatastreamXMLMetadata;
import org.fcrepo.server.storage.types.DigitalObject;
import org.fcrepo.server.storage.types.Disseminator;
import org.fcrepo.server.utilities.StreamUtility;
import org.fcrepo.utilities.Base64;
import org.fcrepo.utilities.DateUtility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.fcrepo.common.Models.SERVICE_DEPLOYMENT_3_0;
/**
* Serializes objects in the constructor-provider version of the METS Fedora
* Extension format.
*
* @author Sandy Payette
* @author Chris Wilper
*/
@SuppressWarnings("deprecation")
public class METSFedoraExtDOSerializer
implements Constants, DOSerializer {
/**
* The format this serializer will write if unspecified at construction.
* This defaults to the latest METS Fedora Extension format.
*/
public static final XMLFormat DEFAULT_FORMAT = METS_EXT1_1;
private static final Logger logger =
LoggerFactory.getLogger(METSFedoraExtDOSerializer.class);
/** The format this serializer writes. */
private final XMLFormat m_format;
/** The translation utility is use */
private DOTranslationUtility m_translator;
/**
* Creates a serializer that writes the default METS Fedora Extension
* format.
*/
public METSFedoraExtDOSerializer() {
m_format = DEFAULT_FORMAT;
m_translator = DOTranslationUtility.defaultInstance();
}
/**
* Creates a serializer that writes the given METS Fedora Extension format.
*
* @param format
* the version-specific METS Fedora Extension format.
* @throws IllegalArgumentException
* if format is not a known METS Fedora extension format.
*/
public METSFedoraExtDOSerializer(XMLFormat format) {
this(format, null);
}
/**
* Creates a serializer that writes the given METS Fedora Extension format.
*
* @param format
* the version-specific METS Fedora Extension format.
* @throws IllegalArgumentException
* if format is not a known METS Fedora extension format.
*/
public METSFedoraExtDOSerializer(XMLFormat format, DOTranslationUtility translator) {
if (format.equals(METS_EXT1_0) || format.equals(METS_EXT1_1)) {
m_format = format;
} else {
throw new IllegalArgumentException("Not a METS Fedora Extension "
+ "format: " + format.uri);
}
m_translator = (translator == null) ? DOTranslationUtility.defaultInstance() : translator;
}
//---
// DOSerializer implementation
//---
/**
* {@inheritDoc}
*/
public DOSerializer getInstance() {
return new METSFedoraExtDOSerializer(m_format, m_translator);
}
/**
* {@inheritDoc}
*/
public void serialize(DigitalObject obj,
OutputStream out,
String encoding,
int transContext) throws ObjectIntegrityException,
StreamIOException, UnsupportedEncodingException {
if (logger.isDebugEnabled()) {
logger.debug("Serializing " + m_format.uri + " for transContext: "
+ transContext);
}
OutputStreamWriter osWriter = new OutputStreamWriter(out, encoding);
PrintWriter writer = new PrintWriter(osWriter);
try {
appendXMLDeclaration(obj, encoding, writer);
appendRootElementStart(obj, writer);
appendHdr(obj, writer);
appendDescriptiveMD(obj, writer, transContext, encoding);
appendAuditRecordAdminMD(obj, writer);
appendOtherAdminMD(obj, writer, transContext, encoding);
appendFileSecs(obj, writer, transContext);
if (m_format.equals(METS_EXT1_0)) {
appendStructMaps(obj, writer);
appendDisseminators(obj, writer);
}
appendRootElementEnd(writer);
} finally {
writer.close();
}
}
//---
// Instance helpers
//---
private void appendXMLDeclaration(DigitalObject obj,
String encoding,
PrintWriter writer) {
writer.print("<?xml version=\"1.0\" encoding=\"");
writer.print(encoding);
writer.print("\" ?>\n");
}
private void appendRootElementStart(DigitalObject obj, PrintWriter writer)
throws ObjectIntegrityException {
writer.print("<");
writer.print(METS.METS.qName);
if (m_format.equals(METS_EXT1_1)) {
writer.print(" ");
writer.print(METS_EXT.EXT_VERSION.localName);
writer.print("=\"1.1\"");
}
writer.print(" ");
writer.print(METS.OBJID.localName);
writer.print("=\"");
writer.print(obj.getPid());
writer.print("\"");
if (m_format.equals(METS_EXT1_0)
&& DOTranslationUtility.getTypeAttribute(obj) != null) {
writer.print(" ");
writer.print(METS.TYPE.localName);
writer.print("=\"");
writer.print(DOTranslationUtility.getTypeAttribute(obj).localName);
writer.print("\"");
}
writer.print("\n");
String label = obj.getLabel();
if (label != null && label.length() > 0) {
writer.print(METS.LABEL.localName);
writer.print("=\"");
StreamUtility.enc(label, writer);
writer.print("\"\n");
}
writer.print("xmlns:");
writer.print(METS.prefix);
writer.print("=\"");
writer.print(METS.uri);
writer.print("\"\n");
if (m_format.equals(METS_EXT1_0)) {
writer.print("xmlns:");
writer.print(XLINK.prefix);
writer.print("=\"");
writer.print(OLD_XLINK.uri);
writer.print("\"\n");
} else {
writer.print("xmlns:");
writer.print(XLINK.prefix);
writer.print("=\"");
writer.print(XLINK.uri);
writer.print("\"\n");
}
writer.print("xmlns:");
writer.print(XSI.prefix);
writer.print("=\"");
writer.print(XSI.uri);
writer.print("\"\n");
writer.print(XSI.SCHEMA_LOCATION.qName);
writer.print("=\"");
writer.print(METS.uri);
writer.print(" ");
writer.print(m_format.xsdLocation);
writer.print("\">\n");
}
private void appendHdr(DigitalObject obj, PrintWriter writer) throws ObjectIntegrityException {
writer.print("<");
writer.print(METS.prefix);
writer.print(":metsHdr");
Date cDate = obj.getCreateDate();
if (cDate != null) {
writer.print(" CREATEDATE=\"");
writer.print(DateUtility.convertDateToString(cDate));
writer.print("\"");
}
Date mDate = obj.getLastModDate();
if (mDate != null) {
writer.print(" LASTMODDATE=\"");
writer.print(DateUtility.convertDateToString(mDate));
writer.print("\"");
}
writer.print(" RECORDSTATUS=\"");
writer.print(DOTranslationUtility.getStateAttribute(obj));
writer.print("\"");
writer.print(">\n");
// use agent to identify the owner of the digital object
String ownerId = obj.getOwnerId();
if (ownerId != null && !ownerId.isEmpty()) {
writer.print("<");
writer.print(METS.prefix);
writer.print(":agent");
writer.print(" ROLE=\"IPOWNER\">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":name>");
writer.print(ownerId);
writer.print("</");
writer.print(METS.prefix);
writer.print(":name>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":agent>\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":metsHdr>\n");
}
private void appendDescriptiveMD(DigitalObject obj,
PrintWriter writer,
int transContext,
String encoding)
throws ObjectIntegrityException, UnsupportedEncodingException,
StreamIOException {
Iterator<String> iter = obj.datastreamIdIterator();
while (iter.hasNext()) {
String id = iter.next();
Datastream firstDS = obj.datastreams(id).iterator().next();
if (firstDS.DSControlGrp.equals("X")
&& ((DatastreamXMLMetadata) firstDS).DSMDClass == DatastreamXMLMetadata.DESCRIPTIVE) {
appendMDSec(obj,
"dmdSecFedora",
"descMD",
obj.datastreams(id),
writer,
transContext,
encoding);
}
}
}
private void appendMDSec(DigitalObject obj,
String outerName,
String innerName,
Iterable<Datastream> XMLMetadata,
PrintWriter writer,
int transContext,
String encoding) throws ObjectIntegrityException,
UnsupportedEncodingException, StreamIOException {
DatastreamXMLMetadata first =
(DatastreamXMLMetadata) DOTranslationUtility
.setDatastreamDefaults(XMLMetadata
.iterator().next());
writer.print("<");
writer.print(METS.prefix);
writer.print(":");
writer.print(outerName);
writer.print(" ID=\"");
writer.print(first.DatastreamID);
writer.print("\" STATUS=\"");
writer.print(first.DSState);
writer.print("\" VERSIONABLE=\"");
writer.print(first.DSVersionable);
writer.print("\">\n");
for (Datastream d : XMLMetadata) {
DatastreamXMLMetadata ds =
(DatastreamXMLMetadata) DOTranslationUtility
.setDatastreamDefaults(d);
writer.print("<");
writer.print(METS.prefix);
writer.print(":");
writer.print(innerName);
writer.print(" ID=\"");
writer.print(ds.DSVersionID);
writer.print("\"");
if (ds.DSCreateDT != null) {
writer.print(" CREATED=\"");
writer.print(DateUtility.convertDateToString(ds.DSCreateDT));
writer.print("\"");
}
writer.print(">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":mdWrap MIMETYPE=\"");
StreamUtility.enc(ds.DSMIME, writer);
writer.print("\" MDTYPE=\"");
String mdType = ds.DSInfoType;
if (!mdType.equals("MARC") && !mdType.equals("EAD")
&& !mdType.equals("DC") && !mdType.equals("NISOIMG")
&& !mdType.equals("LC-AV") && !mdType.equals("VRA")
&& !mdType.equals("TEIHDR") && !mdType.equals("DDI")
&& !mdType.equals("FGDC")) {
writer.print("OTHER\" OTHERMDTYPE=\"");
StreamUtility.enc(mdType, writer);
} else {
writer.print(mdType);
}
writer.print("\" ");
if (ds.DSLabel != null && !ds.DSLabel.isEmpty()) {
writer.print(" LABEL=\"");
StreamUtility.enc(ds.DSLabel, writer);
writer.print("\"");
}
if (ds.DSFormatURI != null && !ds.DSFormatURI.isEmpty()) {
writer.print(" FORMAT_URI=\"");
StreamUtility.enc(ds.DSFormatURI, writer);
writer.print("\"");
}
String altIds = DOTranslationUtility.oneString(ds.DatastreamAltIDs);
if (altIds != null && !altIds.isEmpty()) {
writer.print(" ALT_IDS=\"");
StreamUtility.enc(altIds, writer);
writer.print("\"");
}
// CHECKSUM and CHECKSUMTYPE are also optional
String csType = ds.DSChecksumType;
if (csType != null
&& csType.length() > 0
&& !csType.equals(Datastream.CHECKSUMTYPE_DISABLED)) {
writer.print(" CHECKSUM=\"");
StreamUtility.enc(ds.DSChecksum, writer);
writer.print("\" CHECKSUMTYPE=\"");
StreamUtility.enc(csType, writer);
writer.print("\"");
}
writer.print(">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":xmlData>\n");
// If WSDL or SERVICE-PROFILE datastream (in SDep)
// make sure that any embedded URLs are encoded
// appropriately for either EXPORT or STORE.
if (obj.hasContentModel(SERVICE_DEPLOYMENT_3_0)
&& ds.DatastreamID.equals("SERVICE-PROFILE")
|| ds.DatastreamID.equals("WSDL")) {
writer.print(m_translator
.normalizeInlineXML(new String(ds.xmlContent, "UTF-8")
.trim(), transContext));
} else {
DOTranslationUtility.appendXMLStream(ds.getContentStream(),
writer,
encoding);
}
writer.print("\n</");
writer.print(METS.prefix);
writer.print(":xmlData>");
writer.print("</");
writer.print(METS.prefix);
writer.print(":mdWrap>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":");
writer.print(innerName);
writer.print(">\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":");
writer.print(outerName);
writer.print(">\n");
}
private void appendAuditRecordAdminMD(DigitalObject obj, PrintWriter writer)
throws ObjectIntegrityException {
if (obj.getAuditRecords().size() > 0) {
writer.print("<");
writer.print(METS.prefix);
writer.print(":amdSec ID=\"AUDIT\" STATUS=\"A\" VERSIONABLE=\"false\">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":digiprovMD ID=\"AUDIT.0\" CREATED=\"");
writer.print(DateUtility.convertDateToString(obj.getCreateDate()));
writer.print("\">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":mdWrap MIMETYPE=\"text/xml\" MDTYPE=\"OTHER\"");
writer.print(" OTHERMDTYPE=\"FEDORA-AUDIT\"");
writer.print(" LABEL=\"Audit Trail for this object\"");
writer.print(" FORMAT_URI=\"");
writer.print(AUDIT1_0.uri);
writer.print("\">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":xmlData>\n");
DOTranslationUtility.appendAuditTrail(obj, writer);
writer.print("</");
writer.print(METS.prefix);
writer.print(":xmlData>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":mdWrap>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":digiprovMD>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":amdSec>\n");
}
}
private void appendOtherAdminMD(DigitalObject obj,
PrintWriter writer,
int transContext,
String encoding)
throws ObjectIntegrityException, UnsupportedEncodingException,
StreamIOException {
Iterator<String> iter = obj.datastreamIdIterator();
while (iter.hasNext()) {
String id = iter.next();
Datastream firstDS =
obj.datastreams(id).iterator().next();
// First, work with the first version to get the mdClass set to
// a proper value required in the METS XML Schema.
if (firstDS.DSControlGrp.equals("X")
&& ((DatastreamXMLMetadata) firstDS).DSMDClass != DatastreamXMLMetadata.DESCRIPTIVE) {
DatastreamXMLMetadata md = (DatastreamXMLMetadata) firstDS;
// Default mdClass to techMD when a valid one does not appear
// (say because the object was born as FOXML)
String mdClass = "techMD";
if (md.DSMDClass == DatastreamXMLMetadata.TECHNICAL) {
mdClass = "techMD";
} else if (md.DSMDClass == DatastreamXMLMetadata.SOURCE) {
mdClass = "sourceMD";
} else if (md.DSMDClass == DatastreamXMLMetadata.RIGHTS) {
mdClass = "rightsMD";
} else if (md.DSMDClass == DatastreamXMLMetadata.DIGIPROV) {
mdClass = "digiprovMD";
}
// Then, pass everything along to do the actual serialization
appendMDSec(obj,
"amdSec",
mdClass,
obj.datastreams(id),
writer,
transContext,
encoding);
}
}
}
private void appendFileSecs(DigitalObject obj, PrintWriter writer, int transContext)
throws ObjectIntegrityException, StreamIOException {
Iterator<String> iter = obj.datastreamIdIterator();
boolean didFileSec = false;
while (iter.hasNext()) {
Datastream ds =
DOTranslationUtility.setDatastreamDefaults(obj
.datastreams(iter.next()).iterator().next());
if (!ds.DSControlGrp.equals("X")) {
if (!didFileSec) {
didFileSec = true;
writer.print("<");
writer.print(METS.prefix);
writer.print(":fileSec>\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":fileGrp ID=\"DATASTREAMS\">\n");
}
writer.print("<");
writer.print(METS.prefix);
writer.print(":fileGrp ID=\"");
writer.print(ds.DatastreamID);
writer.print("\" STATUS=\"");
writer.print(ds.DSState);
writer.print("\" VERSIONABLE=\"");
writer.print(ds.DSVersionable);
writer.print("\">\n");
Iterator<Datastream> contentIter =
obj.datastreams(ds.DatastreamID).iterator();
while (contentIter.hasNext()) {
Datastream dsc =
DOTranslationUtility
.setDatastreamDefaults(contentIter.next());
writer.print("<");
writer.print(METS.prefix);
writer.print(":file ID=\"");
writer.print(dsc.DSVersionID);
writer.print("\"");
if (dsc.DSCreateDT != null) {
writer.print(" CREATED=\"");
writer.print(DateUtility.convertDateToString(dsc.DSCreateDT));
writer.print("\"");
}
writer.print(" MIMETYPE=\"");
StreamUtility.enc(dsc.DSMIME, writer);
writer.print("\"");
if (dsc.DSSize != 0) {
writer.print(" SIZE=\"" + dsc.DSSize + "\"");
}
if (dsc.DSFormatURI != null && !dsc.DSFormatURI.isEmpty()) {
writer.print(" FORMAT_URI=\"");
StreamUtility.enc(dsc.DSFormatURI, writer);
writer.print("\"");
}
String altIds =
DOTranslationUtility
.oneString(dsc.DatastreamAltIDs);
if (altIds != null && !altIds.isEmpty()) {
writer.print(" ALT_IDS=\"");
StreamUtility.enc(altIds, writer);
writer.print("\"");
}
String csType = dsc.DSChecksumType;
if (csType != null
&& csType.length() > 0
&& !csType.equals(Datastream.CHECKSUMTYPE_DISABLED)) {
writer.print(" CHECKSUM=\"");
StreamUtility.enc(dsc.DSChecksum, writer);
writer.print("\"");
writer.print(" CHECKSUMTYPE=\"");
StreamUtility.enc(csType, writer);
writer.print("\"");
}
writer.print(" OWNERID=\"");
writer.print(dsc.DSControlGrp);
writer.print("\">\n");
if (transContext == DOTranslationUtility.SERIALIZE_EXPORT_ARCHIVE
&& dsc.DSControlGrp.equalsIgnoreCase("M")) {
serializeDatastreamContent(dsc, writer);
} else {
writer.print("<");
writer.print(METS.prefix);
writer.print(":FLocat");
if (dsc.DSLabel != null && !dsc.DSLabel.isEmpty()) {
writer.print(" ");
writer.print(XLINK.prefix);
writer.print(":title=\"");
StreamUtility.enc(dsc.DSLabel, writer);
writer.print("\"");
}
writer.print(" LOCTYPE=\"URL\" ");
writer.print(XLINK.prefix);
writer.print(":href=\"");
StreamUtility.enc(
m_translator.normalizeDSLocationURLs(
obj.getPid(),
dsc,
transContext).DSLocation, writer);
writer.print("\"/>\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":file>\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":fileGrp>\n");
}
}
if (didFileSec) {
writer.print("</");
writer.print(METS.prefix);
writer.print(":fileGrp>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":fileSec>\n");
}
}
protected void serializeDatastreamContent(Datastream dsc, PrintWriter writer)
throws StreamIOException {
writer.print("<");
writer.print(METS.prefix);
writer.print(":FContent> \n");
Reader encoded = new InputStreamReader(Base64.encodeToStream(dsc.getContentStream()));
char [] buffer = new char[80];
int len = 0;
try{
while ((len = encoded.read(buffer)) > -1){
writer.write(DOSerializer.DS_INDENT);
writer.write(buffer,0,len);
writer.write('\n');
}
} catch (IOException ioe) {
throw new StreamIOException(ioe.getMessage());
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":FContent>\n");
}
private void appendStructMaps(DigitalObject obj, PrintWriter writer)
throws ObjectIntegrityException {
Iterator<String> dissIdIter = obj.disseminatorIdIterator();
while (dissIdIter.hasNext()) {
String did = dissIdIter.next();
Iterator<Disseminator> dissIter = obj.disseminators(did).iterator();
while (dissIter.hasNext()) {
Disseminator diss =
DOTranslationUtility.setDisseminatorDefaults(dissIter
.next());
writer.print("<");
writer.print(METS.prefix);
writer.print(":structMap ID=\"");
writer.print(diss.dsBindMapID);
writer.print("\" TYPE=\"fedora:dsBindingMap\">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":div TYPE=\"");
writer.print(diss.sDepID);
writer.print("\"");
if (diss.dsBindMap.dsBindMapLabel != null
&& !diss.dsBindMap.dsBindMapLabel.isEmpty()) {
writer.print(" LABEL=\"");
StreamUtility.enc(diss.dsBindMap.dsBindMapLabel, writer);
writer.print("\"");
}
writer.print(">\n");
DSBinding[] bindings = diss.dsBindMap.dsBindings;
for (int i = 0; i < bindings.length; i++) {
if (bindings[i].bindKeyName == null
|| bindings[i].bindKeyName.isEmpty()) {
throw new ObjectIntegrityException("Object's disseminator"
+ " binding map binding must have a binding key name.");
}
writer.print("<");
writer.print(METS.prefix);
writer.print(":div TYPE=\"");
writer.print(bindings[i].bindKeyName);
if (bindings[i].bindLabel != null
&& !bindings[i].bindLabel.isEmpty()) {
writer.print("\" LABEL=\"");
StreamUtility.enc(bindings[i].bindLabel, writer);
}
if (bindings[i].seqNo != null
&& !bindings[i].seqNo.isEmpty()) {
writer.print("\" ORDER=\"");
writer.print(bindings[i].seqNo);
}
if (bindings[i].datastreamID == null
|| bindings[i].datastreamID.isEmpty()) {
throw new ObjectIntegrityException("Object's disseminator"
+ " binding map binding must point to a datastream.");
}
writer.print("\">\n<");
writer.print(METS.prefix);
writer.print(":fptr FILEID=\"");
writer.print(bindings[i].datastreamID);
writer.print("\"/>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":div>\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":div>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":structMap>\n");
}
}
}
private void appendDisseminators(DigitalObject obj, PrintWriter writer)
throws ObjectIntegrityException {
Iterator<String> dissIdIter = obj.disseminatorIdIterator();
while (dissIdIter.hasNext()) {
String did = dissIdIter.next();
Disseminator diss =
DOTranslationUtility.setDisseminatorDefaults(obj
.disseminators(did).get(0));
writer.print("<");
writer.print(METS.prefix);
writer.print(":behaviorSec ID=\"");
writer.print(did);
writer.print("\" STATUS=\"");
writer.print(diss.dissState);
writer.print("\">\n");
for (int i = 0; i < obj.disseminators(did).size(); i++) {
diss =
DOTranslationUtility
.setDisseminatorDefaults(obj
.disseminators(did).get(i));
writer.print("<");
writer.print(METS.prefix);
writer.print(":serviceBinding ID=\"");
writer.print(diss.dissVersionID);
writer.print("\" STRUCTID=\"");
writer.print(diss.dsBindMapID);
writer.print("\" BTYPE=\"");
writer.print(diss.bDefID);
writer.print("\" CREATED=\"");
writer.print(DateUtility.convertDateToString(diss.dissCreateDT));
writer.print("\"");
if (diss.dissLabel != null && !diss.dissLabel.isEmpty()) {
writer.print(" LABEL=\"");
StreamUtility.enc(diss.dissLabel, writer);
writer.print("\"");
}
writer.print(">\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":interfaceMD LOCTYPE=\"URN\" ");
writer.print(XLINK.prefix);
writer.print(":href=\"");
writer.print(diss.bDefID);
writer.print("\"/>\n");
writer.print("<");
writer.print(METS.prefix);
writer.print(":serviceBindMD LOCTYPE=\"URN\" ");
writer.print(XLINK.prefix);
writer.print(":href=\"");
writer.print(diss.sDepID);
writer.print("\"/>\n");
writer.print("</");
writer.print(METS.prefix);
writer.print(":serviceBinding>\n");
}
writer.print("</");
writer.print(METS.prefix);
writer.print(":behaviorSec>\n");
}
}
private void appendRootElementEnd(PrintWriter writer) {
writer.print("</");
writer.print(METS.prefix);
writer.print(":mets>");
}
}