ServerSetup.java
/*
* Copyright (c) 2014 Wael Chatila / Icegreen Technologies. All Rights Reserved.
* This software is released under the Apache license 2.0
*/
package com.icegreen.greenmail.util;
import java.util.Objects;
import java.util.Properties;
import java.util.function.UnaryOperator;
/**
* Defines the default ports
* <table>
* <tr><td>smtp</td><td>25</td></tr>
* <tr><td>smtps</td><td>465</td></tr>
* <tr><td>pop3</td><td>110</td></tr>
* <tr><td>pop3s</td><td>995</td></tr>
* <tr><td>imap</td><td>143</td></tr>
* <tr><td>imaps</td><td>993</td></tr>
* </table>
* Use {@link ServerSetupTest} for non-default ports
* <p/>
*
* @author Wael Chatila
* @version $Id: $
* @since Jan 28, 2006
* Use {@link ServerSetupTest} for non-default ports
*/
public class ServerSetup {
public static final String PROTOCOL_SMTP = "smtp";
public static final String PROTOCOL_SMTPS = "smtps";
public static final String PROTOCOL_POP3 = "pop3";
public static final String PROTOCOL_POP3S = "pop3s";
public static final String PROTOCOL_IMAP = "imap";
public static final String PROTOCOL_IMAPS = "imaps";
public static final String[] PROTOCOLS = {PROTOCOL_SMTP, PROTOCOL_SMTPS, PROTOCOL_IMAP, PROTOCOL_IMAPS, PROTOCOL_POP3, PROTOCOL_POP3S};
public static final int PORT_SMTP = 25;
public static final int PORT_SMTPS = 465;
public static final int PORT_POP3 = 110;
public static final int PORT_POP3S = 995;
public static final int PORT_IMAP = 143;
public static final int PORT_IMAPS = 993;
public static final ServerSetup SMTP = new ServerSetup(PORT_SMTP, null, PROTOCOL_SMTP);
public static final ServerSetup SMTPS = new ServerSetup(PORT_SMTPS, null, PROTOCOL_SMTPS);
public static final ServerSetup POP3 = new ServerSetup(PORT_POP3, null, PROTOCOL_POP3);
public static final ServerSetup POP3S = new ServerSetup(PORT_POP3S, null, PROTOCOL_POP3S);
public static final ServerSetup IMAP = new ServerSetup(PORT_IMAP, null, PROTOCOL_IMAP);
public static final ServerSetup IMAPS = new ServerSetup(PORT_IMAPS, null, PROTOCOL_IMAPS);
public static final ServerSetup[] SMTP_POP3 = new ServerSetup[]{SMTP, POP3};
public static final ServerSetup[] SMTP_IMAP = new ServerSetup[]{SMTP, IMAP};
public static final ServerSetup[] SMTP_POP3_IMAP = new ServerSetup[]{SMTP, POP3, IMAP};
public static final ServerSetup[] SMTPS_POP3S = new ServerSetup[]{SMTPS, POP3S};
public static final ServerSetup[] SMTPS_POP3S_IMAPS = new ServerSetup[]{SMTPS, POP3S, IMAPS};
public static final ServerSetup[] SMTPS_IMAPS = new ServerSetup[]{SMTPS, IMAPS};
public static final ServerSetup[] ALL = new ServerSetup[]{SMTP, SMTPS, POP3, POP3S, IMAP, IMAPS};
/**
* Default socket read timeout. See JavaMail session properties.
*/
public static final long READ_TIMEOUT = 15000L;
/**
* Default socket connection timeout. See JavaMail session properties.
*/
public static final long CONNECTION_TIMEOUT = 15000L;
/**
* Default server startup timeout in milliseconds.
*/
public static final long SERVER_STARTUP_TIMEOUT = 2000L;
private static final String MAIL_DOT = "mail.";
private final int port;
private final String bindAddress;
private final String protocol;
private long readTimeout = -1L;
private long connectionTimeout = -1L;
private long writeTimeout = -1L;
private boolean verbose = false;
/**
* Timeout when GreenMail starts a server, in milliseconds.
*/
private long serverStartupTimeout = SERVER_STARTUP_TIMEOUT;
private final Properties mailSessionProperties = new Properties();
/**
* Creates a configuration.
*
* @param port the port for this service. Set to 0 if an available port should be autodetected.
* @param bindAddress the bind address, eg 'localhost'
* @param protocol the protocol, see {@link ServerSetup#PROTOCOLS}
*/
public ServerSetup(int port, String bindAddress, String protocol) {
this.port = port;
if (null == bindAddress || bindAddress.length() == 0) {
this.bindAddress = getLocalHostAddress();
} else {
this.bindAddress = bindAddress;
}
this.protocol = protocol;
}
public static String getLocalHostAddress() {
// Always pretend that we are 127.0.0.1.
// Doesn't matter what we return here, and we have no way of guessing the
// "correct" address anyway if we have multiple external interfaces.
// InetAddress.getLocalHost().getHostAddress() is unreliable.
return "127.0.0.1";
}
/**
* Gets the public default host address "0.0.0.0" .
*
* @return the public IP host address.
*/
public String getDefaultBindAddress() {
return "0.0.0.0";
}
public boolean isSecure() {
return protocol.endsWith("s");
}
public String getProtocol() {
return protocol;
}
public String getBindAddress() {
return bindAddress;
}
public int getPort() {
return port;
}
public long getConnectionTimeout() {
return connectionTimeout;
}
public void setConnectionTimeout(long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}
public long getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(long readTimeout) {
this.readTimeout = readTimeout;
}
public long getWriteTimeout() {
return writeTimeout;
}
public void setWriteTimeout(long writeTimeout) {
this.writeTimeout = writeTimeout;
}
public long getServerStartupTimeout() {
return serverStartupTimeout;
}
/**
* Creates a deep copy and updates port.
*
* @param port the port (0 for dynamic port allocation).
* @return a modified copy.
* @deprecated use {@link #port(int)} - will be deprecated in 2.1
*/
@Deprecated()
public ServerSetup withPort(int port) {
return port(port);
}
/**
* Creates a deep copy and updates port.
*
* @param port the port (0 for dynamic port allocation).
* @return a modified copy.
*/
public ServerSetup port(int port) {
return createCopy(port, getBindAddress(), getProtocol());
}
/**
* True if available port gets dynamically allocated.
*
* @return true if enabled.
*/
public boolean isDynamicPort() {
return getPort() == 0;
}
/**
* Enable dynamic port allocation.
*
* @return a modified copy with dynamic port allocation enabled.
*/
public ServerSetup dynamicPort() {
return createCopy(0, getBindAddress(), getProtocol());
}
public boolean isVerbose() {
return verbose;
}
/**
* @param verbose if true enables JavaMail debug output by setting JavaMail property 'mail.debug'
*/
public ServerSetup setVerbose(boolean verbose) {
this.verbose = verbose;
return this;
}
/**
* Creates a deep copy with verbose configured.
*
* @param verbose if true enables JavaMail debug output by setting JavaMail property 'mail.debug'
* @return a deep copy with verbose configured
*/
public ServerSetup verbose(boolean verbose) {
return createCopy().setVerbose(verbose);
}
/**
* Sets the server startup timeout in milliseconds.
*
* @param timeoutInMs timeout in milliseconds.
*/
public void setServerStartupTimeout(long timeoutInMs) {
this.serverStartupTimeout = timeoutInMs;
}
/**
* Creates default properties for a JavaMail session.
* <p/>
* Concrete server implementations can add protocol specific settings.
* <p/>
* Order of application:
* <ol>
* <li>Default GreenMail properties - e.g. common properties such as
* <code>mail.debug</code> when log level is debug or protocol specific settings
* such as <code>mail.smtp.port</code></li>
* <li>ServerSetup configured mail session properties</li>
* <li>Parameter provided properties</li>
* </ol>
* Note: Be careful to not override essential GreenMail provided properties, e.g. <code>mail.PROTOCOL.port</code>
* <p/>
* For details see
* <ul>
* <li>https://jakarta.ee/specifications/mail/2.0/apidocs/jakarta.mail/module-summary.html for some general settings</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/smtp/package-summary.html for valid SMTP properties.</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/imap/package-summary.html for valid IMAP properties</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/pop3/package-summary.html for valid POP3 properties.</li>
* </ul
*
* @param properties additional and optional properties which overwrite automatically added properties. Can be null.
* @param debug sets JavaMail debug properties
* @return default properties.
*/
public Properties configureJavaMailSessionProperties(Properties properties, boolean debug) {
Properties props = new Properties();
if (debug) {
props.setProperty("mail.debug", "true");
// System.setProperty("mail.socket.debug", "true");
}
// Port and host
props.setProperty(MAIL_DOT + getProtocol() + ".port", String.valueOf(getPort()));
final String address = getBindAddress();
props.setProperty(MAIL_DOT + getProtocol() + ".host", String.valueOf(address));
// Fixes slow Transport.send->UniqueValue.getUniqueMessageIDValue->javax.mail.internet.InternetAddress.getLocalAddress
if (!props.containsKey("mail.host") && null != address) {
props.setProperty("mail.host", address);
}
if (isSecure()) {
props.setProperty(MAIL_DOT + getProtocol() + ".starttls.enable", Boolean.TRUE.toString());
props.setProperty(MAIL_DOT + getProtocol() + ".socketFactory.class", DummySSLSocketFactory.class.getName());
props.setProperty(MAIL_DOT + getProtocol() + ".socketFactory.fallback", "false");
// Required for Angus Mail, see: https://github.com/eclipse-ee4j/angus-mail/issues/12
props.setProperty(MAIL_DOT + getProtocol() + ".ssl.checkserveridentity", "false");
}
// Timeouts
props.setProperty(MAIL_DOT + getProtocol() + ".connectiontimeout",
Long.toString(getConnectionTimeout() < 0L ? ServerSetup.CONNECTION_TIMEOUT : getConnectionTimeout()));
props.setProperty(MAIL_DOT + getProtocol() + ".timeout",
Long.toString(getReadTimeout() < 0L ? ServerSetup.READ_TIMEOUT : getReadTimeout()));
// Note: "mail." + setup.getProtocol() + ".writetimeout" breaks TLS/SSL Dummy Socket and makes tests run 6x slower!!!
// Therefore, we do not by default configure writetimeout.
if (getWriteTimeout() >= 0L) {
props.setProperty(MAIL_DOT + getProtocol() + ".writetimeout", Long.toString(getWriteTimeout()));
}
// Protocol specific extensions
if (getProtocol().startsWith(PROTOCOL_SMTP)) {
props.setProperty("mail.transport.protocol", getProtocol());
props.setProperty("mail.transport.protocol.rfc822", getProtocol());
}
// Auto configure stores.
props.setProperty("mail.store.protocol", getProtocol());
// Merge with default properties
if (!mailSessionProperties.isEmpty()) {
props.putAll(mailSessionProperties);
}
// Merge with optional additional properties
if (null != properties && !properties.isEmpty()) {
props.putAll(properties);
}
return props;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ServerSetup)) return false;
ServerSetup that = (ServerSetup) o;
return port == that.port &&
readTimeout == that.readTimeout &&
connectionTimeout == that.connectionTimeout &&
writeTimeout == that.writeTimeout &&
verbose == that.verbose &&
serverStartupTimeout == that.serverStartupTimeout &&
bindAddress.equals(that.bindAddress) &&
protocol.equals(that.protocol) &&
mailSessionProperties.equals(that.mailSessionProperties);
}
@Override
public int hashCode() {
return Objects.hash(port, bindAddress, protocol, readTimeout, connectionTimeout, writeTimeout, verbose, serverStartupTimeout, mailSessionProperties);
}
@Override
public String toString() {
return "ServerSetup{" +
"port=" + port +
", bindAddress='" + bindAddress + '\'' +
", protocol='" + protocol + '\'' +
", readTimeout=" + readTimeout +
", connectionTimeout=" + connectionTimeout +
", writeTimeout=" + writeTimeout +
", verbose=" + verbose +
", serverStartupTimeout=" + serverStartupTimeout +
", mailProperties=" + mailSessionProperties +
'}';
}
/**
* Create a deep copy.
*
* @return a copy of the server setup configuration.
*/
public ServerSetup createCopy() {
return createCopy(getBindAddress());
}
/**
* Create a deep copy.
*
* @param bindAddress overwrites bind address when creating deep copy.
* @return a copy of the server setup configuration.
*/
public ServerSetup createCopy(String bindAddress) {
return createCopy(getPort(), bindAddress, getProtocol());
}
/**
* Create a deep copy.
*
* @param port overwrites the port.
* @param bindAddress overwrites bind address.
* @param protocol overwrites the protocol.
*
* @return a copy of the server setup configuration.
*/
public ServerSetup createCopy(int port, String bindAddress, String protocol) {
ServerSetup setup = new ServerSetup(port, bindAddress, protocol);
setup.setServerStartupTimeout(getServerStartupTimeout());
setup.setConnectionTimeout(getConnectionTimeout());
setup.setReadTimeout(getReadTimeout());
setup.setWriteTimeout(getWriteTimeout());
setup.setVerbose(isVerbose());
setup.mailSessionProperties.putAll(mailSessionProperties);
return setup;
}
/**
* Create a new server setup copy, configured with given JavaMail session property.
* <p>
* For protocol specific properties see
* <ul>
* <li>https://jakarta.ee/specifications/mail/2.0/apidocs/jakarta.mail/jakarta/mail/package-summary.html for general settings</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/smtp/package-summary.html for SMTP properties.</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/imap/package-summary.html for IMAP properties</li>
* <li>https://jakarta.ee/specifications/mail/1.6/apidocs/index.html?com/sun/mail/pop3/package-summary.html for POP3 properties.</li>
* </ul
*
* @param key the key
* @param value the value
* @return a deep copy with configured session property
*/
public ServerSetup mailSessionProperty(String key, String value) {
ServerSetup updatedServerSetup = createCopy();
updatedServerSetup.mailSessionProperties.put(key, value);
return updatedServerSetup;
}
/**
* Creates a copy applying given mutator on each copy.
*
* @param serverSetups the server setups
* @param copyMutator the mutator receiving the original server setup and returning a modified copy
* @return mutated copies
*/
public static ServerSetup[] createCopy(ServerSetup[] serverSetups,
UnaryOperator<ServerSetup> copyMutator) {
ServerSetup[] copies = new ServerSetup[serverSetups.length];
for (int i = 0; i < serverSetups.length; i++) {
copies[i] = copyMutator.apply(serverSetups[i]);
}
return copies;
}
/**
* Creates a copy with verbose mode enabled.
*
* @param serverSetups the server setups.
* @return copies of server setups with verbose mode enabled.
*/
public static ServerSetup[] verbose(ServerSetup[] serverSetups) {
return createCopy(serverSetups, serverSetup -> serverSetup.verbose(true));
}
/**
* Creates a copy with dynamic ports (auto-detecting available ports) enabled.
*
* @param serverSetups the server setups.
* @return copies of server setups with verbose mode enabled.
*/
public static ServerSetup[] dynamicPort(ServerSetup[] serverSetups) {
return createCopy(serverSetups, ServerSetup::dynamicPort);
}
/**
* Creates a copy with configured default mail session property.
* @see #mailSessionProperty(String, String)
*
* @param serverSetups the server setups.
* @param key the key
* @param value the value
* @return copies of server setups with verbose mode enabled.
*/
public static ServerSetup[] mailSessionProperty(ServerSetup[] serverSetups,
String key, String value) {
return createCopy(serverSetups, serverSetup -> serverSetup.mailSessionProperty(key, value));
}
}