All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.fedora.server.management.DefaultManagement Maven / Gradle / Ivy

Go to download

The Fedora Client is a Java Library that allows API access to a Fedora Repository. The client is typically one part of a full Fedora installation.

The newest version!
/*
 * -----------------------------------------------------------------------------
 *
 * 

License and Copyright: The contents of this file are subject to the * Apache License, Version 2.0 (the "License"); you may not use * this file except in compliance with the License. You may obtain a copy of * the License at * http://www.fedora-commons.org/licenses.

* *

Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific language governing rights and limitations under the License.

* *

The entire file consists of original code.

*

Copyright © 2008 Fedora Commons, Inc.
*

Copyright © 2002-2007 The Rector and Visitors of the University of * Virginia and Cornell University
* All rights reserved.

* * ----------------------------------------------------------------------------- */ package fedora.server.management; import java.io.*; import java.text.*; import java.util.Arrays; import java.util.ArrayList; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.Map; import javax.xml.parsers.*; import org.w3c.dom.*; import org.apache.xml.serialize.*; import org.apache.commons.betwixt.XMLUtils; import org.apache.log4j.Logger; import fedora.server.*; import fedora.server.errors.*; import fedora.server.errors.authorization.AuthzException; import fedora.server.storage.*; import fedora.server.storage.types.*; import fedora.server.utilities.*; import fedora.server.validation.*; import fedora.server.security.Authorization; import fedora.common.Constants; /** * Implements API-M without regard to the transport/messaging protocol. * * @author [email protected] * @version $Id: DefaultManagement.java 7652 2008-08-05 13:53:52Z bbranan $ */ public class DefaultManagement extends Module implements Management, ManagementDelegate { /** Logger for this class. */ private static Logger LOG = Logger.getLogger(DefaultManagement.class.getName()); private DOManager m_manager; private int m_uploadStorageMinutes; private int m_lastId; private File m_tempDir; private Hashtable m_uploadStartTime; private ExternalContentManager m_contentManager; private Authorization m_fedoraXACMLModule; /** * Creates and initializes the Management Module. *

* When the server is starting up, this is invoked as part of the * initialization process. * * @param moduleParameters A pre-loaded Map of name-value pairs comprising * the intended configuration of this Module. * @param server The Server instance. * @param role The role this module fulfills, a java class name. * @throws ModuleInitializationException If initilization values are * invalid or initialization fails for some other reason. */ public DefaultManagement(Map moduleParameters, Server server, String role) throws ModuleInitializationException { super(moduleParameters, server, role); } public void initModule() throws ModuleInitializationException { // how many minutes should we hold on to uploaded files? default=5 String min=getParameter("uploadStorageMinutes"); if (min==null) min="5"; try { m_uploadStorageMinutes=Integer.parseInt(min); if (m_uploadStorageMinutes<1) { throw new ModuleInitializationException("uploadStorageMinutes " + "must be 1 or more, if specified.", getRole()); } } catch (NumberFormatException nfe) { throw new ModuleInitializationException("uploadStorageMinutes must " + "be an integer, if specified.", getRole()); } // initialize storage area by 1) ensuring the directory is there // and 2) reading in the existing files, if any, and setting their // startTime to the current time. try { m_tempDir=new File(getServer().getHomeDir(), "management/upload"); if (!m_tempDir.isDirectory()) { m_tempDir.mkdirs(); } // put leftovers in hash, while saving highest id as m_lastId m_uploadStartTime=new Hashtable(); String[] fNames=m_tempDir.list(); Long leftoverStartTime=new Long(System.currentTimeMillis()); m_lastId=0; for (int i=0; im_lastId) m_lastId=id; m_uploadStartTime.put(fNames[i], leftoverStartTime); } catch (NumberFormatException nfe) { // skip files that aren't named numerically } } } catch (Exception e) { throw new ModuleInitializationException("Error while initializing " + "temporary storage area: " + e.getClass().getName() + ": " + e.getMessage(), getRole(), e); } // initialize variables pertaining to checksumming datastreams. if (Datastream.defaultChecksumType == null) { Datastream.defaultChecksumType = "DISABLED"; String auto =getParameter("autoChecksum"); LOG.debug("Got Parameter: autoChecksum = " + auto); if (auto.equalsIgnoreCase("true")) { Datastream.autoChecksum = true; Datastream.defaultChecksumType = getParameter("checksumAlgorithm"); } else { Datastream.autoChecksum = false; Datastream.defaultChecksumType = "DISABLED"; } LOG.debug("autoChecksum is "+ auto); LOG.debug("defaultChecksumType is "+ Datastream.defaultChecksumType); } } public void postInitModule() throws ModuleInitializationException { m_manager=(DOManager) getServer().getModule( "fedora.server.storage.DOManager"); if (m_manager==null) { throw new ModuleInitializationException("Can't get a DOManager " + "from Server.getModule", getRole()); } m_contentManager=(ExternalContentManager) getServer().getModule( "fedora.server.storage.ExternalContentManager"); if (m_contentManager==null) { throw new ModuleInitializationException("Can't get an ExternalContentManager " + "from Server.getModule", getRole()); } m_fedoraXACMLModule = (Authorization) getServer().getModule("fedora.server.security.Authorization"); if (m_fedoraXACMLModule == null) { throw new ModuleInitializationException("Can't get Authorization module (in default management) from Server.getModule", getRole()); } } public String ingestObject(Context context, InputStream serialization, String logMessage, String format, String encoding, boolean newPid) throws ServerException { DOWriter w = null; try { LOG.info("Entered ingestObject"); w=m_manager.getIngestWriter(Server.USE_DEFINITIVE_STORE, context, serialization, format, encoding, newPid); String pid=w.GetObjectPID(); m_fedoraXACMLModule.enforceIngestObject(context, pid, format, encoding); // Only create an audit record if there is a log message to capture if (logMessage != null && !logMessage.equals("")) { Date nowUTC = Server.getCurrentDate(context); addAuditRecord(context, w, "ingestObject", "", logMessage, nowUTC); } w.commit(logMessage); return pid; } finally { finishModification(w, "ingestObject"); } } private void finishModification(DOWriter w, String method) throws ServerException { LOG.info("Exiting " + method); if (w != null) { m_manager.releaseWriter(w); } if (LOG.isDebugEnabled()) { Runtime r=Runtime.getRuntime(); LOG.debug("Memory: " + r.freeMemory() + " bytes free of " + r.totalMemory() + " available."); } } public Date modifyObject(Context context, String pid, String state, String label, String ownerId, String logMessage) throws ServerException { DOWriter w = null; try { LOG.info("Entered modifyObject"); m_fedoraXACMLModule.enforceModifyObject(context, pid, state, ownerId); checkObjectLabel(label); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); if (state!=null && !state.equals("")) { if (!state.equals("A") && !state.equals("D") && !state.equals("I")) { throw new InvalidStateException("The object state of \"" + state + "\" is invalid. The allowed values for state are: " + " A (active), D (deleted), and I (inactive)."); } w.setState(state); } //if (label!=null && !label.equals("")) if (label!=null) { w.setLabel(label); } if (ownerId!=null) { w.setOwnerId(ownerId); } // Update audit trail Date nowUTC = Server.getCurrentDate(context); addAuditRecord(context, w, "modifyObject", "", logMessage, nowUTC); w.commit(logMessage); return w.getLastModDate(); } finally { finishModification(w, "modifyObject"); } } public Property[] getObjectProperties(Context context, String pid) throws ServerException { try { LOG.info("Entered getObjectProperties"); m_fedoraXACMLModule.enforceGetObjectProperties(context, pid); ArrayList props = new ArrayList(); DOReader reader=m_manager.getReader(Server.USE_DEFINITIVE_STORE, context, pid); props.add(new Property( "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", reader.getFedoraObjectType())); props.add(new Property( "info:fedora/fedora-system:def/model#contentModel", reader.getContentModelId())); props.add(new Property( "info:fedora/fedora-system:def/model#label", reader.GetObjectLabel())); props.add(new Property( "info:fedora/fedora-system:def/model#state", reader.GetObjectState())); props.add(new Property( "info:fedora/fedora-system:def/model#ownerId", reader.getOwnerId())); props.add(new Property( "info:fedora/fedora-system:def/model#createdDate", DateUtility.convertDateToString(reader.getCreateDate()))); props.add(new Property( "info:fedora/fedora-system:def/view#lastModifiedDate", DateUtility.convertDateToString(reader.getLastModDate()))); //Property[] extProps=reader.getExtProperties(); return (Property[])props.toArray(new Property[0]); } finally { LOG.info("Exiting getObjectProperties"); } } public InputStream getObjectXML(Context context, String pid, String encoding) throws ServerException { try { LOG.info("Entered getObjectXML"); m_fedoraXACMLModule.enforceGetObjectXML(context, pid, encoding); DOReader reader=m_manager.getReader(Server.USE_DEFINITIVE_STORE, context, pid); InputStream instream=reader.GetObjectXML(); return instream; } finally { LOG.info("Exiting getObjectXML"); } } public InputStream exportObject(Context context, String pid, String format, String exportContext, String encoding) throws ServerException { try { LOG.info("Entered exportObject"); m_fedoraXACMLModule.enforceExportObject(context, pid, format, exportContext, encoding); DOReader reader=m_manager.getReader(Server.USE_DEFINITIVE_STORE, context, pid); InputStream instream=reader.ExportObject(format,exportContext); return instream; } finally { LOG.info("Exiting exportObject"); } } public Date purgeObject(Context context, String pid, String logMessage, boolean force) throws ServerException { if (force) { throw new GeneralException("Forced object removal is not " + "yet supported."); } DOWriter w = null; try { LOG.info("Entered purgeObject"); m_fedoraXACMLModule.enforcePurgeObject(context, pid); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); w.remove(); w.commit(logMessage); return Server.getCurrentDate(context); } finally { // Log completion if (LOG.isInfoEnabled()) { StringBuilder logMsg = new StringBuilder("Completed purgeObject("); logMsg.append("pid: ").append(pid); logMsg.append(", logMessage: ").append(logMessage); logMsg.append(")"); LOG.info(logMsg.toString()); } finishModification(w, "purgeObject"); } } public String addDatastream(Context context, String pid, String dsID, String[] altIDs, String dsLabel, boolean versionable, String MIMEType, String formatURI, String dsLocation, String controlGroup, String dsState, String checksumType, String checksum, String logMessage) throws ServerException { LOG.info("Entered addDatastream"); // empty MIME types are allowed. assume they meant "" if they provide it as null. if (MIMEType == null) MIMEType = ""; // empty altIDs are allowed. assume they meant String[0] if they provide it as null. if (altIDs == null) altIDs = new String[0]; // If the datastream ID is not specified directly, see // if we can get it from the RecoveryContext if (dsID == null && context instanceof RecoveryContext) { RecoveryContext rContext = (RecoveryContext) context; dsID = rContext.getRecoveryValue(Constants.RECOVERY.DATASTREAM_ID.uri); if (dsID != null) { LOG.debug("Using new dsID from recovery context"); } } // check for valid xml name for datastream ID if (dsID != null) { if (!XMLUtils.isWellFormedXMLName(dsID)) { throw new InvalidXMLNameException("Invalid syntax for datastream ID. " + "The datastream ID of \""+dsID+"\" is" + "not a valid XML Name"); } } if ( dsID!=null && (dsID.equals("AUDIT") || dsID.equals("FEDORA-AUDITTRAIL"))) { throw new GeneralException("Creation of a datastream with an" + " identifier of 'AUDIT' or 'FEDORA-AUDITTRAIL' is not permitted."); } DOWriter w=null; try { m_fedoraXACMLModule.enforceAddDatastream(context, pid, dsID, altIDs, MIMEType, formatURI, dsLocation, controlGroup, dsState, checksumType, checksum); checkDatastreamID(dsID); checkDatastreamLabel(dsLabel); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); Datastream ds; if (controlGroup.equals("X")) { ds=new DatastreamXMLMetadata(); ds.DSInfoType=""; // field is now deprecated try { InputStream in; if (dsLocation.startsWith(DatastreamManagedContent.UPLOADED_SCHEME)) { in=getTempStream(dsLocation); } else { in=m_contentManager.getExternalContent(dsLocation, context).getStream(); } ((DatastreamXMLMetadata) ds).xmlContent = getEmbeddableXML(in); // If it's a RELS-EXT datastream, do validation if (dsID!=null && dsID.equals("RELS-EXT")){ validateRelsExt(pid, new ByteArrayInputStream( ((DatastreamXMLMetadata) ds).xmlContent)); } in.close(); } catch (Exception e) { String extraInfo; if (e.getMessage()==null) extraInfo=""; else extraInfo=" : " + e.getMessage(); throw new GeneralException("Error with " + dsLocation + extraInfo); } } else if (controlGroup.equals("M")) { ds=new DatastreamManagedContent(); ds.DSInfoType="DATA"; } else if (controlGroup.equals("R") || controlGroup.equals("E")) { ds=new DatastreamReferencedContent(); ds.DSInfoType="DATA"; } else { throw new GeneralException("Invalid control group: " + controlGroup); } ds.isNew=true; ds.DSControlGrp=controlGroup; ds.DSVersionable=versionable; if (!dsState.equals("A") && !dsState.equals("D") && !dsState.equals("I")) { throw new InvalidStateException("The datastream state of \"" + dsState + "\" is invalid. The allowed values for state are: " + " A (active), D (deleted), and I (inactive)."); } ds.DSState= dsState; // set new datastream id if not provided... if (dsID==null || dsID.length()==0) { ds.DatastreamID=w.newDatastreamID(); } else { if (dsID.indexOf(" ")!=-1) { throw new GeneralException("Datastream ids cannot contain spaces."); } if (dsID.indexOf("+")!=-1) { throw new GeneralException("Datastream ids cannot contain plusses."); } if (dsID.indexOf(":")!=-1) { throw new GeneralException("Datastream ids cannot contain colons."); } if (w.GetDatastream(dsID, null)!=null) { throw new GeneralException("A datastream already exists with ID: " + dsID); } else { ds.DatastreamID=dsID; } } // add version level attributes and // create new ds version id ... ds.DSVersionID=ds.DatastreamID + ".0"; ds.DSLabel=dsLabel; ds.DSLocation=dsLocation; if (dsLocation != null) { ValidationUtility.validateURL(dsLocation, false); } ds.DSFormatURI=formatURI; ds.DatastreamAltIDs = altIDs; ds.DSMIME=MIMEType; ds.DSChecksumType = Datastream.validateChecksumType(checksumType); if (checksum != null && checksumType != null) { String check = ds.getChecksum(); if (!checksum.equals(check)) { throw new ValidationException("Checksum Mismatch: " + check); } } Date nowUTC = Server.getCurrentDate(context); ds.DSCreateDT=nowUTC; AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="addDatastream"; audit.componentID=ds.DatastreamID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; w.getAuditRecords().add(audit); w.addDatastream(ds, true); w.commit("Added a new datastream"); return ds.DatastreamID; } finally { finishModification(w, "addDatastream"); } } public String addDisseminator(Context context, String pid, String bDefPid, String bMechPid, String dissLabel, DSBindingMap bindingMap, String dissState, String logMessage) throws ServerException { DOWriter w=null; try { LOG.info("Entered addDisseminator"); m_fedoraXACMLModule.enforceAddDisseminator(context, pid, bDefPid, bMechPid, dissState); checkDisseminatorLabel(dissLabel); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); Disseminator diss = new Disseminator(); diss.isNew=true; diss.parentPID = pid; if (!dissState.equals("A") && !dissState.equals("D") && !dissState.equals("I")) { throw new InvalidStateException("The disseminator state of \"" + dissState + "\" is invalid. The allowed values for state are: " + " A (active), D (deleted), and I (inactive)."); } diss.dissState= dissState; diss.dissLabel = dissLabel; diss.bMechID = bMechPid; diss.bDefID = bDefPid; Date nowUTC = Server.getCurrentDate(context); diss.dissCreateDT = nowUTC; // See if we can get the new disseminator ID from the RecoveryContext String dissID = null; if (context instanceof RecoveryContext) { RecoveryContext rContext = (RecoveryContext) context; dissID = rContext.getRecoveryValue(Constants.RECOVERY.DISSEMINATOR_ID.uri); } if (dissID == null) { diss.dissID = w.newDisseminatorID(); } else { diss.dissID = dissID; LOG.debug("Using new dissID from recovery context"); } diss.dissVersionID = diss.dissID + ".0"; // Generate the binding map ID here - ignore the value passed in // and set the field on both the disseminator and the binding map, // then set the disseminator's binding map to the one passed in. diss.dsBindMapID=w.newDatastreamBindingMapID(); bindingMap.dsBindMapID=diss.dsBindMapID; diss.dsBindMap=bindingMap; AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="addDisseminator"; audit.componentID=diss.dissID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; w.getAuditRecords().add(audit); w.addDisseminator(diss); w.commit("Added a new disseminator"); return diss.dissID; } finally { finishModification(w, "addDisseminator"); } } public Date modifyDatastreamByReference(Context context, String pid, String datastreamId, String[] altIDs, String dsLabel, String mimeType, String formatURI, String dsLocation, String checksumType, String checksum, String logMessage, boolean force) throws ServerException { // check for valid xml name for datastream ID if ( datastreamId!=null) { if(!XMLUtils.isWellFormedXMLName(datastreamId)) { throw new InvalidXMLNameException("Invalid syntax for " + "datastream ID. The datastream ID of \"" +datastreamId+"\" is not a valid XML Name"); } } if (datastreamId.equals("AUDIT") || datastreamId.equals("FEDORA-AUDITTRAIL")) { throw new GeneralException("Modification of the system-controlled AUDIT" + " datastream is not permitted."); } DOWriter w = null; try { LOG.info("Entered modifyDatastreamByReference"); // FIXME: enforceModifyDatastreamByReference expects a parameter // of dsState, we no longer have. I'm passing in a value // of "" but this could cause a problem. m_fedoraXACMLModule.enforceModifyDatastreamByReference(context, pid, datastreamId, altIDs, mimeType, formatURI, dsLocation, checksumType, checksum); checkDatastreamLabel(dsLabel); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); fedora.server.storage.types.Datastream orig=w.GetDatastream(datastreamId, null); Date nowUTC; // variable for ds modified date // some forbidden scenarios... if (orig.DSControlGrp.equals("X")) { throw new GeneralException("Inline XML datastreams must be modified by value, not by reference."); } if (orig.DSState.equals("D")) { throw new GeneralException("Changing attributes on deleted datastreams is forbidden."); } // A NULL INPUT PARM MEANS NO CHANGE TO DS ATTRIBUTE... // if input parms are null, the ds attribute should not be changed, // so set the parm values to the existing values in the datastream. if (dsLabel == null) dsLabel = orig.DSLabel; if (mimeType == null) mimeType = orig.DSMIME; if (formatURI == null) formatURI = orig.DSFormatURI; if (altIDs == null) altIDs = orig.DatastreamAltIDs; if (checksumType == null) checksumType = orig.DSChecksumType; else { checksumType = Datastream.validateChecksumType(checksumType); } // In cases where an empty attribute value is not allowed, then // NULL or EMPTY PARM means no change to ds attribute... if (dsLocation==null || dsLocation.equals("")) { if (orig.DSControlGrp.equals("M")) { // if managed content location is unspecified, // cause a copy of the prior content to be made at commit-time dsLocation=DatastreamManagedContent.COPY_SCHEME + orig.DSLocation; } else { dsLocation=orig.DSLocation; } } else { ValidationUtility.validateURL(dsLocation, false); } // if "force" is false and the mime type changed, validate the // original datastream with respect to any disseminators it is // involved in, and keep a record of that information for later // (so we can determine whether the mime type change would cause // data contract invalidation) Map oldValidationReports = null; if ( !mimeType.equals(orig.DSMIME) && !force) { oldValidationReports = getAllBindingMapValidationReports( context, w, datastreamId); } // instantiate the right class of datastream // (inline xml "X" datastreams have already been rejected) Datastream newds; if (orig.DSControlGrp.equals("M")) { newds=new DatastreamManagedContent(); } else { newds=new DatastreamReferencedContent(); } // update ds attributes that are common to all versions... // first, those that cannot be changed by client... newds.DatastreamID=orig.DatastreamID; newds.DSControlGrp=orig.DSControlGrp; newds.DSInfoType=orig.DSInfoType; // next, those that can be changed by client... newds.DSState = orig.DSState; newds.DSVersionable=orig.DSVersionable; // update ds version-level attributes, and // make sure ds gets a new version id newds.DSVersionID=w.newDatastreamID(datastreamId); newds.DSLabel=dsLabel; newds.DSMIME = mimeType; newds.DSFormatURI=formatURI; newds.DatastreamAltIDs = altIDs; nowUTC = Server.getCurrentDate(context); newds.DSCreateDT=nowUTC; //newds.DSSize will be computed later newds.DSLocation=dsLocation; newds.DSChecksumType = checksumType; // next, add the datastream via the object writer w.addDatastream(newds, orig.DSVersionable); // if a checksum is passed in verify that the checksum computed for the datastream // matches the one that is passed in. if (checksum != null) { if (checksumType == null) newds.DSChecksumType = orig.DSChecksumType; String check = newds.getChecksum(); if (!checksum.equals(check)) { throw new ValidationException("Checksum Mismatch: " + check); } } // add the audit record fedora.server.storage.types.AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="modifyDatastreamByReference"; audit.componentID=newds.DatastreamID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; w.getAuditRecords().add(audit); // if all went ok, check if we need to validate, then commit. if (oldValidationReports != null) { // mime changed and force=false rejectMimeChangeIfCausedInvalidation( oldValidationReports, getAllBindingMapValidationReports(context, w, datastreamId)); } w.commit(logMessage); return nowUTC; } finally { finishModification(w, "modifyDatastreamByReference"); } } public Date modifyDatastreamByValue(Context context, String pid, String datastreamId, String[] altIDs, String dsLabel, String mimeType, String formatURI, InputStream dsContent, String checksumType, String checksum, String logMessage, boolean force) throws ServerException { // check for valid xml name for datastream ID if ( datastreamId!=null) { if(!XMLUtils.isWellFormedXMLName(datastreamId)) { throw new InvalidXMLNameException("Invalid syntax for " + "datastream ID. The datastream ID of \"" +datastreamId+"\" is not a valid XML Name"); } } if (datastreamId.equals("AUDIT") || datastreamId.equals("FEDORA-AUDITTRAIL")) { throw new GeneralException("Modification of the system-controlled AUDIT" + " datastream is not permitted."); } DOWriter w=null; boolean mimeChanged = false; try { LOG.info("Entered modifyDatastreamByValue"); // FIXME: enforceModifyDatastreamByReference expects a parameter // of dsState, we no longer have. I'm passing in a value // of "" but this could cause a problem. m_fedoraXACMLModule.enforceModifyDatastreamByValue(context, pid, datastreamId, altIDs, mimeType, formatURI, checksumType, checksum); checkDatastreamLabel(dsLabel); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); fedora.server.storage.types.Datastream orig=w.GetDatastream(datastreamId, null); // some forbidden scenarios... if (orig.DSState.equals("D")) { throw new GeneralException("Changing attributes on deleted datastreams is forbidden."); } if (!orig.DSControlGrp.equals("X")) { throw new GeneralException("Only content of inline XML datastreams may" + " be modified by value.\n" + "Use modifyDatastreamByReference instead."); } if (orig.DatastreamID.equals("METHODMAP") || orig.DatastreamID.equals("DSINPUTSPEC") || orig.DatastreamID.equals("WSDL")) { throw new GeneralException("METHODMAP, DSINPUTSPEC, and WSDL datastreams cannot be modified."); } // A NULL INPUT PARM MEANS NO CHANGE TO DS ATTRIBUTE... // if input parms are null, the ds attribute should not be changed, // so set the parm values to the existing values in the datastream. if (dsLabel == null) dsLabel = orig.DSLabel; if (mimeType == null) mimeType = orig.DSMIME; if (formatURI == null) formatURI = orig.DSFormatURI; if (altIDs == null) altIDs = orig.DatastreamAltIDs; if (checksumType == null) checksumType = orig.DSChecksumType; else { checksumType = Datastream.validateChecksumType(checksumType); } // If "force" is false and the mime type changed, validate the // original datastream with respect to any disseminators it is // involved in, and keep a record of that information for later // (so we can determine whether the mime type change would cause // data contract invalidation) Map oldValidationReports = null; if ( !mimeType.equals(orig.DSMIME) && !force) { oldValidationReports = getAllBindingMapValidationReports( context, w, datastreamId); } DatastreamXMLMetadata newds=new DatastreamXMLMetadata(); newds.DSMDClass=((DatastreamXMLMetadata) orig).DSMDClass; if (dsContent==null) { // If the dsContent input stream parm is null, // that means "do not change the content". // Accordingly, here we just make a copy of the old content. newds.xmlContent = ((DatastreamXMLMetadata) orig).xmlContent; } else { // If it's not null, use it newds.xmlContent = getEmbeddableXML(dsContent); // If it's a RELS-EXT datastream, do validation if (orig.DatastreamID.equals("RELS-EXT")){ validateRelsExt(pid, new ByteArrayInputStream( ((DatastreamXMLMetadata) newds).xmlContent)); } } // update ds attributes that are common to all versions... // first, those that cannot be changed by client... newds.DatastreamID=orig.DatastreamID; newds.DSControlGrp=orig.DSControlGrp; newds.DSInfoType=orig.DSInfoType; // next, those that can be changed by client... newds.DSState = orig.DSState; newds.DSVersionable = orig.DSVersionable; // update ds version level attributes, and // make sure ds gets a new version id newds.DSVersionID=w.newDatastreamID(datastreamId); newds.DSLabel=dsLabel; newds.DatastreamAltIDs=altIDs; newds.DSMIME=mimeType; newds.DSFormatURI=formatURI; Date nowUTC = Server.getCurrentDate(context); newds.DSCreateDT=nowUTC; newds.DSChecksumType = checksumType; // next, add the datastream via the object writer w.addDatastream(newds, orig.DSVersionable); // if a checksum is passed in verify that the checksum computed for the datastream // matches the one that is passed in. if (checksum != null) { String check = newds.getChecksum(); if (!checksum.equals(check)) { throw new ValidationException("Checksum Mismatch: " + check); } } // add the audit record fedora.server.storage.types.AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="modifyDatastreamByValue"; audit.componentID=newds.DatastreamID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; w.getAuditRecords().add(audit); // if all went ok, check if we need to validate, then commit. if (oldValidationReports != null) { // mime changed and force=false rejectMimeChangeIfCausedInvalidation( oldValidationReports, getAllBindingMapValidationReports(context, w, datastreamId)); } w.commit(logMessage); return nowUTC; } finally { finishModification(w, "modifyDatastreamByValue"); } } public Date modifyDisseminator(Context context, String pid, String disseminatorId, String bMechPid, String dissLabel, DSBindingMap dsBindingMap, String dissState, String logMessage, boolean force) throws ServerException { DOWriter w=null; DOReader r=null; try { LOG.info("Entered modifyDisseminator"); m_fedoraXACMLModule.enforceModifyDisseminator(context, pid, disseminatorId, bMechPid, dissState); checkDisseminatorLabel(dissLabel); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); fedora.server.storage.types.Disseminator orig=w.GetDisseminator(disseminatorId, null); String oldValidationReport = null; if (!force) { oldValidationReport = getBindingMapValidationReport(context, w, orig.bMechID); } r=m_manager.getReader(Server.USE_DEFINITIVE_STORE, context,pid); // FIXME: Unnecessary? Is // there a reason "w" isn't // used for the call below? Date[] d=r.getDisseminatorVersions(disseminatorId); Disseminator newdiss=new Disseminator(); // use original diss values for attributes that can't be changed by client newdiss.dissID=orig.dissID; newdiss.bDefID=orig.bDefID; newdiss.parentPID=orig.parentPID; // make sure disseminator has a new version id newdiss.dissVersionID=w.newDisseminatorID(disseminatorId); // make sure disseminator has a new version date Date nowUTC = Server.getCurrentDate(context); newdiss.dissCreateDT=nowUTC; // for testing; null indicates a new (uninitialized) instance // of dsBindingMap was passed in which is what you get if // you pass null in for dsBindingMap using MangementConsole if (dsBindingMap.dsBindMapID!=null) { newdiss.dsBindMap=dsBindingMap; } else { newdiss.dsBindMap=orig.dsBindMap; } // make sure dsBindMapID has a different id newdiss.dsBindMapID=w.newDatastreamBindingMapID(); newdiss.dsBindMap.dsBindMapID=w.newDatastreamBindingMapID(); // NULL INPUT PARMS MEANS NO CHANGE in these cases: // set any diss attributes whose input parms value // is NULL to the original attribute value on the disseminator if (dissLabel==null) { //if (dissLabel==null || dissLabel.equals("")) { newdiss.dissLabel=orig.dissLabel; } else { newdiss.dissLabel=dissLabel; } // NULL OR "" INPUT PARM MEANS NO CHANGE: // for diss attributes whose values MUST NOT be empty, // either NULL or "" on the input parm indicates no change // (keep original value) if (bMechPid==null || bMechPid.equals("")) { newdiss.bMechID=orig.bMechID; } else { newdiss.bMechID=bMechPid; } if (dissState==null || dissState.equals("")) { // If reference unspecified leave state unchanged newdiss.dissState=orig.dissState; } else { // Check that supplied value for state is one of the allowable values if (!dissState.equals("A") && !dissState.equals("D") && !dissState.equals("I")) { throw new InvalidStateException("The disseminator state of \"" + dissState + "\" is invalid. The allowed values for state are: " + " A (active), D (deleted), and I (inactive)."); } newdiss.dissState=dissState; } // just add the disseminator w.addDisseminator(newdiss); if (!orig.dissState.equals(newdiss.dissState)) { w.setDisseminatorState(disseminatorId, newdiss.dissState); } // add the audit record fedora.server.storage.types.AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="modifyDisseminator"; audit.componentID=newdiss.dissID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; w.getAuditRecords().add(audit); // if all went ok, check if we need to validate, then commit. if (!force && oldValidationReport == null) { String cause = getBindingMapValidationReport(context, w, newdiss.bMechID); if (cause != null) { throw new GeneralException("That change would invalidate " + "the disseminator: " + cause); } } w.commit(logMessage); return nowUTC; } finally { finishModification(w, "modifyDisseminator"); } } public Date[] purgeDatastream(Context context, String pid, String datastreamID, Date startDT, Date endDT, String logMessage, boolean force) throws ServerException { if (force) { throw new GeneralException("Forced datastream removal is not " + "yet supported."); } DOWriter w=null; try { LOG.info("Entered purgeDatastream"); m_fedoraXACMLModule.enforcePurgeDatastream(context, pid, datastreamID, endDT); w=m_manager.getWriter(Server.USE_DEFINITIVE_STORE, context, pid); Date[] deletedDates=w.removeDatastream(datastreamID, startDT, endDT); // check if there's at least one version with this id... if (w.GetDatastream(datastreamID, null)==null) { // if deleting would result in no versions remaining, // only continue if there are no disseminators that use // this datastream. // to do this, we must look through all versions of every // disseminator, regardless of state SimpleDateFormat formatter=new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); ArrayList usedList=new ArrayList(); if (datastreamID.equals("DC")) { usedList.add("The default disseminator"); } // ...for each disseminator Disseminator[] disses=w.GetDisseminators(null, null); for (int i=0; i0) { StringBuffer msg=new StringBuffer(); msg.append("Cannot purge entire datastream because it\n"); msg.append("is used by the following disseminators:"); for (int i=0; i0) { buf.append(", "); } buf.append(formatter.format(deletedDates[i])); } buf.append(") and all associated audit records."); return buf.toString(); } public Datastream getDatastream(Context context, String pid, String datastreamID, Date asOfDateTime) throws ServerException { try { LOG.info("Entered getDatastream"); m_fedoraXACMLModule.enforceGetDatastream(context, pid, datastreamID, asOfDateTime); DOReader r=m_manager.getReader(Server.GLOBAL_CHOICE, context, pid); return r.GetDatastream(datastreamID, asOfDateTime); } finally { LOG.info("Exiting getDatastream"); } } public Datastream[] getDatastreams(Context context, String pid, Date asOfDateTime, String state) throws ServerException { try { LOG.info("Entered getDatastreams"); m_fedoraXACMLModule.enforceGetDatastreams(context, pid, asOfDateTime, state); DOReader r=m_manager.getReader(Server.GLOBAL_CHOICE, context, pid); return r.GetDatastreams(asOfDateTime, state); } finally { LOG.info("Exiting getDatastreams"); } } public Datastream[] getDatastreamHistory(Context context, String pid, String datastreamID) throws ServerException { try { LOG.info("Entered getDatastreamHistory"); m_fedoraXACMLModule.enforceGetDatastreamHistory(context, pid, datastreamID); DOReader r=m_manager.getReader(Server.GLOBAL_CHOICE, context, pid); Date[] versionDates=r.getDatastreamVersions(datastreamID); Datastream[] versions=new Datastream[versionDates.length]; for (int i=0; ims2) return 1; return 0; } } public Date[] purgeDisseminator(Context context, String pid, String disseminatorID, Date endDT, String logMessage) throws ServerException { DOWriter w=null; try { LOG.info("Entered purgeDisseminator"); m_fedoraXACMLModule.enforcePurgeDisseminator(context, pid, disseminatorID, endDT); w=m_manager.getWriter(Server.GLOBAL_CHOICE, context, pid); Date start=null; Date[] deletedDates=w.removeDisseminator(disseminatorID, start, endDT); // add an explanation of what happened to the user-supplied message. if (logMessage == null) { logMessage = ""; } else { logMessage += " . . . "; } logMessage += getPurgeLogMessage("disseminator", disseminatorID, start, endDT, deletedDates); Date nowUTC = Server.getCurrentDate(context); fedora.server.storage.types.AuditRecord audit=new fedora.server.storage.types.AuditRecord(); audit.id=w.newAuditRecordID(); audit.processType="Fedora API-M"; audit.action="purgeDisseminator"; audit.componentID=disseminatorID; audit.responsibility=context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date=nowUTC; audit.justification=logMessage; // Normally we associate an audit record with a specific version // of a disseminator, but in this case we are talking about a range // of versions. So we'll just add it to the object, but not associate // it with anything. w.getAuditRecords().add(audit); // It looks like all went ok, so commit // ... then give the response w.commit(logMessage); return deletedDates; } finally { finishModification(w, "purgeDisseminator"); } } public Disseminator getDisseminator(Context context, String pid, String disseminatorId, Date asOfDateTime) throws ServerException { try { LOG.info("Entered getDisseminator"); m_fedoraXACMLModule.enforceGetDisseminator(context, pid, disseminatorId, asOfDateTime); DOReader r=m_manager.getReader(Server.GLOBAL_CHOICE, context, pid); return r.GetDisseminator(disseminatorId, asOfDateTime); } finally { LOG.info("Exiting getDisseminator"); } } public Disseminator[] getDisseminators(Context context, String pid, Date asOfDateTime, String dissState) throws ServerException { try { LOG.info("Entered getDisseminators"); m_fedoraXACMLModule.enforceGetDisseminators(context, pid, asOfDateTime, dissState); DOReader r=m_manager.getReader(Server.GLOBAL_CHOICE, context, pid); return r.GetDisseminators(asOfDateTime, dissState); } finally { LOG.info("Exiting getDisseminators"); } } public Disseminator[] getDisseminatorHistory(Context context, String pid, String disseminatorID) throws ServerException { try { LOG.info("Entered getDisseminatorHistory"); m_fedoraXACMLModule.enforceGetDisseminatorHistory(context, pid, disseminatorID); DOReader r=m_manager.getReader(Server.USE_DEFINITIVE_STORE, context, pid); Date[] versionDates=r.getDisseminatorVersions(disseminatorID); Disseminator[] versions=new Disseminator[versionDates.length]; for (int i=0; i 0) { LOG.debug("Reserving and returning PID_LIST " + "from recovery context"); m_manager.reservePIDs(pidList); } } if (pidList == null || pidList.length == 0) { pidList = m_manager.getNextPID(numPIDs, namespace); } return pidList; } finally { LOG.info("Exiting getNextPID"); } } public class DisseminatorDateComparator implements Comparator { public int compare(Object o1, Object o2) { long ms1=((Disseminator) o1).dissCreateDT.getTime(); long ms2=((Disseminator) o2).dissCreateDT.getTime(); if (ms1ms2) return 1; return 0; } } public String putTempStream(Context context, InputStream in) throws StreamWriteException, AuthzException { m_fedoraXACMLModule.enforceUpload(context); // first clean up after old stuff long minStartTime=System.currentTimeMillis()-(60*1000*m_uploadStorageMinutes); ArrayList removeList=new ArrayList(); Iterator iter=m_uploadStartTime.keySet().iterator(); while (iter.hasNext()) { String id=(String) iter.next(); Long startTime=(Long) m_uploadStartTime.get(id); if (startTime.longValue() * This will ensure that the xml is: *
    *
  • well-formed. If not, an exception will be raised.
  • *
  • encoded in UTF-8. It will be converted otherwise.
  • *
  • devoid of processing instructions. These will be stripped if present.
  • *
  • devoid of DOCTYPE declarations. These will be stripped if present.
  • *
  • devoid of internal entity references. These will be expanded if present.
  • *
*

*/ private byte[] getEmbeddableXML(InputStream in) throws GeneralException { // parse with xerces and re-serialize the fixed xml to a byte array try { ByteArrayOutputStream out = new ByteArrayOutputStream(); OutputFormat fmt = new OutputFormat("XML", "UTF-8", true); fmt.setIndent(2); fmt.setLineWidth(120); fmt.setPreserveSpace(false); fmt.setOmitXMLDeclaration(true); fmt.setOmitDocumentType(true); XMLSerializer ser = new XMLSerializer(out, fmt); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(in); ser.serialize(doc); return out.toByteArray(); } catch (Exception e) { String message = e.getMessage(); if (message == null) message = ""; throw new GeneralException("XML was not well-formed. " + message, e); } } /** * Get a string indicating whether the associated binding map (or an empty * binding map, if none is found) is valid or invalid according to the * data contract defined by the indicated behavior mechanism. * * Returns null if valid, otherwise returns a String explaining why not. * * This assumes the indicated bMech actually exists, and the binding * map, if it exists and specifies any datastreams, refers to existing * datastreams within the object. If these conditions are not met, an * exception is thrown. */ private String getBindingMapValidationReport(Context context, DOReader doReader, String bMechPID) throws ServerException { // find the associated datastream binding map, else use an empty one. DSBindingMapAugmented augMap = new DSBindingMapAugmented(); DSBindingMapAugmented[] augMaps = doReader.GetDSBindingMaps(null); for (int i = 0; i < augMaps.length; i++) { if (augMaps[i].dsBindMechanismPID.equals(bMechPID)) { augMap = augMaps[i]; } } // load the bmech, then validate the bindings BMechReader mReader = m_manager.getBMechReader(Server.USE_DEFINITIVE_STORE, context, bMechPID); BMechDSBindSpec spec = mReader.getServiceDSInputSpec(null); return spec.validate(augMap.dsBindingsAugmented); } /** * Get a combined report indicating failure or success of data contract * validation for every disseminator in the given object that the indicated * datastream is bound to. * * The returned map's keys will be Disseminator objects. * The values will be null in the case of successful validation, * or Strings (explaining why) in the case of failure. * * This assumes that all bMechs specified in the binding maps of the * disseminators that use the indicated datastream actually exist, and * the binding map, if it exists and specifies any datastreams, refers to * existing datastreams within the object. If these conditions are not * met, an exception is thrown. */ private Map getAllBindingMapValidationReports(Context context, DOReader doReader, String dsID) throws ServerException { HashMap map = new HashMap(); // for all disseminators in the object, Disseminator[] disses = doReader.GetDisseminators(null, null); for (int i = 0; i < disses.length; i++) { DSBinding[] bindings = disses[i].dsBindMap.dsBindings; boolean isUsed = false; // check each binding to see if it's the indicated datastream for (int j = 0; j < bindings.length && !isUsed; j++) { if (bindings[j].datastreamID.equals(dsID)) isUsed = true; } if (isUsed) { // if it's used, add it's validation information to the map. map.put(disses[i], getBindingMapValidationReport(context, doReader, disses[i].bMechID)); } } return map; } private Map getNewFailedValidationReports(Map oldReport, Map newReport) { HashMap map = new HashMap(); Iterator newIter = newReport.keySet().iterator(); // For each disseminator in the new report: while (newIter.hasNext()) { Disseminator diss = (Disseminator) newIter.next(); String failedMessage = (String) newReport.get(diss); // Did it fail in the new report . . . if (failedMessage != null) { // . . . but not in the old one? if (oldReport.get(diss) == null) { map.put(diss, failedMessage); } } } return map; } private void rejectMimeChangeIfCausedInvalidation(Map oldReports, Map newReports) throws ServerException { Map causedFailures = getNewFailedValidationReports(oldReports, newReports); int numFailures = causedFailures.keySet().size(); if (numFailures > 0) { StringBuffer buf = new StringBuffer(); buf.append("This mime type change would invalidate " + numFailures + " disseminator(s):"); Iterator iter = causedFailures.keySet().iterator(); while (iter.hasNext()) { Disseminator diss = (Disseminator) iter.next(); String reason = (String) causedFailures.get(diss); buf.append("\n" + diss.dissID + ": " + reason); } throw new GeneralException(buf.toString()); } } private void validateRelsExt(String pid, InputStream relsext) throws ServerException { // RELATIONSHIP METADATA VALIDATION: try { RelsExtValidator deser=new RelsExtValidator("UTF-8", false); if (relsext!=null) { LOG.debug("API-M: Validating RELS-EXT datastream..."); deser.deserialize(relsext, "info:fedora/" + pid); LOG.debug("API-M: RELS-EXT datastream passed validation."); } } catch (Exception e) { String message = e.getMessage(); if (message == null) message = e.getClass().getName(); throw new GeneralException("RELS-EXT validation failed: " + message); } } private void checkDatastreamID(String id) throws ValidationException { checkString(id, "Datastream id", ValidationConstants.DATASTREAM_ID_MAXLEN, ValidationConstants.DATASTREAM_ID_BADCHARS); } private void checkDatastreamLabel(String label) throws ValidationException { checkString(label, "Datastream label", ValidationConstants.DATASTREAM_LABEL_MAXLEN, null); } private void checkDisseminatorID(String id) throws ValidationException { checkString(id, "Disseminator id", ValidationConstants.DISSEMINATOR_ID_MAXLEN, ValidationConstants.DISSEMINATOR_ID_BADCHARS); } private void checkDisseminatorLabel(String label) throws ValidationException { checkString(label, "Disseminator label", ValidationConstants.DISSEMINATOR_LABEL_MAXLEN, null); } private void checkObjectLabel(String label) throws ValidationException { checkString(label, "Object label", ValidationConstants.OBJECT_LABEL_MAXLEN, null); } private void checkString(String string, String kind, int maxLen, char[] badChars) throws ValidationException { if (string != null) { if (string.length() > maxLen) { throw new ValidationException(kind + " is too long. Maximum " + "length is " + maxLen + " characters."); } else if (badChars != null) { for (int i = 0; i < badChars.length; i++) { char c = badChars[i]; if (string.indexOf(c) != -1) { throw new ValidationException(kind + " contains a " + "'" + c + "', but that character is not " + "allowed."); } } } } } public boolean adminPing(Context context) throws ServerException { m_fedoraXACMLModule.enforceAdminPing(context); return true; } /** * Creates a new audit record and adds it to the digital object audit trail. */ private void addAuditRecord(Context context, DOWriter w, String action, String componentID, String justification, Date nowUTC) throws ServerException { AuditRecord audit = new AuditRecord(); audit.id = w.newAuditRecordID(); audit.processType = "Fedora API-M"; audit.action = action; audit.componentID = componentID; audit.responsibility = context.getSubjectValue(Constants.SUBJECT.LOGIN_ID.uri); audit.date = nowUTC; audit.justification = justification; w.getAuditRecords().add(audit); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy