org.apache.james.mailrepository.jcr.JCRMailRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.james.mailrepository.jcr.JCRMailRepository.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.james.mailrepository.jcr;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.jcr.Value;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.jackrabbit.commons.cnd.CndImporter;
import org.apache.jackrabbit.util.ISO9075;
import org.apache.jackrabbit.util.Text;
import org.apache.james.core.MailImpl;
import org.apache.james.mailrepository.api.MailRepository;
import org.apache.james.mailrepository.lib.AbstractMailRepository;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;
import org.slf4j.Logger;

/**
 * Mail repository that is backed by a JCR content repository.
 */
public class JCRMailRepository extends AbstractMailRepository implements MailRepository {

    private final static String MAIL_PATH = "mailrepository";

    private Repository repository;
    private SimpleCredentials creds;
    private String workspace;

    private Logger logger;

    @Inject
    public void setRepository(Repository repository) {
        this.repository = repository;
    }

    @Override
    @PostConstruct
    public void init() throws Exception {
        // register the nodetype
        CndImporter.registerNodeTypes(new InputStreamReader(Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("org/apache/james/imap/jcr/james.cnd")), login());
    }

    /**
     * @see
     * org.apache.james.mailrepository.lib.AbstractMailRepository
     * #doConfigure(org.apache.commons.configuration.HierarchicalConfiguration)
     */
    @Override
    public void doConfigure(HierarchicalConfiguration config) throws ConfigurationException {
        this.workspace = config.getString("workspace", null);
        String username = config.getString("username", null);
        String password = config.getString("password", null);

        if (username != null && password != null) {
            this.creds = new SimpleCredentials(username, password.toCharArray());
        }
    }

    protected String toSafeName(String key) {
        return ISO9075.encode(Text.escapeIllegalJcrChars(key));
    }

    private Session login() throws RepositoryException {
        return repository.login(creds, workspace);
    }

    public Iterator<String> list() throws MessagingException {
        try {
            Session session = login();
            try {
                Collection<String> keys = new ArrayList<String>();
                QueryManager manager = session.getWorkspace().getQueryManager();
                @SuppressWarnings("deprecation")
                Query query = manager.createQuery("/jcr:root/" + MAIL_PATH + "//element(*,james:mail)",
                        Query.XPATH);
                NodeIterator iterator = query.execute().getNodes();
                while (iterator.hasNext()) {
                    String name = iterator.nextNode().getName();
                    keys.add(Text.unescapeIllegalJcrChars(name));
                }
                return keys.iterator();
            } finally {
                session.logout();
            }
        } catch (RepositoryException e) {
            throw new MessagingException("Unable to list messages", e);
        }
    }

    public Mail retrieve(String key) throws MessagingException {
        try {
            Session session = login();
            try {
                String name = toSafeName(key);
                QueryManager manager = session.getWorkspace().getQueryManager();
                @SuppressWarnings("deprecation")
                Query query = manager.createQuery("/jcr:root/" + MAIL_PATH + "//element(" + name + ",james:mail)",
                        Query.XPATH);
                NodeIterator iterator = query.execute().getNodes();
                if (iterator.hasNext()) {
                    return getMail(iterator.nextNode());
                } else {
                    return null;
                }
            } finally {
                session.logout();
            }
        } catch (IOException e) {
            throw new MessagingException("Unable to retrieve message: " + key, e);
        } catch (RepositoryException e) {
            throw new MessagingException("Unable to retrieve message: " + key, e);
        }
    }

    // -------------------------------------------------------------< private >

    /**
     * Reads a mail message from the given mail node.
     * 
     * @param node
     *            mail node
     * @return mail message
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    private Mail getMail(Node node) throws MessagingException, RepositoryException, IOException {
        String name = Text.unescapeIllegalJcrChars(node.getName());
        MailImpl mail = new MailImpl(name, getSender(node), getRecipients(node), getMessage(node));
        mail.setState(getState(node));
        mail.setLastUpdated(getLastUpdated(node));
        mail.setErrorMessage(getError(node));
        mail.setRemoteHost(getRemoteHost(node));
        mail.setRemoteAddr(getRemoteAddr(node));
        getAttributes(node, mail);
        return mail;
    }

    /**
     * Writes the mail message to the given mail node.
     * 
     * @param node
     *            mail node
     * @param mail
     *            mail message
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    private void setMail(Node node, Mail mail) throws MessagingException, RepositoryException, IOException {
        setState(node, mail.getState());
        setLastUpdated(node, mail.getLastUpdated());
        setError(node, mail.getErrorMessage());
        setRemoteHost(node, mail.getRemoteHost());
        setRemoteAddr(node, mail.getRemoteAddr());
        setSender(node, mail.getSender());
        setRecipients(node, mail.getRecipients());
        setMessage(node, mail.getMessage());
        setAttributes(node, mail);
    }

    /**
     * Reads the message state from the <code>james:state</code> property.
     * 
     * @param node
     *            mail node
     * @return message state, or {@link Mail#DEFAULT} if not set
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private String getState(Node node) throws RepositoryException {
        try {
            return node.getProperty("james:state").getString();
        } catch (PathNotFoundException e) {
            return Mail.DEFAULT;
        }
    }

    /**
     * Writes the message state to the <code>james:state</code> property.
     * 
     * @param node
     *            mail node
     * @param state
     *            message state
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setState(Node node, String state) throws RepositoryException {
        node.setProperty("james:state", state);
    }

    /**
     * Reads the update timestamp from the
     * <code>jcr:content/jcr:lastModified</code> property.
     * 
     * @param node
     *            mail node
     * @return update timestamp
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private Date getLastUpdated(Node node) throws RepositoryException {
        try {
            node = node.getNode("jcr:content");
        } catch (PathNotFoundException e) {
            node = node.getProperty("jcr:content").getNode();
        }
        return node.getProperty("jcr:lastModified").getDate().getTime();
    }

    /**
     * Writes the update timestamp to the
     * <code>jcr:content/jcr:lastModified</code> property.
     * 
     * @param node
     *            mail node
     * @param updated
     *            update timestamp, or <code>null</code> if not set
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setLastUpdated(Node node, Date updated) throws RepositoryException {
        try {
            node = node.getNode("jcr:content");
        } catch (PathNotFoundException e) {
            node = node.getProperty("jcr:content").getNode();
        }
        Calendar calendar = Calendar.getInstance();
        if (updated != null) {
            calendar.setTime(updated);
        }
        node.setProperty("jcr:lastModified", calendar);
    }

    /**
     * Reads the error message from the <code>james:error</code> property.
     * 
     * @param node
     *            mail node
     * @return error message, or <code>null</code> if not set
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private String getError(Node node) throws RepositoryException {
        try {
            return node.getProperty("james:error").getString();
        } catch (PathNotFoundException e) {
            return null;
        }
    }

    /**
     * Writes the error message to the <code>james:error</code> property.
     * 
     * @param node
     *            mail node
     * @param error
     *            error message
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setError(Node node, String error) throws RepositoryException {
        node.setProperty("james:error", error);
    }

    /**
     * Reads the remote host name from the <code>james:remotehost</code>
     * property.
     * 
     * @param node
     *            mail node
     * @return remote host name, or <code>null</code> if not set
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private String getRemoteHost(Node node) throws RepositoryException {
        try {
            return node.getProperty("james:remotehost").getString();
        } catch (PathNotFoundException e) {
            return null;
        }
    }

    /**
     * Writes the remote host name to the <code>james:remotehost</code>
     * property.
     * 
     * @param node
     *            mail node
     * @param host
     *            remote host name
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setRemoteHost(Node node, String host) throws RepositoryException {
        node.setProperty("james:remotehost", host);
    }

    /**
     * Reads the remote address from the <code>james:remoteaddr</code> property.
     * 
     * @param node
     *            mail node
     * @return remote address, or <code>null</code> if not set
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private String getRemoteAddr(Node node) throws RepositoryException {
        try {
            return node.getProperty("james:remoteaddr").getString();
        } catch (PathNotFoundException e) {
            return null;
        }
    }

    /**
     * Writes the remote address to the <code>james:remoteaddr</code> property.
     * 
     * @param node
     *            mail node
     * @param addr
     *            remote address
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setRemoteAddr(Node node, String addr) throws RepositoryException {
        node.setProperty("james:remoteaddr", addr);
    }

    /**
     * Reads the envelope sender from the <code>james:sender</code> property.
     * 
     * @param node
     *            mail node
     * @return envelope sender, or <code>null</code> if not set
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private MailAddress getSender(Node node) throws MessagingException, RepositoryException {
        try {
            String sender = node.getProperty("james:sender").getString();
            return new MailAddress(sender);
        } catch (PathNotFoundException e) {
            return null;
        }
    }

    /**
     * Writes the envelope sender to the <code>james:sender</code> property.
     * 
     * @param node
     *            mail node
     * @param sender
     *            envelope sender
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setSender(Node node, MailAddress sender) throws RepositoryException {
        node.setProperty("james:sender", sender.toString());
    }

    /**
     * Reads the list of recipients from the <code>james:recipients</code>
     * property.
     * 
     * @param node
     *            mail node
     * @return list of recipient, or an empty list if not set
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     */
    @SuppressWarnings("unchecked")
    private Collection<MailAddress> getRecipients(Node node) throws MessagingException, RepositoryException {
        try {
            Value[] values = node.getProperty("james:recipients").getValues();
            Collection<MailAddress> recipients = new ArrayList<MailAddress>(values.length);
            for (Value value : values) {
                recipients.add(new MailAddress(value.getString()));
            }
            return recipients;
        } catch (PathNotFoundException e) {
            return Collections.EMPTY_LIST;
        }
    }

    /**
     * Writes the list of recipients to the <code>james:recipients</code>
     * property.
     * 
     * @param node
     *            mail node
     * @param recipients
     *            list of recipient
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     */
    private void setRecipients(Node node, Collection<MailAddress> recipients) throws RepositoryException {
        String[] values = new String[recipients.size()];
        Iterator<MailAddress> iterator = recipients.iterator();
        for (int i = 0; iterator.hasNext(); i++) {
            values[i] = iterator.next().toString();
        }
        node.setProperty("james:recipients", values);
    }

    /**
     * Reads the message content from the <code>jcr:content/jcr:data</code>
     * binary property.
     * 
     * @param node
     *            mail node
     * @return mail message
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    private MimeMessage getMessage(Node node) throws MessagingException, RepositoryException, IOException {
        try {
            node = node.getNode("jcr:content");
        } catch (PathNotFoundException e) {
            node = node.getProperty("jcr:content").getNode();
        }

        @SuppressWarnings("deprecation")
        InputStream stream = node.getProperty("jcr:data").getStream();
        try {
            Properties properties = System.getProperties();
            return new MimeMessage(javax.mail.Session.getDefaultInstance(properties), stream);
        } finally {
            stream.close();
        }
    }

    /**
     * Writes the message content to the <code>jcr:content/jcr:data</code>
     * binary property.
     * 
     * @param node
     *            mail node
     * @param message
     *            mail message
     * @throws MessagingException
     *             if a messaging error occurs
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    @SuppressWarnings("deprecation")
    private void setMessage(Node node, final MimeMessage message) throws RepositoryException, IOException {
        try {
            node = node.getNode("jcr:content");
        } catch (PathNotFoundException e) {
            node = node.getProperty("jcr:content").getNode();
        }

        PipedInputStream input = new PipedInputStream();
        final PipedOutputStream output = new PipedOutputStream(input);
        new Thread() {
            public void run() {
                try {
                    message.writeTo(output);
                } catch (Exception e) {
                } finally {
                    try {
                        output.close();
                    } catch (IOException e) {
                    }
                }
            }
        }.start();
        node.setProperty("jcr:data", input);
    }

    /**
     * Writes the mail attributes from the <code>jamesattr:*</code> property.
     * 
     * @param node
     *            mail node
     * @param mail
     *            mail message
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    private void getAttributes(Node node, Mail mail) throws RepositoryException, IOException {
        PropertyIterator iterator = node.getProperties("jamesattr:*");
        while (iterator.hasNext()) {
            Property property = iterator.nextProperty();
            String name = Text.unescapeIllegalJcrChars(property.getName().substring("jamesattr:".length()));
            if (property.getType() == PropertyType.BINARY) {
                @SuppressWarnings("deprecation")
                InputStream input = property.getStream();
                try {
                    ObjectInputStream stream = new ObjectInputStream(input);
                    mail.setAttribute(name, (Serializable) stream.readObject());
                } catch (ClassNotFoundException e) {
                    throw new IOException(e.getMessage());
                } finally {
                    input.close();
                }
            } else {
                mail.setAttribute(name, property.getString());
            }
        }
    }

    /**
     * Writes the mail attributes to the <code>jamesattr:*</code> property.
     * 
     * @param node
     *            mail node
     * @param mail
     *            mail message
     * @throws RepositoryException
     *             if a repository error occurs
     * @throws IOException
     *             if an IO error occurs
     */
    @SuppressWarnings("deprecation")
    private void setAttributes(Node node, Mail mail) throws RepositoryException, IOException {
        Iterator<String> iterator = mail.getAttributeNames();
        while (iterator.hasNext()) {
            String name = iterator.next();
            Object value = mail.getAttribute(name);
            name = "jamesattr:" + Text.escapeIllegalJcrChars(name);
            if (value instanceof String || value == null) {
                node.setProperty(name, (String) value);
            } else {
                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
                ObjectOutputStream output = new ObjectOutputStream(buffer);
                output.writeObject(value);
                output.close();
                node.setProperty(name, new ByteArrayInputStream(buffer.toByteArray()));
            }
        }
    }

    @Override
    protected void internalRemove(String key) throws MessagingException {
        try {
            Session session = login();
            try {
                String name = ISO9075.encode(Text.escapeIllegalJcrChars(key));
                QueryManager manager = session.getWorkspace().getQueryManager();
                @SuppressWarnings("deprecation")
                Query query = manager.createQuery("/jcr:root/" + MAIL_PATH + "//element(" + name + ",james:mail)",
                        Query.XPATH);
                NodeIterator nodes = query.execute().getNodes();
                if (nodes.hasNext()) {
                    while (nodes.hasNext()) {
                        nodes.nextNode().remove();
                    }
                    session.save();
                    logger.info("Mail " + key + " removed from repository");
                } else {
                    logger.warn("Mail " + key + " not found");
                }
            } finally {
                session.logout();
            }
        } catch (RepositoryException e) {
            throw new MessagingException("Unable to remove message: " + key, e);
        }
    }

    @Override
    protected void internalStore(Mail mail) throws MessagingException, IOException {
        try {
            Session session = login();
            try {
                String name = Text.escapeIllegalJcrChars(mail.getName());
                final String xpath = "/jcr:root/" + MAIL_PATH + "//element(" + name + ",james:mail)";

                QueryManager manager = session.getWorkspace().getQueryManager();
                @SuppressWarnings("deprecation")
                Query query = manager.createQuery(xpath, Query.XPATH);
                NodeIterator iterator = query.execute().getNodes();

                if (iterator.hasNext()) {
                    while (iterator.hasNext()) {
                        setMail(iterator.nextNode(), mail);
                    }
                } else {
                    Node parent = session.getRootNode().getNode(MAIL_PATH);
                    Node node = parent.addNode(name, "james:mail");
                    Node resource = node.addNode("jcr:content", "nt:resource");
                    resource.setProperty("jcr:mimeType", "message/rfc822");
                    setMail(node, mail);
                }
                session.save();
                logger.info("Mail " + mail.getName() + " stored in repository");
            } finally {
                session.logout();
            }
        } catch (IOException e) {
            throw new MessagingException("Unable to store message: " + mail.getName(), e);
        } catch (RepositoryException e) {
            throw new MessagingException("Unable to store message: " + mail.getName(), e);
        }
    }

}