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

at.molindo.notify.channel.mail.DirectMailClient Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2010 Molindo GmbH
 *
 * Licensed 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 at.molindo.notify.channel.mail;

import java.util.Collections;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.SendFailedException;
import javax.mail.Session;
import javax.naming.NamingException;

import org.springframework.beans.factory.InitializingBean;

import at.molindo.utils.collections.CollectionUtils;
import at.molindo.utils.data.ExceptionUtils;
import at.molindo.utils.net.DnsUtils;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.sun.mail.smtp.SMTPAddressFailedException;
import com.sun.mail.smtp.SMTPSendFailedException;

public class DirectMailClient extends AbstractMailClient implements InitializingBean {

	private static final int DEFAULT_CACHE_CONCURRENCY = 4;
	private static final long DEFAULT_CACHE_EXPIRATION_MIN = 10;

	private static final String CONNECTION_TIMEOUT_MS = "60000";
	private static final String READ_TIMEOUT_MS = "60000";

	// permanent errors
	private static final int MAILBOX_UNAVAILABLE = 550;
	private static final int MAILBOX_NOT_LOCAL = 551;
	private static final int MAILBOX_NAME_NOT_ALLOWED = 553;
	private static final int TRANSACTION_FAILED = 554;
	private static final Set PERMANENT_ERROR_CODES = Collections.unmodifiableSet(CollectionUtils.set(
			MAILBOX_UNAVAILABLE, MAILBOX_NOT_LOCAL, MAILBOX_NAME_NOT_ALLOWED, TRANSACTION_FAILED));

	private LoadingCache _sessionCache;
	private int _cacheConcurrency = DEFAULT_CACHE_CONCURRENCY;
	private long _cacheExpirationMin = DEFAULT_CACHE_EXPIRATION_MIN;
	private String _localAddress;
	private String _socksProxyHost;
	private String _socksProxyPort;
	private Boolean _proxySet;
	private String _localHost;
	private boolean _startTLSEnabled = false;

	@Override
	public DirectMailClient init() throws MailException {
		super.init();
		_sessionCache = CacheBuilder.newBuilder().concurrencyLevel(_cacheConcurrency)
				.expireAfterAccess(_cacheExpirationMin, TimeUnit.MINUTES).build(new CacheLoader() {
					@Override
					public Session load(String domain) throws MailException {
						return createSmtpSession(domain);
					}
				});
		return this;
	}

	@Override
	protected Session getSmtpSession(String recipient) throws MailException {
		try {
			return _sessionCache.get(MailUtils.domainFromAddress(recipient));
		} catch (ExecutionException e) {
			if (e.getCause() instanceof MailException) {
				throw (MailException) e.getCause();
			} else {
				throw new RuntimeException("unexpected exception while getting SMTP session for " + recipient, e);
			}
		}
	}

	protected Session createSmtpSession(String domain) throws MailException {
		try {
			final Properties props = new Properties();
			props.setProperty("mail.smtp.host", DnsUtils.lookupMailHosts(domain)[0]);
			props.setProperty("mail.smtp.port", "25");
			props.setProperty("mail.smtp.auth", "false");
			props.setProperty("mail.smtp.starttls.enable", Boolean.toString(getStartTLSEnabled()));

			// set proxy
			if (Boolean.TRUE.equals(getProxySet())) {
				props.setProperty("proxySet", "true");
				props.setProperty("socksProxyHost", getSocksProxyHost());
				props.setProperty("socksProxyPort", getSocksProxyPort());
			}

			if (getLocalHost() != null) {
				props.setProperty("mail.smtp.localhost", getLocalHost());
			}
			if (getLocalAddress() != null) {
				props.setProperty("mail.smtp.localaddress", getLocalAddress());
			}

			props.setProperty("mail.smtp.connectiontimeout", CONNECTION_TIMEOUT_MS);
			props.setProperty("mail.smtp.timeout", READ_TIMEOUT_MS);

			// props.put("mail.debug", "true");
			return Session.getInstance(props);
		} catch (NamingException e) {
			throw new MailException("can't lookup mail host: " + domain, e, true);
		}
	}

	@Override
	protected String toErrorMessage(MessagingException e) {
		if (e instanceof SendFailedException) {
			if (e.getNextException() instanceof SMTPSendFailedException) {
				final SMTPSendFailedException se = (SMTPSendFailedException) e.getNextException();
				return se.getCommand() + " failed " + " with " + se.getReturnCode() + " (" + e.getMessage() + ")";
			} else if (e.getNextException() instanceof SMTPAddressFailedException) {
				// copied from above, as there is no common base class but same
				// methods
				final SMTPAddressFailedException se = (SMTPAddressFailedException) e.getNextException();
				return se.getCommand() + " failed " + " with " + se.getReturnCode() + " (" + e.getMessage() + ")";
			} else {
				final StringBuilder buf = new StringBuilder();
				Address[] addresses = ((SendFailedException) e).getInvalidAddresses();
				if (addresses != null) {
					for (final Address a : addresses) {
						buf.append(a).append(" ");
					}
				}
				return "invalied address(es): " + buf + "(" + ExceptionUtils.getAllMessages(e) + ")";
			}
		} else {
			return super.toErrorMessage(e);
		}
	}

	@Override
	protected boolean isTemporary(MessagingException e) {
		if (e instanceof SendFailedException) {
			if (e.getNextException() instanceof SMTPSendFailedException) {
				final SMTPSendFailedException se = (SMTPSendFailedException) e.getNextException();
				final int rc = se.getReturnCode();
				return !PERMANENT_ERROR_CODES.contains(rc);
			} else if (e.getNextException() instanceof SMTPAddressFailedException) {
				// copied from above, as there is no common base class but same
				// methods
				final SMTPAddressFailedException se = (SMTPAddressFailedException) e.getNextException();
				final int rc = se.getReturnCode();
				return !PERMANENT_ERROR_CODES.contains(rc);
			} else {
				return true;
			}
		} else {
			return true;
		}
	}

	public int getCacheConcurrency() {
		return _cacheConcurrency;
	}

	public void setCacheConcurrency(int cacheConcurrency) {
		_cacheConcurrency = cacheConcurrency;
	}

	public long getCacheExpirationMin() {
		return _cacheExpirationMin;
	}

	public void setCacheExpirationMin(long cacheExpirationMin) {
		_cacheExpirationMin = cacheExpirationMin;
	}

	public void setLocalAddress(final String localAddress) {
		_localAddress = localAddress;
	}

	public void setLocalHost(final String localHost) {
		_localHost = localHost;
	}

	public String getLocalAddress() {
		return _localAddress;
	}

	public String getLocalHost() {
		return _localHost;
	}

	public void setStartTLSEnabled(boolean startTLSEnabled) {
		_startTLSEnabled = startTLSEnabled;
	}

	public boolean getStartTLSEnabled() {
		return _startTLSEnabled;
	}

	public void setSocksProxyHost(final String socksProxyHost) {
		_socksProxyHost = socksProxyHost;
	}

	public void setSocksProxyPort(final String socksProxyPort) {
		_socksProxyPort = socksProxyPort;
	}

	public void setProxySet(final Boolean proxySet) {
		_proxySet = proxySet;
	}

	private String getSocksProxyHost() {
		return _socksProxyHost;
	}

	private String getSocksProxyPort() {
		return _socksProxyPort;
	}

	private Boolean getProxySet() {
		return _proxySet;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy