cz.cas.lib.proarc.desa.SIP2DESATransporter.java Source code

Java tutorial

Introduction

Here is the source code for cz.cas.lib.proarc.desa.SIP2DESATransporter.java

Source

/*
 * Copyright (C) 2013 Vladimir Lahoda, 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.desa;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.Holder;

import cz.cas.lib.proarc.desa.pspsip.ObjectFactory;
import cz.cas.lib.proarc.desa.pspsip.PSPSIP;
import cz.cas.lib.proarc.desa.pspsip.ResultType;
import cz.cas.lib.proarc.desa.pspsip.SipType;
import cz.cas.lib.proarc.desa.soap.FileHashAlg;
import cz.cas.lib.proarc.desa.soap.SIPSubmission;
import cz.cas.lib.proarc.desa.soap.SIPSubmissionFault;
import org.apache.commons.io.FileUtils;

/**
 * Main class for import of the SIP packages to DESA repository and for checking
 * the progress of the import.
 */
public final class SIP2DESATransporter {

    private static final Logger log = Logger.getLogger(SIP2DESATransporter.class.toString());
    private static JAXBContext PSPSIP_JAXB;

    private final SIPSubmission desaPort;
    private final DesaClient desaClient;
    private final String desaFolderPath;
    private final String operatorName;
    private final String producerCode;
    private boolean checkMTDPSP = false;
    private final boolean useRest;
    private Marshaller marshaller = null;
    private Unmarshaller unmarshaller = null;

    private int filesIndex = -1;

    private int mtdpspIndex = -1;

    private String packageid = null;

    private final ObjectFactory resultsFactory = new ObjectFactory();

    private PSPSIP results = resultsFactory.createPSPSIP();

    /**
     * Returns the parsed result from the result file
     *
     * @return
     */
    public PSPSIP getResults() {
        return results;
    }

    /** used just in case non REST usage! */
    private File desaFolder;

    private String logRoot = "";

    //    /**
    //     * The transporter entry point
    //     *
    //     * @param args
    //     *            command line arguments for transport mode : transport
    //     *            <input-folder> <results-folder> <log-folder> command line
    //     *            arguments for checkStatus mode : checkStatus <results-file>
    //     *            <log-folder>
    //     */
    //    public static void main(String[] args) {
    //        try {
    //            if (args.length == 4 && "transport".equalsIgnoreCase(args[0])) {
    //                String importRoot = args[1];
    //                String resultsRoot = args[2];
    //                String logRoot = args[3];
    //                new SIP2DESATransporter().transport(importRoot, resultsRoot, logRoot);
    //            } else if (args.length == 3 && "checkStatus".equalsIgnoreCase(args[0])) {
    //                String resultsRoot = args[1];
    //                String logRoot = args[2];
    //                new SIP2DESATransporter().checkStatus(resultsRoot, logRoot);
    //            } else if (args.length == 2 && "adminUpload".equalsIgnoreCase(args[0])) {
    //                String importRoot = args[1];
    //                new SIP2DESATransporter().adminUpload(importRoot);
    //            } else {
    //                System.out.println("SIP to DESA transporter.\n");
    //                System.out.println("Usage for transport: sip2desa.SIP2DESATransporter transport <input-folder> <results-folder> <log-folder>");
    //                System.out.println("Usage for checkStatus: sip2desa.SIP2DESATransporter checkStatus <results-file> <log-folder>");
    //                System.out.println("Usage for adminUpload: sip2desa.SIP2DESATransporter adminUpload <input-folder>");
    //
    //                System.exit(1);
    //            }
    //        } catch (Exception e) {
    //            log.log(Level.SEVERE, e.getMessage(), e);
    //        }
    //    }

    /**
     * Submits SIPs through REST interface.
     */
    SIP2DESATransporter(DesaClient desaClient, String operator, String producerCode) {
        this(desaClient, true, null, operator, producerCode);
    }

    /**
     * Ignores REST interface.
     */
    SIP2DESATransporter(DesaClient desaClient, String desaFolderPath, String operator, String producerCode) {
        this(desaClient, false, desaFolderPath, operator, producerCode);
    }

    private SIP2DESATransporter(DesaClient desaClient, boolean useRest, String desaFolderPath, String operator,
            String producerCode) {
        this.desaPort = desaClient.getSoapClient();
        this.desaClient = desaClient;
        this.useRest = useRest;
        this.desaFolderPath = desaFolderPath;
        this.operatorName = operator;
        this.producerCode = producerCode;
    }

    /**
     * Main execution method for the transport mode. Imports the SIP files in
     * the sourceRoot to DESA repository (configured in config property file).
     * The status of the import of each SIP is written in the results XML file
     * named TRANSF_packageid.xml in the resultsRoot folder. The JDK logging
     * output is mirrored in the packageid.log file in the logRoot folder.
     *
     * @param sourceRoot
     * @param resultsRoot
     * @param logRootIn
     */
    public int[] transport(String sourceRoot, String resultsRoot, String logRootIn) {
        long timeStart = System.currentTimeMillis();
        logRoot = logRootIn;
        Handler handler = null;
        File[] sourceFiles;
        File resultsFolder;
        try {
            try {
                initJAXB();

                File sourceFolder = new File(sourceRoot);

                if (!sourceFolder.exists()) {
                    handler = setupLogHandler(sourceFolder.getName());
                    log.log(Level.SEVERE, "Source folder doesn't exist: " + sourceFolder.getAbsolutePath());
                    throw new IllegalStateException(
                            "Source folder doesn't exist: " + sourceFolder.getAbsolutePath());
                }

                sourceFiles = sourceFolder.listFiles(new FileFilter() {
                    @Override
                    public boolean accept(File pathname) {
                        return (pathname.getName().endsWith(".zip"));
                    }
                });
                if (sourceFiles == null || sourceFiles.length == 0) {
                    handler = setupLogHandler(sourceFolder.getName());
                    log.log(Level.SEVERE, "Empty source folder: " + sourceFolder.getAbsolutePath());
                    throw new IllegalStateException("Empty source folder: " + sourceFolder.getAbsolutePath());
                }

                preprocessFiles(sourceFolder, sourceFiles);

                handler = setupLogHandler(packageid);

                resultsFolder = checkDirectory(resultsRoot);

                log.info("Loaded source folder: " + sourceFolder);
                log.info("Results directory: " + resultsFolder);

            } catch (Exception e) {
                log.log(Level.SEVERE, "Error in transporter initialization.", e);
                throw new IllegalStateException("Error in transporter initialization.", e);
            }

            try {
                for (int i = 0; i < sourceFiles.length; i++) {
                    if (i != filesIndex && i != mtdpspIndex) {
                        uploadFile(sourceFiles[i], SipType.RECORD, true);
                    }
                }
                if (mtdpspIndex > -1) {
                    uploadFile(sourceFiles[mtdpspIndex], SipType.RECORD, true);
                }

                if (filesIndex >= 0) {
                    uploadFile(sourceFiles[filesIndex], SipType.FILE, true);
                }

            } catch (Throwable th) {
                log.log(Level.SEVERE, "Error in file upload: ", th);
                throw new IllegalStateException("Error in file upload: ", th);
            } finally {
                results.setPspResultCode("progress");

                try {
                    marshaller.marshal(results, new File(resultsFolder, "TRANSF_" + packageid + ".xml"));
                } catch (JAXBException e) {
                    log.log(Level.SEVERE, "Error writing results file: ", e);
                    throw new IllegalStateException(e);
                }
            }
            long timeFinish = System.currentTimeMillis();
            log.info("Elapsed time: " + ((timeFinish - timeStart) / 1000.0) + " seconds. " + sourceFiles.length
                    + " SIP packages transported.");
            log.info("RESULT: OK");
        } finally {
            if (handler != null) {
                removeLogHandler(handler);
            }
        }

        return countSip();
    }

    private Handler setupLogHandler(String fileName) {
        Handler handler;
        try {
            checkDirectory(logRoot);
            handler = new FileHandler(logRoot + System.getProperty("file.separator") + fileName + ".txt", 0, 1,
                    true);
            handler.setFormatter(new SimpleFormatter());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Logger.getLogger("").addHandler(handler);

        return handler;
    }

    private static void removeLogHandler(Handler handler) {
        Logger.getLogger("").removeHandler(handler);
        handler.close();
    }

    public void adminUpload(String sourceRoot) {
        long timeStart = System.currentTimeMillis();
        File[] sourceFiles;

        try {
            initJAXB();

            File sourceFolder = new File(sourceRoot);

            if (!sourceFolder.exists()) {
                log.log(Level.SEVERE, "Source folder doesn't exist: " + sourceFolder.getAbsolutePath());
                throw new IllegalStateException("Source folder doesn't exist: " + sourceFolder.getAbsolutePath());
            }

            sourceFiles = sourceFolder.listFiles(new FileFilter() {
                @Override
                public boolean accept(File pathname) {
                    return (pathname.getName().endsWith(".zip"));
                }
            });
            if (sourceFiles == null || sourceFiles.length == 0) {
                log.log(Level.SEVERE, "Empty source folder: " + sourceFolder.getAbsolutePath());
                throw new IllegalStateException("Empty source folder: " + sourceFolder.getAbsolutePath());
            }

            log.info("Loaded source folder: " + sourceFolder);

        } catch (Exception e) {
            log.log(Level.SEVERE, "Error in transporter initialization.", e);
            throw new IllegalStateException("Error in transporter initialization.", e);
        }

        try {
            for (int i = 0; i < sourceFiles.length; i++) {
                uploadFile(sourceFiles[i], null, false);
            }

        } catch (Throwable th) {
            log.log(Level.SEVERE, "Error in file upload: ", th);
            throw new IllegalStateException("Error in file upload: ", th);
        }
        long timeFinish = System.currentTimeMillis();
        log.info("Elapsed time: " + ((timeFinish - timeStart) / 1000.0) + " seconds. " + sourceFiles.length
                + " SIP packages transported.");
        log.info("RESULT: OK");

    }

    /**
     * Gets the cached thread safe JAXB context.
     */
    private static JAXBContext getPspipJaxb() throws JAXBException {
        if (PSPSIP_JAXB == null) {
            PSPSIP_JAXB = JAXBContext.newInstance(PSPSIP.class);
        }
        return PSPSIP_JAXB;
    }

    /**
     * Initialize JAXB transformers
     *
     * @throws JAXBException
     */
    private void initJAXB() throws JAXBException {
        if (marshaller != null) {
            return;
        }
        JAXBContext jaxbContext = getPspipJaxb();
        marshaller = jaxbContext.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        unmarshaller = jaxbContext.createUnmarshaller();
    }

    /**
     * Upload one SIP file from the input folder. First call the DESA API method
     * asyncSubmitPackageStart, then copy the zip file to the mapped DESA target
     * folder and finally call the DESA API method asyncSubmitPackageEnd. Add
     * the corresponding entry into the results file JAXB representation.
     *
     * @param file
     * @param sipType
     * @param writeResults
     */
    private void uploadFile(File file, SipType sipType, boolean writeResults) {
        Holder<String> sipId = new Holder<String>(getSipId(file));
        Holder<String> idSipVersion = new Holder<String>();
        String checksum = getMD5Checksum(file);
        log.info("Transporting file: " + file.getName());

        if (useRest) {
            idSipVersion.value = desaClient.submitPackage(file, operatorName, producerCode, sipId.value,
                    FileHashAlg.MD_5, checksum, "cs");
            log.info("Received idSipVersion:" + idSipVersion.value);
            if (idSipVersion.value == null || "".equals(idSipVersion.value)) {
                throw new RuntimeException("DESA REST call did not return idSipVersion for file " + file.getName());
            }
        } else {
            try {
                desaPort.asyncSubmitPackageStart(0, producerCode, operatorName, sipId, (int) file.length(),
                        checksum, FileHashAlg.MD_5, idSipVersion);
            } catch (SIPSubmissionFault sipSubmissionFault) {
                throw new RuntimeException(sipSubmissionFault);
            }
            if (idSipVersion.value == null || "".equals(idSipVersion.value)) {
                throw new RuntimeException("DESA SOAP call did not return idSipVersion for file " + file.getName());
            }
            File target = new File(getDesaFolder(), idSipVersion.value + ".sip");
            try {
                FileUtils.copyFile(file, target);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            log.info("Received idSipVersion:" + idSipVersion.value);
            try {
                desaPort.asyncSubmitPackageEnd(0, producerCode, operatorName, idSipVersion.value);
            } catch (SIPSubmissionFault sipSubmissionFault) {
                throw new RuntimeException(sipSubmissionFault);
            }
        }
        if (writeResults) {
            XMLGregorianCalendar currentDate = desaClient.getXmlTypes()
                    .newXMLGregorianCalendar(new GregorianCalendar());
            PSPSIP.SIP entry = resultsFactory.createPSPSIPSIP();
            entry.setIdentifier(sipId.value);
            entry.setIdSIPVersion(idSipVersion.value);
            entry.setResultTime(currentDate);
            entry.setResultCode(ResultType.PROGRESS);
            entry.setType(sipType);
            results.getSIP().add(entry);
        }
    }

    private String getSipId(File sipFile) {
        return sipFile.getName().replace(".zip", "");
    }

    /**
     * Check the contents of the input directory and find packageid and FILES
     * and MTDPSP SIP packages (which will be imported last)
     */
    private void preprocessFiles(File sourceFolder, File[] sourceFiles) {
        for (int i = 0; i < sourceFiles.length; i++) {
            if (sourceFiles[i].getName().endsWith("_FILE.zip")) {
                packageid = sourceFiles[i].getName().replace("_FILE.zip", "");
                filesIndex = i;
            } else if (sourceFiles[i].getName().endsWith("_MTDPSP.zip")) {
                mtdpspIndex = i;
            }
        }

        if (packageid == null) {
            for (int i = 0; i < sourceFiles.length; i++) {
                if (sourceFiles[i].getName().endsWith(".zip")) {
                    if (sourceFiles[i].getName().indexOf("_") > 0) {
                        packageid = sourceFiles[i].getName().substring(0, sourceFiles[i].getName().indexOf("_"));
                    } else {
                        packageid = sourceFiles[i].getName().replace(".zip", "");
                    }
                    break;
                }
            }
        }

        if (packageid == null) {
            Handler handler = setupLogHandler(sourceFolder.getName());
            log.log(Level.SEVERE, "Invalid source folder contents. Missing FILE.zip.");
            removeLogHandler(handler);
            throw new IllegalStateException("Invalid source folder contents. Missing FILE.zip.");
        }
        if (checkMTDPSP && mtdpspIndex == -1) {
            Handler handler = setupLogHandler(sourceFolder.getName());
            log.log(Level.SEVERE, "Invalid source folder contents. Missing MTDPSP.zip.");
            removeLogHandler(handler);
            throw new IllegalStateException("Invalid source folder contents. Missing MTDPSP.zip.");
        }
        results.setPackageID(packageid);
    }

    /**
     * Main execution method for the checkStatus mode. The status of the import
     * of each SIP in the results XML file named TRANSF_packageid.xml in the
     * resultsRoot folder is checked by call to the DESA API getPackageStatus
     * method. The results file is then updated accordingly. The JDK logging
     * output is mirrored in the packageid.log file in the logRoot folder.
     *
     * @param resultsFileStr
     * @param logRootIn
     */
    public int[] checkStatus(String resultsFileStr, String logRootIn) {
        long timeStart = System.currentTimeMillis();
        logRoot = logRootIn;
        Handler handler = null;

        try {
            File resultsFile = new File(resultsFileStr);
            if (!resultsFile.exists()) {
                handler = setupLogHandler(resultsFile.getName());
                log.log(Level.SEVERE, "Results file does not exist.");
                throw new IllegalStateException("Results file does not exist.");
            }

            try {
                initJAXB();

                log.info("Loaded results file: " + resultsFile);

                parseResultsFile(resultsFile);

            } catch (Exception e) {
                handler = setupLogHandler(resultsFile.getName());
                log.log(Level.SEVERE, "Error in transporter initialization.", e);
                throw new IllegalStateException("Error in transporter initialization.", e);
            }

            handler = setupLogHandler(packageid);

            int objectCounter = 0;

            try {
                boolean finishedAll = true;
                for (PSPSIP.SIP sip : results.getSIP()) {
                    if (!checkSIP(sip)) {
                        finishedAll = false;
                    }
                    ;
                    objectCounter++;
                }

                if (finishedAll) {
                    results.setPspResultCode("finished");
                } else {
                    results.setPspResultCode("progress");
                }

                marshaller.marshal(results, resultsFile);
            } catch (Exception e) {
                log.log(Level.SEVERE, "Error checking results: ", e);
                throw new IllegalStateException("Error checking results: ", e);
            }

            long timeFinish = System.currentTimeMillis();
            log.info("Elapsed time: " + ((timeFinish - timeStart) / 1000.0) + " seconds. " + objectCounter
                    + " SIP packages checked.");
            log.info("RESULT: OK");

        } finally {
            if (handler != null) {
                removeLogHandler(handler);
            }
        }

        return countSip();
    }

    private int[] countSip() {
        List<PSPSIP.SIP> sipList = results.getSIP();
        int sipCount = sipList.size();
        int sipFinishedCount = 0;
        for (PSPSIP.SIP sip : sipList) {
            if (ResultType.FINISHED.equals(sip.getResultCode())) {
                sipFinishedCount++;
            }
        }

        return new int[] { sipCount, sipFinishedCount };
    }

    /**
     * Convert the existing results file to JAXB representation
     *
     * @param resultsFile
     */
    private void parseResultsFile(File resultsFile) {
        try {
            results = (PSPSIP) unmarshaller.unmarshal(resultsFile);
            packageid = results.getPackageID();
        } catch (Exception e) {
            Handler handler = setupLogHandler(resultsFile.getName());
            log.log(Level.SEVERE, "Error in parsing results file.", e);
            removeLogHandler(handler);
            throw new IllegalStateException("Error in parsing results file.", e);
        }
    }

    /**
     * Check the status of the SIP of one entry in the results file and update
     * the entry in the JAXB object.
     *
     * @param sip
     * @return
     */
    private boolean checkSIP(PSPSIP.SIP sip) {
        Holder<String> sipId = new Holder<String>(sip.getIdentifier());
        Holder<String> idSipVersion = new Holder<String>(sip.getIdSIPVersion());
        Holder<String> packageStateCode = new Holder<String>();
        Holder<String> packageStateText = new Holder<String>();
        Holder<String> errorCode = new Holder<String>();
        log.info("Checking file: " + sipId.value);
        /*
         * if(config.getBoolean("desa.rest")){ try { URLConnection connection =
         * new
         * URL(config.getString("desa.restapi")+"/packagestatus"+"?userName="
         * +config.getString("desa.user")
         * +"&producerCode="+config.getString("desa.producer"
         * )+"&producerSipId="+
         * sipId.value+"&idSIPVersion="+idSipVersion.value).openConnection();
         * packageStateCode
         * .value=connection.getHeaderField("X-DEA-packageStateCode");
         * packageStateText
         * .value=connection.getHeaderField("X-DEA-packageStateText");
         * errorCode.value=connection.getHeaderField("X-DEA-errorCode"); } catch
         * (Exception e) { throw new RuntimeException(e); } }else{
         */
        try {
            desaPort.getPackageStatus(0, producerCode, operatorName, idSipVersion, sipId, packageStateCode,
                    packageStateText, errorCode);
        } catch (SIPSubmissionFault sipSubmissionFault) {
            throw new RuntimeException(sipSubmissionFault);
        } catch (Exception e1) {
            log.log(Level.SEVERE, "DESA exception", e1);
            throw new IllegalStateException("DESA exception", e1);
        }
        /* } */
        log.info("Status: " + packageStateCode.value + " (" + packageStateText.value + ")"
                + (errorCode.value != null ? (", errorCode:" + errorCode.value) : ""));
        XMLGregorianCalendar currentDate = desaClient.getXmlTypes()
                .newXMLGregorianCalendar(new GregorianCalendar());
        ;
        boolean retval = false;
        sip.setResultTime(currentDate);
        if ("AI_ACC_OK".equalsIgnoreCase(packageStateCode.value)) {
            sip.setResultCode(ResultType.FINISHED);
            retval = true;
        } else if ("AI_ERROR".equalsIgnoreCase(packageStateCode.value)
                || "AI_INVALID".equalsIgnoreCase(packageStateCode.value)
                || "AI_REJECT".equalsIgnoreCase(packageStateCode.value)
                || "AI_INFECTED".equalsIgnoreCase(packageStateCode.value)
                || "AI_QA_ERR".equalsIgnoreCase(packageStateCode.value)) {
            sip.setResultCode(ResultType.ERROR);
        } else {
            sip.setResultCode(ResultType.PROGRESS);
        }
        return retval;
    }

    /** Gets target folder for SOAP submit package. */
    private File getDesaFolder() {
        if (!useRest) {
            if (desaFolder == null) {
                desaFolder = checkDirectory(desaFolderPath);
            }
        }
        return desaFolder;
    }

    /**
     * Check if the directory with the given path exists and create it if
     * necessary
     *
     * @param name
     *            The path of the requested directory
     * @return The File representation of the requested directory
     */
    private static File checkDirectory(String name) {
        File directory = new File(name);
        try {
            FileUtils.forceMkdir(directory);
            return directory;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Calculate the MD5 checksum of the given file
     *
     * @param file
     * @return
     */
    private static String getMD5Checksum(File file) {
        try {
            InputStream fis = new FileInputStream(file);

            byte[] buffer = new byte[1024];
            MessageDigest complete = MessageDigest.getInstance("MD5");
            int numRead;

            do {
                numRead = fis.read(buffer);
                if (numRead > 0) {
                    complete.update(buffer, 0, numRead);
                }
            } while (numRead != -1);

            fis.close();
            byte[] bytes = complete.digest();
            StringBuilder sb = new StringBuilder(2 * bytes.length);
            for (byte b : bytes) {
                sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]);
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

    private static final char[] hexDigits = "0123456789abcdef".toCharArray();

}