/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This 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 2.1 of
* the License, or (at your option) any later version.
*
* This software 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 this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.mail.integration;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.inject.Provider;
import javax.mail.BodyPart;
import javax.mail.Message.RecipientType;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.xwiki.bridge.event.ApplicationReadyEvent;
import org.xwiki.component.phase.Disposable;
import org.xwiki.component.util.DefaultParameterizedType;
import org.xwiki.context.Execution;
import org.xwiki.context.ExecutionContext;
import org.xwiki.context.ExecutionContextManager;
import org.xwiki.environment.internal.EnvironmentConfiguration;
import org.xwiki.environment.internal.StandardEnvironment;
import org.xwiki.mail.MailListener;
import org.xwiki.mail.MailSender;
import org.xwiki.mail.MailSenderConfiguration;
import org.xwiki.mail.MimeBodyPartFactory;
import org.xwiki.mail.internal.DefaultMailSender;
import org.xwiki.mail.internal.FileSystemMailContentStore;
import org.xwiki.mail.internal.MemoryMailListener;
import org.xwiki.mail.internal.factory.attachment.AttachmentMimeBodyPartFactory;
import org.xwiki.mail.internal.factory.html.HTMLMimeBodyPartFactory;
import org.xwiki.mail.internal.factory.text.TextMimeBodyPartFactory;
import org.xwiki.mail.internal.thread.MailSenderInitializerListener;
import org.xwiki.mail.internal.thread.PrepareMailQueueManager;
import org.xwiki.mail.internal.thread.PrepareMailRunnable;
import org.xwiki.mail.internal.thread.SendMailQueueManager;
import org.xwiki.mail.internal.thread.SendMailRunnable;
import org.xwiki.mail.internal.thread.context.Copier;
import org.xwiki.model.ModelContext;
import org.xwiki.model.reference.WikiReference;
import org.xwiki.observation.EventListener;
import org.xwiki.test.annotation.BeforeComponent;
import org.xwiki.test.annotation.ComponentList;
import org.xwiki.test.mockito.MockitoComponentManagerRule;
import com.icegreen.greenmail.junit.GreenMailRule;
import com.icegreen.greenmail.util.ServerSetupTest;
import com.xpn.xwiki.XWikiContext;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* Integration tests to prove that mail sending is working fully end to end with the Java API.
*
* @version $Id: 47f6f2287701de21e0db830b1a6a8b4368d55e8b $
* @since 6.1M2
*/
@ComponentList({
MailSenderInitializerListener.class,
TextMimeBodyPartFactory.class,
HTMLMimeBodyPartFactory.class,
AttachmentMimeBodyPartFactory.class,
StandardEnvironment.class,
DefaultMailSender.class,
MemoryMailListener.class,
SendMailRunnable.class,
PrepareMailRunnable.class,
PrepareMailQueueManager.class,
SendMailQueueManager.class,
FileSystemMailContentStore.class
})
public class JavaIntegrationTest
{
@Rule
public GreenMailRule mail = new GreenMailRule(ServerSetupTest.SMTP);
@Rule
public MockitoComponentManagerRule componentManager = new MockitoComponentManagerRule();
private TestMailSenderConfiguration configuration;
private MimeBodyPartFactory<String> defaultBodyPartFactory;
private MimeBodyPartFactory<String> htmlBodyPartFactory;
private MailSender sender;
@BeforeComponent
public void registerConfiguration() throws Exception
{
this.configuration = new TestMailSenderConfiguration(
this.mail.getSmtp().getPort(), null, null, new Properties());
this.componentManager.registerComponent(MailSenderConfiguration.class, this.configuration);
// Set the current wiki in the Context
ModelContext modelContext = this.componentManager.registerMockComponent(ModelContext.class);
when(modelContext.getCurrentEntityReference()).thenReturn(new WikiReference("wiki"));
XWikiContext xcontext = mock(XWikiContext.class);
when(xcontext.getWikiId()).thenReturn("wiki");
Provider<XWikiContext> xwikiContextProvider = this.componentManager.registerMockComponent(
XWikiContext.TYPE_PROVIDER);
when(xwikiContextProvider.get()).thenReturn(xcontext);
this.componentManager.registerMockComponent(ExecutionContextManager.class);
this.componentManager.registerMockComponent(Execution.class);
this.componentManager.registerMockComponent(new DefaultParameterizedType(null, Copier.class,
ExecutionContext.class));
EnvironmentConfiguration environmentConfiguration =
this.componentManager.registerMockComponent(EnvironmentConfiguration.class);
when(environmentConfiguration.getPermanentDirectoryPath()).thenReturn(System.getProperty("java.io.tmpdir"));
}
@Before
public void initialize() throws Exception
{
this.defaultBodyPartFactory = this.componentManager.getInstance(
new DefaultParameterizedType(null, MimeBodyPartFactory.class, String.class));
this.htmlBodyPartFactory = this.componentManager.getInstance(
new DefaultParameterizedType(null, MimeBodyPartFactory.class, String.class), "text/html");
this.sender = this.componentManager.getInstance(MailSender.class);
// Set the EC
Execution execution = this.componentManager.getInstance(Execution.class);
ExecutionContext executionContext = new ExecutionContext();
XWikiContext xContext = new XWikiContext();
xContext.setWikiId("wiki");
executionContext.setProperty(XWikiContext.EXECUTIONCONTEXT_KEY, xContext);
when(execution.getContext()).thenReturn(executionContext);
Copier<ExecutionContext> executionContextCloner =
this.componentManager.getInstance(new DefaultParameterizedType(null, Copier.class, ExecutionContext.class));
// Just return the same execution context
when(executionContextCloner.copy(executionContext)).thenReturn(executionContext);
// Simulate receiving the Application Ready Event to start the mail threads
MailSenderInitializerListener listener =
this.componentManager.getInstance(EventListener.class, MailSenderInitializerListener.LISTENER_NAME);
listener.onEvent(new ApplicationReadyEvent(), null, null);
}
@After
public void cleanUp() throws Exception
{
// Make sure we stop the Mail Sender thread after each test (since it's started automatically when looking
// up the MailSender component.
Disposable listener =
this.componentManager.getInstance(EventListener.class, MailSenderInitializerListener.LISTENER_NAME);
listener.dispose();
}
@Test
public void sendTextMail() throws Exception
{
// Step 1: Create a JavaMail Session
Session session = Session.getInstance(this.configuration.getAllProperties());
// Step 2: Create the Message to send
MimeMessage message = new MimeMessage(session);
message.setSubject("subject");
message.setRecipient(RecipientType.TO, new InternetAddress("john@doe.com"));
// Step 3: Add the Message Body
Multipart multipart = new MimeMultipart("mixed");
// Add text in the body
multipart.addBodyPart(this.defaultBodyPartFactory.create("some text here",
Collections.<String, Object>singletonMap("mimetype", "text/plain")));
message.setContent(multipart);
// We also test using some default BCC addresses from configuration in this test
this.configuration.setBCCAddresses(Arrays.asList("bcc1@doe.com", "bcc2@doe.com"));
// Ensure we do not reuse the same message identifier for multiple similar messages in this test
MimeMessage message2 = new MimeMessage(message);
message2.saveChanges();
MimeMessage message3 = new MimeMessage(message);
message3.saveChanges();
// Step 4: Send the mail and wait for it to be sent
// Send 3 mails (3 times the same mail) to verify we can send several emails at once.
MailListener memoryMailListener = this.componentManager.getInstance(MailListener.class, "memory");
this.sender.sendAsynchronously(Arrays.asList(message, message2, message3), session, memoryMailListener);
// Note: we don't test status reporting from the listener since this is already tested in the
// ScriptingIntegrationTest test class.
// Verify that the mails have been received (wait maximum 30 seconds).
this.mail.waitForIncomingEmail(30000L, 3);
MimeMessage[] messages = this.mail.getReceivedMessages();
// Note: we're receiving 9 messages since we sent 3 with 3 recipients (2 BCC and 1 to)!
assertEquals(9, messages.length);
// Assert the email parts that are the same for all mails
assertEquals("subject", messages[0].getHeader("Subject", null));
assertEquals(1, ((MimeMultipart) messages[0].getContent()).getCount());
BodyPart textBodyPart = ((MimeMultipart) messages[0].getContent()).getBodyPart(0);
assertEquals("text/plain", textBodyPart.getHeader("Content-Type")[0]);
assertEquals("some text here", textBodyPart.getContent());
assertEquals("john@doe.com", messages[0].getHeader("To", null));
// Note: We cannot assert that the BCC worked since by definition BCC information are not visible in received
// messages ;) But we checked that we received 9 emails above so that's good enough.
}
@Test
public void sendHTMLAndCalendarInvitationMail() throws Exception
{
// Step 1: Create a JavaMail Session
Session session = Session.getInstance(this.configuration.getAllProperties());
// Step 2: Create the Message to send
MimeMessage message = new MimeMessage(session);
message.setSubject("subject");
message.setRecipient(RecipientType.TO, new InternetAddress("john@doe.com"));
// Step 3: Add the Message Body
Multipart multipart = new MimeMultipart("alternative");
// Add an HTML body part
multipart.addBodyPart(this.htmlBodyPartFactory.create(
"<font size=\"\\\"2\\\"\">simple meeting invitation</font>", Collections.<String, Object>emptyMap()));
// Add the Calendar invitation body part
String calendarContent = "BEGIN:VCALENDAR\r\n"
+ "METHOD:REQUEST\r\n"
+ "PRODID: Meeting\r\n"
+ "VERSION:2.0\r\n"
+ "BEGIN:VEVENT\r\n"
+ "DTSTAMP:20140616T164100\r\n"
+ "DTSTART:20140616T164100\r\n"
+ "DTEND:20140616T194100\r\n"
+ "SUMMARY:test request\r\n"
+ "UID:324\r\n"
+ "ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:MAILTO:john@doe.com\r\n"
+ "ORGANIZER:MAILTO:john@doe.com\r\n"
+ "LOCATION:on the net\r\n"
+ "DESCRIPTION:learn some stuff\r\n"
+ "SEQUENCE:0\r\n"
+ "PRIORITY:5\r\n"
+ "CLASS:PUBLIC\r\n"
+ "STATUS:CONFIRMED\r\n"
+ "TRANSP:OPAQUE\r\n"
+ "BEGIN:VALARM\r\n"
+ "ACTION:DISPLAY\r\n"
+ "DESCRIPTION:REMINDER\r\n"
+ "TRIGGER;RELATED=START:-PT00H15M00S\r\n"
+ "END:VALARM\r\n"
+ "END:VEVENT\r\n"
+ "END:VCALENDAR";
Map<String, Object> parameters = new HashMap<>();
parameters.put("mimetype", "text/calendar;method=CANCEL");
parameters.put("headers", Collections.singletonMap("Content-Class", "urn:content-classes:calendarmessage"));
multipart.addBodyPart(this.defaultBodyPartFactory.create(calendarContent, parameters));
message.setContent(multipart);
// Step 4: Send the mail and wait for it to be sent
this.sender.sendAsynchronously(Arrays.asList(message), session, null);
// Verify that the mail has been received (wait maximum 30 seconds).
this.mail.waitForIncomingEmail(30000L, 1);
MimeMessage[] messages = this.mail.getReceivedMessages();
assertEquals("subject", messages[0].getHeader("Subject", null));
assertEquals("john@doe.com", messages[0].getHeader("To", null));
assertEquals(2, ((MimeMultipart) messages[0].getContent()).getCount());
BodyPart htmlBodyPart = ((MimeMultipart) messages[0].getContent()).getBodyPart(0);
assertEquals("text/html; charset=UTF-8", htmlBodyPart.getHeader("Content-Type")[0]);
assertEquals("<font size=\"\\\"2\\\"\">simple meeting invitation</font>", htmlBodyPart.getContent());
BodyPart calendarBodyPart = ((MimeMultipart) messages[0].getContent()).getBodyPart(1);
assertEquals("text/calendar;method=CANCEL", calendarBodyPart.getHeader("Content-Type")[0]);
InputStream is = (InputStream) calendarBodyPart.getContent();
assertEquals(calendarContent, IOUtils.toString(is));
}
@Test
public void sendMailWithCustomMessageId() throws Exception
{
Session session = Session.getInstance(this.configuration.getAllProperties());
MimeMessage message = new MimeMessage(session) {
@Override
protected void updateMessageID() throws MessagingException
{
if (getMessageID() == null) {
super.updateMessageID();
}
}
};
message.setRecipient(RecipientType.TO, new InternetAddress("john@doe.com"));
message.setText("Test message Id support");
message.setHeader("Message-ID", "<custom@domain>");
message.setSubject("subject");
MailListener memoryMailListener = this.componentManager.getInstance(MailListener.class, "memory");
this.sender.sendAsynchronously(Arrays.asList(message), session, memoryMailListener);
// Verify that the mails have been received (wait maximum 30 seconds).
this.mail.waitForIncomingEmail(30000L, 1);
MimeMessage[] messages = this.mail.getReceivedMessages();
assertEquals("<custom@domain>", messages[0].getMessageID());
}
}