/* 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; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.text.SimpleDateFormat; 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 org.apache.log4j.Logger; import org.jrdf.graph.ObjectNode; import org.jrdf.graph.PredicateNode; import org.jrdf.graph.SubjectNode; import fedora.common.Models; import fedora.server.Context; import fedora.server.errors.DisseminationException; import fedora.server.errors.MethodNotFoundException; import fedora.server.errors.ObjectIntegrityException; import fedora.server.errors.ServerException; import fedora.server.errors.StorageException; import fedora.server.errors.StreamIOException; import fedora.server.errors.UnsupportedTranslationException; import fedora.server.storage.translation.DOTranslationUtility; import fedora.server.storage.translation.DOTranslator; import fedora.server.storage.types.AuditRecord; import fedora.server.storage.types.BasicDigitalObject; import fedora.server.storage.types.Datastream; import fedora.server.storage.types.DigitalObject; import fedora.server.storage.types.MethodDef; import fedora.server.storage.types.MethodParmDef; import fedora.server.storage.types.ObjectMethodsDef; import fedora.server.storage.types.RelationshipTuple; import fedora.server.utilities.DateUtility; import static fedora.common.Constants.MODEL; /** * A DOReader backed by a DigitalObject. * * @author Chris Wilper */ public class SimpleDOReader implements DOReader { /** Logger for this class. */ private static final Logger LOG = Logger.getLogger(SimpleDOReader.class.getName()); 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; private final SimpleDateFormat m_formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 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} */ public DigitalObject getObject() { return m_obj; } /** * {@inheritDoc} */ public Date getCreateDate() { return m_obj.getCreateDate(); } /** * {@inheritDoc} */ public Date getLastModDate() { return m_obj.getLastModDate(); } /** * {@inheritDoc} */ public String getOwnerId() { return m_obj.getOwnerId(); } /** * {@inheritDoc} */ public List<AuditRecord> getAuditRecords() { return m_obj.getAuditRecords(); } /** * Return the object as an XML input stream in the internal serialization * format. */ public InputStream GetObjectXML() throws ObjectIntegrityException, StreamIOException, UnsupportedTranslationException, ServerException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); m_translator.serialize(m_obj, bytes, m_storageFormat, "UTF-8", DOTranslationUtility.SERIALIZE_STORAGE_INTERNAL); return new ByteArrayInputStream(bytes.toByteArray()); } /** * {@inheritDoc} */ public InputStream Export(String format, String exportContext) throws ObjectIntegrityException, StreamIOException, UnsupportedTranslationException, ServerException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); int transContext; // first, set the translation context... LOG.debug("Export context: " + exportContext); if (exportContext == null || exportContext.equals("") || 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.equals("") || format.equalsIgnoreCase("default")) { LOG.debug("Export in default format: " + m_exportFormat); m_translator.serialize(m_obj, bytes, m_exportFormat, "UTF-8", transContext); } else { LOG.debug("Export in format: " + format); m_translator.serialize(m_obj, bytes, format, "UTF-8", transContext); } return new ByteArrayInputStream(bytes.toByteArray()); } /** * @deprecated in Fedora 3.0, use Export instead */ @Deprecated public InputStream ExportObject(String format, String exportContext) throws ObjectIntegrityException, StreamIOException, UnsupportedTranslationException, ServerException { return Export(format, exportContext); } /** * {@inheritDoc} */ public String GetObjectPID() { return m_obj.getPid(); } /** * {@inheritDoc} */ public String GetObjectLabel() { return m_obj.getLabel(); } /** * {@inheritDoc} */ public String GetObjectState() { if (m_obj.getState() == null) { return "A"; // shouldn't happen, but if it does don't die } return m_obj.getState(); } public List<String> getContentModels() throws ServerException { List<String> list = new ArrayList<String>(); for (RelationshipTuple rel : getRelationships(MODEL.HAS_MODEL,null)) { list.add(rel.object); } return list; } public boolean hasContentModel(ObjectNode contentModel) throws ServerException { return hasRelationship(MODEL.HAS_MODEL,contentModel); } /** * {@inheritDoc} */ 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); } } } iter = al.iterator(); String[] out = new String[al.size()]; int i = 0; while (iter.hasNext()) { out[i] = iter.next(); i++; } return out; } /** * {@inheritDoc} */ public Datastream getDatastream(String dsID, String versionID) { for (Datastream ds : m_obj.datastreams(dsID)) { if (ds.DSVersionID.equals(versionID)) { return ds; } } return null; } /** * {@inheritDoc} */ public Datastream GetDatastream(String datastreamID, Date versDateTime) { // get the one with the closest creation date // without going over Datastream closestWithoutGoingOver = null; Datastream latestCreated = null; long bestTimeDifference = -1; 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.getTime() > latestCreateTime) { latestCreateTime = ds.DSCreateDT.getTime(); latestCreated = ds; } } else { long diff = vTime - ds.DSCreateDT.getTime(); if (diff >= 0) { if (diff < bestTimeDifference || bestTimeDifference == -1) { bestTimeDifference = diff; closestWithoutGoingOver = ds; } } } } if (versDateTime == null) { return latestCreated; } else { return closestWithoutGoingOver; } } /** * {@inheritDoc} */ 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(new Date[0]); } /** * {@inheritDoc} */ public Datastream[] GetDatastreams(Date versDateTime, String state) { String[] ids = ListDatastreamIDs(null); ArrayList<Datastream> al = new ArrayList<Datastream>(); for (String element : ids) { Datastream ds = GetDatastream(element, versDateTime); if (ds != null && (state == null || ds.DSState.equals(state))) { al.add(ds); } } Datastream[] out = new Datastream[al.size()]; Iterator<Datastream> iter = al.iterator(); int i = 0; while (iter.hasNext()) { out[i] = iter.next(); i++; } return out; } /** * {@inheritDoc} */ public String[] getObjectHistory(String PID) { String[] dsIDs = ListDatastreamIDs("A"); 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(new String[0]); } /** * {@inheritDoc} */ 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 m_formatter.format(versDateTime); } else { return "the current time"; } } /** * {@inheritDoc} */ 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} */ public boolean hasRelationship(SubjectNode subject, PredicateNode predicate, ObjectNode object) { return m_obj.hasRelationship(subject, predicate, object); } /** * {@inheritDoc} */ public boolean hasRelationship(PredicateNode predicate, ObjectNode object) { return m_obj.hasRelationship(predicate, object); } /** * {@inheritDoc} */ public Set<RelationshipTuple> getRelationships(SubjectNode subject, PredicateNode predicate, ObjectNode object) { return m_obj.getRelationships(subject, predicate, object); } /** * {@inheritDoc} */ public Set<RelationshipTuple> getRelationships(PredicateNode predicate, ObjectNode object) { return m_obj.getRelationships(predicate, object); } /** * {@inheritDoc} */ public Set<RelationshipTuple> getRelationships() { return m_obj.getRelationships(); } }