com.linuxbox.enkive.archiver.AbstractMessageArchivingService.java Source code

Java tutorial

Introduction

Here is the source code for com.linuxbox.enkive.archiver.AbstractMessageArchivingService.java

Source

/*******************************************************************************
 * Copyright 2013 The Linux Box Corporation.
 *
 * This file is part of Enkive CE (Community Edition).
 *
 * Enkive CE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * Enkive CE 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with Enkive CE. If not, see
 * <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package com.linuxbox.enkive.archiver;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.channels.FileLock;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.Date;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.linuxbox.enkive.GeneralConstants;
import com.linuxbox.enkive.archiver.exceptions.CannotArchiveException;
import com.linuxbox.enkive.archiver.exceptions.FailedToEmergencySaveException;
import com.linuxbox.enkive.archiver.exceptions.MessageArchivingServiceException;
import com.linuxbox.enkive.audit.AuditService;
import com.linuxbox.enkive.audit.AuditServiceException;
import com.linuxbox.enkive.audit.AuditTrailException;
import com.linuxbox.enkive.docstore.DocStoreService;
import com.linuxbox.enkive.message.Message;

public abstract class AbstractMessageArchivingService implements MessageArchivingService {

    protected final static Log LOGGER = LogFactory.getLog("com.linuxbox.enkive.archiver");
    private final static int EMERGENCY_SAVE_ATTEMPTS = 4;

    /**
     * When an emergency save file is created, it's given a name with a
     * timestamp plus a random 8-digit hex value to avoid naming conflicts.
     * Should be at least 268,435,547 but less than 2,147,483,648 to fit.
     */
    private final static long RANDOM_FILENAME_SUFFIX_RANGE = 2147480000;

    protected DocStoreService docStoreService;
    protected String emergencySavePath;
    protected AuditService auditService;

    @SuppressWarnings("serial")
    class SaveFileAlreadyExistsException extends FailedToEmergencySaveException {
        public SaveFileAlreadyExistsException(String message) {
            super(message);
        }
    }

    protected abstract void subStartup() throws MessageArchivingServiceException;

    protected abstract void subShutdown() throws MessageArchivingServiceException;

    public void startup() throws MessageArchivingServiceException {
        if (docStoreService == null) {
            throw new MessageArchivingServiceException("DocStore service not set");
        }

        subStartup();
    }

    public void shutdown() throws MessageArchivingServiceException {
        subShutdown();
    }

    public String storeOrFindMessage(Message message)
            throws CannotArchiveException, FailedToEmergencySaveException, AuditServiceException, IOException {
        String uuid = null;
        try {
            uuid = findMessage(message);
            if (uuid == null) {
                uuid = storeMessage(message);
            }
        } catch (Exception e) {
            LOGGER.error("Could not archive Message " + message.getCleanMessageId(), e);
            emergencySave(message.getReconstitutedEmail());
        }
        return uuid;
    }

    public DocStoreService getDocStoreService() {
        return docStoreService;
    }

    public void setDocStoreService(DocStoreService docStoreService) {
        this.docStoreService = docStoreService;
    }

    public static String calculateMessageId(Message message) throws CannotArchiveException {
        String messageUUID = null;
        try {
            MessageDigest sha1calc = MessageDigest.getInstance("SHA-1");
            sha1calc.reset();
            messageUUID = new String(
                    (new Hex()).encode(sha1calc.digest(message.getReconstitutedEmail().getBytes())));
        } catch (NoSuchAlgorithmException e) {
            throw new CannotArchiveException("Could not calculate UUID for message", e);
        } catch (IOException e) {
            throw new CannotArchiveException("Could not calculate UUID for message", e);
        }
        return messageUUID;
    }

    /**
     * Convenience method assumes the message is not incomplete (i.e., the
     * message is complete).
     * 
     * @param data
     * @throws FailedToEmergencySaveException
     * @throws AuditTrailException
     */
    public boolean emergencySave(final String data) throws FailedToEmergencySaveException, AuditServiceException {
        return emergencySave(data, false);
    }

    /**
     * Contains basic logic for doing an emergency save since this needs to be
     * done from a few points in the code.
     * 
     * @param data
     * @param messageIsIncomplete
     * @throws FailedToEmergencySaveException
     * @throws AuditTrailException
     * @throws AuditServiceException
     */
    public boolean emergencySave(final String data, boolean messageIsIncomplete)
            throws FailedToEmergencySaveException, AuditServiceException {
        boolean messageSaved = false;
        if (!data.isEmpty()) {
            final String fileName = saveToDisk(data, messageIsIncomplete);
            auditService.addEvent(AuditService.MESSAGE_EMERGENCY_SAVED, AuditService.USER_SYSTEM, fileName);
            if (!fileName.isEmpty() && !messageIsIncomplete)
                messageSaved = true;

        } else {
            if (LOGGER.isWarnEnabled())
                LOGGER.warn("emergency save data is empty; nothing saved");
        }
        return messageSaved;
    }

    /**
     * Generates a file name based on the current date and time, including
     * milliseconds. It's unlikely that another save file would occur during the
     * same millisecond. But as added protection a random number (over 30 bits)
     * is appended as well (in base 16).
     * 
     * @param data
     * @throws IOException
     */
    private String saveToDisk(String data, boolean messageIsIncomplete) throws FailedToEmergencySaveException {
        SaveFileAlreadyExistsException lastExistsException = null;

        for (int attempt = 0; attempt < EMERGENCY_SAVE_ATTEMPTS; ++attempt) {
            // choose one of two billion random numbers
            String random = String.format("%08x", Math.round(Math.random() * RANDOM_FILENAME_SUFFIX_RANGE));

            Date now = Calendar.getInstance(GeneralConstants.STANDARD_TIME_ZONE).getTime();

            String fileName = GeneralConstants.NUMERIC_FORMAT_W_MILLIS.format(now) + "_" + random;
            if (messageIsIncomplete) {
                fileName += "-incomplete";
            }
            fileName += ".eml";

            try {
                saveToDisk(data, fileName);
                return fileName;
            } catch (SaveFileAlreadyExistsException e) {
                // empty ; loop again
            }
        }

        // throw the last exception encapsulated in a
        // FailedToEmergencySaveException
        throw new FailedToEmergencySaveException(lastExistsException);
    }

    private String saveToDisk(String messageData, String fileName)
            throws FailedToEmergencySaveException, SaveFileAlreadyExistsException {
        File emergencySaveFile = null;
        String emergencySaveFilePath = "UNKNOWN";
        BufferedWriter out = null;
        FileOutputStream fileStream = null;
        try {
            emergencySaveFile = new File(getEmergencySaveRoot(), fileName);
            emergencySaveFilePath = emergencySaveFile.getCanonicalPath();
            fileStream = new FileOutputStream(emergencySaveFile);
            FileLock lock = null;
            try {
                lock = fileStream.getChannel().tryLock();
                if (lock == null) {
                    throw new SaveFileAlreadyExistsException(emergencySaveFilePath);
                }

                out = new BufferedWriter(new OutputStreamWriter(fileStream));
                out.write(messageData);

                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("Saved message to file: \"" + emergencySaveFilePath + "\"");
                }

                return emergencySaveFilePath;
            } finally {
                if (lock != null) {
                    lock.release();
                }
            }
        } catch (IOException e) {
            LOGGER.fatal("Emergency save to disk failed. ", e);
            throw new FailedToEmergencySaveException(e);
        } finally {
            try {
                if (out != null) {
                    out.close();
                } else if (fileStream != null) {
                    fileStream.close();
                }
            } catch (IOException e) {
                if (LOGGER.isWarnEnabled()) {
                    LOGGER.warn("Could not close emergency save file \"" + emergencySavePath + "\".");
                }
            }
        }
    }

    public String getEmergencySaveRoot() {
        return emergencySavePath;
    }

    public void setEmergencySaveRoot(String emergencySavePath) throws MessageArchivingServiceException {
        this.emergencySavePath = emergencySavePath;

        final File emergencySaveDir = new File(emergencySavePath);

        if (!emergencySaveDir.exists()) {
            if (!emergencySaveDir.mkdirs()) {
                final String message = "Failed to create emergency save directory \"" + emergencySavePath + "\".";
                LOGGER.fatal(message);
                throw new MessageArchivingServiceException(message);
            } else {
                LOGGER.info("Created emergency save directory \"" + emergencySavePath + "\".");
            }
        }

        if (!emergencySaveDir.isDirectory()) {
            final String message = "Emergency save directory \"" + emergencySavePath
                    + "\" is not actually a directory.";
            LOGGER.fatal(message);
            throw new MessageArchivingServiceException(message);
        } else if (!emergencySaveDir.canWrite()) {
            final String message = "Unable to write to emergency save directory \"" + emergencySavePath
                    + "\"; please check permissions.";
            LOGGER.fatal(message);
            throw new MessageArchivingServiceException(message);
        }
    }

    public AuditService getAuditService() {
        return auditService;
    }

    public void setAuditService(AuditService auditService) {
        this.auditService = auditService;
    }

}