/*
 * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
 * Copyright 2009 Jason Mehrens. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


import java.io.*;
import java.security.*;
import java.util.logging.*;

/**
 * An error manager used to store mime messages from the <tt>MailHandler</tt>
 * to the file system when the email server is unavailable or unreachable.
 * The code to manually setup this error manager can be as simple as the
 * following:
 * <tt><pre>
 *      File dir = new File("path to dir");
 *      FileErrorManager em = new FileErrorManager(dir);
 * </pre></tt>
 *
 * <p>
 * <b>Configuration:</b>
 * The code to setup this error manager via the logging properties
 * can be as simple as the following:
 * <tt><pre>
 *      #Default FileErrorManager settings.
 *      com.sun.mail.util.logging.FileErrorManager.pattern = path to directory
 * </pre></tt>
 *
 * If properties are not defined, or contain invalid values, then the specified
 * default values are used.
 * <ul>
 * <li>com.sun.mail.util.logging.FileErrorManager.pattern the absolute file path
 * to the directory which will store any failed email messages. (defaults
 * to the value of the system property <tt>java.io.tmpdir</tt>)
 * </ul>
 *
 * @author Jason Mehrens
 */
public class FileErrorManager extends ErrorManager {

    /**
     * Stores the LogManager.
     */
    private static final LogManager manager = LogManager.getLogManager();
    /**
     * Used to report internal errors.
     */
    private final ErrorManager internal = new ErrorManager();
    /**
     * Directory of the email store.
     */
    private final File emailStore;

    /**
     * Creates a new error manager.  Files are stored in the users temp 
     * directory.
     * @exception SecurityException if unable to access system properties or
     * if a security manager is present and unable to read or write to users
     * temp directory.
     */
    public FileErrorManager() {
        this.emailStore = getEmailStore();
        init();
    }

    /**
     * Creates a new error manager.
     * @param dir a directory to store the email files.
     * @throws NullPointerException if <tt>dir</tt> is <tt>null</tt>
     * @throws IllegalArgumentException if <tt>dir</tt> is a
     * <tt>java.io.File</tt> subclass, not a directory, or is not an absolute
     * path.
     * @throws SecurityException if a security manager is present and unable
     * to read or write to a given directory.
     */
    public FileErrorManager(File dir) {
        this.emailStore = dir;
        init();
    }

    /**
     * If the message parameter is a raw email, and passes the store term, then
     * this method will store the email to the file system.  If the message
     * parameter is not a raw email then the message is forwarded to the super
     * class. If an email is written to the filesystem without error, then the
     * orignal reported error is ignored.
     * @param msg String raw email or plain error message.
     * @param ex Exception that occured in the mail handler.
     * @param code int error manager code.
     */
    public void error(String msg, Exception ex, int code) {
        if (isRawEmail(msg, ex, code)) {
            try {
                storeEmail(msg);
            } catch (final IOException IOE) {
                super.error(msg, ex, code);
                internal.error(emailStore.toString(), IOE, ErrorManager.WRITE_FAILURE);
            } catch (final RuntimeException RE) {
                super.error(msg, ex, code);
                internal.error(emailStore.toString(), RE, ErrorManager.WRITE_FAILURE);
            }
        } else {
            super.error(msg, ex, code);
        }
    }

    private void init() {
        File dir = this.emailStore;
        if (dir.getClass() != File.class) { //for security.
            throw new IllegalArgumentException(dir.getClass().getName());
        }

        if (!dir.isDirectory()) {
            throw new IllegalArgumentException("File must be a directory.");
        }

        //For now, only absolute paths are allowed.
        if (!dir.isAbsolute()) {
            throw new IllegalArgumentException("Only absolute paths are allowed.");
        }

        if (!dir.canRead()) { //Can throw under a security manager.
            internal.error(dir.getAbsolutePath(),
                    new SecurityException("read"), ErrorManager.OPEN_FAILURE);
        }

        if (!dir.canWrite()) { //Can throw under a security manager.
            internal.error(dir.getAbsolutePath(),
                    new SecurityException("write"), ErrorManager.OPEN_FAILURE);
        }
    }

    private String prefixName() {
        return "FileErrorManager";
    }

    private String suffixName() {
        return ".eml";
    }

    private boolean isRawEmail(String msg, Exception ex, int code) {
        if (msg != null && msg.length() > 0) {
            return !msg.startsWith(Level.SEVERE.getName());
        }
        return false;
    }

    private void storeEmail(String email) throws IOException {
        File tmp = null;
        FileOutputStream out = null;
        for (;;) {
            tmp = File.createTempFile(prefixName(), suffixName(), emailStore);
            try {
                out = new FileOutputStream(tmp);
                break;
            } catch (FileNotFoundException FNFE) {
                if (!tmp.exists()) { //retry if file is locked
                    throw FNFE;
                }
            }
        }

        try {
            PrintStream ps = new PrintStream(new NewlineOutputStream(out));
            ps.print(email);
            ps.flush();
            tmp = null; //Don't delete 'tmp' if all bytes were written.
            ps.close();

        } finally {
            close(out);
            delete(tmp); //Only deletes if not null.
        }
    }

    private void close(OutputStream out) {
        if (out != null) {
            try {
                out.close();
            } catch (IOException IOE) {
                internal.error(out.toString(), IOE, ErrorManager.CLOSE_FAILURE);
            }
        }
    }

    private void delete(File tmp) {
        if (tmp != null) {
            try {
                if (!tmp.delete() && tmp.exists()) {
                    try {
                        tmp.deleteOnExit();
                    } catch (final RuntimeException shutdown) {
                        if (!tmp.delete()) {
                            internal.error(tmp.getAbsolutePath(), shutdown, ErrorManager.CLOSE_FAILURE);
                        }
                    }
                }
            } catch (SecurityException SE) {
                internal.error(tmp.toString(), SE, ErrorManager.CLOSE_FAILURE);
            }
        }
    }

    /**
     * Gets the location of the email store.
     * @return the File location.
     */
    private File getEmailStore() {
        String dir = manager.getProperty(
                getClass().getName().concat(".pattern"));
        if (dir == null) {
            dir = (String) AccessController.doPrivileged(new PrivilegedAction() {

                public Object run() {
                    return System.getProperty("java.io.tmpdir", "");
                }
            });
        }
        return new File(dir);
    }
}
