cz.cas.lib.proarc.common.export.mets.structure.MetsElementVisitor.java Source code

Java tutorial

Introduction

Here is the source code for cz.cas.lib.proarc.common.export.mets.structure.MetsElementVisitor.java

Source

/*
 * Copyright (C) 2014 Robert Simonovsky
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package cz.cas.lib.proarc.common.export.mets.structure;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.codec.binary.Hex;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.yourmediashelf.fedora.client.FedoraClient;
import com.yourmediashelf.fedora.client.FedoraClientException;
import com.yourmediashelf.fedora.client.request.GetDatastreamDissemination;
import com.yourmediashelf.fedora.generated.foxml.DatastreamType;
import com.yourmediashelf.fedora.generated.foxml.DatastreamVersionType;

import cz.cas.lib.proarc.common.device.Device;
import cz.cas.lib.proarc.common.device.DeviceException;
import cz.cas.lib.proarc.common.device.DeviceRepository;
import cz.cas.lib.proarc.common.export.mets.Const;
import cz.cas.lib.proarc.common.export.mets.JHoveOutput;
import cz.cas.lib.proarc.common.export.mets.JhoveUtility;
import cz.cas.lib.proarc.common.export.mets.MetsContext;
import cz.cas.lib.proarc.common.export.mets.FileMD5Info;
import cz.cas.lib.proarc.common.export.mets.MetsExportException;
import cz.cas.lib.proarc.common.export.mets.MetsUtils;
import cz.cas.lib.proarc.common.export.mets.MimeType;
import cz.cas.lib.proarc.common.fedora.FoxmlUtils;
import cz.cas.lib.proarc.common.fedora.MixEditor;
import cz.cas.lib.proarc.common.ocr.AltoDatastream;
import cz.cas.lib.proarc.mets.AmdSecType;
import cz.cas.lib.proarc.mets.AreaType;
import cz.cas.lib.proarc.mets.DivType;
import cz.cas.lib.proarc.mets.MetsType;
import cz.cas.lib.proarc.mets.DivType.Fptr;
import cz.cas.lib.proarc.mets.FileType;
import cz.cas.lib.proarc.mets.FileType.FLocat;
import cz.cas.lib.proarc.mets.MdSecType;
import cz.cas.lib.proarc.mets.MdSecType.MdWrap;
import cz.cas.lib.proarc.mets.MdSecType.MdWrap.XmlData;
import cz.cas.lib.proarc.mets.Mets;
import cz.cas.lib.proarc.mets.MetsType.FileSec;
import cz.cas.lib.proarc.mets.MetsType.MetsHdr;
import cz.cas.lib.proarc.mets.MetsType.FileSec.FileGrp;
import cz.cas.lib.proarc.mets.MetsType.MetsHdr.Agent;
import cz.cas.lib.proarc.mets.MetsType.StructLink;
import cz.cas.lib.proarc.mets.StructLinkType.SmLink;
import cz.cas.lib.proarc.mets.StructMapType;
import cz.cas.lib.proarc.mix.BasicImageInformationType.BasicImageCharacteristics.PhotometricInterpretation;
import cz.cas.lib.proarc.mix.Mix;
import cz.cas.lib.proarc.mods.ModsDefinition;
import cz.cas.lib.proarc.oaidublincore.OaiDcType;
import cz.cas.lib.proarc.premis.AgentComplexType;
import cz.cas.lib.proarc.premis.AgentIdentifierComplexType;
import cz.cas.lib.proarc.premis.CreatingApplicationComplexType;
import cz.cas.lib.proarc.premis.EventComplexType;
import cz.cas.lib.proarc.premis.EventIdentifierComplexType;
import cz.cas.lib.proarc.premis.EventOutcomeInformationComplexType;
import cz.cas.lib.proarc.premis.FixityComplexType;
import cz.cas.lib.proarc.premis.FormatComplexType;
import cz.cas.lib.proarc.premis.FormatDesignationComplexType;
import cz.cas.lib.proarc.premis.FormatRegistryComplexType;
import cz.cas.lib.proarc.premis.LinkingAgentIdentifierComplexType;
import cz.cas.lib.proarc.premis.LinkingEventIdentifierComplexType;
import cz.cas.lib.proarc.premis.LinkingObjectIdentifierComplexType;
import cz.cas.lib.proarc.premis.ObjectCharacteristicsComplexType;
import cz.cas.lib.proarc.premis.ObjectFactory;
import cz.cas.lib.proarc.premis.ObjectIdentifierComplexType;
import cz.cas.lib.proarc.premis.OriginalNameComplexType;
import cz.cas.lib.proarc.premis.PremisComplexType;
import cz.cas.lib.proarc.premis.PreservationLevelComplexType;
import cz.cas.lib.proarc.premis.RelatedEventIdentificationComplexType;
import cz.cas.lib.proarc.premis.RelatedObjectIdentificationComplexType;
import cz.cas.lib.proarc.premis.RelationshipComplexType;

/**
 * Visitor class for creating mets document out of Mets objects
 *
 * @author Robert Simonovsky
 *
 */

public class MetsElementVisitor implements IMetsElementVisitor {
    private final Logger LOG = Logger.getLogger(MetsElementVisitor.class.getName());
    private Mets mets;
    private StructMapType logicalStruct;
    private StructMapType physicalStruct;
    private HashMap<String, FileGrp> fileGrpMap;
    private final Map<StructLinkMapping, String> pageOrderToDivMap = new HashMap<StructLinkMapping, String>();
    private final Map<String, List<StructLinkMapping>> structToPageMap = new HashMap<String, List<StructLinkMapping>>();
    int pageCounter = 0;
    int articleCounter = 0;
    int chapterCounter = 0;

    /**
     * creates directory structure for mets elements
     */

    private void createDirectoryStructure(MetsContext metsContext) {
        for (String directory : Const.streamMappingFile.values()) {
            File file = new File(metsContext.getOutputPath() + File.separator + metsContext.getPackageID()
                    + File.separator + directory);
            if (file.exists()) {
                deleteFolder(file);
            }
            file.mkdir();
        }
    }

    /**
     * Deletes a folder
     *
     * @param folder
     */
    private static void deleteFolder(File folder) {
        File[] files = folder.listFiles();
        if (files != null) {
            for (File f : files) {
                if (f.isDirectory()) {
                    deleteFolder(f);
                } else {
                    f.delete();
                }
            }
        }
        folder.delete();
    }

    /**
     * Inits the Mets header info
     */
    protected void initHeader(IMetsElement metsElement) {
        mets.setLabel1(metsElement.getLabel());
        MetsHdr metsHdr = new MetsHdr();
        metsHdr.setCREATEDATE(metsElement.getCreateDate());
        metsHdr.setLASTMODDATE(metsElement.getLastUpdateDate());
        Agent agent = new Agent();
        agent.setName(metsElement.getMetsContext().getCreatorOrganization());
        agent.setROLE("CREATOR");
        agent.setTYPE("ORGANIZATION");
        metsHdr.getAgent().add(agent);
        mets.setMetsHdr(metsHdr);
        fileGrpMap = MetsUtils.initFileGroups();
    }

    /**
     * Prepares the generic mets information
     *
     * @param metsElement
     * @return
     * @throws MetsExportException
     */
    private Mets prepareMets(IMetsElement metsElement) throws MetsExportException {
        Mets mets = new Mets();
        logicalStruct = new StructMapType();
        logicalStruct.setTYPE("LOGICAL");
        logicalStruct.setLabel2("Logical_Structure");
        mets.getStructMap().add(logicalStruct);
        physicalStruct = new StructMapType();
        physicalStruct.setTYPE("PHYSICAL");
        physicalStruct.setLabel2("Physical_Structure");
        mets.getStructMap().add(physicalStruct);
        return mets;
    }

    /**
     * Saves the mets document into a file
     *
     * @param mets
     * @param outputFile
     * @throws MetsExportException
     */
    private void saveMets(Mets mets, File outputFile, IMetsElement metsElement) throws MetsExportException {
        String fileMd5Name;
        try {
            addFileGrpToMets(fileGrpMap);
            addStructLink();
            try {
                JAXBContext jaxbContext = JAXBContext.newInstance(Mets.class, OaiDcType.class,
                        ModsDefinition.class);
                Marshaller marshaller = jaxbContext.createMarshaller();
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
                marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
                // marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
                // "http://www.w3.org/2001/XMLSchema-instance http://www.w3.org/2001/XMLSchema.xsd http://www.loc.gov/METS/ http://www.loc.gov/standards/mets/mets.xsd http://www.loc.gov/mods/v3 http://www.loc.gov/standards/mods/mods.xsd http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd");
                marshaller.marshal(mets, outputFile);
                MessageDigest md;
                try {
                    md = MessageDigest.getInstance("MD5");
                } catch (NoSuchAlgorithmException e) {
                    throw new MetsExportException("Unable to create MD5 hash", false, e);
                }
                md.reset();
                InputStream is;
                try {
                    is = new FileInputStream(outputFile);
                } catch (FileNotFoundException e) {
                    throw new MetsExportException("Unable to open file:" + outputFile.getAbsolutePath(), false, e);
                }
                byte[] bytes = new byte[2048];
                int numBytes;
                long totalBytes = 0;
                try {
                    while ((numBytes = is.read(bytes)) != -1) {
                        totalBytes = totalBytes + numBytes;

                        md.update(bytes, 0, numBytes);
                    }
                } catch (IOException e) {
                    throw new MetsExportException("Unable to generate MD5 hash", false, e);
                }
                byte[] digest = md.digest();
                String result = new String(Hex.encodeHex(digest));
                metsElement.getMetsContext().getFileList()
                        .add(new FileMD5Info("." + File.separator + outputFile.getName(), result, totalBytes));
                fileMd5Name = "MD5_" + MetsUtils.removeNonAlpabetChars(metsElement.getMetsContext().getPackageID())
                        + ".md5";
                File fileMd5 = new File(metsElement.getMetsContext().getOutputPath() + File.separator
                        + metsElement.getMetsContext().getPackageID() + File.separator + fileMd5Name);
                OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(fileMd5));
                for (FileMD5Info info : metsElement.getMetsContext().getFileList()) {
                    osw.write(info.getMd5() + " " + info.getFileName() + "\n");
                }
                osw.close();
                is.close();

                // calculate md5 for md5file - it's inserted into info.xml
                is = new FileInputStream(fileMd5);
                FileMD5Info md5InfoMd5File = MetsUtils.getDigest(is);
                is.close();
                metsElement.getMetsContext().getFileList()
                        .add(new FileMD5Info("." + File.separator + fileMd5Name, null, fileMd5.length()));
                MetsUtils.saveInfoFile(metsElement.getMetsContext().getOutputPath(), metsElement.getMetsContext(),
                        md5InfoMd5File.getMd5(), fileMd5Name, outputFile);
            } catch (Exception ex) {
                throw new MetsExportException(metsElement.getOriginalPid(),
                        "Unable to save mets file:" + outputFile.getAbsolutePath(), false, ex);
            }
            List<String> validationErrors;
            try {
                validationErrors = MetsUtils.validateAgainstXSD(outputFile,
                        Mets.class.getResourceAsStream("mets.xsd"));
            } catch (Exception ex) {
                throw new MetsExportException("Error while validation document:" + outputFile, false, ex);
            }
            if (validationErrors.size() > 0) {
                MetsExportException metsException = new MetsExportException("Invalid mets file:" + outputFile,
                        false, null);
                metsException.getExceptions().get(0).setValidationErrors(validationErrors);
                for (String error : validationErrors) {
                    LOG.fine(error);
                }
                throw metsException;
            }
            LOG.log(Level.FINE,
                    "Element validated:" + metsElement.getOriginalPid() + "(" + metsElement.getElementType() + ")");
        } finally {
            JhoveUtility.destroyConfigFiles(metsElement.getMetsContext().getJhoveContext());
        }
        metsElement.getMetsContext().getGeneratedPSP().add(metsElement.getMetsContext().getPackageID());
    }

    /**
     * Adds all non-empty filegroups to the mets
     *
     * @param fileGrpMap
     * @param fileSec
     */
    private void addFileGrpToMets(Map<String, FileGrp> fileGrpMap) {
        for (String key : fileGrpMap.keySet()) {
            FileGrp fileGrp = fileGrpMap.get(key);
            if (fileGrp.getFile().size() > 0) {
                if (mets.getFileSec() == null) {
                    mets.setFileSec(new FileSec());
                }
                mets.getFileSec().getFileGrp().add(fileGrp);
            }
        }
    }

    /**
     *
     * Adds an element descriptors (DC, BIBLIO_MODS) to the mets document
     *
     * @param metsElement
     */
    private void addDmdSec(IMetsElement metsElement) {
        // MODS
        if (metsElement.getModsStream() != null) {
            MdSecType modsMdSecType = new MdSecType();
            metsElement.setModsMetsElement(modsMdSecType);
            mets.getDmdSec().add(modsMdSecType);
            modsMdSecType.setID("MODSMD_" + metsElement.getModsElementID());
            MdWrap modsMdWrap = new MdWrap();
            modsMdWrap.setMDTYPE("MODS");
            modsMdWrap.setMIMETYPE("text/xml");
            XmlData modsxmlData = new XmlData();
            metsElement.getModsStream().get(0).setAttribute("ID", "MODS_" + metsElement.getModsElementID());
            modsxmlData.getAny().addAll(metsElement.getModsStream());
            modsMdWrap.setXmlData(modsxmlData);
            modsMdSecType.setMdWrap(modsMdWrap);
        }

        // DC
        if (metsElement.getDescriptor() != null) {
            MdSecType dcMdSecType = new MdSecType();
            mets.getDmdSec().add(dcMdSecType);
            dcMdSecType.setID("DCMD_" + metsElement.getModsElementID());
            MdWrap dcMdWrap = new MdWrap();
            dcMdWrap.setMDTYPE("DC");
            dcMdWrap.setMIMETYPE("text/xml");
            XmlData dcxmlData = new XmlData();
            dcxmlData.getAny().addAll(metsElement.getDescriptor());
            dcMdWrap.setXmlData(dcxmlData);
            dcMdSecType.setMdWrap(dcMdWrap);
        }
    }

    /**
     * adds an order and index attributes to pageDiv
     *
     * @param metsElement
     * @param pageDiv
     * @throws MetsExportException
     */
    private void fillPageIndexOrder(IMetsElement metsElement, DivType pageDiv) throws MetsExportException {
        Node partNode = MetsUtils.xPathEvaluateNode(metsElement.getModsStream(),
                "*[local-name()='modsCollection']/*[local-name()='mods']/*[local-name()='part']");
        if (partNode == null) {
            partNode = MetsUtils.xPathEvaluateNode(metsElement.getModsStream(),
                    "*[local-name()='mods']/*[local-name()='part']");
        }
        if ((partNode.getAttributes() != null) && (partNode.getAttributes().getNamedItem("type") != null)) {
            pageDiv.setTYPE(partNode.getAttributes().getNamedItem("type").getNodeValue());
        } else {
            pageDiv.setTYPE("NormalPage");
        }
        NodeList nodeList = partNode.getChildNodes();
        for (int a = 0; a < nodeList.getLength(); a++) {
            if ((nodeList.item(a).getLocalName() != null)
                    && (nodeList.item(a).getLocalName().equalsIgnoreCase("detail"))) {
                Node numberNode = nodeList.item(a).getChildNodes().item(0).getFirstChild();
                if (nodeList.item(a).getAttributes().getNamedItem("type").getNodeValue()
                        .equalsIgnoreCase("pageNumber")) {
                    pageDiv.setORDERLABEL(numberNode.getNodeValue());
                }
                if (nodeList.item(a).getAttributes().getNamedItem("type").getNodeValue()
                        .equalsIgnoreCase("pageIndex")) {
                    pageDiv.setORDER(new BigInteger(numberNode.getNodeValue()));
                }
            }
        }
    }

    private InputStream addLabelToAmdSec(InputStream is, MetsContext metsContext) throws MetsExportException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            MetsUtils.copyStream(is, bos);
            bos.close();
        } catch (IOException ex) {
            throw new MetsExportException("Unable to copy stream", false, ex);
        }
        Document TECHDoc = MetsUtils.getDocumentFromBytes(bos.toByteArray());
        Element element = (Element) TECHDoc.getFirstChild();
        element.setAttribute("LABEL", mets.getLabel1());
        element.setAttribute("TYPE", mets.getTYPE());
        DOMSource domSource = new DOMSource(TECHDoc);
        StringWriter xmlAsWriter = new StringWriter();
        StreamResult result = new StreamResult(xmlAsWriter);
        try {
            TransformerFactory.newInstance().newTransformer().transform(domSource, result);
            InputStream resultIS = new ByteArrayInputStream(xmlAsWriter.toString().getBytes("UTF-8"));
            is.close();
            return resultIS;
        } catch (Exception ex) {
            throw new MetsExportException("Unable to transform Tech metadata to XML", false, ex);
        }
    }

    /**
     * Prepares a mets FileType element for a file
     *
     * @param seq
     * @param metsStreamName
     * @return
     */
    private FileType prepareFileType(int seq, String metsStreamName, HashMap<String, Object> fileNames,
            HashMap<String, String> mimeTypes, MetsContext metsContext, HashMap<String, String> outputFileNames,
            HashMap<String, FileMD5Info> md5InfosMap) throws MetsExportException {
        // String streamName = Const.streamMapping.get(metsStreamName);
        FileType fileType = new FileType();
        fileType.setCHECKSUMTYPE("MD5");
        GregorianCalendar gregory = new GregorianCalendar();
        gregory.setTime(new Date());

        XMLGregorianCalendar calendar;
        try {
            calendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregory);
        } catch (DatatypeConfigurationException e1) {
            throw new MetsExportException("Unable to create XMLGregorianDate", false, e1);
        }
        fileType.setCREATED(calendar);
        fileType.setSEQ(seq);
        fileType.setMIMETYPE(mimeTypes.get(metsStreamName));
        InputStream is = null;
        fileType.setID(Const.streamMappingPrefix.get(metsStreamName) + "_"
                + MetsUtils.removeNonAlpabetChars(metsContext.getPackageID()) + "_" + String.format("%04d", seq));
        if (fileNames.get(metsStreamName) instanceof String) {
            String fileNameOriginal = (String) fileNames.get(metsStreamName);
            int lastIndex = fileNameOriginal.lastIndexOf('/');
            int preLastIndex = fileNameOriginal.substring(1, lastIndex).lastIndexOf('/');
            String fileName = metsContext.getPath() + fileNameOriginal.substring(preLastIndex + 2);
            File file = new File(fileName);
            try {
                is = new FileInputStream(file);

            } catch (FileNotFoundException e) {
                throw new MetsExportException("File not found:" + fileName, false, e);
            }
        }
        if (fileNames.get(metsStreamName) instanceof byte[]) {
            byte[] bytes = (byte[]) fileNames.get(metsStreamName);
            is = new ByteArrayInputStream(bytes);
        }
        if (fileNames.get(metsStreamName) instanceof InputStream) {
            is = (InputStream) fileNames.get(metsStreamName);
        }

        if (metsStreamName.equalsIgnoreCase("TECHMDGRP")) {
            is = addLabelToAmdSec(is, metsContext);
        }

        String outputFileName = fileType.getID() + "." + MimeType.getExtension(mimeTypes.get(metsStreamName));
        String fullOutputFileName = metsContext.getPackageDir().getAbsolutePath() + File.separator
                + Const.streamMappingFile.get(metsStreamName) + File.separator + outputFileName;
        outputFileNames.put(metsStreamName, fullOutputFileName);
        try {
            FileMD5Info fileMD5Info;
            if (md5InfosMap.get(metsStreamName) == null) {
                fileMD5Info = MetsUtils.getDigestAndCopy(is, new FileOutputStream(fullOutputFileName));
                md5InfosMap.put(metsStreamName, fileMD5Info);
            } else {
                FileMD5Info tempMd5 = MetsUtils.getDigestAndCopy(is, new FileOutputStream(fullOutputFileName));
                fileMD5Info = md5InfosMap.get(metsStreamName);
                fileMD5Info.setSize(tempMd5.getSize());
                fileMD5Info.setMd5(tempMd5.getMd5());
            }
            fileType.setSIZE(Long.valueOf(fileMD5Info.getSize()));
            fileMD5Info.setFileName("." + File.separator + Const.streamMappingFile.get(metsStreamName)
                    + File.separator + outputFileName);
            fileMD5Info.setMimeType(fileType.getMIMETYPE());
            fileType.setCHECKSUM(fileMD5Info.getMd5());
            metsContext.getFileList().add(fileMD5Info);
        } catch (Exception e) {
            throw new MetsExportException("Unable to process file " + fullOutputFileName, false, e);
        }
        FLocat flocat = new FLocat();
        flocat.setLOCTYPE("URL");
        String href = "." + "/" + Const.streamMappingFile.get(metsStreamName) + "/" + outputFileName;
        URI uri;
        uri = URI.create(href);
        flocat.setHref(uri.toASCIIString());
        fileType.getFLocat().add(flocat);
        return fileType;
    }

    /**
     * Reads files/streams for each stream and puts it into the map (fileNames)
     *
     * @param metsElement
     * @param seq
     * @param fileNames
     * @param mimeTypes
     * @throws MetsExportException
     */
    private void processPageFiles(IMetsElement metsElement, int seq, HashMap<String, Object> fileNames,
            HashMap<String, String> mimeTypes, HashMap<String, XMLGregorianCalendar> createDates,
            HashMap<String, FileMD5Info> md5InfosMap) throws MetsExportException {
        for (String streamName : Const.streamMapping.keySet()) {
            if (metsElement.getMetsContext().getFedoraClient() != null) {
                try {
                    // GetDatastreamsResponse streams =
                    // FedoraClient.getDatastreams(metsElement.getOriginalPid()).execute(metsElement.getMetsContext().getFedoraClient());
                    // List<DatastreamProfile> profiles =
                    // streams.getDatastreamProfiles();
                    // for (DatastreamProfile profile : profiles) {
                    // if (profile.getDsID().contains(streamName)) {
                    for (String dataStream : Const.streamMapping.get(streamName)) {
                        DatastreamType rawDS = FoxmlUtils.findDatastream(metsElement.getSourceObject(), dataStream);
                        if (rawDS != null) {
                            FileMD5Info fileMd5Info;
                            if (md5InfosMap.get(streamName) == null) {
                                fileMd5Info = new FileMD5Info();
                                md5InfosMap.put(streamName, fileMd5Info);
                            } else {
                                fileMd5Info = md5InfosMap.get(streamName);
                            }
                            fileMd5Info.setCreated(rawDS.getDatastreamVersion().get(0).getCREATED());

                            GetDatastreamDissemination dsRaw = FedoraClient
                                    .getDatastreamDissemination(metsElement.getOriginalPid(), dataStream);
                            createDates.put(streamName, rawDS.getDatastreamVersion().get(0).getCREATED());
                            try {
                                InputStream is = dsRaw.execute(metsElement.getMetsContext().getFedoraClient())
                                        .getEntityInputStream();
                                fileNames.put(streamName, is);
                            } catch (FedoraClientException e) {
                                throw new MetsExportException(metsElement.getOriginalPid(),
                                        "Unable to read raw datastream content", false, e);
                            }
                            // mimeTypes.put(streamName, profile.getDsMIME());
                            mimeTypes.put(streamName, rawDS.getDatastreamVersion().get(0).getMIMETYPE());
                            break;
                            //
                        }
                    }
                } catch (Exception ex) {
                    throw new MetsExportException(metsElement.getOriginalPid(),
                            "Error while getting file datastreams for " + metsElement.getOriginalPid(), false, ex);
                }
            } else {
                List<DatastreamType> datastreams = metsElement.getSourceObject().getDatastream();
                for (String dataStream : Const.streamMapping.get(streamName)) {
                    if (fileNames.get(streamName) != null) {
                        break;
                    }
                    for (DatastreamType ds : datastreams) {
                        if (MetsUtils.equalDataStreams(ds.getID(), dataStream)) {
                            Iterator<DatastreamVersionType> dvIter = ds.getDatastreamVersion().iterator();
                            while (dvIter.hasNext()) {
                                DatastreamVersionType dv = dvIter.next();
                                mimeTypes.put(streamName, dv.getMIMETYPE());
                                if (dv.getContentLocation() != null) {
                                    fileNames.put(streamName, dv.getContentLocation().getREF());
                                    FileMD5Info fileMd5Info;
                                    if (md5InfosMap.get(streamName) == null) {
                                        fileMd5Info = new FileMD5Info();
                                        md5InfosMap.put(streamName, fileMd5Info);
                                    } else {
                                        fileMd5Info = md5InfosMap.get(streamName);
                                    }
                                    fileMd5Info.setCreated(dv.getCREATED());
                                }
                                if (dv.getBinaryContent() != null) {
                                    fileNames.put(streamName, dv.getBinaryContent());
                                    FileMD5Info fileMd5Info;
                                    if (md5InfosMap.get(streamName) == null) {
                                        fileMd5Info = new FileMD5Info();
                                        md5InfosMap.put(streamName, fileMd5Info);
                                    } else {
                                        fileMd5Info = md5InfosMap.get(streamName);
                                    }
                                    fileMd5Info.setCreated(dv.getCREATED());
                                }
                            }
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * Returns the description of scanner
     *
     * @param metsElement
     * @return
     * @throws MetsExportException
     */
    private Mix getScannerMix(IMetsElement metsElement) throws MetsExportException {
        if (metsElement.getMetsContext().getRemoteStorage() != null) {
            Node deviceNode = MetsUtils.xPathEvaluateNode(metsElement.getRelsExt(),
                    "*[local-name()='RDF']/*[local-name()='Description']/*[local-name()='hasDevice']");
            if (deviceNode == null) {
                return null;
            }
            Node attrNode = deviceNode.getAttributes().getNamedItem("rdf:resource");
            if (attrNode == null) {
                return null;
            }
            DeviceRepository deviceRepository = new DeviceRepository(
                    metsElement.getMetsContext().getRemoteStorage());
            String deviceId = attrNode.getNodeValue().replaceAll("info:fedora/", "");
            List<Device> deviceList;
            try {
                deviceList = deviceRepository.find(deviceId, true);
            } catch (DeviceException e) {
                throw new MetsExportException(metsElement.getOriginalPid(), "Unable to get scanner info", false, e);
            }
            if (deviceList.size() != 1) {
                throw new MetsExportException(metsElement.getOriginalPid(),
                        "Unable to get scanner info - expected 1 device, got:" + deviceList.size(), false, null);
            }
            Device device = deviceList.get(0);
            if ((device.getDescription() == null) || (device.getDescription().getImageCaptureMetadata() == null)) {
                throw new MetsExportException(metsElement.getOriginalPid(),
                        "Scanner device does not have the description/imageCaptureMetadata set", false, null);
            }
            Mix mix = device.getDescription();
            return mix;
        }
        return null;
    }

    private Node getAgent(IMetsElement metsElement) throws MetsExportException {
        AgentComplexType agent = new AgentComplexType();
        ObjectFactory factory = new ObjectFactory();
        JAXBElement<AgentComplexType> jaxbPremix = factory.createAgent(agent);
        AgentIdentifierComplexType agentIdentifier = new AgentIdentifierComplexType();
        agent.getAgentIdentifier().add(agentIdentifier);
        agentIdentifier.setAgentIdentifierType("ProArc_AgentID");
        agentIdentifier.setAgentIdentifierValue("ProArc");
        agent.setAgentType("software");
        agent.getAgentName().add("ProArc");

        JAXBContext jc;
        try {
            jc = JAXBContext.newInstance(AgentComplexType.class);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.newDocument();

            // Marshal the Object to a Document
            Marshaller marshaller = jc.createMarshaller();
            marshaller.marshal(jaxbPremix, document);
            XPath xpath = XPathFactory.newInstance().newXPath();
            Node agentNode = (Node) xpath.compile("*[local-name()='agent']").evaluate(document,
                    XPathConstants.NODE);
            return agentNode;
        } catch (Exception e) {
            throw new MetsExportException(metsElement.getOriginalPid(), "Error while generating premis data", false,
                    e);
        }

    }

    private Node getPremisEvent(IMetsElement metsElement, String datastream, FileMD5Info md5Info,
            String eventDetail) throws MetsExportException {
        PremisComplexType premis = new PremisComplexType();
        ObjectFactory factory = new ObjectFactory();
        JAXBElement<PremisComplexType> jaxbPremix = factory.createPremis(premis);
        EventComplexType event = factory.createEventComplexType();
        premis.getEvent().add(event);
        event.setEventDateTime(md5Info.getCreated().toXMLFormat());
        event.setEventDetail(eventDetail);
        EventIdentifierComplexType eventIdentifier = new EventIdentifierComplexType();
        event.setEventIdentifier(eventIdentifier);
        event.setEventType("derivation");
        eventIdentifier.setEventIdentifierType("ProArc_EventID");
        eventIdentifier.setEventIdentifierValue(Const.dataStreamToEvent.get(datastream));
        EventOutcomeInformationComplexType eventInformation = new EventOutcomeInformationComplexType();
        event.getEventOutcomeInformation().add(eventInformation);
        eventInformation.getContent().add(factory.createEventOutcome("successful"));
        LinkingAgentIdentifierComplexType linkingAgentIdentifier = new LinkingAgentIdentifierComplexType();
        linkingAgentIdentifier.setLinkingAgentIdentifierType("ProArc_AgentID");
        linkingAgentIdentifier.setLinkingAgentIdentifierValue("ProArc");
        linkingAgentIdentifier.getLinkingAgentRole().add("software");
        LinkingObjectIdentifierComplexType linkingObject = new LinkingObjectIdentifierComplexType();
        linkingObject.setLinkingObjectIdentifierType("ProArc_URI");
        linkingObject.setLinkingObjectIdentifierValue(
                Const.FEDORAPREFIX + metsElement.getOriginalPid() + "/" + Const.dataStreamToModel.get(datastream));
        event.getLinkingObjectIdentifier().add(linkingObject);
        event.getLinkingAgentIdentifier().add(linkingAgentIdentifier);
        JAXBContext jc;
        try {
            jc = JAXBContext.newInstance(PremisComplexType.class);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.newDocument();

            // Marshal the Object to a Document
            Marshaller marshaller = jc.createMarshaller();
            marshaller.marshal(jaxbPremix, document);
            XPath xpath = XPathFactory.newInstance().newXPath();
            Node premisNode = (Node) xpath.compile("*[local-name()='premis']/*[local-name()='event']")
                    .evaluate(document, XPathConstants.NODE);
            return premisNode;
        } catch (Exception e) {
            throw new MetsExportException(metsElement.getOriginalPid(), "Error while generating premis data", false,
                    e);
        }
    }

    /**
     * Generates the premis for amdSec
     *
     * @param metsElement
     * @param datastream
     * @param md5Info
     * @param created
     * @param formatVersion
     * @return
     * @throws MetsExportException
     */
    private Node getPremisFile(IMetsElement metsElement, String datastream, FileMD5Info md5Info)
            throws MetsExportException {
        PremisComplexType premis = new PremisComplexType();
        ObjectFactory factory = new ObjectFactory();
        JAXBElement<PremisComplexType> jaxbPremix = factory.createPremis(premis);
        cz.cas.lib.proarc.premis.File file = factory.createFile();
        premis.getObject().add(file);
        ObjectIdentifierComplexType objectIdentifier = new ObjectIdentifierComplexType();
        objectIdentifier.setObjectIdentifierType("ProArc_URI");
        objectIdentifier.setObjectIdentifierValue(
                Const.FEDORAPREFIX + metsElement.getOriginalPid() + "/" + Const.dataStreamToModel.get(datastream));
        file.getObjectIdentifier().add(objectIdentifier);
        PreservationLevelComplexType preservation = new PreservationLevelComplexType();
        if ("RAW".equals(datastream)) {
            preservation.setPreservationLevelValue("deleted");
        } else {
            preservation.setPreservationLevelValue("preservation");
        }
        file.getPreservationLevel().add(preservation);
        ObjectCharacteristicsComplexType characteristics = new ObjectCharacteristicsComplexType();
        characteristics.setCompositionLevel(BigInteger.ZERO);
        file.getObjectCharacteristics().add(characteristics);
        FixityComplexType fixity = new FixityComplexType();
        fixity.setMessageDigest(md5Info.getMd5());
        fixity.setMessageDigestAlgorithm("MD5");
        fixity.setMessageDigestOriginator("ProArc");
        characteristics.getFixity().add(fixity);
        characteristics.setSize(md5Info.getSize());
        FormatComplexType format = new FormatComplexType();
        characteristics.getFormat().add(format);
        FormatDesignationComplexType formatDesignation = new FormatDesignationComplexType();
        formatDesignation.setFormatName(md5Info.getMimeType());
        formatDesignation.setFormatVersion(md5Info.getFormatVersion());
        JAXBElement<FormatDesignationComplexType> jaxbDesignation = factory
                .createFormatDesignation(formatDesignation);
        format.getContent().add(jaxbDesignation);
        FormatRegistryComplexType formatRegistry = new FormatRegistryComplexType();
        formatRegistry.setFormatRegistryName("PRONOM");
        formatRegistry.setFormatRegistryKey(Const.mimeToFmtMap.get(md5Info.getMimeType()));
        JAXBElement<FormatRegistryComplexType> jaxbRegistry = factory.createFormatRegistry(formatRegistry);
        format.getContent().add(jaxbRegistry);

        CreatingApplicationComplexType creatingApplication = new CreatingApplicationComplexType();
        characteristics.getCreatingApplication().add(creatingApplication);
        creatingApplication.getContent().add(factory.createCreatingApplicationName("ProArc"));

        creatingApplication.getContent()
                .add(factory.createCreatingApplicationVersion(metsElement.getMetsContext().getProarcVersion()));
        creatingApplication.getContent()
                .add(factory.createDateCreatedByApplication(MetsUtils.getCurrentDate().toXMLFormat()));

        RelationshipComplexType relationShip = new RelationshipComplexType();

        if (!("RAW").equals(datastream)) {
            relationShip.setRelationshipType("derivation");
            relationShip.setRelationshipSubType("created from");
            RelatedObjectIdentificationComplexType relatedObject = new RelatedObjectIdentificationComplexType();
            relationShip.getRelatedObjectIdentification().add(relatedObject);
            relatedObject.setRelatedObjectIdentifierType("ProArc_URI");
            relatedObject.setRelatedObjectIdentifierValue(
                    Const.FEDORAPREFIX + metsElement.getOriginalPid() + "/" + Const.dataStreamToModel.get("RAW"));
            RelatedEventIdentificationComplexType eventObject = new RelatedEventIdentificationComplexType();
            relationShip.getRelatedEventIdentification().add(eventObject);
            eventObject.setRelatedEventIdentifierType("ProArc_EventID");
            eventObject.setRelatedEventIdentifierValue(Const.dataStreamToEvent.get(datastream));
            eventObject.setRelatedEventSequence(BigInteger.ONE);
            file.getRelationship().add(relationShip);
        } else {
            relationShip.setRelationshipType("creation");
            relationShip.setRelationshipSubType("created from");
            LinkingEventIdentifierComplexType eventIdentifier = new LinkingEventIdentifierComplexType();
            file.getLinkingEventIdentifier().add(eventIdentifier);
            eventIdentifier.setLinkingEventIdentifierType("ProArc_EventID");
            eventIdentifier.setLinkingEventIdentifierValue(Const.dataStreamToEvent.get(datastream));
        }

        String originalFile = MetsUtils.xPathEvaluateString(metsElement.getRelsExt(),
                "*[local-name()='RDF']/*[local-name()='Description']/*[local-name()='importFile']");
        OriginalNameComplexType originalName = factory.createOriginalNameComplexType();
        originalName.setValue(originalFile);
        file.setOriginalName(originalName);

        JAXBContext jc;
        try {
            jc = JAXBContext.newInstance(PremisComplexType.class);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document document = db.newDocument();

            // Marshal the Object to a Document
            Marshaller marshaller = jc.createMarshaller();
            marshaller.marshal(jaxbPremix, document);
            XPath xpath = XPathFactory.newInstance().newXPath();
            Node premisNode = (Node) xpath.compile("*[local-name()='premis']/*[local-name()='object']")
                    .evaluate(document, XPathConstants.NODE);
            return premisNode;
        } catch (Exception e) {
            throw new MetsExportException(metsElement.getOriginalPid(), "Error while generating premis data", false,
                    e);
        }
    }

    private void addPremisNodeToMets(Node premisNode, AmdSecType amdSec, String Id, boolean isDigiprov,
            HashMap<String, FileGrp> amdSecFileGrpMap) {
        MdSecType mdSec = new MdSecType();
        mdSec.setID(Id);
        MdWrap mdWrap = new MdWrap();
        mdWrap.setMIMETYPE("text/xml");
        mdWrap.setMDTYPE("PREMIS");
        XmlData xmlData = new XmlData();
        xmlData.getAny().add(premisNode);
        mdWrap.setXmlData(xmlData);
        mdSec.setMdWrap(mdWrap);
        if (isDigiprov) {
            amdSec.getDigiprovMD().add(mdSec);
        } else {
            amdSec.getTechMD().add(mdSec);
        }
        if ("OBJ_002".equals(Id) || ("EVT_002".equals(Id))) {
            if ((amdSecFileGrpMap.get("MC_IMGGRP") != null)
                    && (amdSecFileGrpMap.get("MC_IMGGRP").getFile().get(0) != null)) {
                amdSecFileGrpMap.get("MC_IMGGRP").getFile().get(0).getADMID().add(mdSec);
            }
        }
        if ("OBJ_003".equals(Id) || ("EVT_003".equals(Id))) {
            if ((amdSecFileGrpMap.get("ALTOGRP") != null)
                    && (amdSecFileGrpMap.get("ALTOGRP").getFile().get(0) != null)) {
                amdSecFileGrpMap.get("ALTOGRP").getFile().get(0).getADMID().add(mdSec);
            }
        }
    }

    private void addPremisToAmdSec(AmdSecType amdSec, HashMap<String, FileMD5Info> md5InfosMap,
            IMetsElement metsElement, HashMap<String, FileGrp> amdSecFileGrpMap) throws MetsExportException {
        HashMap<String, String> toGenerate = new HashMap<String, String>();
        toGenerate.put("OBJ_001", "RAW");
        toGenerate.put("OBJ_002", "MC_IMGGRP");
        toGenerate.put("OBJ_003", "ALTOGRP");

        for (String obj : toGenerate.keySet()) {
            String stream = toGenerate.get(obj);
            if (md5InfosMap.get(stream) == null) {
                continue;
            }
            addPremisNodeToMets(getPremisFile(metsElement, stream, md5InfosMap.get(stream)), amdSec, obj, false,
                    amdSecFileGrpMap);
        }

        if (md5InfosMap.get("RAW") != null) {
            addPremisNodeToMets(getPremisEvent(metsElement, "RAW", md5InfosMap.get("RAW"), "capture/digitization"),
                    amdSec, "EVT_001", true, null);
        }

        if (md5InfosMap.get("MC_IMGGRP") != null) {
            addPremisNodeToMets(
                    getPremisEvent(metsElement, "MC_IMGGRP", md5InfosMap.get("MC_IMGGRP"), "migration/MC_creation"),
                    amdSec, "EVT_002", true, amdSecFileGrpMap);
        }
        if (md5InfosMap.get("ALTOGRP") != null) {
            addPremisNodeToMets(
                    getPremisEvent(metsElement, "ALTOGRP", md5InfosMap.get("ALTOGRP"), "capture/XML_creation"),
                    amdSec, "EVT_003", true, amdSecFileGrpMap);
        }

        addPremisNodeToMets(getAgent(metsElement), amdSec, "AGENT_001", true, null);
    }

    /**
     * Fixes PS Mix
     *
     * @param jHoveOutputRaw
     * @param metsElement
     * @param rawCreated
     */
    public static void fixPSMix(JHoveOutput jHoveOutputRaw, String originalPid, XMLGregorianCalendar rawCreated) {
        JhoveUtility.insertObjectIdentifier(jHoveOutputRaw.getMix(), originalPid, "RAW");
        JhoveUtility.addDenominator(jHoveOutputRaw);
        JhoveUtility.addOrientation(jHoveOutputRaw);
        JhoveUtility.insertDateCreated(jHoveOutputRaw.getMix(), rawCreated);
    }

    /**
     * Fixes MC Mix
     *
     * @param jHoveOutputMC
     * @param metsElement
     * @param mcCreated
     * @param originalFile
     * @param photometricInterpretation
     */
    public static void fixMCMix(JHoveOutput jHoveOutputMC, String originalPid, XMLGregorianCalendar mcCreated,
            String originalFile, PhotometricInterpretation photometricInterpretation) {
        JhoveUtility.insertChangeHistory(jHoveOutputMC.getMix(), mcCreated, originalFile);
        JhoveUtility.insertObjectIdentifier(jHoveOutputMC.getMix(), originalPid, "MC_IMGGRP");
        JhoveUtility.addPhotometricInformation(jHoveOutputMC, photometricInterpretation);
        JhoveUtility.addDenominator(jHoveOutputMC);
        JhoveUtility.addOrientation(jHoveOutputMC);
        JhoveUtility.insertDateCreated(jHoveOutputMC.getMix(), mcCreated);
    }

    /**
     * Generates technical metadata using JHOVE
     *
     * @param metsElement
     * @param fileNames
     * @param seq
     * @param fileTypes
     * @param mimeTypes
     * @param pageDiv
     * @throws MetsExportException
     */
    private void generateTechMetadata(IMetsElement metsElement, HashMap<String, Object> fileNames, int seq,
            HashMap<String, FileGrp> fileGrpPage, HashMap<String, String> mimeTypes, DivType pageDiv,
            HashMap<String, String> outputFileNames, HashMap<String, FileMD5Info> md5InfosMap)
            throws MetsExportException {
        if (fileNames.get("TECHMDGRP") == null) {
            LOG.log(Level.FINE, "Generating tech");
            Mets amdSecMets = new Mets();
            amdSecMets.setLabel1(mets.getLabel1());
            amdSecMets.setTYPE(mets.getTYPE());
            StructMapType mapType = new StructMapType();
            mapType.setTYPE(Const.DIV_PHYSICAL_ID);
            amdSecMets.getStructMap().add(mapType);
            AmdSecType amdSec = new AmdSecType();
            amdSec.setID(metsElement.getElementID());
            amdSecMets.getAmdSec().add(amdSec);
            DivType divType = new DivType();
            if (Const.PERIODICAL_TITLE
                    .equalsIgnoreCase(metsElement.getMetsContext().getRootElement().getElementType())) {
                divType.setTYPE("PERIODICAL_PAGE");
            } else {
                divType.setTYPE("MONOGRAPH_PAGE");
            }

            FileSec fileSec = new FileSec();
            amdSecMets.setFileSec(fileSec);
            HashMap<String, FileGrp> amdSecFileGrpMap = new HashMap<String, MetsType.FileSec.FileGrp>();
            for (String fileMap : fileGrpPage.keySet()) {
                FileGrp fileGrp = fileGrpPage.get(fileMap);
                if (fileGrp.getFile().size() > 0) {
                    FileGrp fileGrpAmd = new FileGrp();
                    amdSecFileGrpMap.put(fileMap, fileGrpAmd);
                    fileGrpAmd.setID(fileGrp.getID());
                    fileGrpAmd.setUSE(fileGrp.getUSE());
                    fileSec.getFileGrp().add(fileGrpAmd);
                    for (FileType fileTypePage : fileGrp.getFile()) {
                        FileType fileTypeAmdSec = new FileType();
                        fileTypeAmdSec.setCHECKSUM(fileTypePage.getCHECKSUM());
                        fileTypeAmdSec.setCHECKSUMTYPE(fileTypePage.getCHECKSUMTYPE());
                        fileTypeAmdSec.setCREATED(fileTypePage.getCREATED());
                        fileTypeAmdSec.setID(fileTypePage.getID());
                        fileTypeAmdSec.setMIMETYPE(fileTypePage.getMIMETYPE());
                        fileTypeAmdSec.setSEQ(fileTypePage.getSEQ());
                        fileTypeAmdSec.setSIZE(fileTypePage.getSIZE());
                        fileGrpAmd.getFile().add(fileTypeAmdSec);
                        if (fileTypePage.getFLocat().get(0) != null) {
                            FLocat flocatAmd = new FLocat();
                            FLocat pageFlocat = fileTypePage.getFLocat().get(0);
                            if (pageFlocat.getHref() != null) {
                                flocatAmd.setHref(".." + pageFlocat.getHref().substring(1));
                            }
                            flocatAmd.setLOCTYPE(pageFlocat.getLOCTYPE());
                            fileTypeAmdSec.getFLocat().add(flocatAmd);
                        }
                        Fptr fptr = new Fptr();
                        fptr.setFILEID(fileTypeAmdSec);
                        divType.getFptr().add(fptr);
                    }
                }
            }

            HashMap<String, String> toGenerate = new HashMap<String, String>();
            File rawFile = null;
            XMLGregorianCalendar rawCreated = null;
            Mix mixDevice = getScannerMix(metsElement);
            // RAW datastream for MIX_001 - only for Fedora
            PhotometricInterpretation photometricInterpretation = null;
            JHoveOutput jHoveOutputRaw = null;
            JHoveOutput jHoveOutputMC = null;
            if (metsElement.getMetsContext().getFedoraClient() != null) {
                try {
                    DatastreamType rawDS = FoxmlUtils.findDatastream(metsElement.getSourceObject(), "RAW");
                    if (rawDS != null) {
                        GetDatastreamDissemination dsRaw = FedoraClient
                                .getDatastreamDissemination(metsElement.getOriginalPid(), "RAW");
                        try {
                            rawCreated = rawDS.getDatastreamVersion().get(0).getCREATED();
                            InputStream is = dsRaw.execute(metsElement.getMetsContext().getFedoraClient())
                                    .getEntityInputStream();
                            String rawExtendsion = MimeType
                                    .getExtension(rawDS.getDatastreamVersion().get(0).getMIMETYPE());
                            rawFile = new File(metsElement.getMetsContext().getOutputPath() + File.separator
                                    + metsElement.getMetsContext().getPackageID() + File.separator + "raw" + "."
                                    + rawExtendsion);
                            FileMD5Info rawinfo;
                            try {
                                rawinfo = MetsUtils.getDigestAndCopy(is, new FileOutputStream(rawFile));
                            } catch (NoSuchAlgorithmException e) {
                                throw new MetsExportException(metsElement.getOriginalPid(),
                                        "Unable to copy RAW image and get digest", false, e);
                            }
                            rawinfo.setMimeType(rawDS.getDatastreamVersion().get(0).getMIMETYPE());
                            rawinfo.setCreated(rawDS.getDatastreamVersion().get(0).getCREATED());
                            md5InfosMap.put("RAW", rawinfo);
                            outputFileNames.put("RAW", rawFile.getAbsolutePath());
                            toGenerate.put("MIX_001", "RAW");

                            // If mix is present in fedora, then use this one
                            if (metsElement.getMetsContext().getFedoraClient() != null) {
                                jHoveOutputRaw = JhoveUtility.getMixFromFedora(metsElement, MixEditor.RAW_ID);
                            }
                            // If not present, then generate new
                            if (jHoveOutputRaw == null) {
                                jHoveOutputRaw = JhoveUtility.getMix(new File(rawFile.getAbsolutePath()),
                                        metsElement.getMetsContext(), mixDevice, rawCreated, null);
                                if (jHoveOutputRaw.getMix() == null) {
                                    throw new MetsExportException(metsElement.getOriginalPid(),
                                            "Unable to generate Mix information for RAW image", false, null);
                                }
                            } else {
                                // Merges the information from the device mix
                                JhoveUtility.mergeMix(jHoveOutputRaw.getMix(), mixDevice);
                            }
                            if ((jHoveOutputRaw.getMix() != null)
                                    && (jHoveOutputRaw.getMix().getBasicImageInformation() != null)
                                    && (jHoveOutputRaw.getMix().getBasicImageInformation()
                                            .getBasicImageCharacteristics() != null)
                                    && (jHoveOutputRaw.getMix().getBasicImageInformation()
                                            .getBasicImageCharacteristics()
                                            .getPhotometricInterpretation() != null)) {
                                photometricInterpretation = jHoveOutputRaw.getMix().getBasicImageInformation()
                                        .getBasicImageCharacteristics().getPhotometricInterpretation();
                            }
                            fixPSMix(jHoveOutputRaw, metsElement.getOriginalPid(), rawCreated);
                        } catch (FedoraClientException e) {
                            throw new MetsExportException(metsElement.getOriginalPid(),
                                    "Unable to read raw datastream content", false, e);
                        }
                    }
                } catch (IOException ex) {
                    throw new MetsExportException(metsElement.getOriginalPid(),
                            "Error while getting RAW datastream " + metsElement.getOriginalPid(), false, ex);
                }
            }

            if (fileNames.get("MC_IMGGRP") != null) {
                toGenerate.put("MIX_002", "MC_IMGGRP");
                String outputFileName = outputFileNames.get("MC_IMGGRP");
                if (outputFileName != null) {
                    String originalFile = MetsUtils.xPathEvaluateString(metsElement.getRelsExt(),
                            "*[local-name()='RDF']/*[local-name()='Description']/*[local-name()='importFile']");
                    if (metsElement.getMetsContext().getFedoraClient() != null) {
                        jHoveOutputMC = JhoveUtility.getMixFromFedora(metsElement, MixEditor.NDK_ARCHIVAL_ID);
                    }
                    if (jHoveOutputMC == null) {
                        jHoveOutputMC = JhoveUtility.getMix(new File(outputFileName), metsElement.getMetsContext(),
                                null, md5InfosMap.get("MC_IMGGRP").getCreated(), originalFile);
                        if (jHoveOutputMC.getMix() == null) {
                            throw new MetsExportException(metsElement.getOriginalPid(),
                                    "Unable to generate Mix information for MC image", false, null);
                        }
                    }
                    fixMCMix(jHoveOutputMC, metsElement.getOriginalPid(), md5InfosMap.get("MC_IMGGRP").getCreated(),
                            originalFile, photometricInterpretation);
                }
            }

            for (String name : toGenerate.keySet()) {
                String streamName = toGenerate.get(name);

                if (streamName != null) {
                    MdSecType mdSec = new MdSecType();
                    mdSec.setID(name);
                    MdWrap mdWrap = new MdWrap();
                    mdWrap.setMIMETYPE("text/xml");
                    mdWrap.setMDTYPE("NISOIMG");
                    XmlData xmlData = new XmlData();
                    Node mixNode = null;

                    if ("RAW".equals(streamName)) {
                        if (jHoveOutputRaw != null) {
                            mixNode = jHoveOutputRaw.getMixNode();
                            if (md5InfosMap.get(streamName) != null) {
                                md5InfosMap.get(streamName).setFormatVersion(jHoveOutputRaw.getFormatVersion());
                            }
                        }
                    } else if (("MC_IMGGRP".equals(streamName)) && (md5InfosMap.get("MC_IMGGRP") != null)) {
                        if (jHoveOutputMC != null) {
                            mixNode = jHoveOutputMC.getMixNode();
                            if (md5InfosMap.get(streamName) != null) {
                                md5InfosMap.get(streamName).setFormatVersion(jHoveOutputMC.getFormatVersion());
                            }
                            if (mixNode != null) {
                                if ((amdSecFileGrpMap.get("MC_IMGGRP") != null)
                                        && (amdSecFileGrpMap.get("MC_IMGGRP").getFile().get(0) != null)) {
                                    amdSecFileGrpMap.get("MC_IMGGRP").getFile().get(0).getADMID().add(mdSec);
                                }
                            }
                        }
                    }

                    if (mixNode != null) {
                        xmlData.getAny().add(mixNode);
                    } else {
                        throw new MetsExportException(metsElement.getOriginalPid(),
                                "Unable to generate image metadata (MIX) for " + streamName, false, null);
                    }

                    mdWrap.setXmlData(xmlData);
                    mdSec.setMdWrap(mdWrap);
                    amdSec.getTechMD().add(mdSec);
                }
            }

            if (rawFile != null) {
                outputFileNames.remove("RAW");
                rawFile.delete();
            }

            if (outputFileNames.get("ALTOGRP") != null) {
                File altoFile = new File(outputFileNames.get("ALTOGRP"));
                if (altoFile.exists()) {
                    Schema altoSchema;
                    try {
                        altoSchema = AltoDatastream.getSchema();
                    } catch (SAXException e) {
                        throw new MetsExportException("Unable to get ALTO schema", false);
                    }
                    try {
                        altoSchema.newValidator().validate(new StreamSource(altoFile));
                    } catch (Exception exSax) {
                        throw new MetsExportException(metsElement.getOriginalPid(), "Invalid ALTO", false, exSax);
                    }
                    md5InfosMap.get("ALTOGRP").setFormatVersion("2.0");
                }
            }

            addPremisToAmdSec(amdSec, md5InfosMap, metsElement, amdSecFileGrpMap);
            mapType.setDiv(divType);
            saveAmdSec(metsElement, amdSecMets, fileNames, mimeTypes);
            FileType fileType = prepareFileType(seq, "TECHMDGRP", fileNames, mimeTypes,
                    metsElement.getMetsContext(), outputFileNames, md5InfosMap);
            this.fileGrpMap.get("TECHMDGRP").getFile().add(fileType);
            Fptr fptr = new Fptr();
            fptr.setFILEID(fileType);
            pageDiv.getFptr().add(fptr);
        }
    }

    /**
     *
     * Parses an ALTO stream and returns a list of internal elements
     *
     * @param document
     * @return
     */
    private List<IntPartInfo> parseAltoInfo(Document document) {
        List<IntPartInfo> intPartInfoList = new ArrayList<IntPartInfo>();
        Node partElement = document.getFirstChild();
        NodeList partsList = partElement.getChildNodes();
        for (int a = 0; a < partsList.getLength(); a++) {
            Node node = partsList.item(a);
            if ((node instanceof Element) && (node.hasAttributes())) {
                String type = node.getAttributes().getNamedItem("type").getNodeValue();
                String alto = node.getAttributes().getNamedItem("alto").getNodeValue();
                String begin = node.getAttributes().getNamedItem("begin").getNodeValue();
                String order = node.getAttributes().getNamedItem("order").getNodeValue();
                IntPartInfo info = new IntPartInfo(type, alto.substring(0, alto.indexOf("/")), begin, order);
                intPartInfoList.add(info);
            }
        }
        return intPartInfoList;
    }

    /**
     *
     * Fills the "isOnPage" structure - currently not used, prepared for future
     * release
     *
     * @param metsElement
     * @throws MetsExportException
     */
    @SuppressWarnings("unused")
    private void fillIsOnPage(IMetsElement metsElement) throws MetsExportException {
        Node node = MetsUtils.xPathEvaluateNode(metsElement.getRelsExt(),
                "*[local-name()='RDF']/*[local-name()='Description']");
        NodeList hasPageNodes = node.getChildNodes();
        for (int a = 0; a < hasPageNodes.getLength(); a++) {
            if (hasPageNodes.item(a).getNodeName().equalsIgnoreCase(Const.ISONPAGE)) {
                String fileName = hasPageNodes.item(a).getAttributes().getNamedItem("rdf:resource").getNodeValue();
                IMetsElement page = metsElement.getMetsContext().getPidElements()
                        .get(fileName.substring(fileName.indexOf(File.separator) + 1));
                SmLink smLink = new SmLink();
                smLink.setFrom(metsElement.getElementID());
                smLink.setTo(page.getElementID());
                if (mets.getStructLink() == null) {
                    mets.setStructLink(new MetsType.StructLink());
                }
                mets.getStructLink().getSmLinkOrSmLinkGrp().add(smLink);
            }
        }
    }

    /**
     *
     * saves technical metadata
     *
     * @param amdSecMets
     */
    private void saveAmdSec(IMetsElement metsElement, Mets amdSecMets, HashMap<String, Object> fileNames,
            HashMap<String, String> mimeTypes) throws MetsExportException {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance(Mets.class);
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
            // marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION,
            // "http://www.w3.org/2001/XMLSchema-instance http://www.w3.org/2001/XMLSchema.xsd http://www.loc.gov/METS/ http://www.loc.gov/standards/mets/mets.xsd http://www.loc.gov/MIX/ http://www.loc.gov/mix/v20");
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            marshaller.marshal(amdSecMets, bos);
            byte[] byteArray = bos.toByteArray();
            fileNames.put("TECHMDGRP", byteArray);
            mimeTypes.put("TECHMDGRP", "text/xml");
            Document document = MetsUtils.getDocumentFromBytes(byteArray);
            MetsUtils.validateAgainstXSD(document, Mets.class.getResourceAsStream("mets.xsd"));
        } catch (Exception ex) {
            throw new MetsExportException(metsElement.getOriginalPid(), "Error while saving AMDSec file", false,
                    ex);
        }
    }

    /**
     * Inserts Issue structure to the mets
     *
     * @param physicalDiv
     * @param logicalDiv
     * @param metsElement
     * @throws MetsExportException
     */
    private void insertIssue(DivType physicalDiv, DivType logicalDiv, IMetsElement metsElement)
            throws MetsExportException {
        addDmdSec(metsElement);
        physicalDiv.setID("DIV_P_0000");
        physicalDiv.setLabel3(metsElement.getLabel());
        physicalDiv.getDMDID().add(metsElement.getModsMetsElement());
        physicalDiv.setTYPE(metsElement.getElementType());

        DivType divType = new DivType();
        divType.setID(metsElement.getElementID());
        divType.setLabel3(metsElement.getMetsContext().getRootElement().getLabel());
        divType.setTYPE(metsElement.getElementType());
        divType.getDMDID().add(metsElement.getModsMetsElement());
        logicalDiv.getDiv().add(divType);

        for (IMetsElement element : metsElement.getChildren()) {
            if (Const.PAGE.equals(element.getElementType())) {
                insertPage(physicalDiv, element, pageCounter, metsElement);
                pageCounter++;
                continue;
            }

            if (Const.SUPPLEMENT.equals(element.getElementType())) {
                insertSupplement(divType, physicalDiv, element);
                continue;
            }

            if (Const.PICTURE.equals(element.getElementType())) {
                insertPicture(divType, physicalDiv, element);
                continue;
            }

            if (Const.ARTICLE.equals(element.getElementType())) {
                continue;
            }

            throw new MetsExportException(element.getOriginalPid(),
                    "This type is not accepted in Issue:" + metsElement.getElementType(), false, null);
        }

        for (IMetsElement element : metsElement.getChildren()) {
            if (Const.ARTICLE.equals(element.getElementType())) {
                insertArticle(divType, physicalDiv, element, articleCounter);
                articleCounter++;
                continue;
            }
        }
    }

    /**
     * Return the first parent, which can contain pages
     *
     * @param metsElement
     * @return
     * @throws MetsExportException
     */
    private IMetsElement findFirstParentWithPage(IMetsElement metsElement) throws MetsExportException {
        if (metsElement == null) {
            throw new MetsExportException("Unable to find parent with pages", false, null);
        }
        if (!Const.canContainPage.contains(metsElement.getElementType())) {
            if (metsElement.getParent() == null) {
                throw new MetsExportException(metsElement.getOriginalPid(), "Unable to find parent with pages",
                        false, null);
            }
            return findFirstParentWithPage(metsElement.getParent());
        }
        return metsElement;
    }

    /**
     * Inserts Page structure to the mets
     *
     * @param physicalDiv
     * @param metsElement
     * @param pageCounter
     * @param sourceElement
     * @throws MetsExportException
     */
    private void insertPage(DivType physicalDiv, IMetsElement metsElement, int pageCounter,
            IMetsElement sourceElement) throws MetsExportException {
        List<IMetsElement> sourceElements = new ArrayList<IMetsElement>();
        sourceElements.add(sourceElement);
        insertPage(physicalDiv, metsElement, pageCounter, sourceElements);
    }

    /**
     * Inserts Page structure to the mets
     *
     * @param physicalDiv
     * @param metsElement
     * @param pageCounter
     * @param sourceElement
     * @throws MetsExportException
     */
    private void insertPage(DivType physicalDiv, IMetsElement metsElement, int pageCounter,
            List<IMetsElement> sourceElements) throws MetsExportException {
        if (metsElement.getMetsContext().getPackageDir() == null) {
            File packageDir = createPackageDir(metsElement);
            metsElement.getMetsContext().setPackageDir(packageDir);
        }
        HashMap<String, String> outputFileNames = new HashMap<String, String>();

        if (!Const.PAGE.equals(metsElement.getElementType())
                && !Const.MONOGRAPH_UNIT.equals(metsElement.getElementType())) {
            throw new MetsExportException(metsElement.getOriginalPid(),
                    "Expected page, got " + metsElement.getElementType(), false, null);
        }
        HashMap<String, FileGrp> fileGrpPage = MetsUtils.initFileGroups();
        DivType pageDiv = new DivType();
        physicalDiv.getDiv().add(pageDiv);
        fillPageIndexOrder(metsElement, pageDiv);
        String ID = "DIV_P_PAGE_" + metsElement.getElementID().replace("PAGE_", "");
        pageDiv.setID(ID);
        HashMap<String, Object> fileNames = new HashMap<String, Object>();
        HashMap<String, String> mimeTypes = new HashMap<String, String>();
        HashMap<String, XMLGregorianCalendar> createDates = new HashMap<String, XMLGregorianCalendar>();
        HashMap<String, FileMD5Info> md5InfosMap = new HashMap<String, FileMD5Info>();
        processPageFiles(metsElement, pageCounter, fileNames, mimeTypes, createDates, md5InfosMap);
        for (String streamName : Const.streamMapping.keySet()) {
            if (fileNames.containsKey(streamName)) {
                FileType fileType = prepareFileType(pageCounter, streamName, fileNames, mimeTypes,
                        metsElement.getMetsContext(), outputFileNames, md5InfosMap);
                fileGrpPage.get(streamName).getFile().add(fileType);
                fileGrpMap.get(streamName).getFile().add(fileType);
                Fptr fptr = new Fptr();
                fptr.setFILEID(fileType);
                pageDiv.getFptr().add(fptr);
                if ("ALTOGRP".equals(streamName)) {
                    metsElement.setAltoFile(fileType);
                }
            } else {
                if ((Const.mandatoryStreams.contains(streamName))
                        && (!metsElement.getMetsContext().isAllowNonCompleteStreams())) {
                    throw new MetsExportException(metsElement.getOriginalPid(),
                            "Stream:" + streamName + " is missing", false, null);
                }
            }
        }
        generateTechMetadata(metsElement, fileNames, pageCounter, fileGrpPage, mimeTypes, pageDiv, outputFileNames,
                md5InfosMap);

        StructLinkMapping structLinkMapping = new StructLinkMapping();
        structLinkMapping.pageDiv = metsElement.getParent().getModsElementID();
        structLinkMapping.pageOrder = pageDiv.getORDER();
        pageOrderToDivMap.put(structLinkMapping, ID);
        for (IMetsElement sourceElement : sourceElements) {
            addMappingPageStruct(structLinkMapping, sourceElement.getModsElementID());
        }
    }

    class StructLinkMapping {
        String pageDiv;
        BigInteger pageOrder;

        @Override
        public int hashCode() {
            return pageDiv.hashCode() * 1000 + pageOrder.hashCode();
        };

        @Override
        public boolean equals(Object obj) {
            StructLinkMapping structLinkMapping = (StructLinkMapping) obj;
            if (structLinkMapping.pageDiv.equals(this.pageDiv)
                    && (structLinkMapping.pageOrder.equals(this.pageOrder))) {
                return true;
            }
            return false;
        }
    }

    private void addMappingPageStruct(StructLinkMapping structLinkMapping, String fromDiv) {
        if (structToPageMap.get(fromDiv) == null) {
            structToPageMap.put(fromDiv, new ArrayList<StructLinkMapping>());
        }
        structToPageMap.get(fromDiv).add(structLinkMapping);
    }

    /**
     * Adds the struct-link to the mets
     *
     * @throws MetsExportException
     */
    private void addStructLink() throws MetsExportException {
        if (structToPageMap.keySet().size() > 0) {
            StructLink structLink = mets.getStructLink();
            if (structLink == null) {
                structLink = new StructLink();
                mets.setStructLink(structLink);
            }

            for (String structFrom : structToPageMap.keySet()) {
                if (structToPageMap.get(structFrom) != null) {
                    for (StructLinkMapping structLinkMapping : structToPageMap.get(structFrom)) {
                        if (pageOrderToDivMap.get(structLinkMapping) != null) {
                            SmLink smLink = new SmLink();
                            smLink.setFrom(structFrom);
                            smLink.setTo(pageOrderToDivMap.get(structLinkMapping));
                            structLink.getSmLinkOrSmLinkGrp().add(smLink);
                        } else {
                            throw new MetsExportException("Unable to find DIV for page order:"
                                    + structLinkMapping.pageDiv + " " + structLinkMapping.pageOrder, false, null);
                        }
                    }
                }
            }
        }
    }

    /**
     * Generates PackageID from the metsElement info
     *
     * @param element
     * @return
     * @throws MetsExportException
     */
    private String getPackageID(IMetsElement element) throws MetsExportException {
        Map<String, String> identifiersMap = element.getModsIdentifiers();
        if (identifiersMap.containsKey(Const.URNNBN)) {
            String urnnbn = identifiersMap.get(Const.URNNBN);
            return urnnbn.substring(urnnbn.lastIndexOf(":") + 1);
        } else if (element.getMetsContext().isAllowMissingURNNBN()) {
            // if missing URNNBN is allowed, then try to use UUID - otherwise
            // throw an exception
            element.getMetsContext().getMetsExportException().addException(element.getOriginalPid(),
                    "URNNBN identifier is missing", true, null);
            if (identifiersMap.containsKey(Const.UUID)) {
                return identifiersMap.get(Const.UUID);
            } else {
                throw new MetsExportException(element.getOriginalPid(),
                        "Unable to find identifier URNNBN and UUID is missing", false, null);
            }
        } else {
            // URNNBN is mandatory
            throw new MetsExportException(element.getOriginalPid(), "URNNBN identifier is missing", true, null);
        }
    }

    /**
     * Creates a directory for package
     *
     * @param metsElement
     * @return
     * @throws MetsExportException
     */
    private File createPackageDir(IMetsElement metsElement) throws MetsExportException {
        if (metsElement.getMetsContext().getPackageID() == null) {
            throw new MetsExportException(metsElement.getOriginalPid(), "Package ID is null", false, null);
        }
        File file = new File(metsElement.getMetsContext().getOutputPath() + File.separator
                + metsElement.getMetsContext().getPackageID());
        if (file.exists()) {
            if (file.isDirectory()) {
                createDirectoryStructure(metsElement.getMetsContext());
                return file;
            } else {
                throw new MetsExportException(metsElement.getOriginalPid(),
                        "File:" + file.getAbsolutePath() + " exists, but is not directory", false, null);
            }
        } else {
            file.mkdir();
            createDirectoryStructure(metsElement.getMetsContext());
            return file;
        }
    }

    /**
     * Inserts Supplement structure to the mets
     *
     * @param logicalDiv
     * @param physicalDiv
     * @param metsElement
     * @throws MetsExportException
     */
    private void insertSupplement(DivType logicalDiv, DivType physicalDiv, IMetsElement metsElement)
            throws MetsExportException {
        addDmdSec(metsElement);
        if (physicalDiv.getID() == null) {
            physicalDiv.setID("DIV_P_0000");
            physicalDiv.setLabel3(metsElement.getLabel());
            physicalDiv.getDMDID().add(metsElement.getModsMetsElement());
            physicalDiv.setTYPE(metsElement.getElementType());
        }

        DivType divType = new DivType();
        divType.setID(metsElement.getElementID());
        divType.setLabel3(metsElement.getMetsContext().getRootElement().getLabel());
        divType.setTYPE(Const.typeNameMap.get(metsElement.getElementType()));
        divType.getDMDID().add(metsElement.getModsMetsElement());
        logicalDiv.getDiv().add(divType);
        for (IMetsElement element : metsElement.getChildren()) {
            if (Const.PAGE.equals(element.getElementType())) {
                insertPage(physicalDiv, element, pageCounter, metsElement);
                pageCounter++;
            } else if (Const.PICTURE.equals(element.getElementType())) {
                insertPicture(divType, physicalDiv, element);
            } else
                throw new MetsExportException(element.getOriginalPid(),
                        "Expected Page or Picture, got:" + element.getElementType(), false, null);
        }
    }

    /**
     * Inserts Volume structure to the mets
     *
     * @param logicalDiv
     * @param physicalDiv
     * @param metsElement
     * @param volumeCounter
     * @param isMultiPartMonograph
     * @throws MetsExportException
     */
    private void insertVolume(DivType logicalDiv, DivType physicalDiv, IMetsElement metsElement,
            boolean isMultiPartMonograph) throws MetsExportException {
        addDmdSec(metsElement);
        DivType divType = new DivType();
        divType.setID(metsElement.getElementID());
        // Label for volume is inherited from the parent monograph
        divType.setLabel3(metsElement.getMetsContext().getRootElement().getLabel());
        if (Const.PERIODICAL_VOLUME.equals(metsElement.getElementType())) {
            divType.setTYPE("PERIODICAL_VOLUME");
        } else if (Const.MONOGRAPH_MULTIPART.equals(metsElement.getElementType())) {
            divType.setTYPE(Const.MONOGRAPH);
        } else if (Const.MONOGRAPH_UNIT.equals(metsElement.getElementType())) {
            divType.setTYPE("VOLUME");
            divType.setID(metsElement.getElementID().replaceAll(Const.MONOGRAPH_UNIT, Const.VOLUME));
            physicalDiv.getDMDID().add(metsElement.getModsMetsElement());
        } else {
            divType.setTYPE(Const.VOLUME);
        }

        divType.getDMDID().add(metsElement.getModsMetsElement());
        logicalDiv.getDiv().add(divType);
        for (IMetsElement element : metsElement.getChildren()) {
            if (Const.ISSUE.equals(element.getElementType())) {
                element.getMetsContext().setPackageID(getPackageID(element));
                insertIssue(physicalDiv, divType, element);
                continue;
            } else if (Const.SUPPLEMENT.equals(element.getElementType())) {
                if (!Const.MONOGRAPH_UNIT.equals(metsElement.getElementType())) {
                    element.getMetsContext().setPackageID(getPackageID(element));
                }
                insertSupplement(divType, physicalDiv, element);
            } else if (Const.PAGE.equals(element.getElementType())) {
                insertPage(physicalDiv, element, pageCounter, metsElement);
                pageCounter++;
                continue;
            } else if (Const.CHAPTER.equals(element.getElementType())) {
                insertChapter(divType, physicalDiv, element, chapterCounter);
                chapterCounter++;
            } else if (Const.PICTURE.equals(element.getElementType())) {
                insertPicture(divType, physicalDiv, element);
            } else
                throw new MetsExportException(element.getOriginalPid(),
                        "Expected Issue, Supplement, Picture or Page, got:" + element.getElementType(), false,
                        null);
        }
    }

    /**
     * Inserts Monograph structure to the mets
     *
     * @param metsElement
     * @throws MetsExportException
     */
    private void insertMonograph(IMetsElement metsElement) throws MetsExportException {
        mets.setTYPE("Monograph");
        DivType logicalDiv = new DivType();
        logicalStruct.setDiv(logicalDiv);
        DivType physicalDiv = new DivType();
        physicalDiv.setLabel3(metsElement.getLabel());
        physicalDiv.setID("DIV_P_0000");
        physicalDiv.setTYPE("MONOGRAPH");
        physicalStruct.setDiv(physicalDiv);

        boolean containsUnit = false;
        if (Const.MONOGRAPH_MULTIPART.equalsIgnoreCase(metsElement.getElementType())) {
            containsUnit = true;
        }
        for (IMetsElement childMetsElement : metsElement.getChildren()) {
            if (Const.MONOGRAPH_UNIT.equals(childMetsElement.getElementType())) {
                containsUnit = true;
                break;
            }
        }
        logicalDiv.setLabel3(metsElement.getLabel());
        logicalDiv.setTYPE("MONOGRAPH");
        logicalDiv.setID("MONOGRAPH_0001");
        if (!containsUnit) {
            metsElement.getMetsContext().setPackageID(getPackageID(metsElement));
            insertVolume(logicalDiv, physicalDiv, metsElement, false);
        } else {
            metsElement.setModsElementID("TITLE_0001");
            addDmdSec(metsElement);
            logicalDiv.getDMDID().add(metsElement.getModsMetsElement());
            physicalDiv.getDMDID().add(metsElement.getModsMetsElement());
            for (IMetsElement childMetsElement : metsElement.getChildren()) {
                if (Const.MONOGRAPH_UNIT.equals(childMetsElement.getElementType())) {
                    continue;
                } else if (Const.SUPPLEMENT.equals(childMetsElement.getElementType())) {
                    childMetsElement.getMetsContext().setPackageID(getPackageID(childMetsElement));
                    insertSupplement(logicalDiv, physicalDiv, childMetsElement);
                } else if (Const.PAGE.equals(childMetsElement.getElementType())) {
                    pageCounter++;
                    insertPage(physicalDiv, childMetsElement, pageCounter, metsElement);
                } else if (Const.CHAPTER.equals(childMetsElement.getElementType())) {
                    insertChapter(logicalDiv, physicalDiv, childMetsElement, chapterCounter);
                    chapterCounter++;
                } else
                    throw new MetsExportException(childMetsElement.getOriginalPid(),
                            "Expected Supplement, Monograph unit, Chapter or Page, got:"
                                    + childMetsElement.getElementType(),
                            false, null);
            }

            for (IMetsElement childMetsElement : metsElement.getChildren()) {
                if (Const.MONOGRAPH_UNIT.equals(childMetsElement.getElementType())) {
                    childMetsElement.getMetsContext().setPackageID(getPackageID(childMetsElement));
                    insertVolume(logicalDiv, physicalDiv, childMetsElement, true);
                }
            }
        }
    }

    /**
     *
     * Adds the internal elements into the mets div
     *
     * @param parentType
     */
    private void addInternalElements(DivType parentType, IMetsElement metsElement) throws MetsExportException {
        byte[] structStream;
        if (metsElement.getMetsContext().getFedoraClient() != null) {
            structStream = MetsUtils.getBinaryDataStreams(metsElement.getMetsContext().getFedoraClient(),
                    metsElement, "STRUCT_MAP");
        } else {
            structStream = MetsUtils.getBinaryDataStreams(metsElement.getSourceObject().getDatastream(),
                    "STRUCT_MAP");
        }
        if (structStream == null) {
            return;
        }
        List<IntPartInfo> partInfoList = parseAltoInfo(MetsUtils.getDocumentFromBytes(structStream));
        for (IntPartInfo partInfo : partInfoList) {
            DivType divType = new DivType();
            divType.setTYPE(partInfo.getType());
            if ((partInfo.getOrder() != null) && (!("null".equalsIgnoreCase(partInfo.getOrder())))) {
                try {
                    divType.setORDER(new BigInteger(partInfo.getOrder()));
                } catch (NumberFormatException ex) {
                    LOG.log(Level.WARNING,
                            partInfo.getOrder() + " is not a number in  object " + metsElement.getOriginalPid(),
                            ex);
                }
            }
            String number = "";
            if (Const.ARTICLE.equals(metsElement.getParent().getElementType())) {
                number = String.format("%04d",
                        metsElement.getMetsContext().addElementId(metsElement.getParent().getElementID()));
            } else {
                number = String.format("%04d",
                        metsElement.getMetsContext().addElementId(metsElement.getElementID()));
            }

            /**
             * if an internal element is part of article, then the ID is
             * inherited
             */
            if ("ARTICLE".equalsIgnoreCase(metsElement.getParent().getElementType())) {
                divType.setID(metsElement.getParent().getElementID() + "_" + number);
            } else {
                divType.setID(metsElement.getElementID() + "_" + number);
            }
            Fptr fptr = new Fptr();
            AreaType area = new AreaType();
            IMetsElement refPage = metsElement.getMetsContext().getPidElements().get(partInfo.getAltoPID());
            area.setFILEID(refPage.getAltoFile());
            area.setBEGIN(partInfo.getBegin());
            area.setBETYPE("IDREF");
            fptr.setArea(area);
            divType.getFptr().add(fptr);
            parentType.getDiv().add(divType);
        }
    }

    /**
     * Inserts Periodical structure to the mets
     *
     * @param metsElement
     * @throws MetsExportException
     */
    private void insertPeriodical(IMetsElement metsElement) throws MetsExportException {
        mets.setTYPE("Periodical");
        addDmdSec(metsElement);
        DivType divType = new DivType();
        logicalStruct.setDiv(divType);
        DivType physicalDiv = new DivType();
        physicalStruct.setDiv(physicalDiv);

        divType.setID(metsElement.getElementID());
        // Label for volume is inherited from the parent monograph
        divType.setLabel3(metsElement.getMetsContext().getRootElement().getLabel());
        divType.setTYPE(metsElement.getElementType());
        divType.getDMDID().add(metsElement.getModsMetsElement());

        for (IMetsElement childMetsElement : metsElement.getChildren()) {
            if (Const.PERIODICAL_VOLUME.equals(childMetsElement.getElementType())) {
                insertVolume(divType, physicalDiv, childMetsElement, false);
            } else if (Const.ISSUE.equals(childMetsElement.getElementType())) {
                childMetsElement.getMetsContext().setPackageID(getPackageID(childMetsElement));
                insertIssue(physicalDiv, divType, childMetsElement);
            } else if (Const.SUPPLEMENT.equals(childMetsElement.getElementType())) {
                childMetsElement.getMetsContext().setPackageID(getPackageID(childMetsElement));
                insertSupplement(divType, physicalDiv, childMetsElement);
            } else
                throw new MetsExportException(childMetsElement.getOriginalPid(),
                        "Expected Supplement, Volume or Issue, got:" + childMetsElement.getElementType(), false,
                        null);
        }
    }

    /**
     * Inserts Picture structure into mets
     *
     * @param logicalDiv
     * @param physicalDiv
     * @param metsElement
     * @param counterIntPart
     * @throws MetsExportException
     */
    private void insertPicture(DivType logicalDiv, DivType physicalDiv, IMetsElement metsElement)
            throws MetsExportException {
        if (!Const.PICTURE.equals(metsElement.getElementType())) {
            throw new MetsExportException(metsElement.getOriginalPid(),
                    "Expected picture got " + metsElement.getElementType(), false, null);
        }
        addDmdSec(metsElement);
        DivType elementDivType = new DivType();
        if (Const.ARTICLE.equalsIgnoreCase(metsElement.getParent().getElementType())) {
            int seq = metsElement.getMetsContext().addElementId(metsElement.getParent().getElementID());
            String number = String.format("%04d", seq);
            elementDivType.setID(metsElement.getParent().getElementID() + "_" + number);
        } else {
            elementDivType.setID(metsElement.getElementID());
        }

        elementDivType.setLabel3(metsElement.getLabel());
        elementDivType.setTYPE(metsElement.getElementType());
        elementDivType.getDMDID().add(metsElement.getModsMetsElement());

        logicalDiv.getDiv().add(elementDivType);
        addInternalElements(elementDivType, metsElement);
        addStructLinkFromMods(metsElement);
    }

    /**
     * Adds the info about linkage between an element and page into the
     * struct-link
     *
     * @param metsElement
     */
    private void addStructLinkFromMods(IMetsElement metsElement) throws MetsExportException {
        if ((metsElement.getModsStart() != null) && (metsElement.getModsEnd() != null)) {
            if (metsElement.getModsEnd().longValue() < metsElement.getModsStart().longValue()) {
                throw new MetsExportException(metsElement.getOriginalPid(), "Mods start is bigger than mods end",
                        false, null);
            }
            for (long i = metsElement.getModsStart().longValue(); i <= metsElement.getModsEnd().longValue(); i++) {
                StructLinkMapping structLinkMapping = new StructLinkMapping();
                structLinkMapping.pageDiv = findFirstParentWithPage(metsElement).getModsElementID();
                structLinkMapping.pageOrder = BigInteger.valueOf(i);
                addMappingPageStruct(structLinkMapping, metsElement.getModsElementID());
            }
        }

    }

    /**
     * Inserts Article element structure into mets for future release
     *
     * @param logicalDiv
     * @param physicalDiv
     * @param metsElement
     * @param counterIntPart
     * @throws MetsExportException
     */
    private void insertArticle(DivType logicalDiv, DivType physicalDiv, IMetsElement metsElement,
            int counterIntPart) throws MetsExportException {
        if (!Const.ARTICLE.equals(metsElement.getElementType())) {
            throw new MetsExportException(metsElement.getOriginalPid(),
                    "Expected article got " + metsElement.getElementType(), false, null);
        }
        addDmdSec(metsElement);
        DivType elementDivType = new DivType();
        elementDivType.setID(metsElement.getElementID());
        elementDivType.setORDER(BigInteger.valueOf(counterIntPart));

        elementDivType.setLabel3(metsElement.getLabel());
        elementDivType.setTYPE(metsElement.getElementType());
        elementDivType.getDMDID().add(metsElement.getModsMetsElement());

        logicalDiv.getDiv().add(elementDivType);
        addInternalElements(elementDivType, metsElement);
        for (MetsElement element : metsElement.getChildren()) {
            if (Const.PICTURE.equals(element.getElementType())) {
                insertPicture(elementDivType, physicalDiv, element);
            } else
                throw new MetsExportException(element.getOriginalPid(),
                        "Expected Picture got:" + element.getElementType(), false, null);
        }
        addStructLinkFromMods(metsElement);
    }

    /**
     * Inserts Chapter element structure into mets for future release
     *
     * @param logicalDiv
     * @param physicalDiv
     * @param metsElement
     * @param counterIntPart
     * @throws MetsExportException
     */
    private void insertChapter(DivType logicalDiv, DivType physicalDiv, IMetsElement metsElement,
            int counterIntPart) throws MetsExportException {
        if (!Const.CHAPTER.equals(metsElement.getElementType())) {
            throw new MetsExportException(metsElement.getOriginalPid(),
                    "Expected chapter got " + metsElement.getElementType(), false, null);
        }
        addDmdSec(metsElement);
        DivType elementDivType = new DivType();
        elementDivType.setID(metsElement.getElementID());
        elementDivType.setORDER(BigInteger.valueOf(counterIntPart));

        elementDivType.setLabel3(metsElement.getLabel());
        elementDivType.setTYPE(metsElement.getElementType());
        elementDivType.getDMDID().add(metsElement.getModsMetsElement());

        logicalDiv.getDiv().add(elementDivType);
        addInternalElements(elementDivType, metsElement);
        addStructLinkFromMods(metsElement);
        for (MetsElement element : metsElement.getChildren()) {
            if (Const.PICTURE.equals(element.getElementType())) {
                insertPicture(elementDivType, physicalDiv, element);
                // } else if (Const.PAGE.equals(element.getElementType())) {
                // List<IMetsElement> sourceElements = new
                // ArrayList<IMetsElement>();
                // sourceElements.add(metsElement);
                // sourceElements.add(metsElement.getParent());
                // insertPage(physicalDiv, element, pageCounter,
                // sourceElements);
                // pageCounter++;
            } else {
                throw new MetsExportException(metsElement.getOriginalPid(),
                        "Unexpected element under Chapter:" + element.getElementType(), false, null);
            }
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see cz.cas.lib.proarc.common.export.mets.structure.IMetsElementVisitor#
     * insertIntoMets
     * (cz.cas.lib.proarc.common.export.mets.structure.IMetsElement)
     */
    @Override
    public void insertIntoMets(IMetsElement metsElement) throws MetsExportException {
        try {
            // clear the output fileList before the generation starts
            metsElement.getMetsContext().getFileList().clear();
            mets = prepareMets(metsElement);
            initHeader(metsElement);
            LOG.log(Level.FINE, "Inserting into Mets:" + metsElement.getOriginalPid() + "("
                    + metsElement.getElementType() + ")");
            // get root element first
            IMetsElement rootElement = metsElement.getMetsContext().getRootElement();
            if (rootElement == null) {
                throw new MetsExportException("Element does not have a root set:" + metsElement.getModel() + " - "
                        + metsElement.getOriginalPid(), false);
            }
            if (Const.PERIODICAL_TITLE.equalsIgnoreCase(rootElement.getElementType())) {
                insertPeriodical(rootElement);
            } else if (Const.MONOGRAPH_UNIT.equalsIgnoreCase(rootElement.getElementType())) {
                insertMonograph(rootElement);
            } else if (Const.MONOGRAPH_MULTIPART.equalsIgnoreCase(rootElement.getElementType())) {
                insertMonograph(rootElement);
            } else
                throw new MetsExportException(rootElement.getOriginalPid(),
                        "Unknown type:" + rootElement.getElementType() + " model:" + rootElement.getModel(), false,
                        null);

            if (metsElement.getMetsContext().getPackageID() == null) {
                throw new MetsExportException(metsElement.getOriginalPid(), "Package ID is null", false, null);
            }

            if (metsElement.getMetsContext().getPackageDir() == null) {
                File packageDirFile = createPackageDir(metsElement);
                metsElement.getMetsContext().setPackageDir(packageDirFile);
            }

            saveMets(mets,
                    new File(metsElement.getMetsContext().getPackageDir().getAbsolutePath() + File.separator
                            + "METS_" + MetsUtils.removeNonAlpabetChars(metsElement.getMetsContext().getPackageID())
                            + ".xml"),
                    metsElement);
        } finally {
            JhoveUtility.destroyConfigFiles(metsElement.getMetsContext().getJhoveContext());
        }
    }
}