alfresco-community-repo/source/java/org/alfresco/repo/mail/AlfrescoJavaMailSender.java

318 lines
10 KiB
Java

/*
* Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
package org.alfresco.repo.mail;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.URLName;
import org.alfresco.util.PropertyCheck;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.mail.javamail.JavaMailSenderImpl;
/**
* This class extends Spring's {@link JavaMailSenderImpl} to pool the {@link Transport}s used to send emails.
*
* This is to overcome problems reported in CLOUD-313.
*
* @author Alex Miller
*/
public class AlfrescoJavaMailSender extends JavaMailSenderImpl
{
private static final long DEAFULT_TIME_BETWEEN_EVICTION_RUNS = 30000l;
private static final Logger log = LoggerFactory.getLogger(AlfrescoJavaMailSender.class);
/**
* {@link KeyedPoolableObjectFactory} which uses the {@link Session} returned by {@link JavaMailSenderImpl.getSession()} to create a new
* {@link Transport}.
*/
private final class TransportFactory implements KeyedPoolableObjectFactory
{
/**
* Create a new {@link Transport} using the {@link Session} returned by {@link JavaMailSenderImpl#getSession() getSession()}.
*
* @param key A {@link URLName} containing the connection details
* @return A new {@link Transport}
*/
@Override
public Object makeObject(Object key) throws Exception
{
if ((key instanceof URLName) == false)
{
throw new IllegalArgumentException("Invlid key type");
}
log.debug("Creating new Transport");
URLName urlName = (URLName) key;
Transport transport = getSession().getTransport(urlName.getProtocol());
transport.connect(urlName.getHost(), urlName.getPort(), urlName.getUsername(), urlName.getPassword());
return transport;
}
/**
* Disconnects the pooled {@link Transport} object.
*
* @param key {@link URLName} containing the connection details.
* @param object Pooled {@link Transport}
*/
@Override
public void destroyObject(Object key, Object object) throws Exception
{
if (object instanceof Transport == false)
{
throw new IllegalArgumentException("Unexpected object type");
}
log.debug("Destroying Transpaort");
Transport transport = (Transport)object;
transport.close();
}
/**
* Checks to see if the pooled {@link Transport} is still connected.
*
* @param key {@link URLName} containing the connection details.
* @param object Pooled {@link Transport}
*
* @return true if the pooled transport is still connected, or false, otherwise.
*/
@Override
public boolean validateObject(Object key, Object object)
{
if (object instanceof Transport == false)
{
throw new IllegalArgumentException("Unexpected object type");
}
log.debug("Validating transport");
Transport transport = (Transport)object;
return transport.isConnected();
}
/**
* Do nothing
*/
@Override
public void activateObject(Object key, Object obj) throws Exception
{
}
/**
* Do Noting
*/
@Override
public void passivateObject(Object key, Object obj) throws Exception
{
}
}
/**
* Wrapper implementation of {@link Transport}, which borrows from a pool on connection, and returns to the pool on close.
*
* @see AlfrescoJavaMailSender#getTranport(Session)
*/
private static class PooledTransportWrapper extends Transport
{
private Transport wrapped = null;
private String protocol;
private GenericKeyedObjectPool pool;
/**
* Default constructor.
*
* @param transportPool Pool to borrow/return transports to/from.
* @param session {@link Session} used to create new transports.
* @param protocol Mail protocol for transport.
*/
private PooledTransportWrapper(GenericKeyedObjectPool transportPool, Session session, String protocol)
{
super(session, new URLName(protocol, null, 0, null, null, null));
this.pool = transportPool;
this.protocol = protocol;
}
/**
* Send a message, using the borrowed {@link Transport}.
*/
@Override
public void sendMessage(Message message, Address[] addresses) throws MessagingException
{
if (wrapped == null)
{
throw new IllegalStateException ("Not connected!");
}
wrapped.sendMessage(message, addresses);
}
/**
* Return the borrowed {@link Transport} to the pool.
*/
@Override
public synchronized void close() throws MessagingException
{
if (this.wrapped == null ||
isConnected() == false)
{
throw new IllegalStateException("Already closed");
}
try
{
log.debug("Returning transport to pool");
pool.returnObject(getURLName(), wrapped);
wrapped = null;
}
catch (Exception error)
{
throw new MessagingException("Unexpected exception returning transport to pool", error);
}
finally
{
super.close();
}
}
/**
* Borrow a transport from the pool.
*/
@Override
protected boolean protocolConnect(String host, int port, String username, String password) throws MessagingException
{
URLName name = new URLName(protocol, host, port, null, username, password);
try
{
log.debug("Borrowing object from pool");
wrapped = (Transport) pool.borrowObject(name);
return true;
}
catch (MessagingException ex)
{
throw ex;
}
catch (Exception ex)
{
throw new MessagingException("Unexpected exception borrwoing connection from pool", ex);
}
}
}
private GenericKeyedObjectPool transportPool = new GenericKeyedObjectPool(new TransportFactory());
public AlfrescoJavaMailSender()
{
transportPool.setMaxActive(-1);
transportPool.setTestOnBorrow(true);
transportPool.setTestOnReturn(true);
transportPool.setTimeBetweenEvictionRunsMillis(DEAFULT_TIME_BETWEEN_EVICTION_RUNS);
transportPool.setMinEvictableIdleTimeMillis(DEAFULT_TIME_BETWEEN_EVICTION_RUNS);
}
/**
* @return A new {@link PooledTransportWrapper} which borrows a pooled {@link Transport} on connect, and returns it to
* the pool on close.
*/
@Override
protected Transport getTransport(Session session) throws NoSuchProviderException
{
return new PooledTransportWrapper(transportPool, session, getProtocol());
}
/**
* Set the maximum number of active transports, managed by the pool. Use a negative value for no limit. Default is -1.
*/
public void setMaxActive(int maxActive)
{
transportPool.setMaxActive(maxActive);
}
/**
* Set the maximum number of transports that can sit idle in the pool. Use a negative value for no limit. Default is 8.
*/
public void setMaxIdle(int maxIdle)
{
transportPool.setMaxIdle(maxIdle);
}
/**
* Set the maximum amount of time (in milliseconds) to wait for a transport to be returned from the pool.
* Set to 0 or less to block indefinitely.
*/
public void setMaxWait(long maxWait)
{
transportPool.setMaxWait(maxWait);
}
/**
* Set the time (in milliseconds) between runs of the idle object eviction thread. Set to non-positive for no eviction.
* Default value is 30 seconds.
*/
public void setTimeBetweenEvictionRuns(long time)
{
transportPool.setTimeBetweenEvictionRunsMillis(time);
}
/**
* Set the minimum amount of time a transport may sit idle, before it is eligible for eviction. Set to non positive prevent
* eviction based on idle time.
*
* This value has no affect if timeBetweenEvictionRuns is non positive.
*
* Default value is 30 seconds.
*/
public void setMinEvictableIdleTime(long time)
{
transportPool.setMinEvictableIdleTimeMillis(time);
}
@Override
public void setUsername(String userName)
{
if (PropertyCheck.isValidPropertyString(userName))
{
super.setUsername(userName);
}
else
{
super.setUsername(null);
}
}
@Override
public void setPassword(String password)
{
if (PropertyCheck.isValidPropertyString(password))
{
super.setPassword(password);
}
else
{
super.setPassword(null);
}
}
}