org.roda.core.common.PremisV3Utils.java Source code

Java tutorial

Introduction

Here is the source code for org.roda.core.common.PremisV3Utils.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE file at the root of the source
 * tree and available online at
 *
 * https://github.com/keeps/roda
 */
package org.roda.core.common;

import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.common.SolrInputDocument;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlValidationError;
import org.roda.core.RodaCoreFactory;
import org.roda.core.data.common.RodaConstants;
import org.roda.core.data.common.RodaConstants.PreservationAgentType;
import org.roda.core.data.exceptions.AlreadyExistsException;
import org.roda.core.data.exceptions.AuthorizationDeniedException;
import org.roda.core.data.exceptions.GenericException;
import org.roda.core.data.exceptions.NotFoundException;
import org.roda.core.data.exceptions.RODAException;
import org.roda.core.data.exceptions.RequestNotValidException;
import org.roda.core.data.utils.URNUtils;
import org.roda.core.data.v2.ip.File;
import org.roda.core.data.v2.ip.metadata.Fixity;
import org.roda.core.data.v2.ip.metadata.LinkingIdentifier;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata;
import org.roda.core.data.v2.ip.metadata.PreservationMetadata.PreservationMetadataType;
import org.roda.core.data.v2.user.RODAMember;
import org.roda.core.data.v2.user.User;
import org.roda.core.data.v2.validation.ValidationException;
import org.roda.core.index.IndexService;
import org.roda.core.model.ModelService;
import org.roda.core.model.utils.ModelUtils;
import org.roda.core.plugins.Plugin;
import org.roda.core.plugins.plugins.characterization.PremisSkeletonPluginUtils;
import org.roda.core.storage.Binary;
import org.roda.core.storage.ContentPayload;
import org.roda.core.util.FileUtility;
import org.roda.core.util.IdUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.util.DateParser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import gov.loc.premis.v3.AgentComplexType;
import gov.loc.premis.v3.AgentDocument;
import gov.loc.premis.v3.AgentIdentifierComplexType;
import gov.loc.premis.v3.ContentLocationComplexType;
import gov.loc.premis.v3.CreatingApplicationComplexType;
import gov.loc.premis.v3.EventComplexType;
import gov.loc.premis.v3.EventDetailInformationComplexType;
import gov.loc.premis.v3.EventDocument;
import gov.loc.premis.v3.EventIdentifierComplexType;
import gov.loc.premis.v3.EventOutcomeDetailComplexType;
import gov.loc.premis.v3.EventOutcomeInformationComplexType;
import gov.loc.premis.v3.FixityComplexType;
import gov.loc.premis.v3.FormatComplexType;
import gov.loc.premis.v3.FormatDesignationComplexType;
import gov.loc.premis.v3.FormatRegistryComplexType;
import gov.loc.premis.v3.LinkingAgentIdentifierComplexType;
import gov.loc.premis.v3.LinkingObjectIdentifierComplexType;
import gov.loc.premis.v3.ObjectCharacteristicsComplexType;
import gov.loc.premis.v3.ObjectComplexType;
import gov.loc.premis.v3.ObjectDocument;
import gov.loc.premis.v3.ObjectIdentifierComplexType;
import gov.loc.premis.v3.RelatedObjectIdentifierComplexType;
import gov.loc.premis.v3.RelationshipComplexType;
import gov.loc.premis.v3.Representation;
import gov.loc.premis.v3.StorageComplexType;
import gov.loc.premis.v3.StringPlusAuthority;

public final class PremisV3Utils {

    private static final Logger LOGGER = LoggerFactory.getLogger(PremisV3Utils.class);
    private static final String FIXITY_ORIGINATOR = "RODA";
    private static final String W3C_XML_SCHEMA_NS_URI = "http://www.w3.org/2001/XMLSchema";

    /** Private empty constructor */
    private PremisV3Utils() {
        // do nothing
    }

    public static List<Fixity> calculateFixities(Binary binary, Collection<String> algorithms, String originator)
            throws IOException, NoSuchAlgorithmException {
        List<Fixity> ret = new ArrayList<>();
        InputStream stream = binary.getContent().createInputStream();

        Map<String, String> checksums = FileUtility.checksums(stream, algorithms);

        for (Entry<String, String> entry : checksums.entrySet()) {
            String algorithm = entry.getKey();
            String checksum = entry.getValue();
            ret.add(new Fixity(algorithm, checksum, originator));
        }

        IOUtils.closeQuietly(stream);
        return ret;
    }

    public static boolean isPremisV2(Binary binary) throws IOException, SAXException {
        boolean premisV2 = true;
        InputStream inputStream = binary.getContent().createInputStream();
        InputStream schemaStream = RodaCoreFactory.getConfigurationFileAsStream("schemas/premis-v2-0.xsd");
        Source xmlFile = new StreamSource(inputStream);
        SchemaFactory schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(new StreamSource(schemaStream));
        Validator validator = schema.newValidator();
        RodaErrorHandler errorHandler = new RodaErrorHandler();
        validator.setErrorHandler(errorHandler);
        try {
            validator.validate(xmlFile);
            List<SAXParseException> errors = errorHandler.getErrors();
            if (!errors.isEmpty()) {
                premisV2 = false;
            }
        } catch (SAXException e) {
            premisV2 = false;
        }
        IOUtils.closeQuietly(inputStream);
        IOUtils.closeQuietly(schemaStream);
        return premisV2;
    }

    private static class RodaErrorHandler extends DefaultHandler {
        List<SAXParseException> errors;

        public RodaErrorHandler() {
            errors = new ArrayList<>();
        }

        @Override
        public void warning(SAXParseException e) throws SAXException {
            errors.add(e);
        }

        @Override
        public void error(SAXParseException e) throws SAXException {
            errors.add(e);
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
            errors.add(e);
        }

        public List<SAXParseException> getErrors() {
            return errors;
        }
    }

    public static void updateFileFormat(gov.loc.premis.v3.File file, String formatDesignationName,
            String formatDesignationVersion, String pronom, String mimeType) {

        if (StringUtils.isNotBlank(formatDesignationName)) {
            FormatDesignationComplexType fdct = getFormatDesignation(file);
            fdct.setFormatName(getStringPlusAuthority(formatDesignationName));
        }

        if (StringUtils.isNotBlank(formatDesignationVersion)) {
            FormatDesignationComplexType fdct = getFormatDesignation(file);
            fdct.setFormatVersion(formatDesignationVersion);
        }

        if (StringUtils.isNotBlank(pronom)) {
            FormatRegistryComplexType frct = getFormatRegistry(file, RodaConstants.PRESERVATION_REGISTRY_PRONOM);
            frct.setFormatRegistryKey(getStringPlusAuthority(pronom));
        }
        if (StringUtils.isNotBlank(mimeType)) {
            FormatRegistryComplexType frct = getFormatRegistry(file, RodaConstants.PRESERVATION_REGISTRY_MIME);
            frct.setFormatRegistryKey(getStringPlusAuthority(mimeType));
        }

    }

    public static void updateCreatingApplication(gov.loc.premis.v3.File file, String creatingApplicationName,
            String creatingApplicationVersion, String dateCreatedByApplication) {
        if (StringUtils.isNotBlank(creatingApplicationName)) {
            CreatingApplicationComplexType cact = getCreatingApplication(file);
            cact.setCreatingApplicationName(getStringPlusAuthority(creatingApplicationName));
        }

        if (StringUtils.isNotBlank(creatingApplicationVersion)) {
            CreatingApplicationComplexType cact = getCreatingApplication(file);
            cact.setCreatingApplicationVersion(creatingApplicationVersion);
        }

        if (StringUtils.isNotBlank(dateCreatedByApplication)) {
            CreatingApplicationComplexType cact = getCreatingApplication(file);
            cact.setDateCreatedByApplication(dateCreatedByApplication);
        }
    }

    private static CreatingApplicationComplexType getCreatingApplication(gov.loc.premis.v3.File f) {
        ObjectCharacteristicsComplexType occt;
        CreatingApplicationComplexType cact;
        if (f.getObjectCharacteristicsArray() != null && f.getObjectCharacteristicsArray().length > 0) {
            occt = f.getObjectCharacteristicsArray(0);
        } else {
            occt = f.addNewObjectCharacteristics();
        }
        if (occt.getCreatingApplicationArray() != null && occt.getCreatingApplicationArray().length > 0) {
            cact = occt.getCreatingApplicationArray(0);
        } else {
            cact = occt.addNewCreatingApplication();
        }
        return cact;
    }

    public static FormatRegistryComplexType getFormatRegistry(gov.loc.premis.v3.File f, String registryName) {
        ObjectCharacteristicsComplexType occt;
        FormatRegistryComplexType frct = null;
        if (f.getObjectCharacteristicsArray() != null && f.getObjectCharacteristicsArray().length > 0) {
            occt = f.getObjectCharacteristicsArray(0);
        } else {
            occt = f.addNewObjectCharacteristics();
        }
        if (occt.getFormatArray() != null && occt.getFormatArray().length > 0) {
            for (FormatComplexType fct : occt.getFormatArray()) {
                if (fct.getFormatRegistry() != null && fct.getFormatRegistry().getFormatRegistryName()
                        .getStringValue().equalsIgnoreCase(registryName)) {
                    frct = fct.getFormatRegistry();
                    break;
                }
            }
            if (frct == null) {
                FormatComplexType fct = occt.addNewFormat();
                frct = fct.addNewFormatRegistry();
                frct.setFormatRegistryName(getStringPlusAuthority(registryName));
            }
        } else {
            FormatComplexType fct = occt.addNewFormat();
            frct = fct.addNewFormatRegistry();
            frct.setFormatRegistryName(getStringPlusAuthority(registryName));
        }
        return frct;
    }

    private static FormatDesignationComplexType getFormatDesignation(gov.loc.premis.v3.File f) {
        ObjectCharacteristicsComplexType occt;
        FormatComplexType fct;
        FormatDesignationComplexType fdct;
        if (f.getObjectCharacteristicsArray() != null && f.getObjectCharacteristicsArray().length > 0) {
            occt = f.getObjectCharacteristicsArray(0);
        } else {
            occt = f.addNewObjectCharacteristics();
        }
        if (occt.getFormatArray() != null && occt.getFormatArray().length > 0) {
            fct = occt.getFormatArray(0);
        } else {
            fct = occt.addNewFormat();
        }
        if (fct.getFormatDesignation() != null) {
            fdct = fct.getFormatDesignation();
        } else {
            fdct = fct.addNewFormatDesignation();
        }
        return fdct;
    }

    public static ContentPayload createPremisEventBinary(String eventID, Date date, String type, String details,
            List<LinkingIdentifier> sources, List<LinkingIdentifier> outcomes, String outcome, String detailNote,
            String detailExtension, List<String> agentIds) throws GenericException, ValidationException {
        EventDocument event = EventDocument.Factory.newInstance();
        EventComplexType ect = event.addNewEvent();
        EventIdentifierComplexType eict = ect.addNewEventIdentifier();
        eict.setEventIdentifierValue(eventID);
        eict.setEventIdentifierType(getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
        ect.setEventDateTime(DateParser.getIsoDate(date));
        ect.setEventType(getStringPlusAuthority(type));
        EventDetailInformationComplexType edict = ect.addNewEventDetailInformation();
        edict.setEventDetail(details);
        if (sources != null) {
            for (LinkingIdentifier identifier : sources) {
                LinkingObjectIdentifierComplexType loict = ect.addNewLinkingObjectIdentifier();
                loict.setLinkingObjectIdentifierValue(identifier.getValue());
                loict.setLinkingObjectIdentifierType(getStringPlusAuthority(identifier.getType()));
                if (identifier.getRoles() != null) {
                    loict.setLinkingObjectRoleArray(getStringPlusAuthorityArray(identifier.getRoles()));
                }
            }
        }

        if (outcomes != null) {
            for (LinkingIdentifier identifier : outcomes) {
                LinkingObjectIdentifierComplexType loict = ect.addNewLinkingObjectIdentifier();
                loict.setLinkingObjectIdentifierValue(identifier.getValue());
                loict.setLinkingObjectIdentifierType(getStringPlusAuthority(identifier.getType()));
                if (identifier.getRoles() != null) {
                    loict.setLinkingObjectRoleArray(getStringPlusAuthorityArray(identifier.getRoles()));
                }
            }
        }

        if (agentIds != null) {
            for (String agentId : agentIds) {
                LinkingAgentIdentifierComplexType agentIdentifier = ect.addNewLinkingAgentIdentifier();
                agentIdentifier.setLinkingAgentIdentifierType(
                        getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
                agentIdentifier.setLinkingAgentIdentifierValue(agentId);
            }
        }
        EventOutcomeInformationComplexType outcomeInformation = ect.addNewEventOutcomeInformation();
        outcomeInformation.setEventOutcome(getStringPlusAuthority(outcome));
        StringBuilder outcomeDetailNote = new StringBuilder(detailNote);
        if (StringUtils.isNotBlank(detailExtension)) {
            outcomeDetailNote.append("\n").append(detailExtension);
        }
        EventOutcomeDetailComplexType eodct = outcomeInformation.addNewEventOutcomeDetail();
        eodct.setEventOutcomeDetailNote(outcomeDetailNote.toString());

        return MetadataUtils.saveToContentPayload(event, true);

    }

    public static ContentPayload createPremisAgentBinary(String id, String name, PreservationAgentType type,
            String extension, String note, String version) throws GenericException, ValidationException {
        AgentDocument agent = AgentDocument.Factory.newInstance();

        AgentComplexType act = agent.addNewAgent();
        AgentIdentifierComplexType agentIdentifier = act.addNewAgentIdentifier();
        agentIdentifier.setAgentIdentifierType(getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
        agentIdentifier.setAgentIdentifierValue(id);

        act.setAgentType(getStringPlusAuthority(type.toString()));

        if (StringUtils.isNotBlank(name)) {
            act.addNewAgentName().setStringValue(name);
        }

        if (StringUtils.isNotBlank(note)) {
            act.addAgentNote(note);
        }

        if (StringUtils.isNotBlank(version)) {
            act.setAgentVersion(version);
        }
        if (StringUtils.isNotBlank(extension)) {
            try {
                act.addNewAgentExtension().set(XmlObject.Factory.parse(extension));
            } catch (XmlException e) {
                throw new ValidationException(e.getMessage());
            }
        }

        return MetadataUtils.saveToContentPayload(agent, true);
    }

    public static Representation createBaseRepresentation(String aipId, String representationId)
            throws GenericException, ValidationException {

        Representation representation = Representation.Factory.newInstance();
        ObjectIdentifierComplexType oict = representation.addNewObjectIdentifier();
        oict.setObjectIdentifierType(getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
        String identifier = IdUtils.getPreservationId(PreservationMetadataType.REPRESENTATION, aipId,
                representationId, null, null);
        oict.setObjectIdentifierValue(identifier);
        representation.addNewPreservationLevel().setPreservationLevelValue(getStringPlusAuthority(""));
        return representation;
    }

    public static ContentPayload createBaseFile(File originalFile, ModelService model,
            Collection<String> fixityAlgorithms) throws GenericException, RequestNotValidException,
            NotFoundException, AuthorizationDeniedException, ValidationException, XmlException {
        ObjectDocument document = ObjectDocument.Factory.newInstance();
        gov.loc.premis.v3.File file = gov.loc.premis.v3.File.Factory.newInstance();
        file.addNewPreservationLevel()
                .setPreservationLevelValue(getStringPlusAuthority(RodaConstants.PRESERVATION_LEVEL_FULL));
        ObjectIdentifierComplexType oict = file.addNewObjectIdentifier();
        oict.setObjectIdentifierValue(
                URNUtils.createRodaPreservationURN(PreservationMetadataType.FILE, originalFile.getId()));
        oict.setObjectIdentifierType(getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
        ObjectCharacteristicsComplexType occt = file.addNewObjectCharacteristics();
        // TODO
        // occt.setCompositionLevel(CompositionLevelComplexType.Factory.parse("0"));
        FormatComplexType fct = occt.addNewFormat();
        FormatDesignationComplexType fdct = fct.addNewFormatDesignation();
        fdct.setFormatName(getStringPlusAuthority(""));
        fdct.setFormatVersion("");
        Binary binary = model.getStorage().getBinary(ModelUtils.getFileStoragePath(originalFile));

        if (binary.getContentDigest() != null && !binary.getContentDigest().isEmpty()) {
            // use binary content digest information
            for (Entry<String, String> entry : binary.getContentDigest().entrySet()) {
                FixityComplexType premisFixity = occt.addNewFixity();
                premisFixity.setMessageDigest(entry.getKey());
                premisFixity.setMessageDigestAlgorithm(getStringPlusAuthority(entry.getValue()));
                premisFixity.setMessageDigestOriginator(getStringPlusAuthority(FIXITY_ORIGINATOR));
            }
        } else {
            // if binary does not contain digest, create a new one
            try {
                List<Fixity> fixities = calculateFixities(binary, fixityAlgorithms, FIXITY_ORIGINATOR);

                for (Fixity fixity : fixities) {
                    FixityComplexType premisFixity = occt.addNewFixity();
                    premisFixity.setMessageDigest(fixity.getMessageDigest());
                    premisFixity
                            .setMessageDigestAlgorithm(getStringPlusAuthority(fixity.getMessageDigestAlgorithm()));
                    premisFixity.setMessageDigestOriginator(
                            getStringPlusAuthority(fixity.getMessageDigestOriginator()));
                }
            } catch (IOException | NoSuchAlgorithmException e) {
                LOGGER.warn("Could not calculate fixity for file " + originalFile);
            }
        }

        occt.setSize(binary.getSizeInBytes());
        // occt.addNewObjectCharacteristicsExtension().set("");
        file.addNewOriginalName().setStringValue(originalFile.getId());
        StorageComplexType sct = file.addNewStorage();
        ContentLocationComplexType clct = sct.addNewContentLocation();
        clct.setContentLocationType(getStringPlusAuthority(""));
        clct.setContentLocationValue("");

        document.setObject(file);

        return MetadataUtils.saveToContentPayload(document, true);
    }

    public static List<Fixity> extractFixities(Binary premisFile)
            throws GenericException, XmlException, IOException {
        List<Fixity> fixities = new ArrayList<>();
        InputStream inputStream = premisFile.getContent().createInputStream();
        gov.loc.premis.v3.File f = binaryToFile(inputStream);
        if (f.getObjectCharacteristicsArray() != null && f.getObjectCharacteristicsArray().length > 0) {
            ObjectCharacteristicsComplexType occt = f.getObjectCharacteristicsArray(0);
            if (occt.getFixityArray() != null && occt.getFixityArray().length > 0) {
                for (FixityComplexType fct : occt.getFixityArray()) {
                    Fixity fix = new Fixity();
                    fix.setMessageDigest(fct.getMessageDigest());
                    fix.setMessageDigestAlgorithm(fct.getMessageDigestAlgorithm().getStringValue());
                    fix.setMessageDigestOriginator(fct.getMessageDigestOriginator().getStringValue());
                    fixities.add(fix);
                }
            }
        }
        IOUtils.closeQuietly(inputStream);
        return fixities;
    }

    public static String extractFixity(Binary premisFile, String fixityType)
            throws IOException, GenericException, XmlException {
        String fixityValue = null;
        InputStream inputStream = premisFile.getContent().createInputStream();
        gov.loc.premis.v3.File f = binaryToFile(inputStream);
        if (f.getObjectCharacteristicsArray() != null && f.getObjectCharacteristicsArray().length > 0) {
            ObjectCharacteristicsComplexType occt = f.getObjectCharacteristicsArray(0);
            if (occt.getFixityArray() != null && occt.getFixityArray().length > 0) {
                for (FixityComplexType fct : occt.getFixityArray()) {
                    if (fct.getMessageDigestAlgorithm().getStringValue().equalsIgnoreCase(fixityType)) {
                        fixityValue = fct.getMessageDigest();
                        break;
                    }
                }
            }
        }
        IOUtils.closeQuietly(inputStream);
        return fixityValue;
    }

    public static gov.loc.premis.v3.Representation binaryToRepresentation(InputStream binaryInputStream)
            throws XmlException, IOException, GenericException {
        ObjectDocument objectDocument = ObjectDocument.Factory.parse(binaryInputStream);

        ObjectComplexType object = objectDocument.getObject();
        if (object instanceof Representation) {
            return (Representation) object;
        } else {
            throw new GenericException(
                    "Trying to load a representation but was a " + object.getClass().getSimpleName());
        }
    }

    public static gov.loc.premis.v3.File binaryToFile(InputStream binaryInputStream)
            throws XmlException, IOException, GenericException {
        ObjectDocument objectDocument = ObjectDocument.Factory.parse(binaryInputStream);

        ObjectComplexType object = objectDocument.getObject();
        if (object instanceof gov.loc.premis.v3.File) {
            return (gov.loc.premis.v3.File) object;
        } else {
            throw new GenericException("Trying to load a file but was a " + object.getClass().getSimpleName());
        }
    }

    public static EventComplexType binaryToEvent(InputStream binaryInputStream) throws XmlException, IOException {
        return EventDocument.Factory.parse(binaryInputStream).getEvent();
    }

    public static AgentComplexType binaryToAgent(InputStream binaryInputStream) throws XmlException, IOException {
        return AgentDocument.Factory.parse(binaryInputStream).getAgent();
    }

    public static gov.loc.premis.v3.Representation binaryToRepresentation(ContentPayload payload, boolean validate)
            throws ValidationException, GenericException {
        Representation representation;
        InputStream inputStream = null;
        try {
            inputStream = payload.createInputStream();
            representation = binaryToRepresentation(inputStream);

            List<XmlValidationError> validationErrors = new ArrayList<>();
            XmlOptions validationOptions = new XmlOptions();
            validationOptions.setErrorListener(validationErrors);

            if (validate && !representation.validate(validationOptions)) {
                throw new ValidationException(
                        MetadataUtils.xmlValidationErrorsToValidationReport(validationErrors));
            }
        } catch (XmlException | IOException e) {
            throw new GenericException("Error loading representation premis file", e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        return representation;
    }

    public static gov.loc.premis.v3.File binaryToFile(ContentPayload payload, boolean validate)
            throws ValidationException, GenericException {
        gov.loc.premis.v3.File file;
        List<XmlValidationError> validationErrors = new ArrayList<>();
        InputStream inputStream = null;
        try {
            inputStream = payload.createInputStream();
            file = binaryToFile(inputStream);

            XmlOptions validationOptions = new XmlOptions();
            validationOptions.setErrorListener(validationErrors);

            if (validate && !file.validate(validationOptions)) {
                throw new ValidationException(
                        MetadataUtils.xmlValidationErrorsToValidationReport(validationErrors));
            }
        } catch (XmlException e) {
            ValidationException exception = new ValidationException(e);
            exception.setReport(MetadataUtils.xmlValidationErrorsToValidationReport(validationErrors));
            throw exception;
        } catch (IOException e) {
            throw new GenericException("Error loading representation premis file", e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        return file;
    }

    public static ContentPayload fileToBinary(gov.loc.premis.v3.File file)
            throws GenericException, ValidationException {
        ObjectDocument d = ObjectDocument.Factory.newInstance();
        d.setObject(file);
        return MetadataUtils.saveToContentPayload(d, true);
    }

    public static ContentPayload representationToBinary(Representation representation)
            throws GenericException, ValidationException {
        ObjectDocument d = ObjectDocument.Factory.newInstance();
        d.setObject(representation);
        return MetadataUtils.saveToContentPayload(d, true);
    }

    public static EventComplexType binaryToEvent(ContentPayload payload, boolean validate)
            throws ValidationException, GenericException {
        EventComplexType event;
        InputStream inputStream = null;
        try {
            inputStream = payload.createInputStream();
            event = binaryToEvent(inputStream);

            List<XmlValidationError> validationErrors = new ArrayList<>();
            XmlOptions validationOptions = new XmlOptions();
            validationOptions.setErrorListener(validationErrors);

            if (validate && !event.validate(validationOptions)) {
                throw new ValidationException(
                        MetadataUtils.xmlValidationErrorsToValidationReport(validationErrors));
            }
        } catch (XmlException | IOException e) {
            throw new GenericException("Error loading representation premis file", e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        return event;
    }

    public static AgentComplexType binaryToAgent(ContentPayload payload, boolean validate)
            throws ValidationException, GenericException {
        AgentComplexType agent;
        InputStream inputStream = null;
        try {
            inputStream = payload.createInputStream();
            agent = binaryToAgent(inputStream);

            List<XmlValidationError> validationErrors = new ArrayList<>();
            XmlOptions validationOptions = new XmlOptions();
            validationOptions.setErrorListener(validationErrors);

            if (validate && !agent.validate(validationOptions)) {
                throw new ValidationException(
                        MetadataUtils.xmlValidationErrorsToValidationReport(validationErrors));
            }
        } catch (XmlException | IOException e) {
            throw new GenericException("Error loading representation premis file", e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        return agent;
    }

    public static SolrInputDocument getSolrDocument(Binary premisBinary) throws GenericException {
        SolrInputDocument doc = new SolrInputDocument();
        InputStream inputStream = null;

        try {
            inputStream = premisBinary.getContent().createInputStream();
            gov.loc.premis.v3.File premisFile = binaryToFile(inputStream);
            if (premisFile.getOriginalName() != null) {
                doc.setField(RodaConstants.FILE_ORIGINALNAME, premisFile.getOriginalName().getStringValue());
                // TODO extension
            }

            if (premisFile.getObjectCharacteristicsArray() != null
                    && premisFile.getObjectCharacteristicsArray().length > 0) {
                ObjectCharacteristicsComplexType occt = premisFile.getObjectCharacteristicsArray(0);
                doc.setField(RodaConstants.FILE_SIZE, occt.getSize());
                if (occt.getFixityArray() != null && occt.getFixityArray().length > 0) {
                    List<String> hashes = new ArrayList<>();
                    for (FixityComplexType fct : occt.getFixityArray()) {
                        StringBuilder fixityPrint = new StringBuilder();
                        fixityPrint.append(fct.getMessageDigest());
                        fixityPrint.append(" (");
                        fixityPrint.append(fct.getMessageDigestAlgorithm().getStringValue());
                        if (StringUtils.isNotBlank(fct.getMessageDigestOriginator().getStringValue())) {
                            fixityPrint.append(", "); //
                            fixityPrint.append(fct.getMessageDigestOriginator().getStringValue());
                        }
                        fixityPrint.append(")");
                        hashes.add(fixityPrint.toString());
                    }
                    doc.addField(RodaConstants.FILE_HASH, hashes);
                }
                if (occt.getFormatArray() != null && occt.getFormatArray().length > 0) {
                    FormatComplexType fct = occt.getFormatArray(0);
                    if (fct.getFormatDesignation() != null) {
                        if (StringUtils.isNotBlank(fct.getFormatDesignation().getFormatName().getStringValue())) {
                            doc.addField(RodaConstants.FILE_FILEFORMAT,
                                    fct.getFormatDesignation().getFormatName().getStringValue());
                        }
                        if (StringUtils.isNotBlank(fct.getFormatDesignation().getFormatVersion())) {
                            doc.addField(RodaConstants.FILE_FORMAT_VERSION,
                                    fct.getFormatDesignation().getFormatVersion());
                        }
                    }

                    FormatRegistryComplexType pronomRegistry = getFormatRegistry(premisFile,
                            RodaConstants.PRESERVATION_REGISTRY_PRONOM);
                    if (pronomRegistry != null && pronomRegistry.getFormatRegistryKey() != null) {
                        doc.addField(RodaConstants.FILE_PRONOM,
                                pronomRegistry.getFormatRegistryKey().getStringValue());
                    }
                    FormatRegistryComplexType mimeRegistry = getFormatRegistry(premisFile,
                            RodaConstants.PRESERVATION_REGISTRY_MIME);
                    if (mimeRegistry != null && mimeRegistry.getFormatRegistryKey() != null) {
                        doc.addField(RodaConstants.FILE_FORMAT_MIMETYPE,
                                mimeRegistry.getFormatRegistryKey().getStringValue());
                    }
                    // TODO extension
                }
                if (occt.getCreatingApplicationArray() != null && occt.getCreatingApplicationArray().length > 0) {
                    CreatingApplicationComplexType cact = occt.getCreatingApplicationArray(0);
                    if (cact.getCreatingApplicationName() != null) {
                        doc.addField(RodaConstants.FILE_CREATING_APPLICATION_NAME,
                                cact.getCreatingApplicationName().getStringValue());
                    }
                    doc.addField(RodaConstants.FILE_CREATING_APPLICATION_VERSION,
                            cact.getCreatingApplicationVersion());
                    doc.addField(RodaConstants.FILE_DATE_CREATED_BY_APPLICATION,
                            cact.getDateCreatedByApplication());
                }
            }

        } catch (XmlException | IOException e) {
            LOGGER.error("Error updating Solr document", e);
        } finally {
            IOUtils.closeQuietly(inputStream);
        }
        return doc;
    }

    public static PreservationMetadata createPremisAgentBinary(Plugin<?> plugin, ModelService model, boolean notify)
            throws GenericException, NotFoundException, RequestNotValidException, AuthorizationDeniedException,
            ValidationException, AlreadyExistsException {
        String id = IdUtils.getPluginAgentId(plugin.getClass().getName(), plugin.getVersion());
        String extension = "";
        ContentPayload agentPayload = PremisV3Utils.createPremisAgentBinary(id, plugin.getName(),
                plugin.getAgentType(), extension, plugin.getDescription(), plugin.getVersion());
        return model.createPreservationMetadata(PreservationMetadataType.AGENT, id, agentPayload, notify);
    }

    public static void linkFileToRepresentation(String fileId, String relationshipType, String relationshipSubType,
            Representation r) throws GenericException, RequestNotValidException, NotFoundException,
            AuthorizationDeniedException, XmlException, IOException, ValidationException {

        RelationshipComplexType relationship = r.addNewRelationship();
        relationship.setRelationshipType(getStringPlusAuthority(relationshipType));
        relationship.setRelationshipSubType(getStringPlusAuthority(relationshipSubType));
        RelatedObjectIdentifierComplexType roict = relationship.addNewRelatedObjectIdentifier();
        roict.setRelatedObjectIdentifierType(getStringPlusAuthority(RodaConstants.PREMIS_IDENTIFIER_TYPE_URN));
        roict.setRelatedObjectIdentifierValue(fileId);
    }

    public static List<LinkingIdentifier> extractAgentsFromEvent(Binary b)
            throws ValidationException, GenericException {
        List<LinkingIdentifier> identifiers = new ArrayList<>();
        EventComplexType event = PremisV3Utils.binaryToEvent(b.getContent(), true);
        if (event.getLinkingAgentIdentifierArray() != null && event.getLinkingAgentIdentifierArray().length > 0) {
            for (LinkingAgentIdentifierComplexType laict : event.getLinkingAgentIdentifierArray()) {
                LinkingIdentifier li = new LinkingIdentifier();
                li.setType(laict.getLinkingAgentIdentifierType().getStringValue());
                li.setValue(laict.getLinkingAgentIdentifierValue());
                li.setRoles(stringplusArrayToStringList(laict.getLinkingAgentRoleArray()));
                identifiers.add(li);
            }
        }
        return identifiers;
    }

    public static List<LinkingIdentifier> extractObjectFromEvent(Binary binary)
            throws ValidationException, GenericException {
        List<LinkingIdentifier> identifiers = new ArrayList<>();
        EventComplexType event = PremisV3Utils.binaryToEvent(binary.getContent(), true);
        if (event.getLinkingObjectIdentifierArray() != null && event.getLinkingObjectIdentifierArray().length > 0) {
            for (LinkingObjectIdentifierComplexType loict : event.getLinkingObjectIdentifierArray()) {
                LinkingIdentifier li = new LinkingIdentifier();
                li.setType(loict.getLinkingObjectIdentifierType().getStringValue());
                li.setValue(loict.getLinkingObjectIdentifierValue());
                li.setRoles(stringplusArrayToStringList(loict.getLinkingObjectRoleArray()));
                identifiers.add(li);
            }
        }
        return identifiers;
    }

    public static StringPlusAuthority getStringPlusAuthority(String value) {
        return getStringPlusAuthority(value, "");
    }

    private static StringPlusAuthority getStringPlusAuthority(String value, String authority) {
        StringPlusAuthority spa = StringPlusAuthority.Factory.newInstance();
        spa.setStringValue(value);
        if (StringUtils.isNotBlank(authority)) {
            spa.setAuthority(authority);
        }
        return spa;
    }

    public static StringPlusAuthority[] getStringPlusAuthorityArray(List<String> values) {
        List<StringPlusAuthority> l = new ArrayList<>();
        if (values != null && !values.isEmpty()) {
            for (String value : values) {
                l.add(getStringPlusAuthority(value));
            }
        }
        return l.toArray(new StringPlusAuthority[l.size()]);
    }

    private static List<String> stringplusArrayToStringList(StringPlusAuthority[] source) {
        List<String> dst = new ArrayList<>();
        if (source != null && source.length > 0) {
            for (StringPlusAuthority spa : source) {
                dst.add(spa.getStringValue());
            }
        }
        return dst;
    }

    public static PreservationMetadata createPremisUserAgentBinary(String username, ModelService model,
            IndexService index, boolean notify) throws GenericException, ValidationException, NotFoundException,
            RequestNotValidException, AuthorizationDeniedException, AlreadyExistsException {
        PreservationMetadata pm = null;

        if (StringUtils.isNotBlank(username)) {

            String id = IdUtils.getUserAgentId(username);
            String fullName = "";
            String extension = "";
            String note = "";
            String version = "";

            try {
                RODAMember member = index.retrieve(RODAMember.class, username, Arrays.asList(
                        RodaConstants.INDEX_UUID, RodaConstants.MEMBERS_FULLNAME, RodaConstants.MEMBERS_EMAIL));
                fullName = member.getFullName();
                if (member instanceof User) {
                    User user = (User) member;
                    note = user.getEmail();
                }
            } catch (NotFoundException e) {
                LOGGER.warn("Could not find user and add its details to the PREMIS agent", e);
            }

            ContentPayload agentPayload = PremisV3Utils.createPremisAgentBinary(id, fullName,
                    PreservationAgentType.PERSON, extension, note, version);
            pm = model.createPreservationMetadata(PreservationMetadataType.AGENT, id, agentPayload, notify);

        }

        return pm;
    }

    public static void updateFormatPreservationMetadata(ModelService model, String aipId, String representationId,
            List<String> fileDirectoryPath, String fileId, String format, String version, String pronom,
            String mime, boolean notify) {
        Binary premisBin;

        try {
            try {
                premisBin = model.retrievePreservationFile(aipId, representationId, fileDirectoryPath, fileId);
            } catch (NotFoundException e) {
                LOGGER.debug("PREMIS object skeleton does not exist yet. Creating PREMIS object!");
                List<String> algorithms = RodaCoreFactory.getFixityAlgorithms();
                if (fileId == null) {
                    PremisSkeletonPluginUtils.createPremisSkeletonOnRepresentation(model, aipId, representationId,
                            algorithms);
                } else {
                    File file = model.retrieveFile(aipId, representationId, fileDirectoryPath, fileId);
                    PremisSkeletonPluginUtils.createPremisSkeletonOnFile(model, file, algorithms);
                }
                premisBin = model.retrievePreservationFile(aipId, representationId, fileDirectoryPath, fileId);
                LOGGER.debug("PREMIS object skeleton created");
            }

            gov.loc.premis.v3.File premisFile = binaryToFile(premisBin.getContent(), false);
            PremisV3Utils.updateFileFormat(premisFile, format, version, pronom, mime);

            PreservationMetadataType type = PreservationMetadataType.FILE;
            String id = IdUtils.getPreservationFileId(fileId);

            ContentPayload premisFilePayload = fileToBinary(premisFile);
            model.updatePreservationMetadata(id, type, aipId, representationId, fileDirectoryPath, fileId,
                    premisFilePayload, notify);
        } catch (RODAException | XmlException | IOException e) {
            LOGGER.error("PREMIS will not be updated due to an error", e);
        }
    }

    public static void updateCreatingApplicationPreservationMetadata(ModelService model, String aipId,
            String representationId, List<String> fileDirectoryPath, String fileId, String creatingApplicationName,
            String creatingApplicationVersion, String dateCreatedByApplication, boolean notify) {
        Binary premisBin;

        try {
            try {
                premisBin = model.retrievePreservationFile(aipId, representationId, fileDirectoryPath, fileId);
            } catch (NotFoundException e) {
                LOGGER.debug("PREMIS object skeleton does not exist yet. Creating PREMIS object!");
                List<String> algorithms = RodaCoreFactory.getFixityAlgorithms();
                PremisSkeletonPluginUtils.createPremisSkeletonOnRepresentation(model, aipId, representationId,
                        algorithms);
                premisBin = model.retrievePreservationFile(aipId, representationId, fileDirectoryPath, fileId);
                LOGGER.debug("PREMIS object skeleton created");
            }

            gov.loc.premis.v3.File premisFile = binaryToFile(premisBin.getContent(), false);
            PremisV3Utils.updateCreatingApplication(premisFile, creatingApplicationName, creatingApplicationVersion,
                    dateCreatedByApplication);

            PreservationMetadataType type = PreservationMetadataType.FILE;
            String id = IdUtils.getPreservationFileId(fileId);

            ContentPayload premisFilePayload = fileToBinary(premisFile);
            model.updatePreservationMetadata(id, type, aipId, representationId, fileDirectoryPath, fileId,
                    premisFilePayload, notify);
        } catch (RODAException | XmlException | IOException e) {
            LOGGER.error("PREMIS will not be updated due to an error", e);
        }
    }

}