/* 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 fedora.server.storage.translation;
import java.io.BufferedWriter;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import fedora.common.Constants;
import fedora.common.rdf.RDFName;
import fedora.common.xml.format.XMLFormat;
import fedora.server.errors.ObjectIntegrityException;
import fedora.server.errors.StreamIOException;
import fedora.server.storage.types.DSBinding;
import fedora.server.storage.types.Datastream;
import fedora.server.storage.types.DatastreamXMLMetadata;
import fedora.server.storage.types.DigitalObject;
import fedora.server.storage.types.Disseminator;
import fedora.server.utilities.DateUtility;
import fedora.server.utilities.StreamUtility;
import fedora.server.utilities.StringUtility;
import fedora.utilities.Base64;
import static fedora.common.Models.SERVICE_DEPLOYMENT_3_0;
/**
* Serializes objects in the constructor-provided version of FOXML.
*
* @author Sandy Payette
* @author Chris Wilper
*/
@SuppressWarnings("deprecation")
public class FOXMLDOSerializer
implements DOSerializer, Constants {
/**
* The format this serializer will write if unspecified at construction.
* This defaults to the latest FOXML format.
*/
public static final XMLFormat DEFAULT_FORMAT = FOXML1_1;
/** Logger for this class. */
private static final Logger LOG = Logger.getLogger(FOXMLDOSerializer.class);
/** The format this serializer writes. */
private final XMLFormat m_format;
/** The current translation context. */
private int m_transContext;
/**
* Creates a serializer that writes the default FOXML format.
*/
public FOXMLDOSerializer() {
m_format = DEFAULT_FORMAT;
}
/**
* Creates a serializer that writes the given FOXML format.
*
* @param format
* the version-specific FOXML format.
* @throws IllegalArgumentException
* if format is not a known FOXML format.
*/
public FOXMLDOSerializer(XMLFormat format) {
if (format.equals(FOXML1_0) || format.equals(FOXML1_1)) {
m_format = format;
} else {
throw new IllegalArgumentException("Not a FOXML format: "
+ format.uri);
}
}
//---
// DOSerializer implementation
//---
/**
* {@inheritDoc}
*/
public DOSerializer getInstance() {
return new FOXMLDOSerializer(m_format);
}
/**
* {@inheritDoc}
*/
public void serialize(DigitalObject obj,
OutputStream out,
String encoding,
int transContext) throws ObjectIntegrityException,
StreamIOException, UnsupportedEncodingException {
LOG.debug("Serializing " + m_format.uri + " for transContext: "
+ transContext);
m_transContext = transContext;
OutputStreamWriter osWriter = new OutputStreamWriter(out, encoding);
PrintWriter writer = new PrintWriter(new BufferedWriter(osWriter));
try {
appendXMLDeclaration(obj, encoding, writer);
appendRootElementStart(obj, writer);
appendProperties(obj, writer, encoding);
appendAudit(obj, writer, encoding);
appendDatastreams(obj, writer, encoding);
if (m_format.equals(FOXML1_0)) {
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(FOXML.DIGITAL_OBJECT.qName);
if (m_format.equals(FOXML1_1)) {
writer.print(" ");
writer.print(FOXML.VERSION.localName);
writer.print("=\"1.1\"");
}
writer.print(" ");
writer.print(FOXML.PID.localName);
writer.print("=\"");
writer.print(obj.getPid());
writer.print("\"");
if (m_transContext == DOTranslationUtility.SERIALIZE_EXPORT_PUBLIC) {
writer.print(" ");
writer.print(FOXML.FEDORA_URI.localName);
writer.print("=\"info:fedora/");
writer.print(obj.getPid());
writer.print("\"");
}
writer.print("\nxmlns:");
writer.print(FOXML.prefix);
writer.print("=\"");
writer.print(FOXML.uri);
writer.print("\"\nxmlns:");
writer.print(XSI.prefix);
writer.print("=\"");
writer.print(XSI.uri);
writer.print("\"\n");
writer.print(XSI.SCHEMA_LOCATION.qName);
writer.print("=\"");
writer.print(FOXML.uri);
writer.print(" ");
writer.print(m_format.xsdLocation);
writer.print("\">\n");
}
private void appendProperties(DigitalObject obj,
PrintWriter writer,
String encoding)
throws ObjectIntegrityException {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":objectProperties>\n");
/*
* fType is eliminated in foxml 1.1+, so choose the best reasonable
* value for 1.0 serializations
*/
if (m_format.equals(FOXML1_0)) {
RDFName ftype = DOTranslationUtility.getTypeAttribute(obj);
if (ftype != null) {
appendProperty(RDF.TYPE.uri, ftype.uri, writer, false);
}
}
appendProperty(MODEL.STATE.uri,
DOTranslationUtility.getStateAttribute(obj),
writer,
false);
appendProperty(MODEL.LABEL.uri, obj.getLabel(), writer, false);
appendProperty(MODEL.OWNER.uri, obj.getOwnerId(), writer, false);
appendProperty(MODEL.CREATED_DATE.uri, obj.getCreateDate(), writer);
appendProperty(VIEW.LAST_MODIFIED_DATE.uri,
obj.getLastModDate(),
writer);
Iterator<String> iter = obj.getExtProperties().keySet().iterator();
while (iter.hasNext()) {
String name = iter.next();
appendProperty(name, obj.getExtProperty(name), writer, true);
}
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":objectProperties>\n");
}
private static void appendProperty(String uri,
String value,
PrintWriter writer,
boolean extProperty) {
if (value != null) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(':');
if (extProperty) {
writer.print("ext");
}
writer.print("property NAME=\"");
writer.print(uri);
writer.print("\" VALUE=\"");
writer.print(StreamUtility.enc(value));
writer.print("\"/>\n");
}
}
private static void appendProperty(String uri,
Date value,
PrintWriter writer) {
if (value != null) {
appendProperty(uri,
DateUtility.convertDateToString(value),
writer,
false);
}
}
private void appendDatastreams(DigitalObject obj,
PrintWriter writer,
String encoding)
throws ObjectIntegrityException, UnsupportedEncodingException,
StreamIOException {
Iterator<String> iter = obj.datastreamIdIterator();
while (iter.hasNext()) {
String dsid = iter.next();
boolean haveWrittenCommonAttributes = false;
// AUDIT datastream is rebuilt from the latest in-memory audit trail
// which is a separate array list in the DigitalObject class.
// So, ignore it here.
if (dsid.equals("AUDIT") || dsid.equals("FEDORA-AUDITTRAIL")) {
continue;
}
// Given a datastream ID, get all the datastream versions.
// Use the first version to pick up the attributes common to all versions.
for (Datastream v : obj.datastreams(dsid)) {
Datastream vds = DOTranslationUtility.setDatastreamDefaults(v);
// insert the ds attributes common to all versions, when necessary
if (!haveWrittenCommonAttributes) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":datastream ID=\"");
writer.print(vds.DatastreamID);
writer.print("\"");
if (m_transContext == DOTranslationUtility.SERIALIZE_EXPORT_PUBLIC) {
writer.print(" FEDORA_URI=\"");
writer.print("info:fedora/");
writer.print(obj.getPid());
writer.print("/");
writer.print(vds.DatastreamID);
writer.print("\"");
}
writer.print(" STATE=\"");
writer.print(vds.DSState);
writer.print("\"");
writer.print(" CONTROL_GROUP=\"");
writer.print(vds.DSControlGrp);
writer.print("\"");
writer.print(" VERSIONABLE=\"");
writer.print(vds.DSVersionable);
writer.print("\">\n");
haveWrittenCommonAttributes = true;
}
// insert the ds version elements
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":datastreamVersion ID=\"");
writer.print(vds.DSVersionID);
writer.print("\"");
writer.print(" LABEL=\"");
writer.print(StreamUtility.enc(vds.DSLabel));
writer.print("\"");
if (vds.DSCreateDT != null) {
writer.print(" CREATED=\"");
writer.print(DateUtility.convertDateToString(vds.DSCreateDT));
writer.print("\"");
}
String altIds =
DOTranslationUtility.oneString(vds.DatastreamAltIDs);
if (altIds != null && !altIds.equals("")) {
writer.print(" ALT_IDS=\"");
writer.print(StreamUtility.enc(altIds));
writer.print("\"");
}
writer.print(" MIMETYPE=\"");
writer.print(StreamUtility.enc(vds.DSMIME));
writer.print("\"");
if (vds.DSFormatURI != null && !vds.DSFormatURI.equals("")) {
writer.print(" FORMAT_URI=\"");
writer.print(StreamUtility.enc(vds.DSFormatURI));
writer.print("\"");
}
// include size if it's non-zero
if (vds.DSSize != 0) {
writer.print(" SIZE=\"");
writer.print(vds.DSSize);
writer.print("\"");
}
writer.print(">\n");
// include checksum if it has a value
String csType = vds.getChecksumType();
if (csType != null && csType.length() > 0
&& !csType.equals(Datastream.CHECKSUMTYPE_DISABLED)) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":contentDigest TYPE=\"");
writer.print(csType);
writer.print("\"");
writer.print(" DIGEST=\"");
writer.print(vds.getChecksum());
writer.print("\"/>\n");
}
// if E or R insert ds content location as URL
if (vds.DSControlGrp.equalsIgnoreCase("E")
|| vds.DSControlGrp.equalsIgnoreCase("R")) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":contentLocation TYPE=\"");
writer.print("URL\"");
writer.print(" REF=\"");
String urls = DOTranslationUtility.normalizeDSLocationURLs(
obj.getPid(),
vds,
m_transContext).DSLocation;
writer.print(StreamUtility.enc(urls));
writer.print("\"/>\n");
// if M insert ds content location as an internal identifier
} else if (vds.DSControlGrp.equalsIgnoreCase("M")) {
if (m_transContext == DOTranslationUtility.SERIALIZE_EXPORT_ARCHIVE) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":binaryContent> \n");
String encoded = Base64.encodeToString(vds.getContentStream());
writer.print(StringUtility.splitAndIndent(encoded,
14,
80));
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":binaryContent> \n");
} else {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":contentLocation TYPE=\"");
writer.print("INTERNAL_ID\" REF=\"");
String urls = DOTranslationUtility.normalizeDSLocationURLs(
obj.getPid(),
vds,
m_transContext).DSLocation;
writer.print(StreamUtility.enc(urls));
writer.print("\"/>\n");
}
// if X insert inline XML
} else if (vds.DSControlGrp.equalsIgnoreCase("X")) {
appendInlineXML(obj,
(DatastreamXMLMetadata) vds,
writer,
encoding);
}
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":datastreamVersion>\n");
}
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":datastream>\n");
}
}
private void appendAudit(DigitalObject obj,
PrintWriter writer,
String encoding) throws ObjectIntegrityException {
if (obj.getAuditRecords().size() > 0) {
// Audit trail datastream re-created from audit records.
// There is only ONE version of the audit trail datastream!
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":datastream ID=\"");
writer.print("AUDIT\"");
if (m_transContext == DOTranslationUtility.SERIALIZE_EXPORT_PUBLIC) {
writer.print(" FEDORA_URI=\"info:fedora/");
writer.print(obj.getPid());
writer.print("/AUDIT\"");
}
writer.print(" STATE=\"A\" CONTROL_GROUP=\"X\" VERSIONABLE=\"false\">\n");
// insert the ds version-level elements
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":datastreamVersion ID=\"AUDIT.0\" LABEL=\"");
writer.print("Audit Trail for this object\" CREATED=\"");
writer.print(DateUtility.convertDateToString(obj.getCreateDate()));
writer.print("\" MIMETYPE=\"text/xml\" FORMAT_URI=\"");
writer.print(AUDIT1_0.uri);
writer.print("\">\n");
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":xmlContent>\n");
DOTranslationUtility.appendAuditTrail(obj, writer);
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":xmlContent>\n");
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":datastreamVersion>\n");
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":datastream>\n");
}
}
private void appendInlineXML(DigitalObject obj,
DatastreamXMLMetadata ds,
PrintWriter writer,
String encoding)
throws ObjectIntegrityException, UnsupportedEncodingException,
StreamIOException {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":xmlContent>\n");
// Relative Repository URLs: If it's a WSDL or SERVICE-PROFILE datastream
// in a SDep object search for any embedded URLs that are relative to
// the local repository (like internal service URLs) and make sure they
// are converted appropriately for the translation context.
if (obj.hasContentModel(SERVICE_DEPLOYMENT_3_0)
&& (ds.DatastreamID.equals("SERVICE-PROFILE") || ds.DatastreamID
.equals("WSDL"))) {
// FIXME! We need a more efficient way than to search
// the whole block of inline XML. We really only want to
// look at service URLs in the XML.
writer.print(DOTranslationUtility
.normalizeInlineXML(new String(ds.xmlContent, "UTF-8")
.trim(), m_transContext));
} else {
DOTranslationUtility.appendXMLStream(ds.getContentStream(),
writer,
encoding);
}
writer.print("\n</");
writer.print(FOXML.prefix);
writer.print(":xmlContent>\n");
}
private void appendDisseminators(DigitalObject obj, PrintWriter writer)
throws ObjectIntegrityException {
Iterator<String> dissIdIter = obj.disseminatorIdIterator();
while (dissIdIter.hasNext()) {
String did = dissIdIter.next();
List<Disseminator> dissList = obj.disseminators(did);
for (int i = 0; i < dissList.size(); i++) {
Disseminator vdiss =
DOTranslationUtility
.setDisseminatorDefaults(obj
.disseminators(did).get(i));
// insert the disseminator elements common to all versions.
if (i == 0) {
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":disseminator ID=\"");
writer.print(did);
writer.print("\" BDEF_CONTRACT_PID=\"");
writer.print(vdiss.bDefID);
writer.print("\" STATE=\"");
writer.print(vdiss.dissState);
writer.print("\" VERSIONABLE=\"");
writer.print(vdiss.dissVersionable);
writer.print("\">\n");
}
// insert the disseminator version-level elements
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":disseminatorVersion ID=\"");
writer.print(vdiss.dissVersionID);
writer.print("\"");
if (vdiss.dissLabel != null && !vdiss.dissLabel.equals("")) {
writer.print(" LABEL=\"");
writer.print(StreamUtility.enc(vdiss.dissLabel));
writer.print("\"");
}
writer.print(" BMECH_SERVICE_PID=\"");
writer.print(vdiss.sDepID);
writer.print("\"");
if (vdiss.dissCreateDT != null) {
writer.print(" CREATED=\"");
writer.print(DateUtility.convertDateToString(vdiss.dissCreateDT));
writer.print("\"");
}
writer.print(">\n");
// datastream bindings...
DSBinding[] bindings = vdiss.dsBindMap.dsBindings;
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":serviceInputMap>\n");
for (int j = 0; j < bindings.length; j++) {
if (bindings[j].seqNo == null) {
bindings[j].seqNo = "";
}
writer.print("<");
writer.print(FOXML.prefix);
writer.print(":datastreamBinding KEY=\"");
writer.print(bindings[j].bindKeyName);
writer.print("\" DATASTREAM_ID=\"");
writer.print(bindings[j].datastreamID);
writer.print("\"");
if (bindings[j].bindLabel != null
&& !bindings[j].bindLabel.equals("")) {
writer.print(" LABEL=\"");
writer.print(StreamUtility.enc(bindings[j].bindLabel));
writer.print("\"");
}
if (bindings[j].seqNo != null
&& !bindings[j].seqNo.equals("")) {
writer.print(" ORDER=\"");
writer.print(bindings[j].seqNo);
writer.print("\"");
}
writer.print("/>\n");
}
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":serviceInputMap>\n");
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":disseminatorVersion>\n");
}
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":disseminator>\n");
}
}
private void appendRootElementEnd(PrintWriter writer) {
writer.print("</");
writer.print(FOXML.prefix);
writer.print(":digitalObject>");
}
}