All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.james.queue.jms.JMSMailQueue Maven / Gradle / Ivy

/****************************************************************
 * 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.queue.jms;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;

import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.Session;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.MimeMessage;

import org.apache.commons.logging.Log;
import org.apache.james.core.MailImpl;
import org.apache.james.core.MimeMessageCopyOnWriteProxy;
import org.apache.james.queue.api.MailPrioritySupport;
import org.apache.james.queue.api.MailQueue;
import org.apache.mailet.Mail;
import org.apache.mailet.MailAddress;

/**
 * {@link MailQueue} implementation which use a JMS Queue for the
 * {@link MailQueue}. This implementation should work with every JMS 1.1.0
 * implementation
 * 
 * It use {@link ObjectMessage} with a byte array as payload to store the {@link Mail} objects.
 * 
 * 
 */
public class JMSMailQueue implements MailQueue, JMSSupport, MailPrioritySupport {

    protected final String queuename;
    protected final ConnectionFactory connectionFactory;
    protected final Log logger;


    public JMSMailQueue(final ConnectionFactory connectionFactory, final String queuename, final Log logger) {
        this.connectionFactory = connectionFactory;
        this.queuename = queuename;
        this.logger = logger;
    }

/**
     * Execute the given {@link DequeueOperation} when a mail is ready to process. As JMS does not support delay scheduling out-of-the box, we use 
     * a messageselector to check if a mail is ready. For this a {@link MessageConsumer#receive(long) is used with a timeout of 10 seconds. 
     * 
     * Many JMS implementations support better solutions for this, so this should get overridden by these implementations
     * 
     */
    public MailQueueItem deQueue() throws MailQueueException {
        Connection connection = null;
        Session session = null;
        Message message = null;
        MessageConsumer consumer = null;

        while (true) {
            try {
                connection = connectionFactory.createConnection();
                connection.start();

                session = connection.createSession(true, Session.SESSION_TRANSACTED);
                Queue queue = session.createQueue(queuename);
                consumer = session.createConsumer(queue, getMessageSelector());

                message = consumer.receive(10000);

                if (message != null) {
                    return createMailQueueItem(connection, session, consumer, message);
                } else {
                    session.commit();

                    if (consumer != null) {

                        try {
                            consumer.close();
                        } catch (JMSException e1) {
                            // ignore on rollback
                        }
                    }
                    try {
                        if (session != null)
                            session.close();
                    } catch (JMSException e1) {
                        // ignore here
                    }

                    try {
                        if (connection != null)
                            connection.close();
                    } catch (JMSException e1) {
                        // ignore here
                    }
                }

            } catch (Exception e) {
                try {
                    session.rollback();
                } catch (JMSException e1) {
                    // ignore on rollback
                }

                if (consumer != null) {

                    try {
                        consumer.close();
                    } catch (JMSException e1) {
                        // ignore on rollback
                    }
                }
                try {
                    if (session != null)
                        session.close();
                } catch (JMSException e1) {
                    // ignore here
                }

                try {
                    if (connection != null)
                        connection.close();
                } catch (JMSException e1) {
                    // ignore here
                }
                throw new MailQueueException("Unable to dequeue next message", e);
            }
        }

    }

    /**
     * Return message selector to use for consuming
     * 
     * @return selector
     */
    protected String getMessageSelector() {
        return JAMES_NEXT_DELIVERY + " <= " + System.currentTimeMillis();
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see org.apache.james.queue.MailQueue#enQueue(org.apache.mailet.Mail,
     * long, java.util.concurrent.TimeUnit)
     */
    public void enQueue(Mail mail, long delay, TimeUnit unit) throws MailQueueException {
        Connection connection = null;
        Session session = null;

        long mydelay = 0;

        if (delay > 0) {
            mydelay = TimeUnit.MILLISECONDS.convert(delay, unit);
        }

        try {

            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            
            int msgPrio = NORMAL_PRIORITY;
            Object prio = mail.getAttribute(MAIL_PRIORITY);
            if (prio instanceof Integer) {
                msgPrio = (Integer) prio;
            }
            
            Map props = getJMSProperties(mail, mydelay);
            
            produceMail(session, props, msgPrio, mail);
            
        } catch (Exception e) {
            if (session != null) {
                try {
                    session.rollback();
                } catch (JMSException e1) {
                    // ignore on rollback
                }
            }
            throw new MailQueueException("Unable to enqueue mail " + mail, e);

        } finally {
            try {
                if (session != null)
                    session.close();
            } catch (JMSException e) {
                // ignore here
            }

            try {
                if (connection != null)
                    connection.close();
            } catch (JMSException e) {
                // ignore here
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.james.queue.MailQueue#enQueue(org.apache.mailet.Mail)
     */
    public void enQueue(Mail mail) throws MailQueueException {
        enQueue(mail, NO_DELAY, TimeUnit.MILLISECONDS);
    }

    /**
     * Produce the mail to the JMS Queue
     */
    protected void produceMail(Session session, Map props, int msgPrio, Mail mail) throws JMSException, MessagingException, IOException {
        MessageProducer producer = null;

        try {
            Queue queue = session.createQueue(queuename);

            producer = session.createProducer(queue);
            ObjectMessage message = session.createObjectMessage();

            Iterator keys = props.keySet().iterator();
            while(keys.hasNext()) {
                String key = keys.next();
                message.setObjectProperty(key, props.get(key));
            }
            
            long size = mail.getMessageSize();
            ByteArrayOutputStream out;
            if (size > -1) {
                out = new ByteArrayOutputStream((int)size);
            } else {
                out = new ByteArrayOutputStream();
            }
            mail.getMessage().writeTo(out);
            
            // store the byte array in a ObjectMessage so we can use a SharedByteArrayInputStream later
            // without the need of copy the day 
            message.setObject(out.toByteArray());

            producer.send(message, Message.DEFAULT_DELIVERY_MODE, msgPrio, Message.DEFAULT_TIME_TO_LIVE);
            
        } finally {

            try {
                if (producer != null)
                    producer.close();
            } catch (JMSException e) {
                // ignore here
            }
        }
        
        
        
    }

    /**
     * Get JMS Message properties with values
     * 
     * @param message
     * @param mail
     * @param delayInMillis
     * @throws JMSException
     * @throws MessagingException
     */
    @SuppressWarnings("unchecked")
    protected Map getJMSProperties(Mail mail, long delayInMillis) throws JMSException, MessagingException {
        Map props = new HashMap();
        long nextDelivery = -1;
        if (delayInMillis > 0) {
            nextDelivery = System.currentTimeMillis() + delayInMillis;

        }
        props.put(JAMES_NEXT_DELIVERY, nextDelivery);
        props.put(JAMES_MAIL_ERROR_MESSAGE, mail.getErrorMessage());
        props.put(JAMES_MAIL_LAST_UPDATED, mail.getLastUpdated().getTime());
        props.put(JAMES_MAIL_MESSAGE_SIZE, mail.getMessageSize());
        props.put(JAMES_MAIL_NAME, mail.getName());

        StringBuilder recipientsBuilder = new StringBuilder();

        Iterator recipients = mail.getRecipients().iterator();
        while (recipients.hasNext()) {
            String recipient = recipients.next().toString();
            recipientsBuilder.append(recipient.trim());
            if (recipients.hasNext()) {
                recipientsBuilder.append(JAMES_MAIL_SEPARATOR);
            }
        }
        props.put(JAMES_MAIL_RECIPIENTS, recipientsBuilder.toString());
        props.put(JAMES_MAIL_REMOTEADDR, mail.getRemoteAddr());
        props.put(JAMES_MAIL_REMOTEHOST, mail.getRemoteHost());

        String sender;
        MailAddress s = mail.getSender();
        if (s == null) {
            sender = "";
        } else {
            sender = mail.getSender().toString();
        }

        StringBuilder attrsBuilder = new StringBuilder();
        Iterator attrs = mail.getAttributeNames();
        while (attrs.hasNext()) {
            String attrName = attrs.next();
            attrsBuilder.append(attrName);

            Object value = convertAttributeValue(mail.getAttribute(attrName));
            props.put(attrName, value);

            if (attrs.hasNext()) {
                attrsBuilder.append(JAMES_MAIL_SEPARATOR);
            }
        }
        props.put(JAMES_MAIL_ATTRIBUTE_NAMES, attrsBuilder.toString());
        props.put(JAMES_MAIL_SENDER, sender);
        props.put(JAMES_MAIL_STATE, mail.getState());
        return props;
    }

    /**
     * Create the complete Mail from the JMS Message. So the created
     * {@link Mail} is completly populated
     * 
     * @param message
     * @return
     * @throws MessagingException
     * @throws JMSException
     */
    protected final Mail createMail(Message message) throws MessagingException, JMSException {
        MailImpl mail = new MailImpl();
        populateMail(message, mail);
        populateMailMimeMessage(message, mail);

        return mail;
    }

    /**
     * Populat the given {@link Mail} instance with a {@link MimeMessage}. The
     * {@link MimeMessage} is read from the JMS Message. This implementation use
     * a {@link BytesMessage}
     * 
     * @param message
     * @param mail
     * @throws MessagingException
     */
    protected void populateMailMimeMessage(Message message, Mail mail) throws MessagingException, JMSException {
        if (message instanceof ObjectMessage) {
            mail.setMessage(new MimeMessageCopyOnWriteProxy(new MimeMessageObjectMessageSource((ObjectMessage)message)));
        } else {
            throw new MailQueueException("Not supported JMS Message received " + message);
        }

    }

    /**
     * Populate Mail with values from Message. This exclude the
     * {@link MimeMessage}
     * 
     * @param message
     * @param mail
     * @throws JMSException
     */
    protected void populateMail(Message message, MailImpl mail) throws JMSException {
        mail.setErrorMessage(message.getStringProperty(JAMES_MAIL_ERROR_MESSAGE));
        mail.setLastUpdated(new Date(message.getLongProperty(JAMES_MAIL_LAST_UPDATED)));
        mail.setName(message.getStringProperty(JAMES_MAIL_NAME));

        List rcpts = new ArrayList();
        String recipients = message.getStringProperty(JAMES_MAIL_RECIPIENTS);
        StringTokenizer recipientTokenizer = new StringTokenizer(recipients, JAMES_MAIL_SEPARATOR);
        while (recipientTokenizer.hasMoreTokens()) {
            try {
                MailAddress rcpt = new MailAddress(recipientTokenizer.nextToken());
                rcpts.add(rcpt);
            } catch (AddressException e) {
                // Should never happen as long as the user does not modify the
                // the header by himself
                // Maybe we should log it anyway
            }
        }
        mail.setRecipients(rcpts);
        mail.setRemoteAddr(message.getStringProperty(JAMES_MAIL_REMOTEADDR));
        mail.setRemoteHost(message.getStringProperty(JAMES_MAIL_REMOTEHOST));

        String attributeNames = message.getStringProperty(JAMES_MAIL_ATTRIBUTE_NAMES);
        StringTokenizer namesTokenizer = new StringTokenizer(attributeNames, JAMES_MAIL_SEPARATOR);
        while (namesTokenizer.hasMoreTokens()) {
            String name = namesTokenizer.nextToken();
            Serializable attrValue = message.getStringProperty(name);

            mail.setAttribute(name, attrValue);
        }

        String sender = message.getStringProperty(JAMES_MAIL_SENDER);
        if (sender == null || sender.trim().length() <= 0) {
            mail.setSender(null);
        } else {
            try {
                mail.setSender(new MailAddress(sender));
            } catch (AddressException e) {
                // Should never happen as long as the user does not modify the
                // the header by himself
                // Maybe we should log it anyway
            }
        }

        mail.setState(message.getStringProperty(JAMES_MAIL_STATE));

    }

    /**
     * Convert the attribute value if necessary.
     * 
     * @param value
     * @return convertedValue
     */
    protected Object convertAttributeValue(Object value) {
        if (value == null || value instanceof String || value instanceof Byte || value instanceof Long || value instanceof Double || value instanceof Boolean || value instanceof Integer || value instanceof Short || value instanceof Float) {
            return value;
        }
        return value.toString();
    }

    @Override
    public String toString() {
        return "MailQueue:" + queuename;
    }

    /**
     * Create a {@link MailQueueItem} for the given parameters
     * 
     * @param connection
     * @param session
     * @param consumer
     * @param message
     * @return item
     * @throws JMSException
     * @throws MessagingException
     */
    protected MailQueueItem createMailQueueItem(Connection connection, Session session, MessageConsumer consumer, Message message) throws JMSException, MessagingException {
        final Mail mail = createMail(message);
        return new JMSMailQueueItem(mail, connection, session, consumer);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy