org.sakaiproject.nakamura.smtp.SakaiSmtpServer.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.smtp.SakaiSmtpServer.java

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.sakaiproject.nakamura.smtp;

import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.jcr.resource.JcrResourceConstants;
import org.osgi.service.component.ComponentContext;
import org.sakaiproject.nakamura.api.lite.ClientPoolException;
import org.sakaiproject.nakamura.api.lite.Repository;
import org.sakaiproject.nakamura.api.lite.Session;
import org.sakaiproject.nakamura.api.lite.StorageClientException;
import org.sakaiproject.nakamura.api.lite.accesscontrol.AccessDeniedException;
import org.sakaiproject.nakamura.api.lite.content.Content;
import org.sakaiproject.nakamura.api.lite.content.ContentManager;
import org.sakaiproject.nakamura.api.message.LiteMessagingService;
import org.sakaiproject.nakamura.api.message.MessageConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.subethamail.smtp.TooMuchDataException;
import org.subethamail.smtp.helper.SimpleMessageListener;
import org.subethamail.smtp.helper.SimpleMessageListenerAdapter;
import org.subethamail.smtp.server.SMTPServer;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.mail.BodyPart;
import javax.mail.Header;
import javax.mail.MessagingException;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeMultipart;

@Component(immediate = true, metatype = true)
public class SakaiSmtpServer implements SimpleMessageListener {

    private static final Logger LOGGER = LoggerFactory.getLogger(SakaiSmtpServer.class);
    private static final int MAX_PROPERTY_SIZE = 32 * 1024;
    private static final Map<String, Object> EMPTY_MAP = Collections.emptyMap();

    private SMTPServer server;

    @Reference
    protected LiteMessagingService messagingService;

    @Reference
    protected Repository contentRepository;

    @Property
    private static String LOCAL_DOMAINS = "smtp.localdomains";

    @Property(intValue = 8025)
    private static String SMTP_SERVER_PORT = "smtp.port";

    @Property(value = "The Sakai Foundation")
    static final String SERVICE_VENDOR = "service.vendor";

    private Set<String> domains = new HashSet<String>();

    @Activate
    protected void activate(ComponentContext context) throws Exception {
        Integer port = PropertiesUtil.toInteger(context.getProperties().get(SMTP_SERVER_PORT), 8025);
        LOGGER.info("Starting SMTP server on port {}", port);
        server = new SMTPServer(new SimpleMessageListenerAdapter(this));
        server.setPort(port);
        server.start();
        String localDomains = PropertiesUtil.toString(context.getProperties().get(LOCAL_DOMAINS), "localhost");
        domains.clear();
        for (String domain : StringUtils.split(localDomains, ';')) {
            domains.add(domain);
        }
    }

    @Deactivate
    protected void deactivate(ComponentContext context) throws Exception {
        LOGGER.info("Stopping SMTP server");
        server.stop();
    }

    /**
     *
     * {@inheritDoc}
     *
     * @see org.subethamail.smtp.helper.SimpleMessageListener#accept(java.lang.String,
     *      java.lang.String)
     */
    public boolean accept(String from, String recipient) {
        Session session = null;
        try {
            session = contentRepository.loginAdministrative();
            List<String> paths = getLocalPath(session, recipient);
            return paths.size() > 0;
        } catch (Exception e) {
            LOGGER.error("Develier message with this handler ", e);
        } finally {
            if (session != null) {
                try {
                    session.logout();
                } catch (ClientPoolException e) {
                    throw new RuntimeException("Failed to logout session.", e);
                }
            }
        }
        return false;
    }

    /**
     * @param recipient
     * @return
     */
    private List<String> getLocalPath(Session session, String recipient) {
        // assume recipient is a fully qualified email address of the form xxx@foo.com
        String[] parts = StringUtils.split(recipient, '@');
        List<String> localPaths = new ArrayList<String>();
        if (domains.contains(parts[1])) {
            List<String> recipients = messagingService.expandAliases(parts[0]);
            for (String localRecipient : recipients) {
                try {
                    String path = messagingService.getFullPathToStore(localRecipient, session);
                    if (path != null && path.length() > 0) {
                        localPaths.add(path);
                    }
                } catch (Exception ex) {
                    LOGGER.warn("Failed to expand recipient {} ", localRecipient, ex);
                }
            }
        }
        return localPaths;
    }

    public void deliver(String from, String recipient, InputStream data) throws TooMuchDataException, IOException {
        LOGGER.info("Got message FROM: " + from + " TO: " + recipient);
        Session session = null;
        try {
            session = contentRepository.loginAdministrative();

            List<String> paths = getLocalPath(session, recipient);
            if (paths.size() > 0) {
                Map<String, Object> mapProperties = new HashMap<String, Object>();
                mapProperties.put(JcrResourceConstants.SLING_RESOURCE_TYPE_PROPERTY,
                        MessageConstants.SAKAI_MESSAGE_RT);
                mapProperties.put(MessageConstants.PROP_SAKAI_READ, false);
                mapProperties.put(MessageConstants.PROP_SAKAI_FROM, from);
                mapProperties.put(MessageConstants.PROP_SAKAI_MESSAGEBOX, MessageConstants.BOX_INBOX);
                Content createdMessage = writeMessage(session, mapProperties, data, paths.get(0));
                if (createdMessage != null) {
                    String messagePath = createdMessage.getPath();
                    String messageId = (String) createdMessage.getProperty("message-id");
                    LOGGER.info("Created message {} at: {} ", messageId, messagePath);

                    // we might want alias expansion
                    for (int i = 1; i < paths.size(); i++) {
                        String targetPath = paths.get(i);
                        messagingService.copyMessageNode(createdMessage, targetPath, session);
                    }
                }

            }
        } catch (MessagingException e) {
            LOGGER.error("Unable to write message", e);
            throw new IOException("Message can not be written to repository");
        } catch (StorageClientException e) {
            LOGGER.error("Unable to write message", e);
        } catch (AccessDeniedException e) {
            LOGGER.error("Unable to write message", e);
        } finally {
            if (session != null) {
                try {
                    session.logout();
                } catch (ClientPoolException e) {
                    LOGGER.error(e.getLocalizedMessage(), e);
                    throw new RuntimeException("Failed to logout session.", e);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private Content writeMessage(Session session, Map<String, Object> mapProperties, InputStream data,
            String storePath)
            throws MessagingException, AccessDeniedException, StorageClientException, IOException {
        InternetHeaders internetHeaders = new InternetHeaders(data);
        // process the headers into a map.
        for (Enumeration<Header> e = internetHeaders.getAllHeaders(); e.hasMoreElements();) {
            Header h = e.nextElement();
            String name = h.getName();
            String[] values = internetHeaders.getHeader(name);
            if (values != null) {
                if (values.length == 1) {
                    mapProperties.put("sakai:" + name.toLowerCase(), values[0]);
                } else {
                    mapProperties.put("sakai:" + name.toLowerCase(), values);
                }
            }
        }
        String[] contentType = internetHeaders.getHeader("content-type");
        if (contentType != null && contentType.length > 0 && contentType[0].contains("boundary")
                && contentType[0].contains("multipart/")) {
            MimeMultipart multipart = new MimeMultipart(new SMTPDataSource(contentType[0], data));
            Content message = messagingService.create(session, mapProperties,
                    (String) mapProperties.get("sakai:message-id"), storePath);
            writeMultipartToNode(session, message, multipart);
            return message;
        } else {
            Content node = messagingService.create(session, mapProperties);
            // set up to stream the body.
            session.getContentManager().writeBody(node.getPath(), data);
            return node;
        }
    }

    private void writeMultipartToNode(Session session, Content message, MimeMultipart multipart)
            throws MessagingException, AccessDeniedException, StorageClientException, IOException {
        int count = multipart.getCount();
        for (int i = 0; i < count; i++) {
            createChildNodeForPart(session, i, multipart.getBodyPart(i), message);
        }
    }

    private boolean isTextType(BodyPart part) throws MessagingException {
        return part.getSize() < MAX_PROPERTY_SIZE && part.getContentType().toLowerCase().startsWith("text/");
    }

    private void createChildNodeForPart(Session session, int index, BodyPart part, Content message)
            throws MessagingException, AccessDeniedException, StorageClientException, IOException {
        ContentManager contentManager = session.getContentManager();
        String childName = String.format("part%1$03d", index);
        String childPath = message.getPath() + "/" + childName;
        // multipart message
        if (part.getContentType().toLowerCase().startsWith("multipart/")) {
            contentManager.update(new Content(childPath, EMPTY_MAP));
            Content childNode = contentManager.get(childPath);
            writePartPropertiesToNode(part, childNode);
            contentManager.update(childNode);
            MimeMultipart multi = new MimeMultipart(
                    new SMTPDataSource(part.getContentType(), part.getInputStream()));
            writeMultipartToNode(session, childNode, multi);
            return;
        }

        // text
        if (!isTextType(part)) {
            writePartAsFile(session, part, childName, message);
            return;
        }

        // not multipart; not text
        contentManager.update(new Content(childPath, EMPTY_MAP));
        Content childNode = contentManager.get(childPath);
        writePartPropertiesToNode(part, childNode);
        contentManager.update(childNode);
        // childNode.setProperty(MessageConstants.PROP_SAKAI_BODY, part.getInputStream());
        contentManager.writeBody(childNode.getPath(), part.getInputStream());
    }

    private void writePartAsFile(Session session, BodyPart part, String nodeName, Content parentNode)
            throws AccessDeniedException, StorageClientException, MessagingException, IOException {
        // String filePath = parentNode.getPath() + "/nt:file";
        // String fileContentPath = filePath + "/jcr:content";
        // session.getContentManager().update(
        // new Content(filePath, new HashMap<String, Object>()));
        // session.getContentManager().update(new Content(fileContentPath, new HashMap<String,
        // Object>()));
        // Content resourceNode = session.getContentManager().get(fileContentPath);
        // resourceNode.setProperty("jcr:primaryType", "nt:resource");

        /*
         * Instead of creating a child node, just write the body part to the parentNode. I
         * think this will work, but may collide/override properties already set on the
         * message content. Let's ensure there are no collisions.
         */
        if (!parentNode.hasProperty(Content.MIMETYPE_FIELD)) {
            parentNode.setProperty(Content.MIMETYPE_FIELD, part.getContentType());
        } else {
            if (part.getContentType().equals(parentNode.getProperty(Content.MIMETYPE_FIELD))) {
                LOGGER.debug("Same mimeType; no worries");
            } else {
                throw new IllegalStateException(
                        "This sparse approach is bust; must create a subpath for file body");
            }
        }
        // parentNode.setProperty("jcr:data", part.getInputStream());
        session.getContentManager().writeBody(parentNode.getPath(), part.getInputStream());
    }

    @SuppressWarnings("unchecked")
    private void writePartPropertiesToNode(BodyPart part, Content childNode) throws MessagingException {
        Enumeration<Header> headers = part.getAllHeaders();
        while (headers.hasMoreElements()) {
            Header header = headers.nextElement();
            childNode.setProperty(header.getName(), header.getValue());
        }
    }

}