package cdr.forms; import edu.unc.lib.schemas.acl.AccessControlType; import edu.unc.lib.schemas.acl.AclFactory; import edu.unc.lib.schemas.acl.GrantType; import gov.loc.mets.AgentType; import gov.loc.mets.AmdSecType; import gov.loc.mets.CHECKSUMTYPEType; import gov.loc.mets.DivType; import gov.loc.mets.FLocatType; import gov.loc.mets.FileGrpType1; import gov.loc.mets.FileSecType; import gov.loc.mets.FileType; import gov.loc.mets.FptrType; import gov.loc.mets.LOCTYPEType; import gov.loc.mets.MDTYPEType; import gov.loc.mets.MdSecType; import gov.loc.mets.MdWrapType; import gov.loc.mets.MetsFactory; import gov.loc.mets.MetsHdrType; import gov.loc.mets.MetsType; import gov.loc.mets.ROLEType; import gov.loc.mets.SmLinkType; import gov.loc.mets.StructLinkType1; import gov.loc.mets.StructMapType; import gov.loc.mets.TYPEType; import gov.loc.mets.XmlDataType1; import gov.loc.mets.util.Link; import gov.loc.mets.util.METSConstants; import gov.loc.mods.mods.MODSFactory; import gov.loc.mods.mods.NameDefinition; import gov.loc.mods.mods.XsString; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.xml.type.internal.XMLCalendar; import org.springframework.util.StringUtils; import crosswalk.DateInputField; import crosswalk.EmailInputField; import crosswalk.FileBlock; import crosswalk.Form; import crosswalk.FormElement; import crosswalk.InputField; import crosswalk.MajorBlock; import crosswalk.MajorEntry; import crosswalk.MetadataBlock; import crosswalk.OutputElement; import crosswalk.OutputMetadataSections; import crosswalk.OutputProfile; import crosswalk.TextInputField; public class Submission { IdentityHashMap<DepositFile, String> files; gov.loc.mets.DocumentRoot metsDocumentRoot; public static Submission create(Deposit deposit, ExternalDepositFileConfigurationProvider externalDepositFileConfigurationProvider) { Submission submission = new Submission(); moveExternalFiles(deposit.getAllFiles(), externalDepositFileConfigurationProvider); submission.files = buildFilenameMap(deposit.getAllFiles(), externalDepositFileConfigurationProvider); submission.metsDocumentRoot = buildMets(deposit, submission.files); return submission; } private static void moveExternalFiles(List<DepositFile> files, ExternalDepositFileConfigurationProvider externalDepositFileConfigurationProvider) { for (DepositFile depositFile : files) { if (depositFile.isExternal()) { try { File temp = File.createTempFile("data", depositFile.getExtension(), new File(externalDepositFileConfigurationProvider.getExternalPath())); Files.move(depositFile.getFile().toPath(), temp.toPath(), StandardCopyOption.REPLACE_EXISTING); depositFile.setFile(temp); } catch (IOException e) { throw new Error(e); } } } } private static IdentityHashMap<DepositFile, String> buildFilenameMap( List<DepositFile> files, ExternalDepositFileConfigurationProvider externalDepositFileConfigurationProvider) { IdentityHashMap<DepositFile, String> filenames = new IdentityHashMap<DepositFile, String>(); int index = 0; for (DepositFile file : files) { if (file.isExternal()) { String path = file.getFile().getAbsolutePath(); if (path.startsWith(externalDepositFileConfigurationProvider.getExternalPath())) path = path.substring(externalDepositFileConfigurationProvider.getExternalPath().length()); else throw new Error("External file's path doesn't start with configured external directory path."); // Ensure the path always starts with a slash if (!path.startsWith("/")) { path = "/" + path; } try { java.net.URI uri = new java.net.URI(externalDepositFileConfigurationProvider.getExternalUriBase() + path); filenames.put(file, uri.toString()); } catch (URISyntaxException e) { throw new Error(e); } } else { filenames.put(file, "data_" + index + file.getExtension()); } index++; } return filenames; } private static gov.loc.mets.DocumentRoot buildMets(Deposit deposit, IdentityHashMap<DepositFile, String> filenames) { gov.loc.mets.DocumentRoot root; MetsType mets; // Mapping between files in the deposit and mets:file elements IdentityHashMap<DepositFile, FileType> filesFiles; // Reference to the root mets:div element DivType rootDiv; // Reference to the mets:div element for the agreement (if present) DivType agreementDiv = null; // Reference to the mets:div element for the main file (if present) DivType mainFileDiv = null; // Mapping between mets:div elements and deposit entries IdentityHashMap<DivType, DepositEntry> fileDivsEntries; // Mapping from mets:div elements to metadata IdentityHashMap<DivType, Map<OutputMetadataSections, EObject>> divsMetadata; // Grab file basics from the deposit: the form, a list of all files, and the main file (if present) Form form = deposit.getForm(); List<DepositFile> files = deposit.getAllFiles(); DepositFile mainFile = deposit.getMainFile(); // Document root { root = MetsFactory.eINSTANCE.createDocumentRoot(); root.setMets(MetsFactory.eINSTANCE.createMetsType1()); mets = root.getMets(); mets.setPROFILE("http://cdr.unc.edu/METS/profiles/Simple"); } // Header { MetsHdrType head = MetsFactory.eINSTANCE.createMetsHdrType(); Date currentTime = new Date(System.currentTimeMillis()); head.setCREATEDATE(new XMLCalendar(currentTime, XMLCalendar.DATETIME)); head.setLASTMODDATE(new XMLCalendar(currentTime, XMLCalendar.DATETIME)); AgentType agent; if (form.getCurrentUser() != null) { agent = MetsFactory.eINSTANCE.createAgentType(); agent.setROLE(ROLEType.CREATOR); agent.setTYPE(TYPEType.INDIVIDUAL); agent.setName(form.getCurrentUser()); head.getAgent().add(agent); } agent = MetsFactory.eINSTANCE.createAgentType(); agent.setROLE(ROLEType.CREATOR); agent.setTYPE(TYPEType.OTHER); agent.setName("CDR Forms"); head.getAgent().add(agent); mets.setMetsHdr(head); } // Files section filesFiles = new IdentityHashMap<DepositFile, FileType>(); { FileSecType fileSec = MetsFactory.eINSTANCE.createFileSecType(); FileGrpType1 fileGrp = MetsFactory.eINSTANCE.createFileGrpType1(); int i = 0; for (DepositFile depositFile : files) { FileType file = MetsFactory.eINSTANCE.createFileType(); file.setID("f_" + i); file.setMIMETYPE(depositFile.getContentType()); if (depositFile.isExternal()) { file.setCHECKSUMTYPE(CHECKSUMTYPEType.MD5); try { file.setCHECKSUM(depositFile.getHexDigest("MD5")); } catch (IOException e) { throw new Error(e); } catch (NoSuchAlgorithmException e) { throw new Error(e); } } FLocatType fLocat = MetsFactory.eINSTANCE.createFLocatType(); fLocat.setHref(filenames.get(depositFile)); if (depositFile.isExternal()) { fLocat.setLOCTYPE(LOCTYPEType.OTHER); fLocat.setOTHERLOCTYPE("tag"); fLocat.setUSE("STAGE"); } else { fLocat.setLOCTYPE(LOCTYPEType.URL); } file.getFLocat().add(fLocat); fileGrp.getFile().add(file); filesFiles.put(depositFile, file); i++; } fileSec.getFileGrp().add(fileGrp); mets.setFileSec(fileSec); } // structMap section fileDivsEntries = new IdentityHashMap<DivType, DepositEntry>(); if (mainFile != null && files.size() == 1) { StructMapType structMap = MetsFactory.eINSTANCE.createStructMapType(); rootDiv = makeDivForFile(filesFiles.get(mainFile)); rootDiv.setID("d_0"); rootDiv.setLABEL1(mainFile.getFilename()); mainFileDiv = rootDiv; mets.getStructMap().add(structMap); structMap.setDiv(rootDiv); } else { StructMapType structMap = MetsFactory.eINSTANCE.createStructMapType(); rootDiv = MetsFactory.eINSTANCE.createDivType(); rootDiv.setTYPE(METSConstants.Div_AggregateWork); rootDiv.setID("d_0"); structMap.setDiv(rootDiv); mets.getStructMap().add(structMap); int i = 1; // Main file (if present) if (mainFile != null) { mainFileDiv = makeDivForFile(filesFiles.get(mainFile)); mainFileDiv.setID("d_" + i++); mainFileDiv.setLABEL1(mainFile.getFilename()); rootDiv.getDiv().add(mainFileDiv); } // Deposit entries for (DepositElement element : deposit.getElements()) { if (element.getFormElement() instanceof FileBlock) { FileBlock fileBlock = (FileBlock) element.getFormElement(); String label = null; if (fileBlock != null && fileBlock.getLabel() != null && fileBlock.getLabel().trim().length() > 0) label = fileBlock.getLabel().trim(); for (DepositEntry entry : element.getEntries()) { if (entry.getFile() != null) { DivType fileDiv = makeDivForFile(filesFiles.get(entry.getFile())); fileDiv.setID("d_" + i++); fileDiv.setLABEL1(label != null ? label : entry.getFile().getFilename()); rootDiv.getDiv().add(fileDiv); fileDivsEntries.put(fileDiv, entry); } } } } // Supplemental files if (deposit.getSupplementalFiles() != null) { for (DepositFile depositFile : deposit.getSupplementalFiles()) { if (depositFile != null) { DivType fileDiv = makeDivForFile(filesFiles.get(depositFile)); fileDiv.setID("d_" + i++); fileDiv.setLABEL1(depositFile.getFilename()); rootDiv.getDiv().add(fileDiv); } } } // Add agreement file if one is set if (deposit.getAgreementFile() != null) { agreementDiv = makeDivForFile(filesFiles.get(deposit.getAgreementFile())); agreementDiv.setID("d_" + i++); agreementDiv.setLABEL1(deposit.getAgreementFile().getFilename()); rootDiv.getDiv().add(agreementDiv); } } // Metadata (amdSec and dmdSec sections) divsMetadata = new IdentityHashMap<DivType, Map<OutputMetadataSections, EObject>>(); // Gather metadata for entries associated with FileBlocks for (Entry<DivType, DepositEntry> entry : fileDivsEntries.entrySet()) { divsMetadata.put(entry.getKey(), makeMetadata(form.getOutputProfiles(), Collections.singletonList(entry.getValue()))); } // Metadata for the root div is gathered from all entries not associated with FileBlocks { ArrayList<DepositEntry> rootEntries = new ArrayList<DepositEntry>(); for (DepositElement element : deposit.getElements()) { if (element.getFormElement() instanceof MetadataBlock && !(element.getFormElement() instanceof FileBlock)) { // If this isn't a repeating metadata block, just all all the entries. // Otherwise, only add entries with all non-blank values. if (((MetadataBlock) element.getFormElement()).getMaxRepeat() == 1) { rootEntries.addAll(element.getEntries()); } else { for (DepositEntry depositEntry : element.getEntries()) { boolean allBlank = true; for (DepositField<?> depositField : depositEntry.getFields()) { Object value = depositField.getValue(); if (value != null && StringUtils.hasText(value.toString())) { allBlank = false; break; } } if (allBlank == false) { rootEntries.add(depositEntry); } } } } } divsMetadata.put(rootDiv, makeMetadata(form.getOutputProfiles(), rootEntries)); } // Special case: Add divs and metadata for supplemental objects { int i = 0; for (SupplementalObject object : deposit.getSupplementalObjects()) { DivType fileDiv = makeDivForFile(filesFiles.get(object.getDepositFile())); fileDiv.setID("s_" + i++); fileDiv.setLABEL1(object.getDepositFile().getFilename()); rootDiv.getDiv().add(fileDiv); IdentityHashMap<OutputMetadataSections, EObject> metadata = new IdentityHashMap<OutputMetadataSections, EObject>(); metadata.put(OutputMetadataSections.DMD_SEC, object.getDescriptiveMetadata()); divsMetadata.put(fileDiv, metadata); } } if (agreementDiv != null) { divsMetadata.put(agreementDiv, makeMetadata(form.getOutputProfiles(), new ArrayList<DepositEntry>())); } // Special cases for metadata // Find the root div's access control metadata object, adding a new one if none is present AccessControlType rootAccessControl; { edu.unc.lib.schemas.acl.DocumentRoot documentRoot = (edu.unc.lib.schemas.acl.DocumentRoot) divsMetadata.get(rootDiv).get(OutputMetadataSections.RIGHTS_MD); if (documentRoot.eContents().isEmpty()) { rootAccessControl = AclFactory.eINSTANCE.createAccessControlType(); documentRoot.setAccessControl(rootAccessControl); } else { rootAccessControl = (AccessControlType) documentRoot.eContents().get(0); } } // Special case: set published="false" if the submission should be reviewed before publication if (form.isReviewBeforePublication()) { rootAccessControl.setPublished(false); } // Add access control for agreement file if one is present if (agreementDiv != null) { AccessControlType agreementAccessControl; edu.unc.lib.schemas.acl.DocumentRoot documentRoot = (edu.unc.lib.schemas.acl.DocumentRoot) divsMetadata.get(agreementDiv).get(OutputMetadataSections.RIGHTS_MD); if (documentRoot.eContents().isEmpty()) { agreementAccessControl = AclFactory.eINSTANCE.createAccessControlType(); documentRoot.setAccessControl(agreementAccessControl); } else { agreementAccessControl = (AccessControlType) documentRoot.eContents().get(0); } agreementAccessControl.setPublished(false); } // Special case: set major-related metadata if a MajorBlock is present MajorBlock majorBlock = null; for (FormElement fe : form.getElements()) { if (fe instanceof MajorBlock) { majorBlock = (MajorBlock) fe; break; } } if (majorBlock != null) { MajorEntry major = majorBlock.getSelectedMajor(); EObject generatedFeature = majorBlock.getNameElement().getGeneratedFeature(); if (generatedFeature != null && generatedFeature instanceof NameDefinition) { NameDefinition nameDef = (NameDefinition) generatedFeature; XsString affiliation = MODSFactory.eINSTANCE.createXsString(); affiliation.setValue(major.getName()); nameDef.getAffiliation().add(affiliation); } // Create access control restrictions from the major for (String group : major.getObserverGroups()) { if (group != null && group.trim().length() > 0) { GrantType grantType = AclFactory.eINSTANCE.createGrantType(); grantType.setGroup(group); grantType.setRole("observer"); rootAccessControl.getGrant().add(grantType); } } for (String group : major.getReviewerGroups()) { if (group != null && group.trim().length() > 0) { GrantType grantType = AclFactory.eINSTANCE.createGrantType(); grantType.setGroup(group); grantType.setRole("processor"); rootAccessControl.getGrant().add(grantType); } } } // Special case: for all non-root divs associated with a file block that has any values for copyGrantsHavingRoles, copy grants // from the root access control object to the access control object for that div having those roles. for (Entry<DivType, Map<OutputMetadataSections, EObject>> pair : divsMetadata.entrySet()) { DepositEntry entry = fileDivsEntries.get(pair.getKey()); if (entry == null) continue; FormElement element = entry.getFormElement(); if (element == null || !(element instanceof FileBlock)) continue; FileBlock fileBlock = (FileBlock) element; if (fileBlock.getCopyGrantsHavingRoles() != null && fileBlock.getCopyGrantsHavingRoles().size() > 0) { AccessControlType accessControl; { edu.unc.lib.schemas.acl.DocumentRoot documentRoot = (edu.unc.lib.schemas.acl.DocumentRoot) divsMetadata.get(pair.getKey()).get(OutputMetadataSections.RIGHTS_MD); if (documentRoot.eContents().isEmpty()) { accessControl = AclFactory.eINSTANCE.createAccessControlType(); documentRoot.setAccessControl(accessControl); } else { accessControl = (AccessControlType) documentRoot.eContents().get(0); } } for (String role : fileBlock.getCopyGrantsHavingRoles()) { for (GrantType grant : rootAccessControl.getGrant()) { if (grant.getRole().equals(role)) accessControl.getGrant().add(EcoreUtil.copy(grant)); } } } } // Wrap, link, and add metadata { AmdSecType amdSec = MetsFactory.eINSTANCE.createAmdSecType(); mets.getAmdSec().add(amdSec); int i = 0; for (Entry<DivType, Map<OutputMetadataSections, EObject>> divMetadataPair : divsMetadata.entrySet()) { DivType div = divMetadataPair.getKey(); for (OutputProfile profile : form.getOutputProfiles()) { EObject output = divMetadataPair.getValue().get(profile.getMetadataSection()); if (output == null || output.eContents() == null || output.eContents().isEmpty()) continue; if (!profile.isStartMappingAtChildren()) output = output.eContents().get(0); // Wrap metadata output MdWrapType mdWrap = MetsFactory.eINSTANCE.createMdWrapType(); MDTYPEType mdType = MDTYPEType.get(profile.getMetadataType()); if (mdType == null) { mdWrap.setMDTYPE(MDTYPEType.OTHER); mdWrap.setOTHERMDTYPE(profile.getMetadataType()); } else { mdWrap.setMDTYPE(mdType); } XmlDataType1 xml = MetsFactory.eINSTANCE.createXmlDataType1(); xml.getAny().add(profile.getParentMappedFeature(), output); mdWrap.setXmlData(xml); MdSecType mdSec = MetsFactory.eINSTANCE.createMdSecType(); mdSec.setMdWrap(mdWrap); mdSec.setID("md_" + i); // Add to the METS object and link with div switch (profile.getMetadataSection()) { case DIGIPROV_MD: amdSec.getDigiprovMD().add(mdSec); div.getMdSec().add(mdSec); break; case RIGHTS_MD: amdSec.getRightsMD().add(mdSec); div.getMdSec().add(mdSec); break; case SOURCE_MD: amdSec.getSourceMD().add(mdSec); div.getMdSec().add(mdSec); break; case TECH_MD: amdSec.getTechMD().add(mdSec); div.getMdSec().add(mdSec); break; case DMD_SEC: mets.getDmdSec().add(mdSec); div.getDmdSec().add(mdSec); break; } i++; } } } // structLink section if (rootDiv.getTYPE().equals(METSConstants.Div_AggregateWork)) { StructLinkType1 structLink = MetsFactory.eINSTANCE.createStructLinkType1(); // Add a link for the mainFileDiv, if we have it. (This means the form doesn't have any FileBlock instances.) if (mainFileDiv != null) { SmLinkType smLink = MetsFactory.eINSTANCE.createSmLinkType(); smLink.setArcrole(Link.DEFAULTACCESS.uri); smLink.setXlinkFrom(rootDiv); smLink.setXlinkTo(mainFileDiv); structLink.getSmLink().add(smLink); } else { // The DepositEntry instances in this map should be guaranteed to have FileBlock instances for their formElement properties // and non-null valid DepositFile instances for their file properties by construction above. for (Entry<DivType, DepositEntry> entry : fileDivsEntries.entrySet()) { FileBlock fileBlock = (FileBlock) entry.getValue().getFormElement(); DepositFile file = (DepositFile) entry.getValue().getFile(); if (fileBlock.isDefaultAccess() || file == mainFile) { DivType fileDiv = entry.getKey(); SmLinkType smLink = MetsFactory.eINSTANCE.createSmLinkType(); smLink.setArcrole(Link.DEFAULTACCESS.uri); smLink.setXlinkFrom(rootDiv); smLink.setXlinkTo(fileDiv); structLink.getSmLink().add(smLink); } } } // Only add the structLink section if there are actually links if (structLink.getSmLink().size() > 0) mets.setStructLink(structLink); } return root; } private static DivType makeDivForFile(FileType file) { DivType div = MetsFactory.eINSTANCE.createDivType(); div.setTYPE(METSConstants.Div_File); FptrType fptr = MetsFactory.eINSTANCE.createFptrType(); fptr.setFILEID(file.getID()); div.getFptr().add(fptr); return div; } private static Map<OutputMetadataSections, EObject> makeMetadata(List<OutputProfile> profiles, List<DepositEntry> entries) { IdentityHashMap<OutputMetadataSections, EObject> metadata = new IdentityHashMap<OutputMetadataSections, EObject>(); for (OutputProfile profile : profiles) { EClass outputElementClass; if (profile.isStartMappingAtChildren()) outputElementClass = profile.getParentMappedFeature().getEReferenceType(); else outputElementClass = profile.getParentMappedFeature().getEContainingClass(); EObject outputElement = outputElementClass.getEPackage().getEFactoryInstance().create(outputElementClass); for (DepositEntry entry : entries) { FormElement formElement = entry.getFormElement(); if (formElement instanceof MetadataBlock) { MetadataBlock metadataBlock = (MetadataBlock) formElement; // For each entry, "fill out" the metadata block's ports using the values from the entry's fields. int portIndex = 0; for (InputField<?> inputField : metadataBlock.getPorts()) { if (inputField instanceof DateInputField) { ((DateInputField) inputField).setEnteredValue((Date) entry.getFields().get(portIndex).getValue()); } else if (inputField instanceof TextInputField) { ((TextInputField) inputField).setEnteredValue((String) entry.getFields().get(portIndex).getValue()); } else if (inputField instanceof EmailInputField) { ((EmailInputField) inputField).setEnteredValue((String) entry.getFields().get(portIndex).getValue()); } else { throw new Error("Unknown input field type"); } portIndex++; } for (OutputElement oe : metadataBlock.getElements()) { oe.updateRecord(outputElement); } } } metadata.put(profile.getMetadataSection(), outputElement); } return metadata; } private Submission() { } public gov.loc.mets.DocumentRoot getMetsDocumentRoot() { return metsDocumentRoot; } public IdentityHashMap<DepositFile, String> getFiles() { return files; } }