org.apache.james.queue.activemq.ActiveMQMailQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.james.queue.activemq.ActiveMQMailQueue.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.queue.activemq;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.List;
import java.util.Map;

import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MapMessage;
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.jms.TemporaryQueue;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.apache.activemq.ActiveMQSession;
import org.apache.activemq.BlobMessage;
import org.apache.activemq.command.ActiveMQBlobMessage;
import org.apache.activemq.util.JMSExceptionSupport;
import org.apache.james.core.MimeMessageCopyOnWriteProxy;
import org.apache.james.core.MimeMessageInputStream;
import org.apache.james.core.MimeMessageSource;
import org.apache.james.queue.api.MailQueue;
import org.apache.james.queue.jms.JMSMailQueue;
import org.apache.mailet.Mail;
import org.slf4j.Logger;
import org.springframework.jms.connection.SessionProxy;

/**
 * <p>
 * {@link MailQueue} implementation which use an ActiveMQ Queue.
 * <p>
 * </p>
 * This implementation require at ActiveMQ 5.4.0+.
 * <p>
 * </p>
 * When a {@link Mail} attribute is found and is not one of the supported
 * primitives, then the toString() method is called on the attribute value to
 * convert it
 * <p>
 * </p>
 * The implementation use {@link BlobMessage} or {@link ObjectMessage},
 * depending on the constructor which was used
 * <p>
 * </p>
 * See <a
 * href="http://activemq.apache.org/blob-messages.html">http://activemq.apache
 * .org/blob-messages.html</a> for more details
 * <p>
 * </p>
 * Some other supported feature is handling of priorities. See:<br>
 * <a href="http://activemq.apache.org/how-can-i-support-priority-queues.html">
 * http://activemq.apache.org/how-can-i-support-priority-queues.html</a>
 * <p>
 * </p>
 * For this just add a {@link Mail} attribute with name {@link #MAIL_PRIORITY}
 * to it. It should use one of the following value {@link #LOW_PRIORITY},
 * {@link #NORMAL_PRIORITY}, {@link #HIGH_PRIORITY}
 * <p>
 * </p>
 * To have a good throughput you should use a caching connection factory. </p>
 */
public class ActiveMQMailQueue extends JMSMailQueue implements ActiveMQSupport {

    private boolean useBlob;

    /**
     * Construct a {@link ActiveMQMailQueue} which only use {@link BlobMessage}
     * 
     * @throws NotCompliantMBeanException
     * 
     * @see #ActiveMQMailQueue(ConnectionFactory, String, boolean, Logger)
     */
    public ActiveMQMailQueue(final ConnectionFactory connectionFactory, final String queuename,
            final Logger logger) {
        this(connectionFactory, queuename, true, logger);
    }

    /**
     * Construct a new ActiveMQ based {@link MailQueue}.
     * 
     * @param connectionFactory
     * @param queuename
     * @param useBlob
     * @param logger
     * @throws NotCompliantMBeanException
     */
    public ActiveMQMailQueue(final ConnectionFactory connectionFactory, final String queuename, boolean useBlob,
            final Logger logger) {
        super(connectionFactory, queuename, logger);
        this.useBlob = useBlob;
    }

    /**
     * @see
     * org.apache.james.queue.jms.JMSMailQueue#populateMailMimeMessage(javax.jms.Message, org.apache.mailet.Mail)
     */
    protected void populateMailMimeMessage(Message message, Mail mail) throws MessagingException, JMSException {
        if (message instanceof BlobMessage) {
            try {
                BlobMessage blobMessage = (BlobMessage) message;
                try {
                    // store URL and queuename for later usage
                    mail.setAttribute(JAMES_BLOB_URL, blobMessage.getURL());
                    mail.setAttribute(JAMES_QUEUE_NAME, queuename);
                } catch (MalformedURLException e) {
                    // Ignore on error
                    logger.debug("Unable to get url from blobmessage for mail " + mail.getName());
                }
                MimeMessageSource source = new MimeMessageBlobMessageSource(blobMessage);
                mail.setMessage(new MimeMessageCopyOnWriteProxy(source));

            } catch (JMSException e) {
                throw new MailQueueException("Unable to populate MimeMessage for mail " + mail.getName(), e);
            }
        } else {
            super.populateMailMimeMessage(message, mail);
        }
    }

    /**
     * Produce the mail to the JMS Queue
     */
    protected void produceMail(Session session, Map<String, Object> props, int msgPrio, Mail mail)
            throws JMSException, MessagingException, IOException {
        MessageProducer producer = null;
        BlobMessage blobMessage = null;
        boolean reuse = false;

        try {

            // check if we should use a blob message here
            if (useBlob) {
                MimeMessage mm = mail.getMessage();
                MimeMessage wrapper = mm;

                ActiveMQSession amqSession = getAMQSession(session);

                /*
                 * Remove this optimization as it could lead to problems when the same blob content
                 * is shared across different messages. 
                 * 
                 * I still think it would be a good idea to somehow do this but at the moment it's just 
                 * safer to disable it.
                 * 
                 * TODO: Re-Enable it again once it works!
                 * 
                 * See JAMES-1240
                if (wrapper instanceof MimeMessageCopyOnWriteProxy) {
                wrapper = ((MimeMessageCopyOnWriteProxy) mm).getWrappedMessage();
                }
                    
                if (wrapper instanceof MimeMessageWrapper) {
                URL blobUrl = (URL) mail.getAttribute(JAMES_BLOB_URL);
                String fromQueue = (String) mail.getAttribute(JAMES_QUEUE_NAME);
                MimeMessageWrapper mwrapper = (MimeMessageWrapper) wrapper;
                    
                if (blobUrl != null && fromQueue != null && mwrapper.isModified() == false) {
                    // the message content was not changed so don't need to
                    // upload it again and can just point to the url
                    blobMessage = amqSession.createBlobMessage(blobUrl);
                    reuse = true;
                }
                    
                }*/
                if (blobMessage == null) {
                    // just use the MimeMessageInputStream which can read every
                    // MimeMessage implementation
                    blobMessage = amqSession.createBlobMessage(new MimeMessageInputStream(wrapper));
                }

                // store the queue name in the props
                props.put(JAMES_QUEUE_NAME, queuename);

                Queue queue = session.createQueue(queuename);

                producer = session.createProducer(queue);
                for (Map.Entry<String, Object> entry : props.entrySet()) {
                    blobMessage.setObjectProperty(entry.getKey(), entry.getValue());
                }
                producer.send(blobMessage, Message.DEFAULT_DELIVERY_MODE, msgPrio, Message.DEFAULT_TIME_TO_LIVE);

            } else {
                super.produceMail(session, props, msgPrio, mail);
            }
        } catch (JMSException e) {
            if (!reuse && blobMessage != null && blobMessage instanceof ActiveMQBlobMessage) {
                ((ActiveMQBlobMessage) blobMessage).deleteFile();
            }
            throw e;
        } finally {

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

    }

    /**
     * Cast the given {@link Session} to an {@link ActiveMQSession}
     * 
     * @param session
     * @return amqSession
     * @throws JMSException
     */
    protected ActiveMQSession getAMQSession(Session session) throws JMSException {
        ActiveMQSession amqSession;

        if (session instanceof SessionProxy) {
            // handle Springs CachingConnectionFactory
            amqSession = (ActiveMQSession) ((SessionProxy) session).getTargetSession();
        } else {
            // just cast as we have no other idea
            amqSession = (ActiveMQSession) session;
        }
        return amqSession;
    }

    @Override
    protected MailQueueItem createMailQueueItem(Connection connection, Session session, MessageConsumer consumer,
            Message message) throws JMSException, MessagingException {
        Mail mail = createMail(message);
        return new ActiveMQMailQueueItem(mail, connection, session, consumer, message, logger);
    }

    @Override
    public List<Message> removeWithSelector(String selector) throws MailQueueException {
        List<Message> mList = super.removeWithSelector(selector);

        // Handle the blob messages
        for (int i = 0; i < mList.size(); i++) {
            Message m = mList.get(i);
            if (m instanceof ActiveMQBlobMessage) {
                try {
                    // Should get remove once this issue is closed:
                    // https://issues.apache.org/activemq/browse/AMQ-3018
                    ((ActiveMQBlobMessage) m).deleteFile();
                } catch (Exception e) {
                    logger.error("Unable to delete blob file for message " + m, e);
                }
            }
        }
        return mList;
    }

    @Override
    protected Message copy(Session session, Message m) throws JMSException {
        if (m instanceof ActiveMQBlobMessage) {
            ActiveMQBlobMessage b = (ActiveMQBlobMessage) m;
            ActiveMQBlobMessage copy = (ActiveMQBlobMessage) getAMQSession(session).createBlobMessage(b.getURL());
            try {
                copy.setProperties(b.getProperties());
            } catch (IOException e) {
                throw JMSExceptionSupport.create("Unable to copy message " + m, e);
            }
            return copy;
        } else {
            return super.copy(session, m);
        }
    }

    /**
     * Try to use ActiveMQ StatisticsPlugin to get size and if that fails
     * fallback to {@link JMSMailQueue#getSize()}
     */
    @Override
    public long getSize() throws MailQueueException {

        Connection connection = null;
        Session session = null;
        MessageConsumer consumer = null;
        MessageProducer producer = null;
        TemporaryQueue replyTo = null;
        long size = -1;

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

            session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            replyTo = session.createTemporaryQueue();
            consumer = session.createConsumer(replyTo);

            Queue myQueue = session.createQueue(queuename);
            producer = session.createProducer(null);

            String queueName = "ActiveMQ.Statistics.Destination." + myQueue.getQueueName();
            Queue query = session.createQueue(queueName);

            Message msg = session.createMessage();
            msg.setJMSReplyTo(replyTo);
            producer.send(query, msg);
            MapMessage reply = (MapMessage) consumer.receive(2000);
            if (reply != null && reply.itemExists("size")) {
                try {
                    size = reply.getLong("size");
                    return size;
                } catch (NumberFormatException e) {
                    // if we hit this we can't calculate the size so just catch
                    // it
                }
            }

        } catch (Exception e) {
            throw new MailQueueException("Unable to remove mails", e);

        } finally {

            if (consumer != null) {

                try {
                    consumer.close();
                } catch (JMSException e1) {
                    e1.printStackTrace();
                    // ignore on rollback
                }
            }

            if (producer != null) {

                try {
                    producer.close();
                } catch (JMSException e1) {
                    // ignore on rollback
                }
            }

            if (replyTo != null) {
                try {

                    // we need to delete the temporary queue to be sure we will
                    // free up memory if thats not done and a pool is used
                    // its possible that we will register a new mbean in jmx for
                    // every TemporaryQueue which will never get unregistered
                    replyTo.delete();
                } catch (JMSException e) {
                }
            }
            try {
                if (session != null)
                    session.close();
            } catch (JMSException e1) {
                // ignore here
            }

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

        // if we came to this point we should just fallback to super method
        return super.getSize();
    }

}