com.linuxbox.enkive.mailprocessor.AbstractMailProcessor.java Source code

Java tutorial

Introduction

Here is the source code for com.linuxbox.enkive.mailprocessor.AbstractMailProcessor.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.mailprocessor;

import static com.linuxbox.enkive.mailprocessor.ProcessorState.ANALYZING_MESSAGE_DATA;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.ARCHIVING;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.ERROR_HANDLING;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.IDLE;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.PARSING_MESSAGE;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.POST_PROCESSING_MESSAGE;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.PREPARING_PROCESSOR;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.RETRIEVING_MESSAGE_DATA;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.SHUTTING_DOWN;
import static com.linuxbox.enkive.mailprocessor.ProcessorState.STARTING_UP_ARCHIVER;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import javax.management.ObjectName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.james.mime4j.MimeException;

import com.linuxbox.enkive.archiver.MessageArchivingService;
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.exception.BadMessageException;
import com.linuxbox.enkive.exception.CannotTransferMessageContentException;
import com.linuxbox.enkive.exception.SocketClosedException;
import com.linuxbox.enkive.exception.UninitializedMailProcessorException;
import com.linuxbox.enkive.filter.EnkiveFiltersBean;
import com.linuxbox.enkive.message.Message;
import com.linuxbox.enkive.message.MessageImpl;
import com.linuxbox.enkive.server.AbstractSocketServer;
import com.linuxbox.util.MBeanUtils;

public abstract class AbstractMailProcessor implements ArchivingProcessor, AbstractMailProcessorMBean {
    protected final static Log LOGGER = LogFactory.getLog("com.linuxbox.enkive.mailprocessor");

    protected Socket socket;
    protected AbstractSocketServer server;
    protected MessageArchivingService archiver;
    protected AuditService auditService;

    protected EnkiveFiltersBean enkiveFilters;
    protected boolean jmxEnabled = false;

    private boolean closeInitiated;
    private boolean initialized;
    protected boolean multiMessage = false;
    protected boolean processingComplete = false;

    // FIXME: to make this multi-threading safe, it would be better to
    // keep this as a local variable that's passed where it's needed; also this
    // does not seem to be used when multiMessage is true
    protected boolean messageSaved = false;

    private ObjectName mBeanName;
    protected ProcessorState processorState = IDLE;
    private int messagesProcessed = 0;
    private long totalProcessingMilliseconds = 0;

    /*
     * Inner Classes
     */

    @SuppressWarnings("serial")
    protected class MessageIncompleteException extends Exception {
        private final String data;

        public MessageIncompleteException(final String message) {
            this(message, "");
        }

        public MessageIncompleteException(final String message, final String data) {
            super(message);
            this.data = data;
        }

        public String getData() {
            return data;
        }
    }

    /*
     * AbstractMailProcessor methods
     */

    public AbstractMailProcessor() {
        super();
    }

    @Override
    public void initializeProcessor(AbstractSocketServer server, Socket socket) {
        if (LOGGER.isTraceEnabled())
            LOGGER.trace("in initializeProcessor");
        this.server = server;
        this.socket = socket;

        closeInitiated = false;

        // set the socket linger options, so the socket closes immediately when
        // closed
        try {
            socket.setSoLinger(false, 0);
        } catch (SocketException e) {
            if (LOGGER.isDebugEnabled())
                LOGGER.debug("enkive session unable to set socket linger " + e);
        }

        if (isJmxEnabled()) {
            String type = getClass().getSimpleName();
            String name = "Port " + socket.getPort();

            mBeanName = MBeanUtils.registerMBean(this, type, name);
            if (LOGGER.isTraceEnabled())
                LOGGER.trace("registered mbean " + mBeanName + " (" + type + "/" + name + ")");
        }

        initialized = true;
    }

    public void initiateStop() {
        if (LOGGER.isTraceEnabled())
            LOGGER.trace("in stopProcessor");
        closeInitiated = true;
        closeSessionResources();
        try {
            archiver.shutdown();
        } catch (MessageArchivingServiceException e) {
            LOGGER.error("Could not shutdown mail processor", e);
        }
    }

    @Override
    public void run() {
        if (!initialized) {
            throw new UninitializedMailProcessorException();
        }

        try {
            processorState = STARTING_UP_ARCHIVER;
            archiver.startup();

            processorState = PREPARING_PROCESSOR;
            prepareProcessor();

            do {
                long startTime = System.currentTimeMillis();
                Message message = null;
                String data = "";

                try {
                    processorState = RETRIEVING_MESSAGE_DATA;
                    data = processInput();

                    processorState = ANALYZING_MESSAGE_DATA;
                    if (!data.isEmpty()) {
                        processorState = PARSING_MESSAGE;
                        message = createMessage(data);

                        processorState = POST_PROCESSING_MESSAGE;
                        message = postProcess(message);

                        processorState = ARCHIVING;
                        archiveMessage(message);
                    }
                } catch (SocketClosedException e) {
                    // just pass this up to the next level
                    throw e;
                } catch (MessageIncompleteException e) {
                    processorState = ERROR_HANDLING;
                    LOGGER.fatal("socket closed with only partial message read", e);
                    messageSaved = archiver.emergencySave(e.getData(), true);
                    processingComplete = true;
                } catch (BadMessageException e) {
                    processorState = ERROR_HANDLING;
                    LOGGER.fatal("could not create message object to archive", e);
                    messageSaved = archiver.emergencySave(data);
                } catch (Exception e) {
                    processorState = ERROR_HANDLING;
                    LOGGER.fatal("could not archive message", e);
                    archiver.emergencySave(data);
                    messageSaved = false;
                }

                processorState = IDLE;
                data = "";

                ++messagesProcessed;
                long endTime = System.currentTimeMillis();
                totalProcessingMilliseconds += (endTime - startTime);
            } while (multiMessage && !processingComplete);
        } catch (SocketClosedException e) {
            // do nothing, as this is a normal outcome
        } catch (Exception e) {
            processorState = ERROR_HANDLING;
            messageSaved = false;
            LOGGER.error("Message archival preparation failure", e);
            try {
                auditService.addEvent(AuditService.MESSAGE_ARCHIVE_FAILURE, AuditService.USER_SYSTEM,
                        "Failed in pre-archival steps");
            } catch (AuditServiceException e2) {
                LOGGER.fatal("failure to audit archiving failure", e2);
            }
        }

        processorState = SHUTTING_DOWN;
        if (!closeInitiated) {
            if (LOGGER.isInfoEnabled())
                LOGGER.info("Shutting down mailprocessor");
            closeProcessor();
            closeSessionResources();
            try {
                archiver.shutdown();
            } catch (MessageArchivingServiceException e) {
                LOGGER.error("Could not shutdown archiver", e);
            }
            server.processorClosed(this);
        }
        if (LOGGER.isTraceEnabled())
            LOGGER.trace("closed session");

        MBeanUtils.unregisterMBean(mBeanName);
        if (LOGGER.isTraceEnabled())
            LOGGER.trace("unregistered mbean " + mBeanName);
    }

    public String getProcessorState() {
        return processorState.toString();
    }

    public int getMessagesProcessed() {
        return messagesProcessed;
    }

    public double getMillisecondsPerMessage() {
        if (messagesProcessed == 0) {
            // avoid NAN for result
            return 0.0;
        } else {
            return totalProcessingMilliseconds / (double) messagesProcessed;
        }
    }

    public boolean isClosed() {
        return socket == null;
    }

    protected void closeSessionResources() {
        if (LOGGER.isTraceEnabled())
            LOGGER.trace("in closeSessionResources");

        if (socket != null) {
            try {
                socket.close();
            } catch (Exception e) {
                // empty
            }
            socket = null;
        }
    }

    private Message createMessage(String data) throws IOException, BadMessageException {
        try {
            return new MessageImpl(data);
        } catch (BadMessageException e) {
            LOGGER.error("unable to archive", e);
            throw new BadMessageException(e);
        } catch (CannotTransferMessageContentException e) {
            throw new BadMessageException(e);
        } catch (MimeException e) {
            throw new BadMessageException(e);
        }
    }

    private void archiveMessage(Message message)
            throws IOException, CannotArchiveException, FailedToEmergencySaveException, AuditServiceException {
        boolean archiveMessage = enkiveFilters.filterMessage(message);

        if (archiveMessage) {
            String messageUUID = archiver.storeOrFindMessage(message);
            if (LOGGER.isInfoEnabled())
                LOGGER.info("Message: " + message.getCleanMessageId() + " successfully archived with UUID "
                        + messageUUID);
            messageSaved = true;
        } else {
            if (LOGGER.isInfoEnabled())
                LOGGER.info("Message Rejected:" + message.getMessageId() + " did not pass message filters");
            messageSaved = true;
        }
    }

    public void setEnkiveFilters(EnkiveFiltersBean filters) {
        enkiveFilters = filters;
    }

    public AuditService getAuditService() {
        return auditService;
    }

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

    public MessageArchivingService getArchiver() {
        return archiver;
    }

    public void setArchiver(MessageArchivingService archiver) {
        this.archiver = archiver;
    }

    public boolean isJmxEnabled() {
        return jmxEnabled;
    }

    public void setJmxEnabled(boolean jmxEnabled) {
        this.jmxEnabled = jmxEnabled;
    }

    /*
     * Subclass API
     */

    /**
     * Prepares the processor. The processor may, for example, want to create an
     * OutputStreamWriter from the OutputStream.
     * 
     * @throws IOException
     * @throws UnknownHostException
     */
    protected abstract void prepareProcessor() throws IOException;

    /**
     * Method responsible for obtaining the Message and returning it as a
     * string. Must either set the data string or the message object
     * 
     * @throws SocketClosedException
     * @throws IOException
     * @throws MimeException
     * @throws BadMessageException
     * @throws CannotTransferMessageContentException
     */
    protected abstract String processInput() throws MessageIncompleteException, SocketClosedException, IOException;

    /**
     * Any processing of the message object that needs to be done before
     * archiving is done here.
     */
    protected abstract Message postProcess(Message message);

    /**
     * This method should close any writers or additional sockets that were
     * opened in prepareProcessor().
     */
    protected abstract void closeProcessor();
}