Java tutorial
/** * Copyright 2008 The University of North Carolina at Chapel Hill * * Licensed under 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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.unc.lib.dl.services; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.xml.transform.Source; import javax.xml.transform.TransformerException; import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.jdom.input.SAXBuilder; import org.joda.time.DateTime; import edu.unc.lib.dl.acl.util.GroupsThreadStore; import edu.unc.lib.dl.fedora.AccessClient; import edu.unc.lib.dl.fedora.FedoraException; import edu.unc.lib.dl.fedora.ManagementClient; import edu.unc.lib.dl.fedora.ManagementClient.ChecksumType; import edu.unc.lib.dl.fedora.ManagementClient.State; import edu.unc.lib.dl.fedora.NotFoundException; import edu.unc.lib.dl.fedora.PID; import edu.unc.lib.dl.fedora.types.MIMETypedStream; import edu.unc.lib.dl.ingest.IngestException; import edu.unc.lib.dl.ingest.aip.AIPIngestPipeline; import edu.unc.lib.dl.ingest.aip.ArchivalInformationPackage; import edu.unc.lib.dl.ingest.aip.DepositRecord; import edu.unc.lib.dl.ingest.sip.SIPProcessor; import edu.unc.lib.dl.ingest.sip.SIPProcessorFactory; import edu.unc.lib.dl.ingest.sip.SubmissionInformationPackage; import edu.unc.lib.dl.schematron.SchematronValidator; import edu.unc.lib.dl.update.UpdateException; import edu.unc.lib.dl.util.Checksum; import edu.unc.lib.dl.util.ContainerContentsHelper; import edu.unc.lib.dl.util.ContentModelHelper; import edu.unc.lib.dl.util.ContentModelHelper.Datastream; import edu.unc.lib.dl.util.IllegalRepositoryStateException; import edu.unc.lib.dl.util.PremisEventLogger; import edu.unc.lib.dl.util.PremisEventLogger.Type; import edu.unc.lib.dl.util.TripleStoreQueryService; import edu.unc.lib.dl.xml.JDOMNamespaceUtil; import edu.unc.lib.dl.xml.ModsXmlHelper; /** * This class orchestrates the transactions that modify repository objects and update ancillary services. * * @author count0 * */ public class DigitalObjectManagerImpl implements DigitalObjectManager { private static final Log log = LogFactory.getLog(DigitalObjectManagerImpl.class); private boolean available = false; private String availableMessage = "The repository manager is not available yet."; private AccessClient accessClient = null; private AIPIngestPipeline aipIngestPipeline = null; private ManagementClient managementClient = null; private SIPProcessorFactory sipProcessorFactory = null; private OperationsMessageSender operationsMessageSender = null; private TripleStoreQueryService tripleStoreQueryService = null; private SchematronValidator schematronValidator = null; private BatchIngestQueue batchIngestQueue = null; private BatchIngestTaskFactory batchIngestTaskFactory = null; private MailNotifier mailNotifier; private String submitterGroupsOverride = null; private PID collectionsPid = null; public String getSubmitterGroupsOverride() { return submitterGroupsOverride; } public void setSubmitterGroupsOverride(String submitterGroupsOverride) { this.submitterGroupsOverride = submitterGroupsOverride; } public void setMailNotifier(MailNotifier mailNotifier) { this.mailNotifier = mailNotifier; } public synchronized void setAvailable(boolean available, String message) { this.available = available; this.availableMessage = message; if (!this.available) { this.mailNotifier.sendAdministratorMessage("DO manager became unavailable", message); } } public synchronized void setAvailable(boolean available) { this.setAvailable(available, "Repository undergoing maintenance, please contact staff for more information."); } public SchematronValidator getSchematronValidator() { return schematronValidator; } public void setSchematronValidator(SchematronValidator schematronValidator) { this.schematronValidator = schematronValidator; } public DigitalObjectManagerImpl() { } public IngestResult addToIngestQueue(SubmissionInformationPackage sip, DepositRecord record) throws IngestException { IngestResult result = new IngestResult(); ArchivalInformationPackage aip = null; try { availableCheck(); if (record.getDepositedBy() == null) { throw new IngestException("A user must be supplied for every ingest"); } // lookup the processor for this SIP SIPProcessor processor = this.getSipProcessorFactory().getSIPProcessor(sip); // process the SIP into a standard AIP aip = processor.createAIP(sip, record); // run routine AIP processing steps aip = this.getAipIngestPipeline().processAIP(aip); // Add depositor as an email notification recipient if provided try { URI email; if (record.getDepositorEmail() != null) { email = new URI(record.getDepositorEmail()); } else { // Attempt to email the user by onyen since no email was provided email = new URI(record.getOwner() + "@email.unc.edu"); } if (email != null) aip.setEmailRecipients(Collections.singletonList(email)); } catch (URISyntaxException e) { log.error("Invalid onyen, cannot create email URI", e); } // Add ingestor groups to the AIP for group forwarding if (this.getSubmitterGroupsOverride() != null) { aip.setSubmitterGroups(this.getSubmitterGroupsOverride()); } else { aip.setSubmitterGroups(GroupsThreadStore.getGroupString()); } aip.prepareIngest(); // move the AIP into the ingest queue. this.getBatchIngestQueue().add(aip.getTempFOXDir()); result.originalDepositID = aip.getDepositRecord().getPid(); result.derivedPIDs = aip.getTopPIDs(); return result; } catch (IngestException e) { // exception on AIP preparation, no transaction started log.error("Exception during submission or enqueuing", e); if (aip != null) { aip.delete(); } throw e; } } private void availableCheck() throws IngestException { if (!this.available) { throw new IngestException(this.availableMessage + " \nContact repository staff for assistance."); } } /** * @param lastKnownGoodTime * @param pids */ private void dumpRollbackInfo(DateTime lastKnownGoodTime, List<PID> pids, String reason) { StringBuffer sb = new StringBuffer(); sb.append("DATA CORRUPTION LOG:\n").append("REASON:").append(reason).append("\n") .append("LAST KNOWN GOOD TIME: ").append(lastKnownGoodTime.toString()).append("\n") .append(pids.size()).append(" FEDORA PIDS CREATED OR MODIFIED:\n"); for (PID p : pids) { sb.append(p.getPid()).append("\n"); } log.error(sb.toString()); } public void addRelationship(PID subject, ContentModelHelper.Relationship rel, PID object) throws NotFoundException, IngestException { try { availableCheck(); this.getManagementClient().addObjectRelationship(subject, rel.getURI().toString(), object); } catch (FedoraException e) { if (e instanceof NotFoundException) { throw (NotFoundException) e; } else { throw new Error("Unexpected Fedora fault when adding relationship.", e); } } } /** * This method destroys a set of objects in Fedora, leaving no preservation data. It will update any ancillary * services and log delete events. * * @param pids * the PIDs of the objects to purge * @param message * the reason for the purge * @return a list of PIDs that were purged * @see edu.unc.lib.dl.services.DigitalObjectManager.purge() */ public List<PID> delete(PID pid, String user, String message) throws IngestException, NotFoundException { availableCheck(); // Prevent deletion of the repository object and the collections object if (pid.equals(ContentModelHelper.Administrative_PID.REPOSITORY.getPID()) || pid.equals(collectionsPid)) throw new IllegalRepositoryStateException("Cannot delete administrative object: " + pid); List<PID> deleted = new ArrayList<PID>(); // FIXME disallow delete of "/admin" folder // TODO add protected delete method for force initializing // Get all children and store for deletion List<PID> toDelete = this.getTripleStoreQueryService().fetchAllContents(pid); toDelete.add(pid); // gathering delete set, checking for object relationships // Find all relationships which refer to the pid being deleted List<PID> refs = this.getReferencesToContents(pid); refs.removeAll(toDelete); if (refs.size() > 0) { StringBuffer s = new StringBuffer(); s.append("Cannot delete ").append(pid) .append(" because it will break object references from these PIDs: "); for (PID b : refs) { s.append("\t").append(b); } throw new IngestException(s.toString()); } PID container = this.getTripleStoreQueryService().fetchContainer(pid); if (container == null) { throw new IllegalRepositoryStateException("Cannot find a container for the specified object: " + pid); } // begin transaction, must delete all content and modify parent or dump // rollback info PremisEventLogger logger = new PremisEventLogger(user); DateTime transactionStart = new DateTime(); Throwable thrown = null; List<PID> removed = new ArrayList<PID>(); removed.add(pid); try { // update container this.removeFromContainer(pid); Element event = logger.logEvent(PremisEventLogger.Type.DELETION, "Deleted " + deleted.size() + "contained object(s).", container); PremisEventLogger.addDetailedOutcome(event, "success", "Message: " + message, null); this.managementClient.writePremisEventsToFedoraObject(logger, container); // delete object and all of its children for (PID obj : toDelete) { try { this.getManagementClient().purgeObject(obj, message, false); deleted.add(obj); } catch (NotFoundException e) { log.error("Delete set referenced an object that didn't exist: " + pid.getPid(), e); } } // Send message to message queue informing it of the deletion(s) if (this.getOperationsMessageSender() != null) { this.getOperationsMessageSender().sendRemoveOperation(user, container, removed, null); } } catch (FedoraException fault) { log.error("Fedora threw an unexpected fault while deleting " + pid.getPid(), fault); thrown = fault; } catch (RuntimeException e) { this.setAvailable(false); log.error("Fedora threw an unexpected runtime exception while deleting " + pid.getPid(), e); thrown = e; } finally { if (thrown != null && toDelete.size() > deleted.size()) { // some objects not deleted List<PID> missed = new ArrayList<PID>(); missed.addAll(toDelete); missed.removeAll(deleted); this.dumpRollbackInfo(transactionStart, missed, "Could not complete delete of " + pid.getPid() + ", please purge objects and check container " + container.getPid() + "."); } } if (thrown != null) { throw new IngestException("There was a problem completing the delete operation", thrown); } return deleted; } public AccessClient getAccessClient() { return accessClient; } public AIPIngestPipeline getAipIngestPipeline() { return aipIngestPipeline; } ManagementClient getManagementClient() { return managementClient; } /** * Generates a list of referring object PIDs. Dependent objects are currently defined as those objects that refer to * the specified pid in RELS-EXT other than it's container. * * @param pid * the object depended upon * @return a list of dependent object PIDs */ private List<PID> getReferencesToContents(PID pid) { List<PID> refs = this.getTripleStoreQueryService().fetchObjectReferences(pid); if (!ContentModelHelper.Administrative_PID.REPOSITORY.equals(pid)) { PID container = this.getTripleStoreQueryService().fetchContainer(pid); refs.remove(container); } return refs; } public SIPProcessorFactory getSipProcessorFactory() { return sipProcessorFactory; } public TripleStoreQueryService getTripleStoreQueryService() { return tripleStoreQueryService; } /** * This must be called after properties are set. It checks for basic repository objects and throws a runtime * exception if they don't exist. */ public void init() { // throw a runtime exception? } public void purgeRelationship(PID subject, ContentModelHelper.Relationship rel, PID object) throws NotFoundException, IngestException { try { availableCheck(); this.getManagementClient().purgeObjectRelationship(subject, rel.getURI().toString(), object); } catch (FedoraException e) { if (e instanceof NotFoundException) { throw (NotFoundException) e; } else { throw new Error("Unexpected Fedora fault when purging relationship.", e); } } } /** * Just removes object from container, does not log this event. MUST finish operation or dump rollback info and * rethrow exception. * * @param pid * the PID of the object to remove * @return the PID of the old container * @throws FedoraException */ private PID removeFromContainer(PID pid) throws FedoraException { boolean relsextDone = false; PID parent = this.getTripleStoreQueryService().fetchContainer(pid); if (parent == null) { // Block removal of repo object if (ContentModelHelper.Administrative_PID.REPOSITORY.getPID().equals(pid)) return null; throw new NotFoundException("Found an object without a parent that is not the REPOSITORY"); } log.debug("removeFromContainer called on PID: " + parent.getPid()); try { // remove ir:contains statement to RELS-EXT relsextDone = this.getManagementClient().purgeObjectRelationship(parent, ContentModelHelper.Relationship.contains.getURI().toString(), pid); if (relsextDone == false) { log.error("failed to purge relationship: " + parent + " contains " + pid); } // if the parent is a container, then make it orderly List<URI> cmtypes = this.getTripleStoreQueryService().lookupContentModels(parent); if (cmtypes.contains(ContentModelHelper.Model.CONTAINER.getURI())) { // edit Contents XML of parent container to append/insert try { Document newXML; Document oldXML; MIMETypedStream mts = this.getAccessClient().getDatastreamDissemination(parent, "MD_CONTENTS", null); ByteArrayInputStream bais = new ByteArrayInputStream(mts.getStream()); oldXML = new SAXBuilder().build(bais); bais.close(); newXML = ContainerContentsHelper.remove(oldXML, pid); this.getManagementClient().modifyInlineXMLDatastream(parent, "MD_CONTENTS", false, "removing child object from this container", new ArrayList<String>(), "List of Contents", newXML); } catch (NotFoundException e) { // MD_CONTENTS was not found, so we will assume this is an unordered container } } } catch (JDOMException e) { IllegalRepositoryStateException irs = new IllegalRepositoryStateException( "Invalid XML for container MD_CONTENTS: " + parent.getPid(), parent, e); log.error(irs); throw irs; } catch (IOException e) { throw new Error("Should not get IOException for reading byte array input", e); } return parent; } public void setAccessClient(AccessClient accessClient) { this.accessClient = accessClient; } public void setAipIngestPipeline(AIPIngestPipeline aipIngestPipeline) { this.aipIngestPipeline = aipIngestPipeline; } public void setManagementClient(ManagementClient managementClient) { this.managementClient = managementClient; } public void setSipProcessorFactory(SIPProcessorFactory sipProcessorFactory) { this.sipProcessorFactory = sipProcessorFactory; } public void setTripleStoreQueryService(TripleStoreQueryService tripleStoreQueryService) { this.tripleStoreQueryService = tripleStoreQueryService; } @Override public String updateSourceData(PID pid, String dsid, File newDataFile, String checksum, String label, String mimetype, String user, String message) throws IngestException { availableCheck(); String result = null; PremisEventLogger logger = new PremisEventLogger(user); // make sure the datastream is source data if (!this.getTripleStoreQueryService().isSourceData(pid, dsid)) { throw new IngestException("You can only update source datastreams. (marked as <pid-uri> <" + ContentModelHelper.CDRProperty.sourceData + "> <ds-uri> in RELS-EXT)"); } // compare checksum if one is supplied if (checksum != null) { try { String sum = new Checksum().getChecksum(newDataFile); if (!checksum.trim().toLowerCase().equals(sum.toLowerCase())) { throw new IngestException("MD5 calculated for file (" + sum + ") does not match supplied checksum (" + checksum + ")"); } else { logger.logEvent(Type.DIGITAL_SIGNATURE_VALIDATION, "Validated MD5 signature for updated source data file", pid, dsid); } } catch (FileNotFoundException e1) { throw new IngestException("New source data file not found", e1); } catch (IOException e1) { throw new IngestException("There was a problem read the new source data file", e1); } } String newref = null; try { // Upload the new file and then update the datastream newref = this.getManagementClient().upload(newDataFile); result = this.getManagementClient().modifyDatastreamByReference(pid, dsid, false, message, null, label, mimetype, checksum, ChecksumType.MD5, newref); // update PREMIS log logger.logEvent(PremisEventLogger.Type.INGESTION, message, pid, dsid); this.managementClient.writePremisEventsToFedoraObject(logger, pid); } catch (FedoraException e) { throw new IngestException("Could not update the specified object.", e); } return result; } @Override public String updateDescription(PID pid, File newMODSFile, String checksum, String user, String message) throws IngestException { availableCheck(); String result = null; PremisEventLogger logger = new PremisEventLogger(user); // compare checksum if one is supplied if (checksum != null) { try { String sum = new Checksum().getChecksum(newMODSFile); if (!checksum.trim().toLowerCase().equals(sum.toLowerCase())) { throw new IngestException("MD5 calculated for file (" + sum + ") does not match supplied checksum (" + checksum + ")"); } else { logger.logEvent(Type.DIGITAL_SIGNATURE_VALIDATION, "Validated MD5 signature for updated descriptive metadata file", pid, "MD_DESCRIPTIVE"); } } catch (FileNotFoundException e1) { throw new IngestException("New MODS file not found", e1); } catch (IOException e1) { throw new IngestException("There was a problem reading the new MODS file", e1); } } // make sure the supplied XML is valid Element event = logger.logEvent(Type.VALIDATION, message, pid, "MD_DESCRIPTIVE"); Source source; try { source = new StreamSource(new FileInputStream(newMODSFile)); } catch (FileNotFoundException e1) { throw new Error("Unexpected exception", e1); } Document svrl = this.getSchematronValidator().validate(source, "vocabularies-mods"); if (!this.getSchematronValidator().hasFailedAssertions(svrl)) { PremisEventLogger.addDetailedOutcome(event, "MODS is valid", "The supplied MODS metadata meets all CDR vocabulary requirements.", null); } else { PremisEventLogger.addDetailedOutcome(event, "MODS is not valid", "The supplied MODS metadata does not meet CDR vocabulary requirements.", svrl.detachRootElement()); IngestException e = new IngestException( "The supplied descriptive metadata (MODS) does not meet CDR vocabulary requirements."); e.setErrorXML(logger.getAllEvents()); throw e; } // Detect if MODS is present by retrieving it. boolean modsExists = false; try { this.getAccessClient().getDatastreamDissemination(pid, "MD_DESCRIPTIVE", null); modsExists = true; } catch (FedoraException ignored) { } String modsID = "MD_DESCRIPTIVE"; String modsLabel = "Descriptive Metadata (MODS)"; Document modsContent; try { modsContent = new SAXBuilder().build(newMODSFile); } catch (JDOMException e1) { throw new Error("Unexpected JDOM parse exception", e1); } catch (IOException e1) { throw new Error("Unexpected IOException", e1); } try { if (modsExists) { result = this.getManagementClient().modifyInlineXMLDatastream(pid, modsID, false, message, new ArrayList<String>(), modsLabel, modsContent); logger.logEvent(Type.INGESTION, message, pid, modsID); } else { result = this.getManagementClient().addInlineXMLDatastream(pid, modsID, false, message, new ArrayList<String>(), modsLabel, true, modsContent); logger.logEvent(Type.CREATION, message, pid, modsID); } } catch (FedoraException e) { throw new IngestException("Could not update the specified object.", e); } // update object label based on new MODS title String label = ModsXmlHelper.getFormattedLabelText(modsContent.getRootElement()); if (label != null && label.trim().length() > 0) { try { this.getManagementClient().modifyObject(pid, label, "", State.ACTIVE, message); } catch (FedoraException e) { throw new IngestException("Could not update label for " + pid.getPid(), e); } } // Dublin Core crosswalk Document dc = new Document(); try { dc = ModsXmlHelper.transform(modsContent.getRootElement()); this.getManagementClient().modifyInlineXMLDatastream(pid, "DC", false, message, new ArrayList<String>(), "Internal XML Metadata", dc); String msg = "Metadata Object Description Schema (MODS) data transformed into Dublin Core (DC)."; logger.logDerivationEvent(PremisEventLogger.Type.NORMALIZATION, msg, pid, "MD_DESCRIPTIVE", "DC"); } catch (TransformerException e) { log.error("Cannot cross walk MODS to Dublin Core on update of " + pid.getPid(), e); } catch (FedoraException e) { log.error("Cannot cross walk MODS to Dublin Core on update of " + pid.getPid(), e); } // update PREMIS log try { this.managementClient.writePremisEventsToFedoraObject(logger, pid); } catch (FedoraException e) { log.error("Cannot log PREMIS events for " + pid.getPid(), e); } return result; } @Override public String addOrReplaceDatastream(PID pid, Datastream datastream, File content, String mimetype, String user, String message) throws UpdateException { return addOrReplaceDatastream(pid, datastream, null, content, mimetype, user, message); } @Override public String addOrReplaceDatastream(PID pid, Datastream datastream, String label, File content, String mimetype, String user, String message) throws UpdateException { String dsLabel = datastream.getLabel(); if (label != null) dsLabel = label; List<String> datastreamNames = tripleStoreQueryService.listDisseminators(pid); log.debug("Current datastreams: " + datastreamNames); String datastreamName = pid.getURI() + "/" + datastream.getName(); log.debug("Adding or replacing datastream: " + datastreamName); try { if (datastream.getControlGroup().equals(ContentModelHelper.ControlGroup.INTERNAL)) { // Handle inline datastreams if (datastreamNames.contains(datastreamName)) { log.debug("Replacing preexisting internal datastream " + datastreamName); return this.managementClient.modifyDatastreamByValue(pid, datastream.getName(), false, message, new ArrayList<String>(), datastream.getLabel(), mimetype, null, null, content); } else { log.debug("Adding internal datastream " + datastreamName); return this.managementClient.addInlineXMLDatastream(pid, datastream.getName(), false, message, new ArrayList<String>(), datastream.getLabel(), datastream.isVersionable(), content); } } else if (datastream.getControlGroup().equals(ContentModelHelper.ControlGroup.MANAGED)) { // Handle managed datastreams String dsLocation = managementClient.upload(content); if (datastreamNames.contains(datastreamName)) { log.debug("Replacing preexisting managed datastream " + datastreamName); return managementClient.modifyDatastreamByReference(pid, datastream.getName(), false, message, Collections.<String>emptyList(), dsLabel, mimetype, null, null, dsLocation); } else { log.debug("Adding managed datastream " + datastreamName); return managementClient.addManagedDatastream(pid, datastream.getName(), false, message, Collections.<String>emptyList(), dsLabel, datastream.isVersionable(), mimetype, dsLocation); } } } catch (FedoraException e) { throw new UpdateException( "Failed to modify datastream " + datastream.getName() + " for " + pid.getPid(), e); } return null; } @Override public void move(List<PID> moving, PID destination, String user, String message) throws IngestException { availableCheck(); PremisEventLogger logger = new PremisEventLogger(user); List<PID> destinationPath = this.getTripleStoreQueryService().lookupRepositoryAncestorPids(destination); if (destinationPath == null || destinationPath.size() == 0) throw new IngestException("Cannot find the destination folder: " + destinationPath); // destination is container List<URI> cmtypes = this.getTripleStoreQueryService().lookupContentModels(destination); if (!cmtypes.contains(ContentModelHelper.Model.CONTAINER.getURI())) { throw new IngestException( "The destination is not a folder: " + destinationPath + " " + destination.getPid()); } for (PID pid : moving) { if (pid.equals(destination)) throw new IngestException( "The destination folder and one of the moving objects are the same: " + destination); for (PID destPid : destinationPath) { if (pid.equals(destPid)) throw new IngestException( "The destination folder is below one of the moving objects: " + destination); } } // check for duplicate PIDs in incoming list Set<PID> noDups = new HashSet<PID>(moving.size()); noDups.addAll(moving); if (noDups.size() < moving.size()) { throw new IngestException("The list of moving PIDs contains duplicates"); } else { noDups = null; } // this is the beginning of the transaction DateTime knownGoodTime = new DateTime(); Throwable thrown = null; boolean relationshipsUpdated = false; boolean destContentInventoryUpdated = false; boolean eventsLoggedOkay = false; List<PID> oldParents = new ArrayList<PID>(); List<PID> reordered = new ArrayList<PID>(); try { // remove pids from old containers, add to new, log for (PID pid : moving) { PID oldParent = this.removeFromContainer(pid); oldParents.add(oldParent); this.getManagementClient().addObjectRelationship(destination, ContentModelHelper.Relationship.contains.getURI().toString(), pid); logger.logEvent(Type.MIGRATION, "object moved from Container " + oldParent.getPid() + " to " + destination.getPid(), pid); } // edit Contents XML of new parent container to append/insert Document newXML; Document oldXML; boolean exists = true; try { MIMETypedStream mts = this.getAccessClient().getDatastreamDissemination(destination, "MD_CONTENTS", null); ByteArrayInputStream bais = new ByteArrayInputStream(mts.getStream()); try { oldXML = new SAXBuilder().build(bais); bais.close(); } catch (JDOMException e) { throw new IllegalRepositoryStateException("Cannot parse MD_CONTENTS: " + destination); } catch (IOException e) { throw new Error(e); } } catch (NotFoundException e) { oldXML = new Document(); Element structMap = new Element("structMap", JDOMNamespaceUtil.METS_NS).addContent( new Element("div", JDOMNamespaceUtil.METS_NS).setAttribute("TYPE", "Container")); oldXML.setRootElement(structMap); exists = false; } newXML = ContainerContentsHelper.addChildContentListInCustomOrder(oldXML, destination, moving, reordered); if (exists) { this.getManagementClient().modifyInlineXMLDatastream(destination, "MD_CONTENTS", false, "adding " + moving.size() + " child resources to container", new ArrayList<String>(), "List of Contents", newXML); } else { this.getManagementClient().addInlineXMLDatastream(destination, "MD_CONTENTS", false, "added " + moving.size() + " child resources to container", new ArrayList<String>(), "List of Contents", false, newXML); } destContentInventoryUpdated = true; // write the log events for (PID pid : moving) { this.managementClient.writePremisEventsToFedoraObject(logger, pid); } // send message for the operation if (this.getOperationsMessageSender() != null) { this.getOperationsMessageSender().sendMoveOperation(user, oldParents, destination, moving, reordered); } } catch (FedoraException e) { thrown = e; } catch (RuntimeException e) { thrown = e; } finally { if (thrown != null) { // some stuff failed to move, log it StringBuffer sb = new StringBuffer(); sb.append("An error occured during a move operation.\n") .append("all contains relationships updated: ").append(relationshipsUpdated).append("\n") .append("destination (").append(destination.getPid()) .append(") content inventory updated: ").append(destContentInventoryUpdated) .append("events logged on moving objects: ").append(eventsLoggedOkay); this.dumpRollbackInfo(knownGoodTime, moving, sb.toString()); throw new IngestException("There was a problem completing the move operation", thrown); } } } /* * (non-Javadoc) * * @see edu.unc.lib.dl.services.DigitalObjectManager#isAvailable() */ @Override public boolean isAvailable() { return this.available; } public OperationsMessageSender getOperationsMessageSender() { return operationsMessageSender; } public void setOperationsMessageSender(OperationsMessageSender operationsMessageSender) { this.operationsMessageSender = operationsMessageSender; } @Override public IngestResult addWhileBlocking(SubmissionInformationPackage sip, DepositRecord record) throws IngestException { boolean reject = true; // normal SIP processing ArchivalInformationPackage aip = null; try { availableCheck(); // lookup the processor for this SIP SIPProcessor processor = this.getSipProcessorFactory().getSIPProcessor(sip); // process the SIP into a standard AIP aip = processor.createAIP(sip, record); // run routine AIP processing steps aip = this.getAipIngestPipeline().processAIP(aip); // no emails for blocking ingests aip.setEmailRecipients(null); if (this.getSubmitterGroupsOverride() != null) { aip.setSubmitterGroups(this.getSubmitterGroupsOverride()); } else { aip.setSubmitterGroups(GroupsThreadStore.getGroupString()); } // persist the AIP to disk aip.prepareIngest(); // move the AIP into the ingest queue. // run ingest task immediately and wait for it. this.ingestBatchNow(aip.getTempFOXDir()); // return the newly minted pid IngestResult result = new IngestResult(); result.derivedPIDs = aip.getTopPIDs(); result.originalDepositID = aip.getDepositRecord().getPid(); return result; } catch (IngestException e) { // exception on AIP preparation, no transaction started log.info("User level exception on ingest, prior to Fedora transaction", e); throw e; } } /* * (non-Javadoc) * * @see edu.unc.lib.dl.services.BatchIngestServiceInterface#ingestBatchNow(java.io.File) */ public void ingestBatchNow(File prepDir) throws IngestException { // File queuedDir = moveToInstant(prepDir); // do not set marker file! log.info("Ingesting batch now, in parallel with queue: " + prepDir.getAbsolutePath()); BatchIngestTask task = this.batchIngestTaskFactory.createTask(); task.setBaseDir(prepDir); try { task.init(); } catch (BatchFailedException e) { throw new IngestException("Batch ingest task failed", e); } task.run(); task = null; } public BatchIngestQueue getBatchIngestQueue() { return batchIngestQueue; } public void setBatchIngestQueue(BatchIngestQueue batchIngestQueue) { this.batchIngestQueue = batchIngestQueue; } public BatchIngestTaskFactory getBatchIngestTaskFactory() { return batchIngestTaskFactory; } public void setBatchIngestTaskFactory(BatchIngestTaskFactory batchIngestTaskFactory) { this.batchIngestTaskFactory = batchIngestTaskFactory; } public void setCollectionsPid(PID collectionsPid) { this.collectionsPid = collectionsPid; } }