Scala and Gmail

I was looking for a Scala-esque wrapper around Apache Commons Email and I’ve found just that in this Gist which I’ve forked to add the configuration details using Typesafe Config.

As far as the addition goes, it’s a function to add the Email hostname/port/ssl/smtpserver/username/password:

      def withResources = (email:Email) => {
        val conf = ConfigFactory.load
        email.setHostName(conf.getString("email.hostname"))
        email.setSmtpPort(conf.getInt("email.port"))
        email.setAuthentication(conf.getString("email.username"),conf.getString("email.password"))
        email.setSSL(conf.getBoolean("email.is.ssl"))
        email
      }

The original Gist is constructing the Email flavour according to the need (html/simple/attachement) which I am wrapping around a Some (since the case is exhaustive) so I can then map it to the above function to add the underlying configs.

Advertisements

Spring Email Testing using GreenMail

I’ve been ignoring setting up a proper unit-test regarding email functionality for some time now mainly because most of the cases a quick and dirty test email to myself would do it. Well, quick’n’dirty hack no more and this is a proper way to test your Spring Email functionality using an easy to use library called GreenMail.

Let’s start with the POM:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>SpringEmailTest</groupId>
    <artifactId>SpringEmailTest</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!--Spring Dependencies-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!--Testing Dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
        <dependency>
            <groupId>com.icegreen</groupId>
            <artifactId>greenmail</artifactId>
            <version>${greenmail.version}</version>
        </dependency>
        <!--Email Dependencies -->
        <dependency>
            <groupId>com.sun.mail</groupId>
            <artifactId>javax.mail</artifactId>
            <version>${javax-mail.version}</version>
        </dependency>


    </dependencies>
    <properties>
        <java.version>1.6</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>3.2.2.RELEASE</spring.version>
        <junit.version>4.10</junit.version>
        <javax-mail.version>1.5.0</javax-mail.version>
       <greenmail.version>1.3.1b</greenmail.version>
    </properties>


</project>

The Spring Configuration looks like this:

package com.dimitrisli.springEmailTest.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
@ImportResource(value = "classpath:/spring/appXMLContext.xml")
@PropertySource(value = "classpath:/properties/application.properties")
public class AppConfig {

    @Bean
    public JavaMailSenderImpl emailSender(@Value("${email.host}") String emailHost,
                                          @Value("${email.port}") Integer emailPort,
                                          @Value("${email.username}") String username,
                                          @Value("${email.pass}") String password){
        JavaMailSenderImpl emailSender = new JavaMailSenderImpl();
            emailSender.setHost(emailHost);
            emailSender.setPort(emailPort);
            emailSender.setUsername(username);
            emailSender.setPassword(password);
            //emailSender.setDefaultEncoding("UTF_8");
            Properties mailProps = new Properties();
                mailProps.setProperty("mail.transport.protocol","smtp");
                mailProps.setProperty("mail.smtp.auth","true");
                mailProps.setProperty("mail.smtp.starttls.enable","true");
                mailProps.setProperty("mail.debug","false");
                emailSender.setJavaMailProperties(mailProps);
        return emailSender;
    }
}

A few things to note here:
– The @Configuration annotation marks the Spring Java Configuration context.
– The @PropertySource is retrieving the properties file from classpath. Apparently @PropertySource is not playing well with @Value injection therefore a workaround this is to include an XML context just to incude the property placeholder reference.
– The @ImportResource is the way of including an XML context file from the classpath.
– The beans are defined using the @Bean annotation.
– The @Value annotation is automatically injecting the properties values into the variables.

The referenced application.properties that is including the Email details (in this case setup to be Gmail) looks like this:

email.host=smtp.gmail.com
email.port=25
email.username=yourGmailAtgmailDotcom
email.pass=yourPass

As noted above to resolve an inconsistency between the @PropertySource and @Value we need to introduce an XML context that looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--In Java @Configuration POJOs the @PropertySource is
        not playing well with @Value. To resolve this we introduce
        just this reference that is doing the trick. No other dependency on
        XML configuration should be needed.-->
    <context:property-placeholder />
</beans>

Finally the JUnit test looks like this:

package com.dimitrisli.springEmailTest;

import com.dimitrisli.springEmailTest.config.AppConfig;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.GreenMailUtil;
import com.icegreen.greenmail.util.ServerSetupTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import javax.mail.Message;
import javax.mail.MessagingException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class EmailTest {

    @Resource
    private JavaMailSenderImpl emailSender;

    private GreenMail testSmtp;

    @Before
    public void testSmtpInit(){
        testSmtp = new GreenMail(ServerSetupTest.SMTP);
        testSmtp.start();

        //don't forget to set the test port!
        emailSender.setPort(3025);
        emailSender.setHost("localhost");
    }

    @Test
    public void testEmail() throws InterruptedException, MessagingException {
        SimpleMailMessage message = new SimpleMailMessage();

        message.setFrom("test@sender.com");
        message.setTo("test@receiver.com");
        message.setSubject("test subject");
        message.setText("test message");
        emailSender.send(message);
        
        Message[] messages = testSmtp.getReceivedMessages();
        assertEquals(1, messages.length);
        assertEquals("test subject", messages[0].getSubject());
        String body = GreenMailUtil.getBody(messages[0]).replaceAll("=\r?\n", "");
        assertEquals("test message", body);
    }

    @After
    public void cleanup(){
        testSmtp.stop();
    }
}

– JUnit is configured to run with Spring using the SpringJUnit4ClassRunner.class
– JUnit is loading the Spring context as instructed by the @ContextConfiguration annotation pointing to the Java Configuration class.
– The @Resource annotation is autowiring the bean dependency directly on our test conveniently having it initialised during the test runtime.
– We have an init() activity marked by JUnit’s @Before annotation where we instantiate our GreenMail mail server and changing the port of our email service to a test one.
– The test method itself is a simple manner of initialising a test SimpleMailMessage and sending it via our email service that gets intercepted by GreenMail and further queried upon concerning the data received.
– A cleanup functionality is closing the resourced during the @After JUnit instructed method.

The source code can be found in this Github repo.

Sending Gmail from Spring

Here’s a working example of how to send an Email (customised for the Gmail client) from a Spring application.

The POM would look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.dimitrisli</groupId>
  <artifactId>springEmail</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <properties>
  	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>3.0.6.RELEASE</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
    	<groupId>org.springframework</groupId>
    	<artifactId>spring-support</artifactId>
    	<version>2.0.8</version>
    </dependency>
    <dependency>
    	<groupId>com.sun.mail</groupId>
    	<artifactId>javax.mail</artifactId>
    	<version>1.4.4</version>
    </dependency>
  </dependencies>
  <build>
  	<plugins>
  		<plugin>
  			<artifactId>maven-compiler-plugin</artifactId>
  			<configuration>
  				<source>1.6</source>
  				<target>1.6</target>
  			</configuration>
  		</plugin>
  	</plugins>
  </build>
</project>

Few things to notice on this POM:

  • Specifying UTF-8 platform agnostic source encoding. If this property is not there Maven would complain about system specific encoding.
  • Due to the dependency tree, specifying spring-context is enough to bring the minimal number of Spring related jars that are needed to construct the minimal Spring project that uses the ApplicationContext, therefore Maven saves us from this trouble. Namely, the jars that are absolutely necessary:
    • spring-context
    • spring-beans
    • spring-core
    • spring-expression
    • spring-asm
    • commons-logging
  • Since we would be using Spring’s SimpleMailMessage the jar spring-support is also needed.
  • Also the javax.mail is needed for the despatching of the email.
  • Explicitly specifying java 1.6 for compilation using the maven maven-compiler-plugin plugin.

Our service layer is represented from the following class:

package com.dimitrisli.springEmail;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailSender;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Service;

@Service("emailService")
public class EmailService {

	@Autowired
	private MailSender emailSender;
	@Autowired
	private SimpleMailMessage exceptionEmail;

	public void sendEmail(String from, String to, String subj, String body){
		SimpleMailMessage message = new SimpleMailMessage();
		message.setFrom(from);
		message.setTo(to);
		message.setSubject(subj);
		message.setText(body);
		emailSender.send(message);
	}

	public void sendExceptionEmail(String error){
		SimpleMailMessage exceptionEmailMessage = new SimpleMailMessage(exceptionEmail);
		exceptionEmailMessage.setText(error);
		emailSender.send(exceptionEmailMessage);
	}
}

Few things to notice:

  • @Service Spring annotation is another way of saying @Component but has a better representation of the service layer. Also specifying the bean id at that stage in the java bean makes the bean reference in an xml  configuration file unnecessary.
  • @Autowired is taking care of injecting the dependencies of the emailSender and exceptionEmail functionality. Loose coupling at its best.

Under /src/main/resources we store the Spring configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"
>
	<!--context namespace and schema for the component-scan to work-->
	<context:component-scan base-package="com.dimitrisli.springEmail" />

	<bean id="emailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
		<property name="host" value="smtp.gmail.com"/>
		<property name="port" value="25"/>
		<property name="username" value="your@gmailhere.com"/>
		<property name="password" value="yourpass"/>
		<property name="javaMailProperties">
			<props>
				<prop key="mail.transport.protocol">smtp</prop>
				<prop key="mail.smtp.auth">true</prop>
				<prop key="mail.smtp.starttls.enable">true</prop>
				<prop key="mail.debug">true</prop>
			</props>
		</property>
	</bean>

	<bean id="exceptionEmail" class="org.springframework.mail.SimpleMailMessage">
		<property name="from">
			<value>from@email.com</value>
		</property>
		<property name="to">
			<value>to@email.com</value>
		</property>
		<property name="subject" value="Something went wrong please investigate"/>
	</bean>

</beans>

Things to notice:

  • Namespaces include the beans reference due to the tags we are using and the context reference due to the component-scan
  • component-scan on the specified package is activating the @Autowired magic.
  • The emailSender bean is tailored for the Gmail specific settings passing all the needed parameters to the JavaMailSenderImpl.
  • The exceptionEmail bean is following another technique having a preconfigured SimpleMailMessage.

Finally the MainSpringContainer which is laying down the two test scenarios creating a Spring Application Context:

package com.dimitrisli.springEmail;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

public class MainSpringContainer {

	public static void main(String[] args) {
		ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/resources/spring.xml");
		EmailService emailService = (EmailService) context.getBean("emailService");
		emailService.sendEmail("from@email.com", "to@email.com", "test subject", "test body");
		emailService.sendExceptionEmail("something went terribly wrong");
	}
}

You can find the code in this Github repo