/* 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;
import static org.fcrepo.common.Constants.MODEL;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.ws.rs.core.StreamingOutput;
import org.fcrepo.common.Models;
import org.fcrepo.server.Context;
import org.fcrepo.server.errors.DisseminationException;
import org.fcrepo.server.errors.MethodNotFoundException;
import org.fcrepo.server.errors.ObjectIntegrityException;
import org.fcrepo.server.errors.ServerException;
import org.fcrepo.server.errors.StorageException;
import org.fcrepo.server.errors.StreamIOException;
import org.fcrepo.server.errors.UnsupportedTranslationException;
import org.fcrepo.server.storage.tasks.StreamingExport;
import org.fcrepo.server.storage.translation.DOTranslationUtility;
import org.fcrepo.server.storage.translation.DOTranslator;
import org.fcrepo.server.storage.types.AuditRecord;
import org.fcrepo.server.storage.types.BasicDigitalObject;
import org.fcrepo.server.storage.types.Datastream;
import org.fcrepo.server.storage.types.DigitalObject;
import org.fcrepo.server.storage.types.MethodDef;
import org.fcrepo.server.storage.types.MethodParmDef;
import org.fcrepo.server.storage.types.ObjectMethodsDef;
import org.fcrepo.server.storage.types.RelationshipTuple;
import org.fcrepo.utilities.DateUtility;
import org.fcrepo.utilities.ReadableByteArrayOutputStream;
import org.jrdf.graph.ObjectNode;
import org.jrdf.graph.PredicateNode;
import org.jrdf.graph.SubjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A DOReader backed by a DigitalObject.
*
* @author Chris Wilper
*/
public class SimpleDOReader
implements DOReader {
private static final Logger logger =
LoggerFactory.getLogger(SimpleDOReader.class);
private static final Datastream[] DATASTREAM_TYPE =
new Datastream[0];
private static final Date[] DATE_TYPE =
new Date[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0];
protected final DigitalObject m_obj;
private final Context m_context;
private final RepositoryReader m_repoReader;
private final DOTranslator m_translator;
private final String m_exportFormat;
private String m_storageFormat;
public SimpleDOReader(Context context,
RepositoryReader repoReader,
DOTranslator translator,
String exportFormat,
String storageFormat,
String encoding,
InputStream serializedObject)
throws ObjectIntegrityException, StreamIOException,
UnsupportedTranslationException, ServerException {
m_context = context;
m_repoReader = repoReader;
m_translator = translator;
m_exportFormat = exportFormat;
m_storageFormat = storageFormat;
m_obj = new BasicDigitalObject();
m_translator.deserialize(serializedObject,
m_obj,
m_storageFormat,
encoding,
DOTranslationUtility.DESERIALIZE_INSTANCE);
}
/**
* Alternate constructor for when a DigitalObject is already available for
* some reason.
*/
public SimpleDOReader(Context context,
RepositoryReader repoReader,
DOTranslator translator,
String exportFormat,
String encoding,
DigitalObject obj) {
m_context = context;
m_repoReader = repoReader;
m_translator = translator;
m_exportFormat = exportFormat;
m_obj = obj;
}
/**
* {@inheritDoc}
*/
@Override
public DigitalObject getObject() {
return m_obj;
}
/**
* {@inheritDoc}
*/
@Override
public Date getCreateDate() {
return m_obj.getCreateDate();
}
/**
* {@inheritDoc}
*/
@Override
public Date getLastModDate() {
return m_obj.getLastModDate();
}
/**
* {@inheritDoc}
*/
@Override
public String getOwnerId() {
return m_obj.getOwnerId();
}
/**
* {@inheritDoc}
*/
@Override
public List<AuditRecord> getAuditRecords() {
return m_obj.getAuditRecords();
}
/**
* Return the object as an XML input stream in the internal serialization
* format.
*/
@Override
public InputStream GetObjectXML() throws ObjectIntegrityException,
StreamIOException, UnsupportedTranslationException, ServerException {
ReadableByteArrayOutputStream bytes = new ReadableByteArrayOutputStream(4096);
m_translator.serialize(m_obj,
bytes,
m_storageFormat,
"UTF-8",
DOTranslationUtility.SERIALIZE_STORAGE_INTERNAL);
return bytes.toInputStream();
}
/**
* {@inheritDoc}
*/
@Override
public InputStream Export(String format, String exportContext)
throws ObjectIntegrityException, StreamIOException,
UnsupportedTranslationException, ServerException {
// allocate the ByteArrayOutputStream with a 4k initial capacity to constrain copying up
ReadableByteArrayOutputStream bytes = new ReadableByteArrayOutputStream(4096);
try {
Stream(format, exportContext).write(bytes);
} catch (Exception e) {
throw new StreamIOException(e.getMessage(),e);
}
return bytes.toInputStream();
}
@Override
public StreamingOutput Stream(String format, String exportContext)
throws ObjectIntegrityException, StreamIOException,
UnsupportedTranslationException, ServerException {
int transContext;
// first, set the translation context...
logger.debug("Export context: {}", exportContext);
if (exportContext == null || exportContext.isEmpty()
|| exportContext.equalsIgnoreCase("default")) {
// null and default is set to PUBLIC translation
transContext = DOTranslationUtility.SERIALIZE_EXPORT_PUBLIC;
} else if (exportContext.equalsIgnoreCase("public")) {
transContext = DOTranslationUtility.SERIALIZE_EXPORT_PUBLIC;
} else if (exportContext.equalsIgnoreCase("migrate")) {
transContext = DOTranslationUtility.SERIALIZE_EXPORT_MIGRATE;
} else if (exportContext.equalsIgnoreCase("archive")) {
transContext = DOTranslationUtility.SERIALIZE_EXPORT_ARCHIVE;
} else {
throw new UnsupportedTranslationException("Export context "
+ exportContext + " is not valid.");
}
// now serialize for export in the proper XML format...
if (format == null || format.isEmpty()
|| format.equalsIgnoreCase("default")) {
logger.debug("Export in default format: {}", m_exportFormat);
format = m_exportFormat;
} else {
logger.debug("Export in format: {}", format);
}
return new StreamingExport(m_translator, m_obj, format, "UTF-8", transContext);
}
/**
* @deprecated in Fedora 3.0, use Export instead
*/
@Override
@Deprecated
public InputStream ExportObject(String format, String exportContext)
throws ObjectIntegrityException, StreamIOException,
UnsupportedTranslationException, ServerException {
return Export(format, exportContext);
}
/**
* {@inheritDoc}
*/
@Override
public String GetObjectPID() {
return m_obj.getPid();
}
/**
* {@inheritDoc}
*/
@Override
public String GetObjectLabel() {
return m_obj.getLabel();
}
/**
* {@inheritDoc}
*/
@Override
public String GetObjectState() {
if (m_obj.getState() == null) {
return "A"; // shouldn't happen, but if it does don't die
}
return m_obj.getState();
}
@Override
public List<String> getContentModels() throws ServerException {
Set<RelationshipTuple> rels = getRelationships(MODEL.HAS_MODEL,null);
List<String> list = new ArrayList<String>(rels.size());
for (RelationshipTuple rel : rels) {
list.add(rel.object);
}
return list;
}
@Override
public boolean hasContentModel(ObjectNode contentModel)
throws ServerException {
return hasRelationship(MODEL.HAS_MODEL,contentModel);
}
/**
* {@inheritDoc}
*/
@Override
public String[] ListDatastreamIDs(String state) {
Iterator<String> iter = m_obj.datastreamIdIterator();
ArrayList<String> al = new ArrayList<String>();
while (iter.hasNext()) {
String dsId = iter.next();
if (state == null) {
al.add(dsId);
} else {
// below should never return null -- already know id exists,
// and am asking for any the latest existing one.
Datastream ds = GetDatastream(dsId, null);
if (ds.DSState.equals(state)) {
al.add(dsId);
}
}
}
return al.toArray(EMPTY_STRING_ARRAY);
}
/**
* {@inheritDoc}
*/
@Override
public Datastream getDatastream(String dsID, String versionID) {
for (Datastream ds : m_obj.datastreams(dsID)) {
if (ds.DSVersionID.equals(versionID)) {
return ds;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Datastream GetDatastream(String datastreamID, Date versDateTime) {
// get the one with the closest creation date
// without going over
Datastream result = null;
long bestTimeDifference = Long.MAX_VALUE;
long latestCreateTime = -1;
long vTime = -1;
if (versDateTime != null) {
vTime = versDateTime.getTime();
}
for (Datastream ds : m_obj.datastreams(datastreamID)) {
if (versDateTime == null) {
if (ds.DSCreateDT == null || ds.DSCreateDT.getTime() > latestCreateTime || result == null) {
result = ds;
if (ds.DSCreateDT != null) latestCreateTime = ds.DSCreateDT.getTime();
}
} else {
//TODO If none of the versions have a create date, what should behavior be?
long diff = (ds.DSCreateDT == null)? vTime : vTime - ds.DSCreateDT.getTime();
if (diff >= 0) {
if (diff < bestTimeDifference) {
bestTimeDifference = diff;
result = ds;
}
}
}
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public Date[] getDatastreamVersions(String datastreamID) {
ArrayList<Date> versionDates = new ArrayList<Date>();
for (Datastream d : m_obj.datastreams(datastreamID)) {
versionDates.add(d.DSCreateDT);
}
return versionDates.toArray(DATE_TYPE);
}
/**
* {@inheritDoc}
*/
@Override
public Datastream[] GetDatastreams(Date versDateTime, String state) {
String[] ids = ListDatastreamIDs(null);
ArrayList<Datastream> al = new ArrayList<Datastream>(ids.length);
for (String element : ids) {
Datastream ds = GetDatastream(element, versDateTime);
if (ds != null && (state == null || ds.DSState.equals(state))) {
al.add(ds);
}
}
return al.toArray(DATASTREAM_TYPE);
}
/**
* {@inheritDoc}
*/
@Override
public String[] getObjectHistory(String PID) {
String[] dsIDs = ListDatastreamIDs(null);
TreeSet<String> modDates = new TreeSet<String>();
for (String element : dsIDs) {
Date[] dsDates = getDatastreamVersions(element);
for (Date element2 : dsDates) {
modDates.add(DateUtility.convertDateToString(element2));
}
}
return modDates.toArray(EMPTY_STRING_ARRAY);
}
private MethodDef[] listMethods(String sDefPID,
ServiceDefinitionReader sDefReader,
Date versDateTime)
throws MethodNotFoundException, ServerException {
if (sDefPID.equalsIgnoreCase("fedora-system:1")
|| sDefPID.equalsIgnoreCase("fedora-system:3")) {
throw new MethodNotFoundException("[getObjectMethods] The object, "
+ m_obj.getPid()
+ ", will not report on dynamic method definitions "
+ "at this time (fedora-system:1 and fedora-system:3.");
}
if (sDefReader == null) {
return null;
}
MethodDef[] methods = sDefReader.getAbstractMethods(versDateTime);
// Filter out parms that are internal to the mechanism and not part
// of the abstract method definition. We just want user parms.
for (int i = 0; i < methods.length; i++) {
methods[i].methodParms = filterParms(methods[i]);
}
return methods;
}
/**
* Filters out mechanism-specific parms (system default parms and datastream
* input parms) so that what is returned is only method parms that reflect
* abstract method definitions. Abstract method definitions only expose
* user-supplied parms.
*
* @param method
* @return
*/
private MethodParmDef[] filterParms(MethodDef method) {
ArrayList<MethodParmDef> filteredParms = new ArrayList<MethodParmDef>();
for (MethodParmDef element : method.methodParms) {
if (element.parmType.equalsIgnoreCase(MethodParmDef.USER_INPUT)) {
filteredParms.add(element);
}
}
return filteredParms.toArray(new MethodParmDef[0]);
}
protected String getWhenString(Date versDateTime) {
if (versDateTime != null) {
return DateUtility.formatMillisTZ(versDateTime);
} else {
return "the current time";
}
}
/**
* {@inheritDoc}
*/
@Override
public ObjectMethodsDef[] listMethods(Date versDateTime)
throws ServerException {
ArrayList<MethodDef> methodList = new ArrayList<MethodDef>();
ArrayList<String> sDefIDList = new ArrayList<String>();
ServiceDefinitionReader sDefReader = null;
/*
Set<RelationshipTuple> cmRels =
getRelationships(MODEL.HAS_MODEL, null);
for (RelationshipTuple element : cmRels) {
*/
for (String cm:getContentModels()){
/*
* FIXME: If the we encounter a relation to one of the "system"
* models, then skip it, since its functionality is hardwired in.
* Ideally, we would actually instantiate the system objects, and
* wire in the default system behaviour based upon their content
* (just like everything else)
*/
if (Models.contains(cm)) {
continue;
}
DOReader cmReader;
String cModelPid = cm.substring(12);
if ("self".equals(cModelPid)) {
cmReader = this;
cModelPid = GetObjectPID();
} else {
try {
cmReader =
m_repoReader.getReader(false, m_context, cModelPid);
} catch (StorageException e) {
throw new DisseminationException(null,
"Content Model Object "
+ cModelPid
+ " does not exist.",
null,
null,
e);
}
}
Set<RelationshipTuple> hasServiceRels =
cmReader
.getRelationships(MODEL.HAS_SERVICE, null);
for (RelationshipTuple element2 : hasServiceRels) {
String sDefPid = element2.getObjectPID();
try {
sDefReader =
m_repoReader.getServiceDefinitionReader(false,
m_context,
sDefPid);
} catch (StorageException se) {
throw new DisseminationException("Service definition "
+ sDefPid + " required by Content Model "
+ cModelPid + " not found.");
}
MethodDef[] methods =
listMethods(sDefPid, sDefReader, versDateTime);
if (methods != null) {
for (MethodDef element3 : methods) {
methodList.add(element3);
sDefIDList.add(element2.getObjectPID());
}
}
}
}
ObjectMethodsDef[] ret = new ObjectMethodsDef[methodList.size()];
for (int i = 0; i < methodList.size(); i++) {
MethodDef def = methodList.get(i);
ret[i] = new ObjectMethodsDef();
ret[i].PID = GetObjectPID();
ret[i].sDefPID = sDefIDList.get(i);
ret[i].methodName = def.methodName;
ret[i].methodParmDefs = def.methodParms;
ret[i].asOfDate = versDateTime;
}
return ret;
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasRelationship(SubjectNode subject, PredicateNode predicate, ObjectNode object) {
return m_obj.hasRelationship(subject, predicate, object);
}
/**
* {@inheritDoc}
*/
@Override
public boolean hasRelationship(PredicateNode predicate, ObjectNode object) {
return m_obj.hasRelationship(predicate, object);
}
/**
* {@inheritDoc}
*/
@Override
public Set<RelationshipTuple> getRelationships(SubjectNode subject,
PredicateNode predicate,
ObjectNode object) {
return m_obj.getRelationships(subject, predicate, object);
}
/**
* {@inheritDoc}
*/
@Override
public Set<RelationshipTuple> getRelationships(PredicateNode predicate,
ObjectNode object) {
return m_obj.getRelationships(predicate, object);
}
/**
* {@inheritDoc}
*/
@Override
public Set<RelationshipTuple> getRelationships() {
return m_obj.getRelationships();
}
}