001    package org.codemonkey.simplejavamail;
002    
003    import java.util.Properties;
004    
005    import javax.mail.Authenticator;
006    import javax.mail.Message;
007    import javax.mail.MessagingException;
008    import javax.mail.PasswordAuthentication;
009    import javax.mail.Session;
010    import javax.mail.Transport;
011    
012    /**
013     * Creates and initializes a {@link Session} instance based on provided host and credentials as well as a {@link TransportStrategy} instance
014     * to apply SMTP, SMTPS or SMTP + TLS protocol semantics.
015     * <p>
016     * The <code>Session</code> needs to be created before assembling the concerning e-mail message in the {@link Mailer} as the Sun JavaMail
017     * API directly wires the e-mail message into the <code>Session</code>. The session again is needed when actually sending the e-mail or when
018     * {@link #toString()} is called.
019     * 
020     * @author Benny Bottema
021     */
022    class MailSession {
023    
024            /**
025             * Used to actually send the email. This session can come from being passed in the default constructor, or made by this
026             * <code>MailSession</code> directly, when no <code>Session</code> instance was provided.
027             * 
028             * @see #MailSession(Session)
029             * @see #MailSession(String, int, String, String, TransportStrategy)
030             */
031            private final Session session;
032    
033            /**
034             * The transport protocol strategy enum that actually handles the session configuration. Session configuration meaning setting the right
035             * properties for the appropriate transport type (ie. <em>"mail.smtp.host"</em> for SMTP, <em>"mail.smtps.host"</em> for SMTPS).
036             */
037            private TransportStrategy transportStrategy;
038    
039            /**
040             * Default constructor, stores the given mail session for later use. It is assumed all properties are set, such as host, credentials and
041             * transport properties.
042             * 
043             * @param session A preconfigured mail {@link Session} object with which a {@link Message} can be produced.
044             */
045            public MailSession(final Session session) {
046                    this.session = session;
047            }
048    
049            /**
050             * Overloaded constructor which produces a new {@link Session} on the fly. Use this if you don't have a mail session configured in your
051             * web container, or Spring context etc.
052             * 
053             * @param host The address URL of the SMTP server to be used.
054             * @param port The port of the SMTP server.
055             * @param username An optional username, may be <code>null</code>.
056             * @param password An optional password, may be <code>null</code>, but only if username is <code>null</code> as well.
057             */
058            public MailSession(final String host, final int port, final String username, final String password,
059                            final TransportStrategy transportStrategy) {
060                    // we're doing this manually instead of using Apache Commons, to avoid another dependency
061                    if (host == null || "".equals(host.trim())) {
062                            throw new RuntimeException("Can't send an email without host");
063                    } else if ((password != null && !"".equals(password.trim())) && (username == null || "".equals(username.trim()))) {
064                            throw new RuntimeException("Can't have a password without username");
065                    }
066                    this.transportStrategy = transportStrategy;
067                    this.session = createMailSession(host, port, username, password);
068            }
069    
070            /**
071             * Actually instantiates and configures the {@link Session} instance. Delegates resolving transport protocol specific properties to the
072             * {@link #transportStrategy} in two ways:
073             * <ol>
074             * <li>request an initial property list which the strategy may pre-populate</li>
075             * <li>by requesting the property names according to the respective transport protocol it handles (for the host property name it would
076             * be <em>"mail.smtp.host"</em> for SMTP and <em>"mail.smtps.host"</em> for SMTPS)</li>
077             * </ol>
078             * 
079             * @param host The address URL of the SMTP server to be used.
080             * @param port The port of the SMTP server.
081             * @param username An optional username, may be <code>null</code>.
082             * @param password An optional password, may be <code>null</code>.
083             * @return A fully configured <code>Session</code> instance complete with transport protocol settings.
084             * @see TransportStrategy#generateProperties()
085             * @see TransportStrategy#propertyNameHost()
086             * @see TransportStrategy#propertyNamePort()
087             * @see TransportStrategy#propertyNameUsername()
088             * @see TransportStrategy#propertyNameAuthenticate()
089             */
090            public Session createMailSession(final String host, final int port, final String username, final String password) {
091                    Properties props = transportStrategy.generateProperties();
092                    props.put(transportStrategy.propertyNameHost(), host);
093                    props.put(transportStrategy.propertyNamePort(), String.valueOf(port));
094    
095                    if (username != null) {
096                            props.put(transportStrategy.propertyNameUsername(), username);
097                    }
098    
099                    if (password != null) {
100                            props.put(transportStrategy.propertyNameAuthenticate(), "true");
101                            return Session.getInstance(props, new Authenticator() {
102                                    @Override
103                                    protected PasswordAuthentication getPasswordAuthentication() {
104                                            return new PasswordAuthentication(username, password);
105                                    }
106                            });
107                    } else {
108                            return Session.getInstance(props);
109                    }
110            }
111    
112            /**
113             * Sends a Sun JavaMail {@link Message} object using the {@link Session#getTransport()}. It will call {@link Transport#connect()}
114             * assuming all connection details have been configured in the provided {@link Session} instance.
115             * <p>
116             * Performs a call to {@link Message#saveChanges()} as the Sun JavaMail API indicates it is needed to configure the message headers and
117             * providing a message id.
118             * 
119             * @param message The message to send. Generate by the {@link Mailer}.
120             * @throws MessagingException Thrown by {@link Message#saveChanges()}, {@link Transport#connect()},
121             *             {@link Transport#sendMessage(Message, javax.mail.Address[])}, {@link Transport#close()}
122             */
123            public void sendMessage(Message message)
124                            throws MessagingException {
125                    message.saveChanges(); // some headers and id's will be set for this specific message
126                    Transport transport = session.getTransport();
127                    transport.connect();
128                    transport.sendMessage(message, message.getAllRecipients());
129                    transport.close();
130            }
131    
132            /**
133             * Returns host details, credentials used and whether authentication will take place and finally the transport protocol used.
134             */
135            @Override
136            public String toString() {
137                    final String logmsg = "host: %s, port: %s, username: %s, authenticate: %s, transport: %s";
138                    return String.format(logmsg, transportStrategy.propertyNameHost(), transportStrategy.propertyNamePort(),
139                                    transportStrategy.propertyNameUsername(), Boolean.parseBoolean(transportStrategy.propertyNameAuthenticate()),
140                                    transportStrategy);
141            }
142    
143            /**
144             * Bean getter for {@link #session}.
145             */
146            public Session getSession() {
147                    return session;
148            }
149    }