From 86da5ecc60200ddd4d4436ce8ba585863ac65f31 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 10 Jan 2015 23:24:28 +0100 Subject: [PATCH 01/55] initial commit --- .gitignore | 10 ++++ pom.xml | 47 +++++++++++++++++++ .../mercury/MercuryApplication.java | 16 +++++++ src/main/resources/application.properties | 0 .../mercury/MercuryApplicationTests.java | 18 +++++++ 5 files changed, 91 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4afb5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Intellij IDEA +.idea/ +*.iml +*.iws + +# Mac +.DS_Store + +# Maven +target/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..e0d93f9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + com.maciejwalkowiak + mercury + 0.0.1-SNAPSHOT + jar + + Hermes Messenger + + + org.springframework.boot + spring-boot-starter-parent + 1.2.1.RELEASE + + + + + UTF-8 + mercury.MercuryApplication + 1.7 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java b/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java new file mode 100644 index 0000000..5de7292 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java @@ -0,0 +1,16 @@ +package com.maciejwalkowiak.mercury; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@EnableAutoConfiguration +public class MercuryApplication { + + public static void main(String[] args) { + SpringApplication.run(MercuryApplication.class, args); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..e69de29 diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java new file mode 100644 index 0000000..c0a0f09 --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -0,0 +1,18 @@ +package com.maciejwalkowiak.mercury; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringApplicationConfiguration(classes = MercuryApplication.class) +@WebAppConfiguration +public class MercuryApplicationTests { + + @Test + public void contextLoads() { + } + +} From b903966d157e9ef2a7c18cb85a216353bdcc8624 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 11 Jan 2015 00:30:23 +0100 Subject: [PATCH 02/55] - sending email with Java Mail --- pom.xml | 11 ++++- .../mercury/MercuryApplication.java | 2 +- .../mercury/core/CoreConfiguration.java | 46 +++++++++++++++++ .../mercury/core/Listener.java | 12 +++++ .../mercury/core/MercuryException.java | 9 ++++ .../mercury/core/MercuryMessage.java | 28 +++++++++++ .../mercury/core/Messenger.java | 5 ++ .../mercury/core/MessengerImpl.java | 22 +++++++++ .../maciejwalkowiak/mercury/core/Request.java | 4 ++ .../mercury/mail/MailConfiguration.java | 12 +++++ .../mercury/mail/MailController.java | 29 +++++++++++ .../mercury/mail/MailListener.java | 31 ++++++++++++ .../mercury/mail/SendMailRequest.java | 49 +++++++++++++++++++ src/main/resources/application.properties | 6 +++ .../mercury/mail/MailListenerTest.java | 21 ++++++++ 15 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Listener.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Request.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java diff --git a/pom.xml b/pom.xml index e0d93f9..f5fa66c 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ UTF-8 mercury.MercuryApplication - 1.7 + 1.8 @@ -28,6 +28,15 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-mail + + + net.engio + mbassador + 1.2.0 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java b/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java index 5de7292..5107145 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java +++ b/src/main/java/com/maciejwalkowiak/mercury/MercuryApplication.java @@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration; @Configuration -@ComponentScan +@ComponentScan(includeFilters = @ComponentScan.Filter(Configuration.class), useDefaultFilters = false) @EnableAutoConfiguration public class MercuryApplication { diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java new file mode 100644 index 0000000..49e81db --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -0,0 +1,46 @@ +package com.maciejwalkowiak.mercury.core; + +import net.engio.mbassy.bus.MBassador; +import net.engio.mbassy.bus.config.BusConfiguration; +import net.engio.mbassy.bus.config.Feature; +import net.engio.mbassy.bus.error.IPublicationErrorHandler; +import net.engio.mbassy.bus.error.PublicationError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@ComponentScan +class CoreConfiguration { + @Autowired + @Listener + private List listeners; + + @Bean + MBassador bus() { + MBassador bus = new MBassador<>(new BusConfiguration() + .addFeature(Feature.SyncPubSub.Default()) + .addFeature(Feature.AsynchronousHandlerInvocation.Default()) + .addFeature(Feature.AsynchronousMessageDispatch.Default())); + + bus.addErrorHandler(new Slf4jPublicationErrorHandler()); + + listeners.forEach(bus::subscribe); + + return bus; + } +} + +class Slf4jPublicationErrorHandler implements IPublicationErrorHandler { + private static final Logger LOG = LoggerFactory.getLogger(Slf4jPublicationErrorHandler.class); + + @Override + public void handleError(PublicationError error) { + LOG.error("Publication failed: " + error.getMessage(), error.getCause()); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java b/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java new file mode 100644 index 0000000..43feb44 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java @@ -0,0 +1,12 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.beans.factory.annotation.Qualifier; + +import java.lang.annotation.*; + +@Target({ElementType.FIELD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Qualifier +public @interface Listener { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java new file mode 100644 index 0000000..907fae6 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java @@ -0,0 +1,9 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.mail.MailException; + +public class MercuryException extends RuntimeException { + public MercuryException(MailException e) { + super(e); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java new file mode 100644 index 0000000..6a03894 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -0,0 +1,28 @@ +package com.maciejwalkowiak.mercury.core; + +public class MercuryMessage { + public enum Status { + QUEUED, + SENT + } + + private final Status status; + private final Request request; + + private MercuryMessage(Status status, Request request) { + this.status = status; + this.request = request; + } + + public static MercuryMessage queued(Request request) { + return new MercuryMessage(Status.QUEUED, request); + } + + public Status getStatus() { + return status; + } + + public Request getRequest() { + return request; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java new file mode 100644 index 0000000..c276610 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java @@ -0,0 +1,5 @@ +package com.maciejwalkowiak.mercury.core; + +public interface Messenger { + MercuryMessage publish(Request request); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java new file mode 100644 index 0000000..37968da --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -0,0 +1,22 @@ +package com.maciejwalkowiak.mercury.core; + +import net.engio.mbassy.bus.MBassador; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +class MessengerImpl implements Messenger { + private final MBassador bus; + + @Autowired + MessengerImpl(MBassador bus) { + this.bus = bus; + } + + @Override + public MercuryMessage publish(Request request) { + bus.publishAsync(request); + + return MercuryMessage.queued(request); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java new file mode 100644 index 0000000..aadbdfe --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java @@ -0,0 +1,4 @@ +package com.maciejwalkowiak.mercury.core; + +public class Request { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java new file mode 100644 index 0000000..df4100c --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java @@ -0,0 +1,12 @@ +package com.maciejwalkowiak.mercury.mail; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +//@ConditionalOnBean(MailProperties.class) +@ComponentScan +class MailConfiguration { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java new file mode 100644 index 0000000..6dd7932 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -0,0 +1,29 @@ +package com.maciejwalkowiak.mercury.mail; + +import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.Messenger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/mail") +class MailController { + private static final Logger LOG = LoggerFactory.getLogger(MailController.class); + + private final Messenger messenger; + + @Autowired + MailController(Messenger messenger) { + this.messenger = messenger; + } + + @RequestMapping(method = RequestMethod.POST) + public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { + return messenger.publish(sendMailRequest); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java new file mode 100644 index 0000000..69a62d3 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java @@ -0,0 +1,31 @@ +package com.maciejwalkowiak.mercury.mail; + +import com.maciejwalkowiak.mercury.core.Listener; +import net.engio.mbassy.listener.Handler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.MailSender; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +@Component +@Listener +class MailListener { + private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); + private final MailSender mailSender; + + @Autowired + MailListener(MailSender javaMailSender) { + this.mailSender = javaMailSender; + } + + @Handler + void sendMail(SendMailRequest sendMailRequest) { + Assert.notNull(sendMailRequest); + + LOG.info("Received send mail request: {}", sendMailRequest); + + mailSender.send(sendMailRequest.toMailMessage()); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java new file mode 100644 index 0000000..c448327 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java @@ -0,0 +1,49 @@ +package com.maciejwalkowiak.mercury.mail; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.maciejwalkowiak.mercury.core.Request; +import org.springframework.mail.SimpleMailMessage; + +class SendMailRequest extends Request { + private final String to; + private final String content; + private final String subject; + + @JsonCreator + public SendMailRequest(@JsonProperty("to") String to, @JsonProperty("content") String content, + @JsonProperty("subject") String subject) { + this.to = to; + this.content = content; + this.subject = subject; + } + + public String getTo() { + return to; + } + + public String getContent() { + return content; + } + + public String getSubject() { + return subject; + } + + public SimpleMailMessage toMailMessage() { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setTo(to); + msg.setText(content); + + return msg; + } + + @Override + public String toString() { + return "SendMailRequest{" + + "to='" + to + '\'' + + ", content='" + content + '\'' + + ", subject='" + subject + '\'' + + '}'; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index e69de29..c0b073f 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -0,0 +1,6 @@ +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=* +spring.mail.password=* +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java new file mode 100644 index 0000000..1d6232e --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java @@ -0,0 +1,21 @@ +package com.maciejwalkowiak.mercury.mail; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mail.MailSender; + +import static org.mockito.Mockito.mock; + +public class MailListenerTest { + private MailListener mailListener; + + @Before + public void setUp() { + mailListener = new MailListener(mock(MailSender.class)); + } + + @Test(expected = IllegalArgumentException.class) + public void should_throw_exception_for_null_message() { + mailListener.sendMail(null); + } +} \ No newline at end of file From ed74e73e2bc46c10de8b42425afcead528b2cb8d Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 11 Jan 2015 02:17:49 +0100 Subject: [PATCH 03/55] - mail listener received mercury message so it can update it's status after completion --- pom.xml | 4 ++++ .../mercury/core/CoreConfiguration.java | 4 ++-- .../mercury/core/MercuryException.java | 9 --------- .../mercury/core/MercuryMessage.java | 20 +++++++++++++------ .../core/MercuryMessageRepository.java | 6 ++++++ .../mercury/core/Messenger.java | 2 +- .../mercury/core/MessengerImpl.java | 14 ++++++++----- .../maciejwalkowiak/mercury/core/Request.java | 3 ++- .../mercury/mail/MailController.java | 2 +- .../mercury/mail/MailListener.java | 10 +++++----- .../mercury/mail/SendMailMercuryMessage.java | 9 +++++++++ 11 files changed, 53 insertions(+), 30 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java diff --git a/pom.xml b/pom.xml index f5fa66c..0035c17 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,10 @@ org.springframework.boot spring-boot-starter-mail + + org.springframework.boot + spring-boot-starter-data-mongodb + net.engio mbassador diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 49e81db..060f646 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -22,8 +22,8 @@ class CoreConfiguration { private List listeners; @Bean - MBassador bus() { - MBassador bus = new MBassador<>(new BusConfiguration() + MBassador bus() { + MBassador bus = new MBassador<>(new BusConfiguration() .addFeature(Feature.SyncPubSub.Default()) .addFeature(Feature.AsynchronousHandlerInvocation.Default()) .addFeature(Feature.AsynchronousMessageDispatch.Default())); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java deleted file mode 100644 index 907fae6..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.mail.MailException; - -public class MercuryException extends RuntimeException { - public MercuryException(MailException e) { - super(e); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index 6a03894..95a6ed9 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -1,28 +1,36 @@ package com.maciejwalkowiak.mercury.core; -public class MercuryMessage { +import org.springframework.data.annotation.Id; + +public class MercuryMessage { public enum Status { QUEUED, SENT } + @Id + private String id; private final Status status; - private final Request request; + private final T request; - private MercuryMessage(Status status, Request request) { + protected MercuryMessage(Status status, T request) { this.status = status; this.request = request; } - public static MercuryMessage queued(Request request) { - return new MercuryMessage(Status.QUEUED, request); + public static MercuryMessage queued(T request) { + return new MercuryMessage<>(Status.QUEUED, request); } public Status getStatus() { return status; } - public Request getRequest() { + public T getRequest() { return request; } + + public String getId() { + return id; + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java new file mode 100644 index 0000000..6480137 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java @@ -0,0 +1,6 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.data.mongodb.repository.MongoRepository; + +interface MercuryMessageRepository extends MongoRepository { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java index c276610..f186cb0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java @@ -1,5 +1,5 @@ package com.maciejwalkowiak.mercury.core; public interface Messenger { - MercuryMessage publish(Request request); + MercuryMessage publish(MercuryMessage mercuryMessage); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index 37968da..08ae289 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -6,17 +6,21 @@ @Component class MessengerImpl implements Messenger { - private final MBassador bus; + private final MBassador bus; + private final MercuryMessageRepository mercuryMessageRepository; @Autowired - MessengerImpl(MBassador bus) { + MessengerImpl(MBassador bus, MercuryMessageRepository mercuryMessageRepository) { this.bus = bus; + this.mercuryMessageRepository = mercuryMessageRepository; } @Override - public MercuryMessage publish(Request request) { - bus.publishAsync(request); + public MercuryMessage publish(MercuryMessage mercuryMessage) { + mercuryMessageRepository.save(mercuryMessage); - return MercuryMessage.queued(request); + bus.publishAsync(mercuryMessage); + + return mercuryMessage; } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java index aadbdfe..b664329 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java @@ -1,4 +1,5 @@ package com.maciejwalkowiak.mercury.core; -public class Request { +public abstract class Request { + } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java index 6dd7932..120a57e 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -24,6 +24,6 @@ class MailController { @RequestMapping(method = RequestMethod.POST) public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { - return messenger.publish(sendMailRequest); + return messenger.publish(new SendMailMercuryMessage(MercuryMessage.Status.QUEUED, sendMailRequest)); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java index 69a62d3..652f596 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java @@ -11,7 +11,7 @@ @Component @Listener -class MailListener { +public class MailListener { private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); private final MailSender mailSender; @@ -21,11 +21,11 @@ class MailListener { } @Handler - void sendMail(SendMailRequest sendMailRequest) { - Assert.notNull(sendMailRequest); + public void sendMail(SendMailMercuryMessage mercuryMessage) { + Assert.notNull(mercuryMessage); - LOG.info("Received send mail request: {}", sendMailRequest); + LOG.info("Received send mail request: {}", mercuryMessage.getRequest()); - mailSender.send(sendMailRequest.toMailMessage()); + mailSender.send(mercuryMessage.getRequest().toMailMessage()); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java new file mode 100644 index 0000000..e5405b5 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java @@ -0,0 +1,9 @@ +package com.maciejwalkowiak.mercury.mail; + +import com.maciejwalkowiak.mercury.core.MercuryMessage; + +class SendMailMercuryMessage extends MercuryMessage { + SendMailMercuryMessage(Status status, SendMailRequest request) { + super(status, request); + } +} From 21eba830d6c0af07c7117d9d72017ca8ec668b93 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 11 Jan 2015 02:35:00 +0100 Subject: [PATCH 04/55] - replaced MBassador with plain Spring application events --- pom.xml | 5 --- .../mercury/core/CoreConfiguration.java | 40 ++++--------------- .../mercury/core/Listener.java | 12 ------ .../mercury/core/MercuryEvent.java | 16 ++++++++ .../mercury/core/Messenger.java | 2 +- .../mercury/core/MessengerImpl.java | 18 +++++---- .../mercury/mail/MailController.java | 2 +- .../mercury/mail/MailListener.java | 17 ++++---- .../mercury/mail/SendMailMercuryMessage.java | 9 ----- .../mercury/mail/MailListenerTest.java | 2 +- 10 files changed, 44 insertions(+), 79 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Listener.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java diff --git a/pom.xml b/pom.xml index 0035c17..5107c07 100644 --- a/pom.xml +++ b/pom.xml @@ -36,11 +36,6 @@ org.springframework.boot spring-boot-starter-data-mongodb - - net.engio - mbassador - 1.2.0 - org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 060f646..edf90aa 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -1,46 +1,20 @@ package com.maciejwalkowiak.mercury.core; -import net.engio.mbassy.bus.MBassador; -import net.engio.mbassy.bus.config.BusConfiguration; -import net.engio.mbassy.bus.config.Feature; -import net.engio.mbassy.bus.error.IPublicationErrorHandler; -import net.engio.mbassy.bus.error.PublicationError; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; - -import java.util.List; +import org.springframework.context.event.ApplicationEventMulticaster; +import org.springframework.context.event.SimpleApplicationEventMulticaster; +import org.springframework.core.task.SimpleAsyncTaskExecutor; @Configuration @ComponentScan class CoreConfiguration { - @Autowired - @Listener - private List listeners; @Bean - MBassador bus() { - MBassador bus = new MBassador<>(new BusConfiguration() - .addFeature(Feature.SyncPubSub.Default()) - .addFeature(Feature.AsynchronousHandlerInvocation.Default()) - .addFeature(Feature.AsynchronousMessageDispatch.Default())); - - bus.addErrorHandler(new Slf4jPublicationErrorHandler()); - - listeners.forEach(bus::subscribe); - - return bus; - } -} - -class Slf4jPublicationErrorHandler implements IPublicationErrorHandler { - private static final Logger LOG = LoggerFactory.getLogger(Slf4jPublicationErrorHandler.class); - - @Override - public void handleError(PublicationError error) { - LOG.error("Publication failed: " + error.getMessage(), error.getCause()); + ApplicationEventMulticaster applicationEventMulticaster() { + SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); + eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); + return eventMulticaster; } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java b/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java deleted file mode 100644 index 43feb44..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Listener.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.beans.factory.annotation.Qualifier; - -import java.lang.annotation.*; - -@Target({ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Qualifier -public @interface Listener { -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java new file mode 100644 index 0000000..872bcd7 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java @@ -0,0 +1,16 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.context.ApplicationEvent; + +public class MercuryEvent extends ApplicationEvent { + private MercuryMessage mercuryMessage; + + public MercuryEvent(MercuryMessage mercuryMessage) { + super(MercuryEvent.class); + this.mercuryMessage = mercuryMessage; + } + + public T getRequest() { + return mercuryMessage.getRequest(); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java index f186cb0..c276610 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java @@ -1,5 +1,5 @@ package com.maciejwalkowiak.mercury.core; public interface Messenger { - MercuryMessage publish(MercuryMessage mercuryMessage); + MercuryMessage publish(Request request); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index 08ae289..a5e3bbc 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -1,26 +1,28 @@ package com.maciejwalkowiak.mercury.core; -import net.engio.mbassy.bus.MBassador; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; @Component class MessengerImpl implements Messenger { - private final MBassador bus; + private final ApplicationEventPublisher applicationEventPublisher; private final MercuryMessageRepository mercuryMessageRepository; @Autowired - MessengerImpl(MBassador bus, MercuryMessageRepository mercuryMessageRepository) { - this.bus = bus; + MessengerImpl(ApplicationEventPublisher applicationEventPublisher, MercuryMessageRepository mercuryMessageRepository) { + this.applicationEventPublisher = applicationEventPublisher; + this.mercuryMessageRepository = mercuryMessageRepository; } @Override - public MercuryMessage publish(MercuryMessage mercuryMessage) { - mercuryMessageRepository.save(mercuryMessage); + public MercuryMessage publish(Request request) { + MercuryMessage message = MercuryMessage.queued(request); + mercuryMessageRepository.save(message); - bus.publishAsync(mercuryMessage); + applicationEventPublisher.publishEvent(new MercuryEvent<>(message)); - return mercuryMessage; + return message; } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java index 120a57e..6dd7932 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -24,6 +24,6 @@ class MailController { @RequestMapping(method = RequestMethod.POST) public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { - return messenger.publish(new SendMailMercuryMessage(MercuryMessage.Status.QUEUED, sendMailRequest)); + return messenger.publish(sendMailRequest); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java index 652f596..b94e212 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java @@ -1,17 +1,16 @@ package com.maciejwalkowiak.mercury.mail; -import com.maciejwalkowiak.mercury.core.Listener; -import net.engio.mbassy.listener.Handler; +import com.maciejwalkowiak.mercury.core.MercuryEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; import org.springframework.mail.MailSender; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @Component -@Listener -public class MailListener { +public class MailListener implements ApplicationListener>{ private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); private final MailSender mailSender; @@ -20,12 +19,12 @@ public class MailListener { this.mailSender = javaMailSender; } - @Handler - public void sendMail(SendMailMercuryMessage mercuryMessage) { - Assert.notNull(mercuryMessage); + @Override + public void onApplicationEvent(MercuryEvent mercuryEvent) { + Assert.notNull(mercuryEvent); - LOG.info("Received send mail request: {}", mercuryMessage.getRequest()); + LOG.info("Received send mail request: {}", mercuryEvent.getRequest()); - mailSender.send(mercuryMessage.getRequest().toMailMessage()); + mailSender.send(mercuryEvent.getRequest().toMailMessage()); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java deleted file mode 100644 index e5405b5..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailMercuryMessage.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.maciejwalkowiak.mercury.mail; - -import com.maciejwalkowiak.mercury.core.MercuryMessage; - -class SendMailMercuryMessage extends MercuryMessage { - SendMailMercuryMessage(Status status, SendMailRequest request) { - super(status, request); - } -} diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java index 1d6232e..7d0c6fd 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java @@ -16,6 +16,6 @@ public void setUp() { @Test(expected = IllegalArgumentException.class) public void should_throw_exception_for_null_message() { - mailListener.sendMail(null); + mailListener.onApplicationEvent(null); } } \ No newline at end of file From 1fd3de45e3ed7bdfa13cd56025128b1b34337ac1 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 11 Jan 2015 03:19:23 +0100 Subject: [PATCH 05/55] changing message status after message is sent --- .../mercury/core/CoreConfiguration.java | 3 +++ .../mercury/core/MercuryEvent.java | 4 ++++ .../mercury/core/MercuryMessage.java | 8 +++++-- .../core/MercuryMessageController.java | 23 +++++++++++++++++++ .../mercury/core/MessageSentEvent.java | 16 +++++++++++++ .../core/MessageSentEventListener.java | 23 +++++++++++++++++++ .../mercury/core/MessengerImpl.java | 1 - .../mercury/mail/MailConfiguration.java | 5 ++-- .../mercury/mail/MailController.java | 4 ---- .../mercury/mail/MailListener.java | 8 ++++++- .../mercury/mail/MailListenerTest.java | 3 ++- 11 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index edf90aa..4b45e04 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -11,6 +11,9 @@ @ComponentScan class CoreConfiguration { + /** + * Provides asynchronous application event processing + */ @Bean ApplicationEventMulticaster applicationEventMulticaster() { SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java index 872bcd7..6ea09c7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java @@ -13,4 +13,8 @@ public MercuryEvent(MercuryMessage mercuryMessage) { public T getRequest() { return mercuryMessage.getRequest(); } + + public MercuryMessage getMessage() { + return mercuryMessage; + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index 95a6ed9..2c57407 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -10,10 +10,10 @@ public enum Status { @Id private String id; - private final Status status; + private Status status; private final T request; - protected MercuryMessage(Status status, T request) { + private MercuryMessage(Status status, T request) { this.status = status; this.request = request; } @@ -22,6 +22,10 @@ public static MercuryMessage queued(T request) { return new MercuryMessage<>(Status.QUEUED, request); } + public void sent() { + this.status = Status.SENT; + } + public Status getStatus() { return status; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java new file mode 100644 index 0000000..954f2da --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -0,0 +1,23 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/message") +class MercuryMessageController { + private final MercuryMessageRepository mercuryMessageRepository; + + @Autowired + MercuryMessageController(MercuryMessageRepository mercuryMessageRepository) { + this.mercuryMessageRepository = mercuryMessageRepository; + } + + @RequestMapping(value = "{id}", method = RequestMethod.GET) + MercuryMessage message(@PathVariable String id) { + return mercuryMessageRepository.findOne(id); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java new file mode 100644 index 0000000..4c4b767 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java @@ -0,0 +1,16 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.context.ApplicationEvent; + +public class MessageSentEvent extends ApplicationEvent { + private final MercuryMessage mercuryMessage; + + public MessageSentEvent(MercuryMessage mercuryMessage) { + super(MessageSentEvent.class); + this.mercuryMessage = mercuryMessage; + } + + public MercuryMessage getMercuryMessage() { + return mercuryMessage; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java new file mode 100644 index 0000000..35652e0 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java @@ -0,0 +1,23 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +@Component +class MessageSentEventListener implements ApplicationListener { + private final MercuryMessageRepository mercuryMessageRepository; + + @Autowired + MessageSentEventListener(MercuryMessageRepository mercuryMessageRepository) { + this.mercuryMessageRepository = mercuryMessageRepository; + } + + @Override + public void onApplicationEvent(MessageSentEvent event) { + MercuryMessage message = event.getMercuryMessage(); + message.sent(); + + mercuryMessageRepository.save(message); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index a5e3bbc..aac2f89 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -12,7 +12,6 @@ class MessengerImpl implements Messenger { @Autowired MessengerImpl(ApplicationEventPublisher applicationEventPublisher, MercuryMessageRepository mercuryMessageRepository) { this.applicationEventPublisher = applicationEventPublisher; - this.mercuryMessageRepository = mercuryMessageRepository; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java index df4100c..3b96865 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java @@ -1,12 +1,11 @@ package com.maciejwalkowiak.mercury.mail; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration -//@ConditionalOnBean(MailProperties.class) +@ConditionalOnProperty(name = "spring.mail.host") @ComponentScan class MailConfiguration { } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java index 6dd7932..adb3308 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -2,8 +2,6 @@ import com.maciejwalkowiak.mercury.core.MercuryMessage; import com.maciejwalkowiak.mercury.core.Messenger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +11,6 @@ @RestController @RequestMapping("/api/mail") class MailController { - private static final Logger LOG = LoggerFactory.getLogger(MailController.class); - private final Messenger messenger; @Autowired diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java index b94e212..f53d273 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java @@ -1,9 +1,11 @@ package com.maciejwalkowiak.mercury.mail; import com.maciejwalkowiak.mercury.core.MercuryEvent; +import com.maciejwalkowiak.mercury.core.MessageSentEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.mail.MailSender; import org.springframework.stereotype.Component; @@ -13,10 +15,12 @@ public class MailListener implements ApplicationListener>{ private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); private final MailSender mailSender; + private final ApplicationEventPublisher applicationEventPublisher; @Autowired - MailListener(MailSender javaMailSender) { + MailListener(MailSender javaMailSender, ApplicationEventPublisher applicationEventPublisher) { this.mailSender = javaMailSender; + this.applicationEventPublisher = applicationEventPublisher; } @Override @@ -26,5 +30,7 @@ public void onApplicationEvent(MercuryEvent mercuryEvent) { LOG.info("Received send mail request: {}", mercuryEvent.getRequest()); mailSender.send(mercuryEvent.getRequest().toMailMessage()); + + applicationEventPublisher.publishEvent(new MessageSentEvent<>(mercuryEvent.getMessage())); } } diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java index 7d0c6fd..b9ec53f 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java @@ -2,6 +2,7 @@ import org.junit.Before; import org.junit.Test; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.mail.MailSender; import static org.mockito.Mockito.mock; @@ -11,7 +12,7 @@ public class MailListenerTest { @Before public void setUp() { - mailListener = new MailListener(mock(MailSender.class)); + mailListener = new MailListener(mock(MailSender.class), mock(ApplicationEventPublisher.class)); } @Test(expected = IllegalArgumentException.class) From 87ffeb44c0ccedfce054ab655eb9dc61e61425d1 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sun, 11 Jan 2015 04:15:04 +0100 Subject: [PATCH 06/55] changing message status after message sending failed --- .../mercury/core/MercuryMessage.java | 17 +++++++++++- .../core/MercuryMessageController.java | 2 ++ .../mercury/core/MessageSentEvent.java | 16 ------------ .../core/MessageSentEventListener.java | 23 ---------------- .../mercury/core/Messenger.java | 2 ++ .../mercury/core/MessengerImpl.java | 26 ++++++++++++++----- .../mercury/mail/MailListener.java | 18 +++++++------ .../mercury/mail/MailListenerTest.java | 4 +-- 8 files changed, 51 insertions(+), 57 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index 2c57407..83cf438 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -1,16 +1,22 @@ package com.maciejwalkowiak.mercury.core; +import com.fasterxml.jackson.annotation.JsonView; import org.springframework.data.annotation.Id; public class MercuryMessage { public enum Status { QUEUED, - SENT + SENT, + FAILED } @Id + @JsonView(View.Summary.class) private String id; + @JsonView(View.Summary.class) private Status status; + @JsonView(View.Summary.class) + private String errorMesssage; private final T request; private MercuryMessage(Status status, T request) { @@ -26,6 +32,11 @@ public void sent() { this.status = Status.SENT; } + public void failed(String errorMessage) { + this.status = Status.FAILED; + this.errorMesssage = errorMessage; + } + public Status getStatus() { return status; } @@ -37,4 +48,8 @@ public T getRequest() { public String getId() { return id; } + + public static class View { + interface Summary {} + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index 954f2da..edcb913 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -1,5 +1,6 @@ package com.maciejwalkowiak.mercury.core; +import com.fasterxml.jackson.annotation.JsonView; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,6 +18,7 @@ class MercuryMessageController { } @RequestMapping(value = "{id}", method = RequestMethod.GET) + @JsonView(MercuryMessage.View.Summary.class) MercuryMessage message(@PathVariable String id) { return mercuryMessageRepository.findOne(id); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java deleted file mode 100644 index 4c4b767..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.context.ApplicationEvent; - -public class MessageSentEvent extends ApplicationEvent { - private final MercuryMessage mercuryMessage; - - public MessageSentEvent(MercuryMessage mercuryMessage) { - super(MessageSentEvent.class); - this.mercuryMessage = mercuryMessage; - } - - public MercuryMessage getMercuryMessage() { - return mercuryMessage; - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java deleted file mode 100644 index 35652e0..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessageSentEventListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -@Component -class MessageSentEventListener implements ApplicationListener { - private final MercuryMessageRepository mercuryMessageRepository; - - @Autowired - MessageSentEventListener(MercuryMessageRepository mercuryMessageRepository) { - this.mercuryMessageRepository = mercuryMessageRepository; - } - - @Override - public void onApplicationEvent(MessageSentEvent event) { - MercuryMessage message = event.getMercuryMessage(); - message.sent(); - - mercuryMessageRepository.save(message); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java index c276610..627e128 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java @@ -2,4 +2,6 @@ public interface Messenger { MercuryMessage publish(Request request); + void messageSent(MercuryMessage message); + void deliveryFailed(MercuryMessage message, String errorMessage); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index aac2f89..5440bad 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -6,22 +6,34 @@ @Component class MessengerImpl implements Messenger { - private final ApplicationEventPublisher applicationEventPublisher; - private final MercuryMessageRepository mercuryMessageRepository; + private final ApplicationEventPublisher eventPublisher; + private final MercuryMessageRepository repository; @Autowired - MessengerImpl(ApplicationEventPublisher applicationEventPublisher, MercuryMessageRepository mercuryMessageRepository) { - this.applicationEventPublisher = applicationEventPublisher; - this.mercuryMessageRepository = mercuryMessageRepository; + MessengerImpl(ApplicationEventPublisher eventPublisher, MercuryMessageRepository repository) { + this.eventPublisher = eventPublisher; + this.repository = repository; } @Override public MercuryMessage publish(Request request) { MercuryMessage message = MercuryMessage.queued(request); - mercuryMessageRepository.save(message); + repository.save(message); - applicationEventPublisher.publishEvent(new MercuryEvent<>(message)); + eventPublisher.publishEvent(new MercuryEvent<>(message)); return message; } + + @Override + public void messageSent(MercuryMessage message) { + message.sent(); + repository.save(message); + } + + @Override + public void deliveryFailed(MercuryMessage message, String errorMessage) { + message.failed(errorMessage); + repository.save(message); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java index f53d273..5bb8909 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java @@ -1,11 +1,10 @@ package com.maciejwalkowiak.mercury.mail; import com.maciejwalkowiak.mercury.core.MercuryEvent; -import com.maciejwalkowiak.mercury.core.MessageSentEvent; +import com.maciejwalkowiak.mercury.core.Messenger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationListener; import org.springframework.mail.MailSender; import org.springframework.stereotype.Component; @@ -15,12 +14,12 @@ public class MailListener implements ApplicationListener>{ private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); private final MailSender mailSender; - private final ApplicationEventPublisher applicationEventPublisher; + private final Messenger messenger; @Autowired - MailListener(MailSender javaMailSender, ApplicationEventPublisher applicationEventPublisher) { + MailListener(MailSender javaMailSender, Messenger messenger) { this.mailSender = javaMailSender; - this.applicationEventPublisher = applicationEventPublisher; + this.messenger = messenger; } @Override @@ -29,8 +28,11 @@ public void onApplicationEvent(MercuryEvent mercuryEvent) { LOG.info("Received send mail request: {}", mercuryEvent.getRequest()); - mailSender.send(mercuryEvent.getRequest().toMailMessage()); - - applicationEventPublisher.publishEvent(new MessageSentEvent<>(mercuryEvent.getMessage())); + try { + mailSender.send(mercuryEvent.getRequest().toMailMessage()); + messenger.messageSent(mercuryEvent.getMessage()); + } catch (Exception e) { + messenger.deliveryFailed(mercuryEvent.getMessage(), e.getMessage()); + } } } diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java index b9ec53f..407a578 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java @@ -1,8 +1,8 @@ package com.maciejwalkowiak.mercury.mail; +import com.maciejwalkowiak.mercury.core.Messenger; import org.junit.Before; import org.junit.Test; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.mail.MailSender; import static org.mockito.Mockito.mock; @@ -12,7 +12,7 @@ public class MailListenerTest { @Before public void setUp() { - mailListener = new MailListener(mock(MailSender.class), mock(ApplicationEventPublisher.class)); + mailListener = new MailListener(mock(MailSender.class), mock(Messenger.class)); } @Test(expected = IllegalArgumentException.class) From 29b26445aa8ba4dafb282ca809499d5c38c3be9a Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 00:57:07 +0100 Subject: [PATCH 07/55] HATEOAS based API controllers --- pom.xml | 8 ++++ .../mercury/core/MercuryMessage.java | 8 +++- .../core/MercuryMessageController.java | 18 +++++++- .../mercury/core/api/ApiController.java | 42 +++++++++++++++++++ .../mercury/core/api/ApiResource.java | 6 +++ .../mercury/core/api/BracketsLink.java | 9 ++++ .../mercury/core/api/HateoasController.java | 9 ++++ .../mercury/mail/MailController.java | 14 ++++++- .../mercury/mail/SendMailRequest.java | 4 +- 9 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java diff --git a/pom.xml b/pom.xml index 5107c07..c258454 100644 --- a/pom.xml +++ b/pom.xml @@ -32,10 +32,18 @@ org.springframework.boot spring-boot-starter-mail + + org.springframework.boot + spring-boot-starter-actuator + org.springframework.boot spring-boot-starter-data-mongodb + + org.springframework.hateoas + spring-hateoas + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index 83cf438..16a75d7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -17,7 +17,13 @@ public enum Status { private Status status; @JsonView(View.Summary.class) private String errorMesssage; - private final T request; + private T request; + + /** + * Public no-arg controller required by Spring Hateoas + */ + public MercuryMessage() { + } private MercuryMessage(Status status, T request) { this.status = status; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index edcb913..c4355bb 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -1,15 +1,24 @@ package com.maciejwalkowiak.mercury.core; import com.fasterxml.jackson.annotation.JsonView; +import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.mvc.BasicLinkBuilder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + @RestController -@RequestMapping("/api/message") -class MercuryMessageController { +@RequestMapping("/api/message/") +class MercuryMessageController implements HateoasController { private final MercuryMessageRepository mercuryMessageRepository; @Autowired @@ -22,4 +31,9 @@ class MercuryMessageController { MercuryMessage message(@PathVariable String id) { return mercuryMessageRepository.findOne(id); } + + @Override + public List links() { + return Arrays.asList(linkTo(methodOn(MercuryMessageController.class).message("{id}")).withRel("message")); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java new file mode 100644 index 0000000..620c756 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -0,0 +1,42 @@ +package com.maciejwalkowiak.mercury.core.api; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +@Controller +@RequestMapping(value = "/api") +class ApiController { + private final Optional> controllers; + + @Autowired + ApiController(Optional> controllers) { + this.controllers = controllers; + } + + @RequestMapping(method = RequestMethod.GET) + public HttpEntity links() { + ApiResource apiResource = new ApiResource(); + apiResource.add(linkTo(ApiController.class).withSelfRel()); + + if (controllers.isPresent()) { + controllers.get().forEach(c -> + apiResource.add( + c.links().stream() + .map(link -> new BracketsLink(link.getHref(), link.getRel())) + .collect(Collectors.toList()) + ) + ); + } + + return new HttpEntity<>(apiResource); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java new file mode 100644 index 0000000..5e6bf98 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java @@ -0,0 +1,6 @@ +package com.maciejwalkowiak.mercury.core.api; + +import org.springframework.hateoas.ResourceSupport; + +class ApiResource extends ResourceSupport { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java new file mode 100644 index 0000000..736804d --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java @@ -0,0 +1,9 @@ +package com.maciejwalkowiak.mercury.core.api; + +import org.springframework.hateoas.Link; + +class BracketsLink extends Link { + public BracketsLink(String href, String rel) { + super(href.replaceAll("%7B", "{").replaceAll("%7D", "}"), rel); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java new file mode 100644 index 0000000..476b087 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java @@ -0,0 +1,9 @@ +package com.maciejwalkowiak.mercury.core.api; + +import org.springframework.hateoas.Link; + +import java.util.List; + +public interface HateoasController { + List links(); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java index adb3308..e1752b7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -2,15 +2,22 @@ import com.maciejwalkowiak.mercury.core.MercuryMessage; import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + @RestController @RequestMapping("/api/mail") -class MailController { +class MailController implements HateoasController { private final Messenger messenger; @Autowired @@ -22,4 +29,9 @@ class MailController { public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { return messenger.publish(sendMailRequest); } + + @Override + public List links() { + return Arrays.asList(linkTo(MailController.class).withRel("mail")); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java index c448327..d9f66c4 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java @@ -11,7 +11,8 @@ class SendMailRequest extends Request { private final String subject; @JsonCreator - public SendMailRequest(@JsonProperty("to") String to, @JsonProperty("content") String content, + public SendMailRequest(@JsonProperty("to") String to, + @JsonProperty("content") String content, @JsonProperty("subject") String subject) { this.to = to; this.content = content; @@ -33,6 +34,7 @@ public String getSubject() { public SimpleMailMessage toMailMessage() { SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo(to); + msg.setSubject(subject); msg.setText(content); return msg; From 808588e2c766694d2f59a443d65c6f89d5e1dd42 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 01:35:18 +0100 Subject: [PATCH 08/55] Replaced Spring ApplicationEvent based messaging with Reactor --- pom.xml | 8 ++++ .../mercury/core/CoreConfiguration.java | 15 +++----- .../mercury/core/MercuryEvent.java | 20 ---------- .../mercury/core/MercuryMessage.java | 4 +- .../core/MercuryMessageController.java | 1 - .../mercury/core/MessengerImpl.java | 14 ++++--- .../mercury/core/QueueNameObtainer.java | 14 +++++++ .../maciejwalkowiak/mercury/core/Request.java | 1 - .../mercury/mail/MailConfiguration.java | 22 +++++++++++ .../mercury/mail/MailConsumer.java | 37 ++++++++++++++++++ .../mercury/mail/MailController.java | 2 + .../mercury/mail/MailListener.java | 38 ------------------- .../mercury/mail/MailListenerTest.java | 22 ----------- 13 files changed, 99 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java delete mode 100644 src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java diff --git a/pom.xml b/pom.xml index c258454..a95a824 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,14 @@ org.springframework.hateoas spring-hateoas + + org.projectreactor.spring + reactor-spring-context + + + com.fasterxml.jackson.core + jackson-databind + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 4b45e04..3457459 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -3,21 +3,16 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.ApplicationEventMulticaster; -import org.springframework.context.event.SimpleApplicationEventMulticaster; -import org.springframework.core.task.SimpleAsyncTaskExecutor; +import reactor.core.Environment; +import reactor.core.Reactor; +import reactor.core.spec.Reactors; @Configuration @ComponentScan class CoreConfiguration { - /** - * Provides asynchronous application event processing - */ @Bean - ApplicationEventMulticaster applicationEventMulticaster() { - SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(); - eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor()); - return eventMulticaster; + public Reactor rootReactor(Environment env) { + return Reactors.reactor().env(env).get(); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java deleted file mode 100644 index 6ea09c7..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.context.ApplicationEvent; - -public class MercuryEvent extends ApplicationEvent { - private MercuryMessage mercuryMessage; - - public MercuryEvent(MercuryMessage mercuryMessage) { - super(MercuryEvent.class); - this.mercuryMessage = mercuryMessage; - } - - public T getRequest() { - return mercuryMessage.getRequest(); - } - - public MercuryMessage getMessage() { - return mercuryMessage; - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index 16a75d7..ffda253 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -1,8 +1,10 @@ package com.maciejwalkowiak.mercury.core; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonView; import org.springframework.data.annotation.Id; +@JsonInclude(JsonInclude.Include.NON_NULL) public class MercuryMessage { public enum Status { QUEUED, @@ -56,6 +58,6 @@ public String getId() { } public static class View { - interface Summary {} + public interface Summary {} } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index c4355bb..067d642 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -4,7 +4,6 @@ import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; -import org.springframework.hateoas.mvc.BasicLinkBuilder; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index 5440bad..acaa844 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -1,26 +1,28 @@ package com.maciejwalkowiak.mercury.core; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Component; +import reactor.core.Reactor; +import reactor.event.Event; @Component class MessengerImpl implements Messenger { - private final ApplicationEventPublisher eventPublisher; private final MercuryMessageRepository repository; + private final Reactor rootReactor; + private final QueueNameObtainer queueNameObtainer; @Autowired - MessengerImpl(ApplicationEventPublisher eventPublisher, MercuryMessageRepository repository) { - this.eventPublisher = eventPublisher; + MessengerImpl(MercuryMessageRepository repository, Reactor rootReactor, QueueNameObtainer queueNameObtainer) { this.repository = repository; + this.rootReactor = rootReactor; + this.queueNameObtainer = queueNameObtainer; } @Override public MercuryMessage publish(Request request) { MercuryMessage message = MercuryMessage.queued(request); repository.save(message); - - eventPublisher.publishEvent(new MercuryEvent<>(message)); + rootReactor.notify(queueNameObtainer.getQueueName(request), Event.wrap(message)); return message; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java b/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java new file mode 100644 index 0000000..b2b2f57 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java @@ -0,0 +1,14 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.stereotype.Component; + +@Component +public class QueueNameObtainer { + public String getQueueName(Class clazz) { + return clazz.getName(); + } + + public String getQueueName(Request request) { + return request.getClass().getName(); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java index b664329..d6f0d05 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java @@ -1,5 +1,4 @@ package com.maciejwalkowiak.mercury.core; public abstract class Request { - } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java index 3b96865..f08c6f0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java @@ -1,11 +1,33 @@ package com.maciejwalkowiak.mercury.mail; +import com.maciejwalkowiak.mercury.core.QueueNameObtainer; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import reactor.core.Reactor; + +import javax.annotation.PostConstruct; + +import java.util.Queue; + +import static reactor.event.selector.Selectors.$; @Configuration @ConditionalOnProperty(name = "spring.mail.host") @ComponentScan class MailConfiguration { + @Autowired + private Reactor reactor; + + @Autowired + private MailConsumer mailConsumer; + + @Autowired + private QueueNameObtainer queueNameObtainer; + + @PostConstruct + void initReactor() { + reactor.on($(queueNameObtainer.getQueueName(SendMailRequest.class)), mailConsumer); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java new file mode 100644 index 0000000..7e6b0f8 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java @@ -0,0 +1,37 @@ +package com.maciejwalkowiak.mercury.mail; + +import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.Messenger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.MailSender; +import org.springframework.stereotype.Component; +import reactor.event.Event; +import reactor.function.Consumer; + +@Component +public class MailConsumer implements Consumer>> { + private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); + private final MailSender mailSender; + private final Messenger messenger; + + @Autowired + public MailConsumer(MailSender mailSender, Messenger messenger) { + this.mailSender = mailSender; + this.messenger = messenger; + } + + @Override + public void accept(Event> mercuryMessageEvent) { + MercuryMessage message = mercuryMessageEvent.getData(); + LOG.info("Received send mail request: {}", message.getRequest()); + + try { + mailSender.send(message.getRequest().toMailMessage()); + messenger.messageSent(message); + } catch (Exception e) { + messenger.deliveryFailed(message, e.getMessage()); + } + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java index e1752b7..b4bc1e6 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java @@ -1,5 +1,6 @@ package com.maciejwalkowiak.mercury.mail; +import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.MercuryMessage; import com.maciejwalkowiak.mercury.core.Messenger; import com.maciejwalkowiak.mercury.core.api.HateoasController; @@ -26,6 +27,7 @@ class MailController implements HateoasController { } @RequestMapping(method = RequestMethod.POST) + @JsonView(MercuryMessage.View.Summary.class) public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { return messenger.publish(sendMailRequest); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java deleted file mode 100644 index 5bb8909..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.maciejwalkowiak.mercury.mail; - -import com.maciejwalkowiak.mercury.core.MercuryEvent; -import com.maciejwalkowiak.mercury.core.Messenger; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.mail.MailSender; -import org.springframework.stereotype.Component; -import org.springframework.util.Assert; - -@Component -public class MailListener implements ApplicationListener>{ - private static final Logger LOG = LoggerFactory.getLogger(MailListener.class); - private final MailSender mailSender; - private final Messenger messenger; - - @Autowired - MailListener(MailSender javaMailSender, Messenger messenger) { - this.mailSender = javaMailSender; - this.messenger = messenger; - } - - @Override - public void onApplicationEvent(MercuryEvent mercuryEvent) { - Assert.notNull(mercuryEvent); - - LOG.info("Received send mail request: {}", mercuryEvent.getRequest()); - - try { - mailSender.send(mercuryEvent.getRequest().toMailMessage()); - messenger.messageSent(mercuryEvent.getMessage()); - } catch (Exception e) { - messenger.deliveryFailed(mercuryEvent.getMessage(), e.getMessage()); - } - } -} diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java deleted file mode 100644 index 407a578..0000000 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/MailListenerTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.maciejwalkowiak.mercury.mail; - -import com.maciejwalkowiak.mercury.core.Messenger; -import org.junit.Before; -import org.junit.Test; -import org.springframework.mail.MailSender; - -import static org.mockito.Mockito.mock; - -public class MailListenerTest { - private MailListener mailListener; - - @Before - public void setUp() { - mailListener = new MailListener(mock(MailSender.class), mock(Messenger.class)); - } - - @Test(expected = IllegalArgumentException.class) - public void should_throw_exception_for_null_message() { - mailListener.onApplicationEvent(null); - } -} \ No newline at end of file From 958c32af578152395e3d145f138c9e7a938fe431 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 10:12:47 +0100 Subject: [PATCH 09/55] Minor refactoring --- pom.xml | 2 +- .../com/maciejwalkowiak/mercury/core/api/ApiController.java | 2 +- .../com/maciejwalkowiak/mercury/core/api/BracketsLink.java | 4 ++-- .../java/com/maciejwalkowiak/mercury/mail/MailConsumer.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index a95a824..b911d8b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ UTF-8 - mercury.MercuryApplication + com.maciejwalkowiak.mercury.MercuryApplication 1.8 diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java index 620c756..63d9a41 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -31,7 +31,7 @@ public HttpEntity links() { controllers.get().forEach(c -> apiResource.add( c.links().stream() - .map(link -> new BracketsLink(link.getHref(), link.getRel())) + .map(link -> new BracketsLink(link)) .collect(Collectors.toList()) ) ); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java index 736804d..a671221 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java @@ -3,7 +3,7 @@ import org.springframework.hateoas.Link; class BracketsLink extends Link { - public BracketsLink(String href, String rel) { - super(href.replaceAll("%7B", "{").replaceAll("%7D", "}"), rel); + public BracketsLink(Link link) { + super(link.getHref().replaceAll("%7B", "{").replaceAll("%7D", "}"), link.getRel()); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java index 7e6b0f8..fbcaa11 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java @@ -11,7 +11,7 @@ import reactor.function.Consumer; @Component -public class MailConsumer implements Consumer>> { +class MailConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); private final MailSender mailSender; private final Messenger messenger; From 01cf84e99f9d902d73380c3423fcd4767eacc10d Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 10:58:18 +0100 Subject: [PATCH 10/55] SendGrid integration --- pom.xml | 5 ++ .../mail/{ => common}/MailConfiguration.java | 11 +-- .../mail/{ => common}/MailConsumer.java | 13 ++- .../mail/{ => common}/MailController.java | 2 +- .../mercury/mail/common/MailingService.java | 5 ++ .../mail/common/SendMailException.java | 11 +++ .../mail/{ => common}/SendMailRequest.java | 14 +--- .../mail/javamail/JavaMailConfiguration.java | 11 +++ .../mail/javamail/JavaMailMailingService.java | 43 ++++++++++ .../mail/sendgrid/SendGridConfiguration.java | 38 +++++++++ .../mail/sendgrid/SendGridMailingService.java | 49 ++++++++++++ .../mail/sendgrid/SendGridProperties.java | 79 +++++++++++++++++++ src/main/resources/application.properties | 5 +- 13 files changed, 258 insertions(+), 28 deletions(-) rename src/main/java/com/maciejwalkowiak/mercury/mail/{ => common}/MailConfiguration.java (78%) rename src/main/java/com/maciejwalkowiak/mercury/mail/{ => common}/MailConsumer.java (75%) rename src/main/java/com/maciejwalkowiak/mercury/mail/{ => common}/MailController.java (96%) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java rename src/main/java/com/maciejwalkowiak/mercury/mail/{ => common}/SendMailRequest.java (72%) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailConfiguration.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java diff --git a/pom.xml b/pom.xml index b911d8b..f52a254 100644 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,11 @@ com.fasterxml.jackson.core jackson-databind + + com.sendgrid + sendgrid-java + 2.0.0 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java similarity index 78% rename from src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java rename to src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java index f08c6f0..9004cff 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java @@ -1,28 +1,25 @@ -package com.maciejwalkowiak.mercury.mail; +package com.maciejwalkowiak.mercury.mail.common; import com.maciejwalkowiak.mercury.core.QueueNameObtainer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import reactor.core.Reactor; import javax.annotation.PostConstruct; -import java.util.Queue; - import static reactor.event.selector.Selectors.$; @Configuration -@ConditionalOnProperty(name = "spring.mail.host") @ComponentScan class MailConfiguration { - @Autowired - private Reactor reactor; @Autowired private MailConsumer mailConsumer; + @Autowired + private Reactor reactor; + @Autowired private QueueNameObtainer queueNameObtainer; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java similarity index 75% rename from src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java rename to src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index fbcaa11..57d705e 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -1,11 +1,10 @@ -package com.maciejwalkowiak.mercury.mail; +package com.maciejwalkowiak.mercury.mail.common; import com.maciejwalkowiak.mercury.core.MercuryMessage; import com.maciejwalkowiak.mercury.core.Messenger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mail.MailSender; import org.springframework.stereotype.Component; import reactor.event.Event; import reactor.function.Consumer; @@ -13,12 +12,12 @@ @Component class MailConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); - private final MailSender mailSender; + private final MailingService mailingService; private final Messenger messenger; @Autowired - public MailConsumer(MailSender mailSender, Messenger messenger) { - this.mailSender = mailSender; + public MailConsumer(MailingService mailingService, Messenger messenger) { + this.mailingService = mailingService; this.messenger = messenger; } @@ -28,9 +27,9 @@ public void accept(Event> mercuryMessageEvent) { LOG.info("Received send mail request: {}", message.getRequest()); try { - mailSender.send(message.getRequest().toMailMessage()); + mailingService.send(message.getRequest()); messenger.messageSent(message); - } catch (Exception e) { + } catch (SendMailException e) { messenger.deliveryFailed(message, e.getMessage()); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java similarity index 96% rename from src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java rename to src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index b4bc1e6..44240d7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -1,4 +1,4 @@ -package com.maciejwalkowiak.mercury.mail; +package com.maciejwalkowiak.mercury.mail.common; import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.MercuryMessage; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java new file mode 100644 index 0000000..218dcaf --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java @@ -0,0 +1,5 @@ +package com.maciejwalkowiak.mercury.mail.common; + +public interface MailingService { + void send(SendMailRequest sendMailRequest) throws SendMailException; +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java new file mode 100644 index 0000000..2ecc149 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java @@ -0,0 +1,11 @@ +package com.maciejwalkowiak.mercury.mail.common; + +public class SendMailException extends Exception { + public SendMailException(Throwable cause) { + super(cause); + } + + public SendMailException(String message) { + super(message); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java similarity index 72% rename from src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java rename to src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java index d9f66c4..08a4555 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java @@ -1,11 +1,10 @@ -package com.maciejwalkowiak.mercury.mail; +package com.maciejwalkowiak.mercury.mail.common; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.maciejwalkowiak.mercury.core.Request; -import org.springframework.mail.SimpleMailMessage; -class SendMailRequest extends Request { +public class SendMailRequest extends Request { private final String to; private final String content; private final String subject; @@ -31,15 +30,6 @@ public String getSubject() { return subject; } - public SimpleMailMessage toMailMessage() { - SimpleMailMessage msg = new SimpleMailMessage(); - msg.setTo(to); - msg.setSubject(subject); - msg.setText(content); - - return msg; - } - @Override public String toString() { return "SendMailRequest{" + diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailConfiguration.java new file mode 100644 index 0000000..9f73339 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailConfiguration.java @@ -0,0 +1,11 @@ +package com.maciejwalkowiak.mercury.mail.javamail; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ComponentScan +@ConditionalOnProperty(name = "spring.mail.host") +class JavaMailConfiguration { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java new file mode 100644 index 0000000..57354ea --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java @@ -0,0 +1,43 @@ +package com.maciejwalkowiak.mercury.mail.javamail; + +import com.maciejwalkowiak.mercury.mail.common.MailingService; +import com.maciejwalkowiak.mercury.mail.common.SendMailException; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.stereotype.Service; + +@Service +class JavaMailMailingService implements MailingService { + private static final Logger LOG = LoggerFactory.getLogger(JavaMailMailingService.class); + private final MailSender mailSender; + + @Autowired + JavaMailMailingService(MailSender mailSender) { + this.mailSender = mailSender; + } + + @Override + public void send(SendMailRequest sendMailRequest) throws SendMailException { + LOG.info("Sending mail: {}", sendMailRequest); + + try { + mailSender.send(toMailMessage(sendMailRequest)); + } catch (Exception e) { + throw new SendMailException(e); + } + + } + + SimpleMailMessage toMailMessage(SendMailRequest sendMailRequest) { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setTo(sendMailRequest.getTo()); + msg.setSubject(sendMailRequest.getSubject()); + msg.setText(sendMailRequest.getContent()); + + return msg; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java new file mode 100644 index 0000000..a2d33ac --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java @@ -0,0 +1,38 @@ +package com.maciejwalkowiak.mercury.mail.sendgrid; + +import com.sendgrid.SendGrid; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnProperty(name = "sendgrid.username") +@ComponentScan +@EnableConfigurationProperties(SendGridProperties.class) +class SendGridConfiguration { + @Autowired + private SendGridProperties properties; + + @Bean + public SendGrid sendGrid() { + SendGrid sendGrid = new SendGrid(properties.getUsername(), properties.getPassword()); + + if (properties.isProxyConfigured()) { + HttpHost proxy = new HttpHost(properties.getProxy().getHost(), properties.getProxy().getPort()); + CloseableHttpClient http = HttpClientBuilder.create() + .setProxy(proxy) + .setUserAgent("sendgrid/" + sendGrid.getVersion() + ";java") + .build(); + + sendGrid.setClient(http); + } + + return sendGrid; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java new file mode 100644 index 0000000..4cd8918 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java @@ -0,0 +1,49 @@ +package com.maciejwalkowiak.mercury.mail.sendgrid; + +import com.maciejwalkowiak.mercury.mail.common.MailingService; +import com.maciejwalkowiak.mercury.mail.common.SendMailException; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import com.sendgrid.SendGrid; +import com.sendgrid.SendGridException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Primary; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +@Primary +class SendGridMailingService implements MailingService { + private static final Logger LOG = LoggerFactory.getLogger(SendGridMailingService.class); + private final SendGrid sendGrid; + + @Autowired + SendGridMailingService(SendGrid sendGrid) { + this.sendGrid = sendGrid; + } + + @Override + public void send(SendMailRequest sendMailRequest) throws SendMailException { + LOG.info("Sending mail: {}", sendMailRequest); + + try { + SendGrid.Response response = sendGrid.send(toSendGridEmail(sendMailRequest)); + + LOG.info("Response: {}, {}", response.getMessage(), response.getCode()); + + if (response.getCode() != HttpStatus.OK.value()) { + throw new SendMailException(response.getMessage()); + } + } catch (SendGridException e) { + throw new SendMailException(e); + } + } + + SendGrid.Email toSendGridEmail(SendMailRequest sendMailRequest) { + return new SendGrid.Email() + .setText(sendMailRequest.getContent()) + .setTo(new String[] { sendMailRequest.getTo() }) + .setSubject(sendMailRequest.getSubject()); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java new file mode 100644 index 0000000..e59b0e1 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java @@ -0,0 +1,79 @@ +package com.maciejwalkowiak.mercury.mail.sendgrid; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "sendgrid") +class SendGridProperties { + /** + * SendGrid username + */ + private String username; + + /** + * SendGrid password + */ + private String password; + + /** + * Proxy configuration + */ + private Proxy proxy; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Proxy getProxy() { + return proxy; + } + + public void setProxy(Proxy proxy) { + this.proxy = proxy; + } + + public boolean isProxyConfigured() { + return proxy != null + && proxy.getHost() != null + && proxy.getPort() != null; + } + + public static class Proxy { + /** + * SendGrid proxy host + */ + private String host; + + /** + * SendGrid proxy port + */ + private Integer port; + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c0b073f..639861b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,4 +3,7 @@ spring.mail.port=587 spring.mail.username=* spring.mail.password=* spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true \ No newline at end of file +spring.mail.properties.mail.smtp.starttls.enable=true + +sendgrid.username=foo +sendgrid.password=foo \ No newline at end of file From b42e75c7282bf12a8de42613ff56187ead59c55f Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 12:01:45 +0100 Subject: [PATCH 11/55] readme update --- README.adoc | 102 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 78 insertions(+), 24 deletions(-) diff --git a/README.adoc b/README.adoc index 54006bb..f8fc221 100644 --- a/README.adoc +++ b/README.adoc @@ -1,36 +1,90 @@ -== _Learning Spring Boot_ Contest += mercury-app +----------- -This is a contest based on _Learning Spring Boot_. The idea is to submit a small, pithy, cool Spring Boot application to win a prize. We will soon announce the prizes. +*Mercury* is an application initially created for _Learning Spring Boot_ +contest. -=== Calling all Spring Boot apps +It's purpose is to provide single entry point for sending +*notifications* in a company with internal applications in mind. -Years ago, the http://www.ioccc.org/years.html[The International Obfuscated C Code Contest] was invented. Seeing http://blog.aerojockey.com/post/iocccsim[Carl Banks' flight simulator] forever impressed me as a neat, pithy little app. +_______________________________________________________________________________________________________________ +If you like the application *please star* this Github repository and +*tweet* about it with #mercuryapp hashtag. +_______________________________________________________________________________________________________________ -Well, we aren't looking for obfuscated, complex, impossible to read apps. But we *ARE* looking for slick, cool apps that show off the power/coolness/wicked abilities of Spring Boot mixed with your hackable creativity. Characteristics the judges evaluate include: +== Features -* Stylish -* Short and sweet -* Custom auto-configurations are welcome -* Custom health indicators, metrics, and fancy usage thereof -* Nice on the server side OR cool frontends (You don't have to build a web frontend to have a slick, elegeant, and original UI.) -* Popularity. Tweet things up while you work on your submission. We will definitely look at stars on your forked repo of this contest, volume of traffic your generate, and other evidence of popularity. +*Notifications* may mean many things. In reality most of notifications +are being sent as an *email* or to applications trying to replace +emails, like **Slack**. -=== How/when to submit +Currently following features are implemented: -IMPORANT: Deadline is 11:59pm January 17th CST (UTC-6). (I hate midnight deadlines, since they're so ambiguous.) +* sending email through *Java Mail* +* sending email through *SendGrid* -To submit an entery: +== API -. Fork this repo. -. Code your solution inside your fork. -. Tweet/blog/reddit/facebook your efforts and gather evidence of your apps popularity (stars on your repo, total hits on your blog entry, total number of registered userse for your slick app, etc.) -. Replace this README.adoc with your own documentation (cuteness rewarded!). -. Submit a pull request (before the deadline!!!) -. Wait to see the announcement. -. Collect your prize! +* `GET http://:/api/` - lists all API methods +* `POST http://:/api/mail` - sends mail message +* `GET http://:/api/message/{id}` - get message status -Those of you that can't wait, use the holiday time as you wish. Those of you that are enjoying time with family and friends, we included enough time so you can still get into the contest. +== Build & Install -NOTE: No member of Pivotal Inc. is permitted to enter this contest. Only one submission per person or team. If there is evidence of multiple "sock puppet" entries coming from the same group of people, the judges reserve the right to disqualify anyone involved. All decisions are final. +Currently in order to use **Mercury** you need to build it first. -Good luck! +. `git clone https://github.com/maciejwalkowiak/contest.git` +. `cd contest` +. `mvn package` +. Copy JAR file to some location for example `/opt/mercuryapp`: `cp target/mercury-0.0.1-SNAPSHOT.jar /opt/mercuryapp` +. Inside `/opt/mercuryapp` create file `application.properties` and configure mail providers +. Execute `java -jar mercury-0.0.1-SNAPSHOT.jar` + +== Configuration options + +*Mercury* supports sending emails through Java Mail API by connecting to +SMTP server or using SendGrid. + +=== Java Mail configuration + +Put following properties to `application.properties` file: + +--------------------- +spring.mail.host= +spring.mail.port= +spring.mail.username= +spring.mail.password= +--------------------- + +To configure additional Java Mail properties use +`spring.mail.properties` prefix. + +Sample configuration for Gmail account: + +----------------------------------------------------- +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username= +spring.mail.password= +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +----------------------------------------------------- + +=== SendGrid configuration + +Put following properties to `application.properties` file: + +------------------ +sendgrid.username= +sendgrid.password= +------------------ + +If you need to access SendGrid through proxy server add: + +-------------------- +sendgrid.proxy.host= +sendgrid.proxy.port= +-------------------- + +*IMPORTANT:* if both Java Mail *and* SendGrid configuration is provided +- all emails will be sent using SendGrid. \ No newline at end of file From df7e6f1cfed09a0e22457536a8a1942ef9b851a5 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 12:04:04 +0100 Subject: [PATCH 12/55] Minor cleanup --- src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 639861b..061be8b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -5,5 +5,5 @@ spring.mail.password=* spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true -sendgrid.username=foo -sendgrid.password=foo \ No newline at end of file +#sendgrid.username= +#sendgrid.password= \ No newline at end of file From 5f1a51fbfdd7f22eb0cb92daee13b7a4478e7e94 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 17:46:01 +0100 Subject: [PATCH 13/55] Unit & Integration tests, minor refactoring --- pom.xml | 6 ++ .../mercury/core/MercuryMessage.java | 8 +- .../mercury/core/MessengerImpl.java | 6 +- .../mercury/mail/common/MailConsumer.java | 20 +++-- src/main/resources/application.properties | 12 +-- src/main/resources/logback.xml | 4 + .../mercury/MercuryApplicationTests.java | 38 +++++++- .../mercury/core/MessengerImplTest.java | 87 +++++++++++++++++++ .../mercury/core/QueueNameObtainerTest.java | 22 +++++ .../mercury/mail/common/MailConsumerTest.java | 66 ++++++++++++++ .../javamail/JavaMailMailingServiceTest.java | 65 ++++++++++++++ .../sendgrid/SendGridConfigurationTest.java | 46 ++++++++++ 12 files changed, 359 insertions(+), 21 deletions(-) create mode 100644 src/main/resources/logback.xml create mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java diff --git a/pom.xml b/pom.xml index f52a254..ce191d0 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,12 @@ spring-boot-starter-test test + + org.assertj + assertj-core + 1.7.0 + test + diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index ffda253..a4fac24 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -18,7 +18,7 @@ public enum Status { @JsonView(View.Summary.class) private Status status; @JsonView(View.Summary.class) - private String errorMesssage; + private String errorMessage; private T request; /** @@ -42,7 +42,7 @@ public void sent() { public void failed(String errorMessage) { this.status = Status.FAILED; - this.errorMesssage = errorMessage; + this.errorMessage = errorMessage; } public Status getStatus() { @@ -57,6 +57,10 @@ public String getId() { return id; } + public String getErrorMessage() { + return errorMessage; + } + public static class View { public interface Summary {} } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index acaa844..123b2f8 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -8,14 +8,14 @@ @Component class MessengerImpl implements Messenger { private final MercuryMessageRepository repository; - private final Reactor rootReactor; private final QueueNameObtainer queueNameObtainer; + private final Reactor rootReactor; @Autowired - MessengerImpl(MercuryMessageRepository repository, Reactor rootReactor, QueueNameObtainer queueNameObtainer) { + MessengerImpl(MercuryMessageRepository repository, QueueNameObtainer queueNameObtainer, Reactor rootReactor) { this.repository = repository; - this.rootReactor = rootReactor; this.queueNameObtainer = queueNameObtainer; + this.rootReactor = rootReactor; } @Override diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index 57d705e..f6a1c9e 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -9,14 +9,16 @@ import reactor.event.Event; import reactor.function.Consumer; +import java.util.Optional; + @Component class MailConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); - private final MailingService mailingService; + private final Optional mailingService; private final Messenger messenger; @Autowired - public MailConsumer(MailingService mailingService, Messenger messenger) { + public MailConsumer(Optional mailingService, Messenger messenger) { this.mailingService = mailingService; this.messenger = messenger; } @@ -26,11 +28,15 @@ public void accept(Event> mercuryMessageEvent) { MercuryMessage message = mercuryMessageEvent.getData(); LOG.info("Received send mail request: {}", message.getRequest()); - try { - mailingService.send(message.getRequest()); - messenger.messageSent(message); - } catch (SendMailException e) { - messenger.deliveryFailed(message, e.getMessage()); + if (mailingService.isPresent()) { + try { + mailingService.get().send(message.getRequest()); + messenger.messageSent(message); + } catch (SendMailException e) { + messenger.deliveryFailed(message, e.getMessage()); + } + } else { + messenger.deliveryFailed(message, "Mailing provider is not configured"); } } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 061be8b..82bbab4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +1,9 @@ -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username=* -spring.mail.password=* -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true +#spring.mail.host=smtp.gmail.com +#spring.mail.port=587 +#spring.mail.username=* +#spring.mail.password=* +#spring.mail.properties.mail.smtp.auth=true +#spring.mail.properties.mail.smtp.starttls.enable=true #sendgrid.username= #sendgrid.password= \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..e364675 --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index c0a0f09..43523b9 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -1,18 +1,50 @@ package com.maciejwalkowiak.mercury; +import com.sendgrid.SendGrid; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.context.ApplicationContext; +import org.springframework.http.MediaType; +import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultHandler; +import org.springframework.test.web.servlet.result.PrintingResultHandler; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MercuryApplication.class) -@WebAppConfiguration +@WebIntegrationTest(value = { "sendgrid.username=foo" , "sendgrid.password=bar"}, randomPort = true) public class MercuryApplicationTests { - @Test - public void contextLoads() { + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } + @Test + public void javaMailSenderIsNotCreatedByDefault() throws Exception { + this.mockMvc.perform(post("/api/mail").content("{ \"to\":\"foo@bar.com\",\"content\":\"bar\" }").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.status").exists()); + } } diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java new file mode 100644 index 0000000..97f58a0 --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java @@ -0,0 +1,87 @@ +package com.maciejwalkowiak.mercury.core; + +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import reactor.core.Reactor; +import reactor.event.Event; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class MessengerImplTest { + @InjectMocks + private MessengerImpl messenger; + @Mock + private MercuryMessageRepository repository; + @Mock + private QueueNameObtainer queueNameObtainer; + @Mock + private Reactor rootReactor; + + private final SendMailRequest request = new SendMailRequest("foo@bar.com", "content", "subject"); + + @Test + public void shouldSaveMessage() { + messenger.publish(request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(MercuryMessage.class); + + verify(repository).save(captor.capture()); + assertThat(captor.getValue().getRequest()).isEqualTo(request); + assertThat(captor.getValue().getStatus()).isEqualTo(MercuryMessage.Status.QUEUED); + } + + @Test + public void shouldPublishMessage() { + // given + when(queueNameObtainer.getQueueName(eq(request))).thenReturn("queue"); + + // when + messenger.publish(request); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); + + verify(rootReactor).notify(eq("queue"), captor.capture()); + + MercuryMessage message = (MercuryMessage) captor.getValue().getData(); + + assertThat(message.getRequest()).isEqualTo(request); + } + + @Test + public void shouldChangeStatusAndSaveWhenSent() { + // given + MercuryMessage message = MercuryMessage.queued(request); + + // when + messenger.messageSent(message); + + // then + assertThat(message.getStatus()).isEqualTo(MercuryMessage.Status.SENT); + verify(repository).save(eq(message)); + } + + @Test + public void shouldChangeStatusAndSaveWhenFailed() { + // given + MercuryMessage message = MercuryMessage.queued(request); + String errorMessage = "some error"; + + // when + messenger.deliveryFailed(message, errorMessage); + + // then + assertThat(message.getStatus()).isEqualTo(MercuryMessage.Status.FAILED); + assertThat(message.getErrorMessage()).isEqualTo(errorMessage); + verify(repository).save(eq(message)); + } +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java new file mode 100644 index 0000000..5d24dfb --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java @@ -0,0 +1,22 @@ +package com.maciejwalkowiak.mercury.core; + +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QueueNameObtainerTest { + final String QUEUE_NAME = "com.maciejwalkowiak.mercury.mail.common.SendMailRequest"; + private QueueNameObtainer queueNameObtainer = new QueueNameObtainer(); + + @Test + public void testQueueNameForClass() { + assertThat(queueNameObtainer.getQueueName(SendMailRequest.class)).isEqualTo(QUEUE_NAME); + } + + @Test + public void testQueueNameForInstance() { + assertThat(queueNameObtainer.getQueueName(new SendMailRequest("to", "text", "subject"))).isEqualTo(QUEUE_NAME); + } + +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java new file mode 100644 index 0000000..838aee4 --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java @@ -0,0 +1,66 @@ +package com.maciejwalkowiak.mercury.mail.common; + +import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.Messenger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.mail.SimpleMailMessage; +import reactor.event.Event; + +import java.util.Optional; + +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class MailConsumerTest { + private MailConsumer mailConsumer; + + @Mock + private MailingService mailingService; + @Mock + private Messenger messenger; + + @Test + public void shouldFailWhenMailingServiceNotPresent() { + mailConsumer = new MailConsumer(Optional.empty(), messenger); + + MercuryMessage message = mock(MercuryMessage.class); + + mailConsumer.accept(new Event<>(message)); + + verify(messenger).deliveryFailed(eq(message), anyString()); + } + + @Test + public void shouldSendMessage() throws SendMailException { + mailConsumer = new MailConsumer(Optional.of(mailingService), messenger); + + MercuryMessage message = mock(MercuryMessage.class); + + mailConsumer.accept(new Event<>(message)); + + verify(mailingService).send(eq(message.getRequest())); + verify(messenger).messageSent(eq(message)); + } + + @Test + public void shouldFailWhenMailingServiceFailed() throws SendMailException { + mailConsumer = new MailConsumer(Optional.of(mailingService), messenger); + + MercuryMessage message = mock(MercuryMessage.class); + doThrow(new SendMailException("some message")).when(mailingService).send(any()); + + mailConsumer.accept(new Event<>(message)); + + verify(messenger).deliveryFailed(eq(message), eq("some message")); + } + + + +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java new file mode 100644 index 0000000..3c241cf --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java @@ -0,0 +1,65 @@ +package com.maciejwalkowiak.mercury.mail.javamail; + +import com.maciejwalkowiak.mercury.mail.common.SendMailException; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.mail.MailSender; +import org.springframework.mail.SimpleMailMessage; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class JavaMailMailingServiceTest { + @InjectMocks + private JavaMailMailingService javaMailMailingService; + @Mock + private MailSender mailSender; + + private final SendMailRequest sendMailRequest = new SendMailRequest("foo@bar.com", "content", "subject"); + + @Test + public void convertsToJavaMailMessage() { + // when + SimpleMailMessage simpleMailMessage = javaMailMailingService.toMailMessage(sendMailRequest); + + // then + areEqual(simpleMailMessage, sendMailRequest); + } + + @Test + public void shouldSendMessage() throws SendMailException { + javaMailMailingService.send(sendMailRequest); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SimpleMailMessage.class); + + verify(mailSender).send(captor.capture()); + + areEqual(captor.getValue(), sendMailRequest); + } + + @Test(expected = SendMailException.class) + public void shouldThrowExceptionOnError() throws SendMailException { + doThrow(Exception.class).when(mailSender).send((SimpleMailMessage) any()); + + javaMailMailingService.send(sendMailRequest); + } + + private void areEqual(SimpleMailMessage mailMessage, SendMailRequest request) { + assertThat(mailMessage.getTo()).contains(request.getTo()); + assertThat(mailMessage.getSubject()).isEqualTo(request.getSubject()); + assertThat(mailMessage.getText()).isEqualTo(request.getContent()); + } +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java new file mode 100644 index 0000000..4f9cb42 --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java @@ -0,0 +1,46 @@ +package com.maciejwalkowiak.mercury.mail.sendgrid; + +import com.maciejwalkowiak.mercury.MercuryApplication; +import com.maciejwalkowiak.mercury.mail.common.MailingService; +import org.junit.Test; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.boot.test.EnvironmentTestUtils; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SendGridConfigurationTest { + + private AnnotationConfigApplicationContext context; + + @Test + public void sendGridMailSenderIsCreatedOnProperty() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(context, "sendgrid.username"); + this.context.register(MercuryApplication.class); + this.context.refresh(); + + assertThat(context.getBean(SendGridMailingService.class)).isNotNull(); + } + + @Test(expected = NoSuchBeanDefinitionException.class) + public void sendGridMailSenderIsNotDefinedByDefault() { + this.context = new AnnotationConfigApplicationContext(); + this.context.register(MercuryApplication.class); + this.context.refresh(); + + context.getBean(SendGridMailingService.class); + } + + @Test + public void sendGridMailSenderIsDefinedWhenBothProvidersAreDefined() { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(context, "sendgrid.username"); + EnvironmentTestUtils.addEnvironment(context, "spring.mail.host"); + this.context.register(MercuryApplication.class); + this.context.refresh(); + + assertThat(context.getBean(MailingService.class)).isNotNull().isInstanceOf(SendGridMailingService.class); + } + +} \ No newline at end of file From 4bf8aa07120e88c197c3ed942b411aa9321c4ac9 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Mon, 12 Jan 2015 17:52:19 +0100 Subject: [PATCH 14/55] Readme update --- README.adoc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index f8fc221..30367f0 100644 --- a/README.adoc +++ b/README.adoc @@ -31,6 +31,8 @@ Currently following features are implemented: == Build & Install +*Mercury* uses MongoDB to store messages data. Make sure you have running MongoDB instance and appropriate configuration. + Currently in order to use **Mercury** you need to build it first. . `git clone https://github.com/maciejwalkowiak/contest.git` @@ -42,8 +44,14 @@ Currently in order to use **Mercury** you need to build it first. == Configuration options -*Mercury* supports sending emails through Java Mail API by connecting to -SMTP server or using SendGrid. +== MongoDB configuration + +------------------------------------------------------------------------ +spring.data.mongodb.uri=mongodb://localhost/test # connection URL +spring.data.mongodb.database= +spring.data.mongodb.username= +spring.data.mongodb.password= +------------------------------------------------------------------------ === Java Mail configuration @@ -87,4 +95,4 @@ sendgrid.proxy.port= -------------------- *IMPORTANT:* if both Java Mail *and* SendGrid configuration is provided -- all emails will be sent using SendGrid. \ No newline at end of file +- all emails will be sent using SendGrid. From 5c80ada2606824a9500b77c8d204e36180accee7 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 10:45:23 +0100 Subject: [PATCH 15/55] JavaMailSenderImpl based mail server health check with MailServerHealthIndicator --- .../javamail/MailServerHealthIndicator.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java new file mode 100644 index 0000000..7be9197 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java @@ -0,0 +1,61 @@ +package com.maciejwalkowiak.mercury.mail.javamail; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.autoconfigure.mail.MailProperties; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.stereotype.Component; + +import javax.mail.MessagingException; +import javax.mail.NoSuchProviderException; +import javax.mail.Session; +import javax.mail.Transport; +import java.util.Properties; + +@Component +class MailServerHealthIndicator extends AbstractHealthIndicator { + private final MailProperties mailProperties; + + @Autowired + MailServerHealthIndicator(MailProperties mailProperties) { + this.mailProperties = mailProperties; + } + + @Override + protected void doHealthCheck(Health.Builder builder) { + Properties properties = new Properties(); + properties.putAll(mailProperties.getProperties()); + + Session session = Session.getInstance(properties, null); + + try { + Transport transport = getTransport(session); + transport.connect(mailProperties.getHost(), getPort(), mailProperties.getUsername(), mailProperties.getPassword()); + transport.close(); + + builder.up(); + } catch (MessagingException e) { + builder.down(e); + } + } + + private int getPort() { + Integer port = mailProperties.getPort(); + + if (port == null) { + port = JavaMailSenderImpl.DEFAULT_PORT; + } + + return port; + } + + private Transport getTransport(Session session) throws NoSuchProviderException { + String protocol = session.getProperty("mail.transport.protocol"); + if (protocol == null) { + protocol = JavaMailSenderImpl.DEFAULT_PROTOCOL; + } + + return session.getTransport(protocol); + } +} From 31b184509355bf0854d16c23ba0592a9a208c15b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 11:09:20 +0100 Subject: [PATCH 16/55] SendGrid proxy-aware health indicator --- .../mail/sendgrid/SendGridConfiguration.java | 19 ++++++ .../sendgrid/SendGridHealthIndicator.java | 68 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java index a2d33ac..67a47f1 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java @@ -10,6 +10,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.net.InetSocketAddress; +import java.net.Proxy; @Configuration @ConditionalOnProperty(name = "sendgrid.username") @@ -35,4 +40,18 @@ public SendGrid sendGrid() { return sendGrid; } + + @Bean(name = "sendGridRestTemplate") + public RestTemplate restTemplate() { + if (properties.isProxyConfigured()) { + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + + Proxy proxy= new Proxy(Proxy.Type.HTTP, new InetSocketAddress(properties.getProxy().getHost(), properties.getProxy().getPort())); + requestFactory.setProxy(proxy); + + return new RestTemplate(requestFactory); + } else { + return new RestTemplate(); + } + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java new file mode 100644 index 0000000..513ee8a --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java @@ -0,0 +1,68 @@ +package com.maciejwalkowiak.mercury.mail.sendgrid; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.actuate.health.AbstractHealthIndicator; +import org.springframework.boot.actuate.health.Health; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +@Component +class SendGridHealthIndicator extends AbstractHealthIndicator { + private static final String GET_PROFILE_URL = "https://api.sendgrid.com/api/profile.get.json"; + + private final RestTemplate restTemplate; + private final SendGridProperties sendGridProperties; + + @Autowired + SendGridHealthIndicator(@Qualifier("sendGridRestTemplate") RestTemplate restTemplate, SendGridProperties sendGridProperties) { + this.restTemplate = restTemplate; + this.sendGridProperties = sendGridProperties; + } + + @Override + protected void doHealthCheck(Health.Builder builder) throws Exception { + SendGridResponse sendGridResponse = restTemplate.getForObject(GET_PROFILE_URL, SendGridResponse.class, + getAuthenticationProperties()); + + if (sendGridResponse.error != null) { + builder.down().withDetail("message", sendGridResponse.error.message); + } else { + builder.up(); + } + } + + private Map getAuthenticationProperties() { + Map map = new HashMap<>(); + map.put("api_user", sendGridProperties.getUsername()); + map.put("api_password", sendGridProperties.getPassword()); + + return map; + } + + private static class SendGridResponse { + private ErrorDetails error; + + @JsonCreator + public SendGridResponse(@JsonProperty("error") ErrorDetails error) { + this.error = error; + } + + private static class ErrorDetails { + private String code; + private String message; + + @JsonCreator + public ErrorDetails(@JsonProperty("code") String code, @JsonProperty("message") String message) { + this.code = code; + this.message = message; + } + } + } + +} From 44c830c9326a9d8010101ad44dc8b2d7bd16c207 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 21:13:58 +0100 Subject: [PATCH 17/55] Javadocs for 'core' module --- .../mercury/core/MercuryMessage.java | 16 +++++++++++-- .../core/MercuryMessageController.java | 7 ++++++ .../core/MercuryMessageRepository.java | 3 +++ .../mercury/core/Messenger.java | 23 +++++++++++++++++++ .../mercury/core/MessengerImpl.java | 5 ++++ .../mercury/core/QueueNameObtainer.java | 9 ++++++++ .../maciejwalkowiak/mercury/core/Request.java | 6 +++++ .../mercury/core/api/ApiController.java | 12 +++++++++- .../mercury/core/api/ApiResource.java | 6 ----- .../mercury/core/api/BracketsLink.java | 8 +++++++ .../mercury/core/api/HateoasController.java | 11 +++++++++ 11 files changed, 97 insertions(+), 9 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java index a4fac24..a88cc12 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java @@ -4,6 +4,18 @@ import com.fasterxml.jackson.annotation.JsonView; import org.springframework.data.annotation.Id; +/** + * Keeps all information about message: + * - original request + * - status + * - and error message if sending failed + * + * It's instances are saved in MongoDB by {@link com.maciejwalkowiak.mercury.core.MercuryMessageRepository} + * + * @param - request class + * + * @author Maciej Walkowiak + */ @JsonInclude(JsonInclude.Include.NON_NULL) public class MercuryMessage { public enum Status { @@ -36,11 +48,11 @@ public static MercuryMessage queued(T request) { return new MercuryMessage<>(Status.QUEUED, request); } - public void sent() { + void sent() { this.status = Status.SENT; } - public void failed(String errorMessage) { + void failed(String errorMessage) { this.status = Status.FAILED; this.errorMessage = errorMessage; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index 067d642..1c58075 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -15,6 +15,13 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; +/** + * Exposes message details through REST interface. + * + * Used to check message if message has been successfully saved. + * + * @author Maciej Walkowiak + */ @RestController @RequestMapping("/api/message/") class MercuryMessageController implements HateoasController { diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java index 6480137..27e84ce 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java @@ -2,5 +2,8 @@ import org.springframework.data.mongodb.repository.MongoRepository; +/** + * MongoDB based repository for {@link com.maciejwalkowiak.mercury.core.MercuryMessage} + */ interface MercuryMessageRepository extends MongoRepository { } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java index 627e128..fa4717c 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java @@ -1,7 +1,30 @@ package com.maciejwalkowiak.mercury.core; +/** + * Responsible for creating messages and changing it's status. + * + * @author Maciej Walkowiak + */ public interface Messenger { + /** + * Creates {@link com.maciejwalkowiak.mercury.core.MercuryMessage} and publishes it into queue for processing + * + * @param request - request contains message details like content, recipients etc + * @return message with status "QUEUED" + */ MercuryMessage publish(Request request); + + /** + * Invoked after message has been successfully sent. Changes it's status to "SENT" + * + * @param message sent message + */ void messageSent(MercuryMessage message); + + /** + * Invoked when sending message has failed. Changes it's status to "FAILED" and saves error details + * @param message - message that sending has failed + * @param errorMessage - error details + */ void deliveryFailed(MercuryMessage message, String errorMessage); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index 123b2f8..035ade9 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -5,6 +5,11 @@ import reactor.core.Reactor; import reactor.event.Event; +/** + * http://reactor.github.io/ based {@link com.maciejwalkowiak.mercury.core.Messenger} implementation + * + * @author Maciej Walkowiak + */ @Component class MessengerImpl implements Messenger { private final MercuryMessageRepository repository; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java b/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java index b2b2f57..d39c8cc 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java @@ -2,6 +2,15 @@ import org.springframework.stereotype.Component; +/** + * Queue name needs to be known when message is sent to queue and also when listener subscribes to queue. + * QueueNameObtainer returns same queue name for object and for it's class. + * + * See {@link com.maciejwalkowiak.mercury.core.MessengerImpl} + * and {@link com.maciejwalkowiak.mercury.mail.common.MailConfiguration} + * + * @author Maciej Walkowiak + */ @Component public class QueueNameObtainer { public String getQueueName(Class clazz) { diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java index d6f0d05..3772556 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Request.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Request.java @@ -1,4 +1,10 @@ package com.maciejwalkowiak.mercury.core; +/** + * Base class for incoming requests data structures. + * {@see com.maciejwalkowiak.mercury.mail.common.SendMailRequest} + * + * @author Maciej Walkowiak + */ public abstract class Request { } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java index 63d9a41..c4fe282 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -1,6 +1,7 @@ package com.maciejwalkowiak.mercury.core.api; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.ResourceSupport; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -12,6 +13,12 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +/** + * In Mercury API is dynamic and depending how application is configured some URLs are available and some are not. + * {@link com.maciejwalkowiak.mercury.core.api.ApiController} exposes an array of all available URLs with current configuration + * + * @author Maciej Walkowiak + */ @Controller @RequestMapping(value = "/api") class ApiController { @@ -39,4 +46,7 @@ public HttpEntity links() { return new HttpEntity<>(apiResource); } -} + + private static class ApiResource extends ResourceSupport { + } +} \ No newline at end of file diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java deleted file mode 100644 index 5e6bf98..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiResource.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.maciejwalkowiak.mercury.core.api; - -import org.springframework.hateoas.ResourceSupport; - -class ApiResource extends ResourceSupport { -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java index a671221..46bc7a3 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java @@ -2,6 +2,14 @@ import org.springframework.hateoas.Link; +/** + * Spring HATEOAS performs URL encoding and replaces characters "{" and "}" that are useful to show templated URL. + * + * {@link com.maciejwalkowiak.mercury.core.api.BracketsLink} is a hacky class that takes {@link org.springframework.hateoas.Link} + * from Spring HATEOAS package and brings back brackets "{" and "}" + * + * @author Maciej Walkowiak + */ class BracketsLink extends Link { public BracketsLink(Link link) { super(link.getHref().replaceAll("%7B", "{").replaceAll("%7D", "}"), link.getRel()); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java index 476b087..c15e757 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/HateoasController.java @@ -4,6 +4,17 @@ import java.util.List; +/** + * Exposes links to {@link com.maciejwalkowiak.mercury.core.api.ApiController} + * so that they become visible under `/api` path + * + * @author Maciej Walkowiak + */ public interface HateoasController { + /** + * Gets HATEOAS links + * + * @return list of links or empty list + */ List links(); } From a34d4cb142f19db7ea253554e70532d522b8e04c Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 21:23:31 +0100 Subject: [PATCH 18/55] Javadocs for 'mail' module --- .../mercury/mail/common/MailConsumer.java | 10 ++++++++++ .../mercury/mail/common/MailController.java | 5 +++++ .../mercury/mail/common/MailingService.java | 8 ++++++++ .../mercury/mail/common/SendMailException.java | 5 +++++ .../mercury/mail/common/SendMailRequest.java | 5 +++++ .../mail/javamail/MailServerHealthIndicator.java | 8 ++++++++ 6 files changed, 41 insertions(+) diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index f6a1c9e..2534e6b 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -11,6 +11,16 @@ import java.util.Optional; +/** + * Consumes incoming {@link com.maciejwalkowiak.mercury.mail.common.SendMailRequest} based events + * and sends emails through configured provider ({@link com.maciejwalkowiak.mercury.mail.common.MailingService}). + * + * Based on Reactor. + * + * If there is no provider configured all incoming emails will get status "FAILED" + * + * @author Maciej Walkowiak + */ @Component class MailConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index 44240d7..d5a774d 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -16,6 +16,11 @@ import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +/** + * API for receiving {@link com.maciejwalkowiak.mercury.mail.common.SendMailRequest} requests + * + * @author Maciej Walkowiak + */ @RestController @RequestMapping("/api/mail") class MailController implements HateoasController { diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java index 218dcaf..62c5557 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailingService.java @@ -1,5 +1,13 @@ package com.maciejwalkowiak.mercury.mail.common; +/** + * Service used to send emails + * + * {@see com.maciejwalkowiak.mercury.mail.javamail.JavaMailMailingService} + * {@see com.maciejwalkowiak.mercury.mail.sendgrid.SendGridMailingService} + * + * @author Maciej Walkowiak + */ public interface MailingService { void send(SendMailRequest sendMailRequest) throws SendMailException; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java index 2ecc149..4a794f7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailException.java @@ -1,5 +1,10 @@ package com.maciejwalkowiak.mercury.mail.common; +/** + * Thrown when anything goes wrong with sending email + * + * @author Maciej Walkowiak + */ public class SendMailException extends Exception { public SendMailException(Throwable cause) { super(cause); diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java index 08a4555..c8da155 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java @@ -4,6 +4,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maciejwalkowiak.mercury.core.Request; +/** + * Data structure containing all data needed to send email + * + * @author Maciej Walkowiak + */ public class SendMailRequest extends Request { private final String to; private final String content; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java index 7be9197..2469c4c 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java @@ -13,6 +13,14 @@ import javax.mail.Transport; import java.util.Properties; +/** + * Mail server health indicator for Spring Boot. + * + * Takes configured {@link org.springframework.boot.autoconfigure.mail.MailProperties} + * and tries to connect {@link javax.mail.Transport}. When no exception is thrown reports status "UP" + * + * @author Maciej Walkowiak + */ @Component class MailServerHealthIndicator extends AbstractHealthIndicator { private final MailProperties mailProperties; From d7fedf69d1e31343b1f0b63bd948bf0ea8648a86 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 21:59:58 +0100 Subject: [PATCH 19/55] Added fields to SendMailRequest: "cc", "bcc". It's possible now to send email to multiple recipients at once --- .../mercury/mail/common/SendMailRequest.java | 34 +++++++++++---- .../mail/common/SendMailRequestBuilder.java | 41 +++++++++++++++++++ .../mail/javamail/JavaMailMailingService.java | 6 ++- .../mail/sendgrid/SendGridMailingService.java | 6 ++- src/main/resources/logback.xml | 2 +- .../mercury/MercuryApplicationTests.java | 15 ++----- .../mercury/core/MessengerImplTest.java | 7 +++- .../mercury/core/QueueNameObtainerTest.java | 8 +++- .../mercury/mail/common/MailConsumerTest.java | 3 -- .../javamail/JavaMailMailingServiceTest.java | 28 +++++++------ .../sendgrid/SendGridConfigurationTest.java | 11 +++-- src/test/resources/logback-test.xml | 4 ++ 12 files changed, 117 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java create mode 100644 src/test/resources/logback-test.xml diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java index c8da155..7de781f 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java @@ -4,42 +4,58 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.maciejwalkowiak.mercury.core.Request; +import java.util.List; + /** * Data structure containing all data needed to send email * * @author Maciej Walkowiak */ public class SendMailRequest extends Request { - private final String to; - private final String content; + private final List to; + private final List cc; + private final List bcc; + private final String text; private final String subject; @JsonCreator - public SendMailRequest(@JsonProperty("to") String to, - @JsonProperty("content") String content, + public SendMailRequest(@JsonProperty("to") List to, + @JsonProperty("cc") List cc, + @JsonProperty("bcc") List bcc, + @JsonProperty("text") String text, @JsonProperty("subject") String subject) { this.to = to; - this.content = content; + this.cc = cc; + this.bcc = bcc; + this.text = text; this.subject = subject; } - public String getTo() { + public List getTo() { return to; } - public String getContent() { - return content; + public String getText() { + return text; } public String getSubject() { return subject; } + public List getCc() { + return cc; + } + + public List getBcc() { + return bcc; + } + @Override public String toString() { return "SendMailRequest{" + "to='" + to + '\'' + - ", content='" + content + '\'' + + ", text='" + text + '\'' + ", subject='" + subject + '\'' + '}'; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java new file mode 100644 index 0000000..6b1ff63 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java @@ -0,0 +1,41 @@ +package com.maciejwalkowiak.mercury.mail.common; + +import java.util.ArrayList; +import java.util.List; + +public class SendMailRequestBuilder { + private List to = new ArrayList<>(); + private List cc = new ArrayList<>();; + private List bcc = new ArrayList<>();; + private String subject; + private String text; + + public SendMailRequestBuilder to(String to) { + this.to.add(to); + return this; + } + + public SendMailRequestBuilder cc(String cc) { + this.cc.add(cc); + return this; + } + + public SendMailRequestBuilder bcc(String bcc) { + this.bcc.add(bcc); + return this; + } + + public SendMailRequestBuilder subject(String subject) { + this.subject = subject; + return this; + } + + public SendMailRequestBuilder text(String text) { + this.text = text; + return this; + } + + public SendMailRequest build() { + return new SendMailRequest(to, cc, bcc, subject, text); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java index 57354ea..fc5c2bc 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingService.java @@ -34,9 +34,11 @@ public void send(SendMailRequest sendMailRequest) throws SendMailException { SimpleMailMessage toMailMessage(SendMailRequest sendMailRequest) { SimpleMailMessage msg = new SimpleMailMessage(); - msg.setTo(sendMailRequest.getTo()); + msg.setTo(sendMailRequest.getTo().stream().toArray(String[]::new)); + msg.setCc(sendMailRequest.getCc().stream().toArray(String[]::new)); + msg.setBcc(sendMailRequest.getBcc().stream().toArray(String[]::new)); msg.setSubject(sendMailRequest.getSubject()); - msg.setText(sendMailRequest.getContent()); + msg.setText(sendMailRequest.getText()); return msg; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java index 4cd8918..37701f2 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridMailingService.java @@ -42,8 +42,10 @@ public void send(SendMailRequest sendMailRequest) throws SendMailException { SendGrid.Email toSendGridEmail(SendMailRequest sendMailRequest) { return new SendGrid.Email() - .setText(sendMailRequest.getContent()) - .setTo(new String[] { sendMailRequest.getTo() }) + .setText(sendMailRequest.getText()) + .setTo(sendMailRequest.getTo().toArray(new String[]{})) + .setCc(sendMailRequest.getCc().toArray(new String[]{})) + .setBcc(sendMailRequest.getBcc().toArray(new String[]{})) .setSubject(sendMailRequest.getSubject()); } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index e364675..a081f76 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index 43523b9..002826e 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -1,26 +1,17 @@ package com.maciejwalkowiak.mercury; -import com.sendgrid.SendGrid; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; -import org.springframework.context.ApplicationContext; import org.springframework.http.MediaType; -import org.springframework.mail.javamail.JavaMailSenderImpl; -import org.springframework.test.context.web.WebAppConfiguration; -import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultHandler; -import org.springframework.test.web.servlet.result.PrintingResultHandler; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -40,8 +31,8 @@ public void setup() { } @Test - public void javaMailSenderIsNotCreatedByDefault() throws Exception { - this.mockMvc.perform(post("/api/mail").content("{ \"to\":\"foo@bar.com\",\"content\":\"bar\" }").contentType(MediaType.APPLICATION_JSON)) + public void testSendingMailRequest() throws Exception { + this.mockMvc.perform(post("/api/mail").content("{ \"to\":[\"foo@bar.com\"],\"text\":\"bar\" }").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").exists()) diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java index 97f58a0..4ca0242 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java @@ -1,6 +1,7 @@ package com.maciejwalkowiak.mercury.core; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -26,7 +27,11 @@ public class MessengerImplTest { @Mock private Reactor rootReactor; - private final SendMailRequest request = new SendMailRequest("foo@bar.com", "content", "subject"); + private final SendMailRequest request = new SendMailRequestBuilder() + .to("foo@bar.com") + .subject("subject") + .text("content") + .build(); @Test public void shouldSaveMessage() { diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java index 5d24dfb..417c6d0 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java @@ -1,6 +1,7 @@ package com.maciejwalkowiak.mercury.core; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -16,7 +17,12 @@ public void testQueueNameForClass() { @Test public void testQueueNameForInstance() { - assertThat(queueNameObtainer.getQueueName(new SendMailRequest("to", "text", "subject"))).isEqualTo(QUEUE_NAME); + SendMailRequest sendMailRequest = new SendMailRequestBuilder() + .to("foo@bar.com") + .subject("subject") + .text("content") + .build(); + assertThat(queueNameObtainer.getQueueName(sendMailRequest)).isEqualTo(QUEUE_NAME); } } \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java index 838aee4..12bacab 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java @@ -4,15 +4,12 @@ import com.maciejwalkowiak.mercury.core.Messenger; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.springframework.mail.SimpleMailMessage; import reactor.event.Event; import java.util.Optional; -import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.*; diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java index 3c241cf..20eca96 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java @@ -2,24 +2,20 @@ import com.maciejwalkowiak.mercury.mail.common.SendMailException; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import org.junit.Before; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import org.mockito.runners.MockitoJUnitRunner; import org.springframework.mail.MailSender; import org.springframework.mail.SimpleMailMessage; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class JavaMailMailingServiceTest { @@ -28,38 +24,44 @@ public class JavaMailMailingServiceTest { @Mock private MailSender mailSender; - private final SendMailRequest sendMailRequest = new SendMailRequest("foo@bar.com", "content", "subject"); + private final SendMailRequest request = new SendMailRequestBuilder() + .to("foo@bar.com") + .subject("subject") + .text("content") + .build(); @Test public void convertsToJavaMailMessage() { // when - SimpleMailMessage simpleMailMessage = javaMailMailingService.toMailMessage(sendMailRequest); + SimpleMailMessage simpleMailMessage = javaMailMailingService.toMailMessage(request); // then - areEqual(simpleMailMessage, sendMailRequest); + areEqual(simpleMailMessage, request); } @Test public void shouldSendMessage() throws SendMailException { - javaMailMailingService.send(sendMailRequest); + javaMailMailingService.send(request); ArgumentCaptor captor = ArgumentCaptor.forClass(SimpleMailMessage.class); verify(mailSender).send(captor.capture()); - areEqual(captor.getValue(), sendMailRequest); + areEqual(captor.getValue(), request); } @Test(expected = SendMailException.class) public void shouldThrowExceptionOnError() throws SendMailException { doThrow(Exception.class).when(mailSender).send((SimpleMailMessage) any()); - javaMailMailingService.send(sendMailRequest); + javaMailMailingService.send(request); } private void areEqual(SimpleMailMessage mailMessage, SendMailRequest request) { - assertThat(mailMessage.getTo()).contains(request.getTo()); + assertThat(mailMessage.getTo()).containsAll(request.getTo()); + assertThat(mailMessage.getCc()).containsAll(request.getCc()); + assertThat(mailMessage.getBcc()).containsAll(request.getBcc()); assertThat(mailMessage.getSubject()).isEqualTo(request.getSubject()); - assertThat(mailMessage.getText()).isEqualTo(request.getContent()); + assertThat(mailMessage.getText()).isEqualTo(request.getText()); } } \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java index 4f9cb42..faeb80f 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfigurationTest.java @@ -2,6 +2,7 @@ import com.maciejwalkowiak.mercury.MercuryApplication; import com.maciejwalkowiak.mercury.mail.common.MailingService; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.boot.test.EnvironmentTestUtils; @@ -11,11 +12,15 @@ public class SendGridConfigurationTest { - private AnnotationConfigApplicationContext context; + private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + + @After + public void closeContext() { + this.context.close(); + } @Test public void sendGridMailSenderIsCreatedOnProperty() { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(context, "sendgrid.username"); this.context.register(MercuryApplication.class); this.context.refresh(); @@ -25,7 +30,6 @@ public void sendGridMailSenderIsCreatedOnProperty() { @Test(expected = NoSuchBeanDefinitionException.class) public void sendGridMailSenderIsNotDefinedByDefault() { - this.context = new AnnotationConfigApplicationContext(); this.context.register(MercuryApplication.class); this.context.refresh(); @@ -34,7 +38,6 @@ public void sendGridMailSenderIsNotDefinedByDefault() { @Test public void sendGridMailSenderIsDefinedWhenBothProvidersAreDefined() { - this.context = new AnnotationConfigApplicationContext(); EnvironmentTestUtils.addEnvironment(context, "sendgrid.username"); EnvironmentTestUtils.addEnvironment(context, "spring.mail.host"); this.context.register(MercuryApplication.class); diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml new file mode 100644 index 0000000..a081f76 --- /dev/null +++ b/src/test/resources/logback-test.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From e1ec988d68f357e25b4582515e5d53a02cc6cfae Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 22:33:06 +0100 Subject: [PATCH 20/55] Added validation to SendMailRequest --- .../mercury/mail/common/MailController.java | 3 ++- .../mercury/mail/common/SendMailRequest.java | 16 ++++++++++++---- .../mercury/MercuryApplicationTests.java | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index d5a774d..392e4c6 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.validation.Valid; import java.util.Arrays; import java.util.List; @@ -33,7 +34,7 @@ class MailController implements HateoasController { @RequestMapping(method = RequestMethod.POST) @JsonView(MercuryMessage.View.Summary.class) - public MercuryMessage send(@RequestBody SendMailRequest sendMailRequest) { + public MercuryMessage send(@RequestBody @Valid SendMailRequest sendMailRequest) { return messenger.publish(sendMailRequest); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java index 7de781f..e121de7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java @@ -3,7 +3,10 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.maciejwalkowiak.mercury.core.Request; +import org.hibernate.validator.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; import java.util.List; /** @@ -12,10 +15,13 @@ * @author Maciej Walkowiak */ public class SendMailRequest extends Request { + @NotEmpty private final List to; private final List cc; private final List bcc; + @NotNull private final String text; + @NotNull private final String subject; @JsonCreator @@ -32,7 +38,7 @@ public SendMailRequest(@JsonProperty("to") List to, } public List getTo() { - return to; + return to == null ? new ArrayList<>() : to; } public String getText() { @@ -44,17 +50,19 @@ public String getSubject() { } public List getCc() { - return cc; + return cc == null ? new ArrayList<>() : cc; } public List getBcc() { - return bcc; + return bcc == null ? new ArrayList<>() : bcc; } @Override public String toString() { return "SendMailRequest{" + - "to='" + to + '\'' + + "to=" + to + + ", cc=" + cc + + ", bcc=" + bcc + ", text='" + text + '\'' + ", subject='" + subject + '\'' + '}'; diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index 002826e..acbed6f 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -32,7 +32,7 @@ public void setup() { @Test public void testSendingMailRequest() throws Exception { - this.mockMvc.perform(post("/api/mail").content("{ \"to\":[\"foo@bar.com\"],\"text\":\"bar\" }").contentType(MediaType.APPLICATION_JSON)) + this.mockMvc.perform(post("/api/mail").content("{ \"to\":[\"foo@bar.com\"],\"text\":\"bar\", \"subject\":\"subject\" }").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").exists()) From 8fe9630e36c528111ba0e9b5d9138842cecabf37 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 22:37:01 +0100 Subject: [PATCH 21/55] Refactored test to not hit SendGrid --- .../com/maciejwalkowiak/mercury/MercuryApplicationTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index acbed6f..f2b4f5c 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -17,7 +17,7 @@ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MercuryApplication.class) -@WebIntegrationTest(value = { "sendgrid.username=foo" , "sendgrid.password=bar"}, randomPort = true) +@WebIntegrationTest(randomPort = true) public class MercuryApplicationTests { @Autowired From 27a1bcd7dea32abfbef0e2e157726a81f9cf440e Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 23:07:19 +0100 Subject: [PATCH 22/55] readme update --- README.adoc | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/README.adoc b/README.adoc index 30367f0..eaac373 100644 --- a/README.adoc +++ b/README.adoc @@ -1,33 +1,49 @@ -= mercury-app += Mercury ----------- +____ +In Roman mythology *Mercury* is the patron god of messages and communication. +____ + *Mercury* is an application initially created for _Learning Spring Boot_ contest. -It's purpose is to provide single entry point for sending -*notifications* in a company with internal applications in mind. - -_______________________________________________________________________________________________________________ -If you like the application *please star* this Github repository and +TIP: If you like the application *star* this Github repository and *tweet* about it with #mercuryapp hashtag. -_______________________________________________________________________________________________________________ -== Features +Mercury goal is to provide single **HTTP API** to send notifications in a company with internal applications in mind. -*Notifications* may mean many things. In reality most of notifications -are being sent as an *email* or to applications trying to replace -emails, like **Slack**. +== Use case -Currently following features are implemented: +Quite often there are many custom internal applications used in companies: HR, accounting, business trips, bug trackers and so on. +Each one of them sends some notification to employees and developers have to solve same problems: +- mail server connection configuration +- sending emails in async manner +- queue + +If mail server location or account changes it's details each application has to be reconfigured. + +== Features * sending email through *Java Mail* * sending email through *SendGrid* +* asynchronous message processing - all incoming requests are saved in queue and processed by consumers +* messages are stored in *MongoDB* - you can anytime check if notification has been sent or not and what's the error message if there is any +* health checks - you can easily check if mail server/SendGrid connection is not misconfigured + +Upcoming features: + +* *Slack* integration +* metrics == API -* `GET http://:/api/` - lists all API methods -* `POST http://:/api/mail` - sends mail message +Exposed API is dynamic - meaning that depending on what notifications provider is configured - corresponding endpoint is available. +For example if properties `slack.url` is not set handler for URL `http://:/api/slack` is not created. + +* `GET http://:/api/` - lists all available API methods in current configuration * `GET http://:/api/message/{id}` - get message status +* `POST http://:/api/mail` - sends mail message - available only if *Java Mail* or *SendGrid* is configured == Build & Install @@ -44,7 +60,7 @@ Currently in order to use **Mercury** you need to build it first. == Configuration options -== MongoDB configuration +=== MongoDB configuration ------------------------------------------------------------------------ spring.data.mongodb.uri=mongodb://localhost/test # connection URL From a57107cc5eeda9f3ac5772d9971714a1cb068043 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 23:08:33 +0100 Subject: [PATCH 23/55] Update README.adoc --- README.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.adoc b/README.adoc index eaac373..e96e854 100644 --- a/README.adoc +++ b/README.adoc @@ -17,9 +17,10 @@ Mercury goal is to provide single **HTTP API** to send notifications in a compan Quite often there are many custom internal applications used in companies: HR, accounting, business trips, bug trackers and so on. Each one of them sends some notification to employees and developers have to solve same problems: -- mail server connection configuration -- sending emails in async manner -- queue + +* mail server connection configuration +* sending emails in async manner +* queue If mail server location or account changes it's details each application has to be reconfigured. From 35f8153e432b3989074d035497d11615d2a57d15 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 23:23:16 +0100 Subject: [PATCH 24/55] Added drone.io badge to README --- README.adoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.adoc b/README.adoc index e96e854..5e0070a 100644 --- a/README.adoc +++ b/README.adoc @@ -1,6 +1,8 @@ = Mercury ----------- +image:https://drone.io/github.com/maciejwalkowiak/contest/status.png[link=https://drone.io/github.com/maciejwalkowiak/contest/latest] + ____ In Roman mythology *Mercury* is the patron god of messages and communication. ____ From a6942d32ded20f0e11db565ff6cf967e901cf3ad Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Tue, 13 Jan 2015 23:57:53 +0100 Subject: [PATCH 25/55] Reactor uses not threadpool --- .../com/maciejwalkowiak/mercury/core/CoreConfiguration.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 3457459..3931585 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -13,6 +13,9 @@ class CoreConfiguration { @Bean public Reactor rootReactor(Environment env) { - return Reactors.reactor().env(env).get(); + return Reactors.reactor() + .env(env) + .dispatcher(Environment.THREAD_POOL) + .get(); } } From e788277025e0a9d82e7d9fe61cc777264382b2b8 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 00:10:05 +0100 Subject: [PATCH 26/55] Added assembly plugin for building zip releases --- etc/application.properties | 17 +++++++++++++++++ pom.xml | 8 ++++++++ src/main/assembly/zip.xml | 25 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 etc/application.properties create mode 100644 src/main/assembly/zip.xml diff --git a/etc/application.properties b/etc/application.properties new file mode 100644 index 0000000..7f8b0f4 --- /dev/null +++ b/etc/application.properties @@ -0,0 +1,17 @@ +# Mercury configuration + +# MongoDB - required +#spring.data.mongodb.uri=mongodb://localhost/test # connection URL +#spring.data.mongodb.database= +#spring.data.mongodb.username= +#spring.data.mongodb.password= + +# JavaMail configuration - optional +#spring.mail.host= +#spring.mail.port= +#spring.mail.username= +#spring.mail.password= + +# SendGrid configuration - optional +#sendgrid.username= +#sendgrid.password= \ No newline at end of file diff --git a/pom.xml b/pom.xml index ce191d0..4cb8e7b 100644 --- a/pom.xml +++ b/pom.xml @@ -76,6 +76,14 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-assembly-plugin + 2.5.1 + + src/main/assembly/zip.xml + + diff --git a/src/main/assembly/zip.xml b/src/main/assembly/zip.xml new file mode 100644 index 0000000..812f7ff --- /dev/null +++ b/src/main/assembly/zip.xml @@ -0,0 +1,25 @@ + + dist + + zip + + false + + + ${project.basedir}/etc + config + + * + + + + ${project.build.directory} + + + *.jar + + + + \ No newline at end of file From 89fd604ce9786154293a86972847efb4f37bbe04 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 00:17:35 +0100 Subject: [PATCH 27/55] Removed defaults from application.properties --- src/main/resources/application.properties | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 82bbab4..e69de29 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,9 +0,0 @@ -#spring.mail.host=smtp.gmail.com -#spring.mail.port=587 -#spring.mail.username=* -#spring.mail.password=* -#spring.mail.properties.mail.smtp.auth=true -#spring.mail.properties.mail.smtp.starttls.enable=true - -#sendgrid.username= -#sendgrid.password= \ No newline at end of file From 73ec13807b3c79cec87c1c9f518e2ed436a9f421 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 00:18:16 +0100 Subject: [PATCH 28/55] Assembly creates now zip with files nested inside a directory --- src/main/assembly/zip.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/assembly/zip.xml b/src/main/assembly/zip.xml index 812f7ff..3df653b 100644 --- a/src/main/assembly/zip.xml +++ b/src/main/assembly/zip.xml @@ -9,14 +9,14 @@ ${project.basedir}/etc - config + ${project.artifactId}-${project.version}/config * ${project.build.directory} - + ${project.artifactId}-${project.version} *.jar From 4f12ee42477a7a98b5368897309372304afd2163 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 00:24:27 +0100 Subject: [PATCH 29/55] Version 0.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4cb8e7b..cb5f038 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.maciejwalkowiak mercury - 0.0.1-SNAPSHOT + 0.0.1 jar Hermes Messenger From 739dd2cb8bf95b7bb20eff9ac7ae589546c0b7fd Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 00:30:06 +0100 Subject: [PATCH 30/55] Update README.adoc --- README.adoc | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/README.adoc b/README.adoc index 5e0070a..9e13653 100644 --- a/README.adoc +++ b/README.adoc @@ -48,18 +48,13 @@ For example if properties `slack.url` is not set handler for URL `http://: * `GET http://:/api/message/{id}` - get message status * `POST http://:/api/mail` - sends mail message - available only if *Java Mail* or *SendGrid* is configured -== Build & Install -*Mercury* uses MongoDB to store messages data. Make sure you have running MongoDB instance and appropriate configuration. +== How to run -Currently in order to use **Mercury** you need to build it first. - -. `git clone https://github.com/maciejwalkowiak/contest.git` -. `cd contest` -. `mvn package` -. Copy JAR file to some location for example `/opt/mercuryapp`: `cp target/mercury-0.0.1-SNAPSHOT.jar /opt/mercuryapp` -. Inside `/opt/mercuryapp` create file `application.properties` and configure mail providers -. Execute `java -jar mercury-0.0.1-SNAPSHOT.jar` +* install required software: Java 8 and MongoDB +* https://github.com/maciejwalkowiak/contest/releases/[Download latest release] +* configure `config/application.properties` +* run with `java -jar mercury-.jar` == Configuration options From a75947d9eb648124f4a6b2ceba02011357d4796b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Wed, 14 Jan 2015 01:20:41 +0100 Subject: [PATCH 31/55] Update README.adoc --- README.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index 9e13653..80e8d43 100644 --- a/README.adoc +++ b/README.adoc @@ -1,5 +1,4 @@ -= Mercury ------------ +image::https://raw.githubusercontent.com/maciejwalkowiak/contest/gh-pages/mercury.png[] image:https://drone.io/github.com/maciejwalkowiak/contest/status.png[link=https://drone.io/github.com/maciejwalkowiak/contest/latest] From c51578400319f0ba72a25cbb2466665a5b7ef2fe Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 15 Jan 2015 16:12:53 +0100 Subject: [PATCH 32/55] Slack integration --- README.adoc | 15 ++++- etc/application.properties | 5 +- .../mercury/core/MessengerImpl.java | 5 ++ .../mercury/slack/SlackConfiguration.java | 31 ++++++++++ .../mercury/slack/SlackConsumer.java | 34 ++++++++++ .../mercury/slack/SlackController.java | 40 ++++++++++++ .../mercury/slack/SlackRequest.java | 62 +++++++++++++++++++ .../mercury/slack/SlackService.java | 21 +++++++ 8 files changed, 210 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/slack/SlackRequest.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java diff --git a/README.adoc b/README.adoc index 80e8d43..48e766c 100644 --- a/README.adoc +++ b/README.adoc @@ -28,14 +28,14 @@ If mail server location or account changes it's details each application has to == Features * sending email through *Java Mail* -* sending email through *SendGrid* +* sending email through *https://sendgrid.com/[SendGrid]* +* sending messages to *https://slack.com[Slack]* * asynchronous message processing - all incoming requests are saved in queue and processed by consumers * messages are stored in *MongoDB* - you can anytime check if notification has been sent or not and what's the error message if there is any * health checks - you can easily check if mail server/SendGrid connection is not misconfigured Upcoming features: -* *Slack* integration * metrics == API @@ -46,6 +46,7 @@ For example if properties `slack.url` is not set handler for URL `http://: * `GET http://:/api/` - lists all available API methods in current configuration * `GET http://:/api/message/{id}` - get message status * `POST http://:/api/mail` - sends mail message - available only if *Java Mail* or *SendGrid* is configured +* `POST http://:/api/slack` - sends slack message - available only if *Slack* is configured == How to run @@ -109,3 +110,13 @@ sendgrid.proxy.port= *IMPORTANT:* if both Java Mail *and* SendGrid configuration is provided - all emails will be sent using SendGrid. + +=== Slack configuration + +Put following properties to `application.properties` file: + +------------------ +slack.hook.url= +------------------ + +Learn more about Slack webhooks at https://api.slack.com/ diff --git a/etc/application.properties b/etc/application.properties index 7f8b0f4..287688c 100644 --- a/etc/application.properties +++ b/etc/application.properties @@ -14,4 +14,7 @@ # SendGrid configuration - optional #sendgrid.username= -#sendgrid.password= \ No newline at end of file +#sendgrid.password= + +# Slack configuration +#slack.hook.url= \ No newline at end of file diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java index 035ade9..0017590 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java @@ -1,5 +1,7 @@ package com.maciejwalkowiak.mercury.core; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import reactor.core.Reactor; @@ -12,6 +14,8 @@ */ @Component class MessengerImpl implements Messenger { + private static final Logger LOG = LoggerFactory.getLogger(MessengerImpl.class); + private final MercuryMessageRepository repository; private final QueueNameObtainer queueNameObtainer; private final Reactor rootReactor; @@ -25,6 +29,7 @@ class MessengerImpl implements Messenger { @Override public MercuryMessage publish(Request request) { + LOG.info("Received request: {}", request); MercuryMessage message = MercuryMessage.queued(request); repository.save(message); rootReactor.notify(queueNameObtainer.getQueueName(request), Event.wrap(message)); diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java new file mode 100644 index 0000000..c945f19 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java @@ -0,0 +1,31 @@ +package com.maciejwalkowiak.mercury.slack; + +import com.maciejwalkowiak.mercury.core.QueueNameObtainer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import reactor.core.Reactor; + +import javax.annotation.PostConstruct; + +import static reactor.event.selector.Selectors.$; + +@Configuration +@ComponentScan +@ConditionalOnProperty(name = "slack.hook.url") +class SlackConfiguration { + @Autowired + private SlackConsumer slackConsumer; + + @Autowired + private Reactor reactor; + + @Autowired + private QueueNameObtainer queueNameObtainer; + + @PostConstruct + void initReactor() { + reactor.on($(queueNameObtainer.getQueueName(SlackRequest.class)), slackConsumer); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java new file mode 100644 index 0000000..e77a5b1 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java @@ -0,0 +1,34 @@ +package com.maciejwalkowiak.mercury.slack; + +import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.Messenger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import reactor.event.Event; +import reactor.function.Consumer; + +@Component +class SlackConsumer implements Consumer>> { + private static final Logger LOG = LoggerFactory.getLogger(SlackConsumer.class); + private final SlackService slackService; + private final Messenger messenger; + + @Autowired + public SlackConsumer(SlackService slackService, Messenger messenger) { + this.slackService = slackService; + this.messenger = messenger; + } + + @Override + public void accept(Event> event) { + try { + slackService.send(event.getData().getRequest()); + messenger.messageSent(event.getData()); + } catch (Exception e) { + LOG.error("Sending Slack message failed", e); + messenger.deliveryFailed(event.getData(), e.getMessage()); + } + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java new file mode 100644 index 0000000..e11fec3 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -0,0 +1,40 @@ +package com.maciejwalkowiak.mercury.slack; + +import com.fasterxml.jackson.annotation.JsonView; +import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.api.HateoasController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.validation.Valid; +import java.util.Arrays; +import java.util.List; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; + +@RestController +@RequestMapping("/api/slack") +class SlackController implements HateoasController { + private final Messenger messenger; + + @Autowired + public SlackController(Messenger messenger) { + this.messenger = messenger; + } + + @RequestMapping(method = RequestMethod.POST) + @JsonView(MercuryMessage.View.Summary.class) + public MercuryMessage send(@RequestBody @Valid SlackRequest slackRequest) { + return messenger.publish(slackRequest); + } + + @Override + public List links() { + return Arrays.asList(linkTo(SlackController.class).withRel("slack")); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackRequest.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackRequest.java new file mode 100644 index 0000000..c27965e --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackRequest.java @@ -0,0 +1,62 @@ +package com.maciejwalkowiak.mercury.slack; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.maciejwalkowiak.mercury.core.Request; + +import javax.validation.constraints.NotNull; + +class SlackRequest extends Request { + @NotNull + private final String text; + private final String username; + private final String iconUrl; + private final String iconEmoji; + private final String channel; + + @JsonCreator + public SlackRequest(@JsonProperty("text") String text, + @JsonProperty("username") String username, + @JsonProperty("icon_url") String iconUrl, + @JsonProperty("icon_emoji") String iconEmoji, + @JsonProperty("channel") String channel) { + this.text = text; + this.username = username; + this.iconUrl = iconUrl; + this.iconEmoji = iconEmoji; + this.channel = channel; + } + + public String getText() { + return text; + } + + public String getUsername() { + return username; + } + + @JsonProperty("icon_url") + public String getIconUrl() { + return iconUrl; + } + + @JsonProperty("icon_emoji") + public String getIconEmoji() { + return iconEmoji; + } + + public String getChannel() { + return channel; + } + + @Override + public String toString() { + return "SlackRequest{" + + "text='" + text + '\'' + + ", username='" + username + '\'' + + ", iconUrl='" + iconUrl + '\'' + + ", iconEmoji='" + iconEmoji + '\'' + + ", channel='" + channel + '\'' + + '}'; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java new file mode 100644 index 0000000..396e181 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java @@ -0,0 +1,21 @@ +package com.maciejwalkowiak.mercury.slack; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +class SlackService { + private final RestTemplate restTemplate = new RestTemplate(); + private final String slackWebHookUrl; + + @Autowired + public SlackService(@Value("${slack.hook.url}") String slackWebHookUrl) { + this.slackWebHookUrl = slackWebHookUrl; + } + + public void send(SlackRequest slackRequest) { + restTemplate.postForEntity(slackWebHookUrl, slackRequest, String.class); + } +} From 824078636664a83f4f20b0d5e012b3bdb6bbe1aa Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Thu, 15 Jan 2015 20:03:29 +0100 Subject: [PATCH 33/55] Version 0.0.2-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cb5f038..7ed6ed0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.maciejwalkowiak mercury - 0.0.1 + 0.0.2-SNAPSHOT jar Hermes Messenger From 4844a93fae580e28aac0fbd1d54f6a3585ba8f3c Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 00:02:52 +0100 Subject: [PATCH 34/55] Improved exception handling and formatting error messages to user friendly form. --- .../GlobalControllerExceptionHandler.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/GlobalControllerExceptionHandler.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/GlobalControllerExceptionHandler.java b/src/main/java/com/maciejwalkowiak/mercury/core/GlobalControllerExceptionHandler.java new file mode 100644 index 0000000..7453ded --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/GlobalControllerExceptionHandler.java @@ -0,0 +1,51 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Stream.concat; + +/** + * Converts exceptions to user friendly error messages. + * + * Inspired by http://www.jayway.com/2012/09/16/improve-your-spring-rest-api-part-i/ + * + * @author Maciej Walkowiak + */ +@ControllerAdvice +class GlobalControllerExceptionHandler { + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + ErrorMessage handleException(MethodArgumentNotValidException ex) { + Stream fieldErrorsStream = ex.getBindingResult().getFieldErrors().stream() + .map(error -> error.getField() + ", " + error.getDefaultMessage()); + + Stream globalErrorsStream = ex.getBindingResult().getGlobalErrors().stream() + .map(error -> error.getObjectName() + ", " + error.getDefaultMessage()); + + List errors = concat(fieldErrorsStream, globalErrorsStream).collect(Collectors.toList()); + + return new ErrorMessage(errors); + } + + private static class ErrorMessage { + private final List errors; + + public ErrorMessage(List errors) { + this.errors = errors; + } + + public List getErrors() { + return errors; + } + } +} From 74d24290dcd58bf947caac445fb4a1f35c608d86 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 00:03:09 +0100 Subject: [PATCH 35/55] Minor refactoring and cleanup --- .../core/MercuryMessageController.java | 8 ++++-- .../mercury/core/api/ApiController.java | 3 +- .../mercury/mail/common/MailConsumer.java | 1 + .../mercury/mail/common/MailController.java | 8 ++++-- .../sendgrid/SendGridHealthIndicator.java | 28 +++++++++++++------ .../mercury/slack/SlackConsumer.java | 1 + .../mercury/slack/SlackController.java | 8 ++++-- 7 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index 1c58075..d24e1f0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -4,6 +4,8 @@ import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,8 +36,10 @@ class MercuryMessageController implements HateoasController { @RequestMapping(value = "{id}", method = RequestMethod.GET) @JsonView(MercuryMessage.View.Summary.class) - MercuryMessage message(@PathVariable String id) { - return mercuryMessageRepository.findOne(id); + ResponseEntity message(@PathVariable String id) { + MercuryMessage message = mercuryMessageRepository.findOne(id); + + return message != null ? new ResponseEntity<>(message, HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NOT_FOUND); } @Override diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java index c4fe282..c2435c7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -1,5 +1,6 @@ package com.maciejwalkowiak.mercury.core.api; +import com.maciejwalkowiak.mercury.core.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.ResourceSupport; import org.springframework.http.HttpEntity; @@ -29,7 +30,7 @@ class ApiController { this.controllers = controllers; } - @RequestMapping(method = RequestMethod.GET) + @RequestMapping(method = { RequestMethod.GET, RequestMethod.OPTIONS }) public HttpEntity links() { ApiResource apiResource = new ApiResource(); apiResource.add(linkTo(ApiController.class).withSelfRel()); diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index 2534e6b..d25d92e 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -24,6 +24,7 @@ @Component class MailConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); + private final Optional mailingService; private final Messenger messenger; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index 392e4c6..cc4cf25 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -6,6 +6,8 @@ import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -34,8 +36,10 @@ class MailController implements HateoasController { @RequestMapping(method = RequestMethod.POST) @JsonView(MercuryMessage.View.Summary.class) - public MercuryMessage send(@RequestBody @Valid SendMailRequest sendMailRequest) { - return messenger.publish(sendMailRequest); + public ResponseEntity send(@RequestBody @Valid SendMailRequest sendMailRequest) { + MercuryMessage message = messenger.publish(sendMailRequest); + + return new ResponseEntity<>(message, HttpStatus.CREATED); } @Override diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java index 513ee8a..b4d8d72 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java @@ -12,6 +12,11 @@ import java.util.HashMap; import java.util.Map; +/** + * Checks if connection to SendGrid is working and if it's possible to get profile with provided credentials. + * + * @author Maciej Walkowiak + */ @Component class SendGridHealthIndicator extends AbstractHealthIndicator { private static final String GET_PROFILE_URL = "https://api.sendgrid.com/api/profile.get.json"; @@ -27,14 +32,19 @@ class SendGridHealthIndicator extends AbstractHealthIndicator { @Override protected void doHealthCheck(Health.Builder builder) throws Exception { - SendGridResponse sendGridResponse = restTemplate.getForObject(GET_PROFILE_URL, SendGridResponse.class, - getAuthenticationProperties()); + try { + SendGridResponse sendGridResponse = restTemplate.getForObject(GET_PROFILE_URL, SendGridResponse.class, + getAuthenticationProperties()); - if (sendGridResponse.error != null) { - builder.down().withDetail("message", sendGridResponse.error.message); - } else { - builder.up(); + if (sendGridResponse.error != null) { + builder.down().withDetail("message", sendGridResponse.error.message); + } else { + builder.up(); + } + } catch (Exception e) { + builder.down().withException(e); } + } private Map getAuthenticationProperties() { @@ -46,7 +56,7 @@ private Map getAuthenticationProperties() { } private static class SendGridResponse { - private ErrorDetails error; + private final ErrorDetails error; @JsonCreator public SendGridResponse(@JsonProperty("error") ErrorDetails error) { @@ -54,8 +64,8 @@ public SendGridResponse(@JsonProperty("error") ErrorDetails error) { } private static class ErrorDetails { - private String code; - private String message; + private final String code; + private final String message; @JsonCreator public ErrorDetails(@JsonProperty("code") String code, @JsonProperty("message") String message) { diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java index e77a5b1..39af05d 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java @@ -12,6 +12,7 @@ @Component class SlackConsumer implements Consumer>> { private static final Logger LOG = LoggerFactory.getLogger(SlackConsumer.class); + private final SlackService slackService; private final Messenger messenger; diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java index e11fec3..ee7d2c1 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -6,6 +6,8 @@ import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -29,8 +31,10 @@ public SlackController(Messenger messenger) { @RequestMapping(method = RequestMethod.POST) @JsonView(MercuryMessage.View.Summary.class) - public MercuryMessage send(@RequestBody @Valid SlackRequest slackRequest) { - return messenger.publish(slackRequest); + public ResponseEntity send(@RequestBody @Valid SlackRequest slackRequest) { + MercuryMessage message = messenger.publish(slackRequest); + + return new ResponseEntity<>(message, HttpStatus.CREATED); } @Override From 27a6e7b8fd24b2a19c2d1f100a3741397d7ef159 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 00:33:05 +0100 Subject: [PATCH 36/55] Improved HTTP responses. Now return proper status code and headers --- .../mercury/core/MercuryMessageController.java | 4 ++-- .../maciejwalkowiak/mercury/core/api/ApiController.java | 1 - .../mercury/mail/common/MailController.java | 9 +++++++-- .../maciejwalkowiak/mercury/slack/SlackController.java | 9 +++++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java index d24e1f0..6f58a65 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java @@ -26,7 +26,7 @@ */ @RestController @RequestMapping("/api/message/") -class MercuryMessageController implements HateoasController { +public class MercuryMessageController implements HateoasController { private final MercuryMessageRepository mercuryMessageRepository; @Autowired @@ -36,7 +36,7 @@ class MercuryMessageController implements HateoasController { @RequestMapping(value = "{id}", method = RequestMethod.GET) @JsonView(MercuryMessage.View.Summary.class) - ResponseEntity message(@PathVariable String id) { + public ResponseEntity message(@PathVariable String id) { MercuryMessage message = mercuryMessageRepository.findOne(id); return message != null ? new ResponseEntity<>(message, HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NOT_FOUND); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java index c2435c7..cd2afad 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -1,6 +1,5 @@ package com.maciejwalkowiak.mercury.core.api; -import com.maciejwalkowiak.mercury.core.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.ResourceSupport; import org.springframework.http.HttpEntity; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index cc4cf25..9715d53 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.MercuryMessageController; import com.maciejwalkowiak.mercury.core.Messenger; import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,7 @@ import java.util.List; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; /** * API for receiving {@link com.maciejwalkowiak.mercury.mail.common.SendMailRequest} requests @@ -36,10 +38,13 @@ class MailController implements HateoasController { @RequestMapping(method = RequestMethod.POST) @JsonView(MercuryMessage.View.Summary.class) - public ResponseEntity send(@RequestBody @Valid SendMailRequest sendMailRequest) { + public ResponseEntity send(@RequestBody @Valid SendMailRequest sendMailRequest) { MercuryMessage message = messenger.publish(sendMailRequest); - return new ResponseEntity<>(message, HttpStatus.CREATED); + return ResponseEntity + .status(HttpStatus.CREATED) + .location(linkTo(methodOn(MercuryMessageController.class).message(message.getId())).toUri()) + .build(); } @Override diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java index ee7d2c1..e185783 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.MercuryMessage; +import com.maciejwalkowiak.mercury.core.MercuryMessageController; import com.maciejwalkowiak.mercury.core.Messenger; import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,7 @@ import java.util.List; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; @RestController @RequestMapping("/api/slack") @@ -31,10 +33,13 @@ public SlackController(Messenger messenger) { @RequestMapping(method = RequestMethod.POST) @JsonView(MercuryMessage.View.Summary.class) - public ResponseEntity send(@RequestBody @Valid SlackRequest slackRequest) { + public ResponseEntity send(@RequestBody @Valid SlackRequest slackRequest) { MercuryMessage message = messenger.publish(slackRequest); - return new ResponseEntity<>(message, HttpStatus.CREATED); + return ResponseEntity + .status(HttpStatus.CREATED) + .location(linkTo(methodOn(MercuryMessageController.class).message(message.getId())).toUri()) + .build(); } @Override From 6f71a383ceee3f4feb21162a387d291cb36675af Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 00:37:15 +0100 Subject: [PATCH 37/55] Fixed unit test --- .../maciejwalkowiak/mercury/MercuryApplicationTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index f2b4f5c..c2dfbfa 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -3,6 +3,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.internal.matchers.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; @@ -33,9 +34,8 @@ public void setup() { @Test public void testSendingMailRequest() throws Exception { this.mockMvc.perform(post("/api/mail").content("{ \"to\":[\"foo@bar.com\"],\"text\":\"bar\", \"subject\":\"subject\" }").contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) - .andExpect(jsonPath("$.id").exists()) - .andExpect(jsonPath("$.status").exists()); + .andExpect(header().string("Location", NotNull.NOT_NULL)); } } From 1a5e04f614169c4e1cbf3727bc9a1b7fa63e294b Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 01:20:14 +0100 Subject: [PATCH 38/55] Running tests is now possible without running MongoDB instance thanks to Fongo --- pom.xml | 6 ++++++ .../mercury/MercuryApplicationTests.java | 11 +++++++++++ 2 files changed, 17 insertions(+) diff --git a/pom.xml b/pom.xml index 7ed6ed0..7de2aa6 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,12 @@ 1.7.0 test + + com.github.fakemongo + fongo + 1.5.9 + test + diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index c2dfbfa..b30b3e6 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -1,5 +1,7 @@ package com.maciejwalkowiak.mercury; +import com.github.fakemongo.Fongo; +import com.mongodb.Mongo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -7,6 +9,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; @@ -20,6 +24,13 @@ @SpringApplicationConfiguration(classes = MercuryApplication.class) @WebIntegrationTest(randomPort = true) public class MercuryApplicationTests { + @Configuration + static class Config { + @Bean + Mongo mongo() { + return new Fongo("mongo").getMongo(); + } + } @Autowired private WebApplicationContext wac; From 24aad566db53b83b502db2deb86f65cb50b79cd6 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 02:05:52 +0100 Subject: [PATCH 39/55] Replaced Reactor with custom solution --- pom.xml | 4 - .../mercury/core/AsyncMessagePublisher.java | 39 ++++++++ .../mercury/core/Consumer.java | 7 ++ .../mercury/core/CoreConfiguration.java | 11 --- .../core/MercuryMessageRepository.java | 9 -- .../mercury/core/MessagePublisher.java | 8 ++ .../mercury/core/Messenger.java | 30 ------ .../mercury/core/MessengerImpl.java | 51 ---------- .../mercury/core/QueueNameObtainer.java | 23 ----- .../core/message/AsyncMessageNotifier.java | 43 +++++++++ .../Message.java} | 15 +-- .../MessageController.java} | 16 ++-- .../mercury/core/message/MessageNotifier.java | 8 ++ .../core/message/MessageRepository.java | 9 ++ .../mercury/core/message/MessageService.java | 19 ++++ .../core/message/MessageServiceImpl.java | 38 ++++++++ .../mercury/core/message/Messenger.java | 18 ++++ .../mercury/core/message/MessengerImpl.java | 35 +++++++ .../mail/common/MailConfiguration.java | 20 ---- .../mercury/mail/common/MailConsumer.java | 26 +++--- .../mercury/mail/common/MailController.java | 12 +-- .../mercury/slack/SlackConfiguration.java | 19 ---- .../mercury/slack/SlackConsumer.java | 23 +++-- .../mercury/slack/SlackController.java | 12 +-- .../mercury/core/MessengerImplTest.java | 92 ------------------- .../mercury/core/QueueNameObtainerTest.java | 28 ------ .../core/message/MessageServiceImplTest.java | 59 ++++++++++++ .../core/message/MessengerImplTest.java | 55 +++++++++++ .../mercury/mail/common/MailConsumerTest.java | 32 +++---- 29 files changed, 404 insertions(+), 357 deletions(-) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java rename src/main/java/com/maciejwalkowiak/mercury/core/{MercuryMessage.java => message/Message.java} (74%) rename src/main/java/com/maciejwalkowiak/mercury/core/{MercuryMessageController.java => message/MessageController.java} (70%) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java delete mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java delete mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java create mode 100644 src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java diff --git a/pom.xml b/pom.xml index 7de2aa6..629c0be 100644 --- a/pom.xml +++ b/pom.xml @@ -44,10 +44,6 @@ org.springframework.hateoas spring-hateoas - - org.projectreactor.spring - reactor-spring-context - com.fasterxml.jackson.core jackson-databind diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java b/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java new file mode 100644 index 0000000..3b863ea --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java @@ -0,0 +1,39 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +class AsyncMessagePublisher implements MessagePublisher { + private final Map consumersMap = new HashMap<>(); + private final List consumers; + + @Autowired + AsyncMessagePublisher(List consumers) { + this.consumers = consumers; + } + + @PostConstruct + public void initConsumersMap() { + consumers.stream().forEach(c -> { + Class persistentClass = (Class) ((ParameterizedType)c.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; + + consumersMap.put(persistentClass, c); + }); + } + + @Override + @Async + public void notifyConsumers(MercuryMessage message) { + if (consumersMap.containsKey(message.getRequest().getClass())) { + consumersMap.get(message.getRequest().getClass()).consume(message); + } + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java b/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java new file mode 100644 index 0000000..565ce7c --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java @@ -0,0 +1,7 @@ +package com.maciejwalkowiak.mercury.core; + +import com.maciejwalkowiak.mercury.core.message.Message; + +public interface Consumer { + void consume(Message message); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 3931585..bccf99a 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -1,21 +1,10 @@ package com.maciejwalkowiak.mercury.core; -import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import reactor.core.Environment; -import reactor.core.Reactor; -import reactor.core.spec.Reactors; @Configuration @ComponentScan class CoreConfiguration { - @Bean - public Reactor rootReactor(Environment env) { - return Reactors.reactor() - .env(env) - .dispatcher(Environment.THREAD_POOL) - .get(); - } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java b/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java deleted file mode 100644 index 27e84ce..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.data.mongodb.repository.MongoRepository; - -/** - * MongoDB based repository for {@link com.maciejwalkowiak.mercury.core.MercuryMessage} - */ -interface MercuryMessageRepository extends MongoRepository { -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java new file mode 100644 index 0000000..b827438 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java @@ -0,0 +1,8 @@ +package com.maciejwalkowiak.mercury.core; + +import org.springframework.scheduling.annotation.Async; + +public interface MessagePublisher { + @Async + void notifyConsumers(MercuryMessage message); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java deleted file mode 100644 index fa4717c..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Messenger.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -/** - * Responsible for creating messages and changing it's status. - * - * @author Maciej Walkowiak - */ -public interface Messenger { - /** - * Creates {@link com.maciejwalkowiak.mercury.core.MercuryMessage} and publishes it into queue for processing - * - * @param request - request contains message details like content, recipients etc - * @return message with status "QUEUED" - */ - MercuryMessage publish(Request request); - - /** - * Invoked after message has been successfully sent. Changes it's status to "SENT" - * - * @param message sent message - */ - void messageSent(MercuryMessage message); - - /** - * Invoked when sending message has failed. Changes it's status to "FAILED" and saves error details - * @param message - message that sending has failed - * @param errorMessage - error details - */ - void deliveryFailed(MercuryMessage message, String errorMessage); -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java deleted file mode 100644 index 0017590..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessengerImpl.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import reactor.core.Reactor; -import reactor.event.Event; - -/** - * http://reactor.github.io/ based {@link com.maciejwalkowiak.mercury.core.Messenger} implementation - * - * @author Maciej Walkowiak - */ -@Component -class MessengerImpl implements Messenger { - private static final Logger LOG = LoggerFactory.getLogger(MessengerImpl.class); - - private final MercuryMessageRepository repository; - private final QueueNameObtainer queueNameObtainer; - private final Reactor rootReactor; - - @Autowired - MessengerImpl(MercuryMessageRepository repository, QueueNameObtainer queueNameObtainer, Reactor rootReactor) { - this.repository = repository; - this.queueNameObtainer = queueNameObtainer; - this.rootReactor = rootReactor; - } - - @Override - public MercuryMessage publish(Request request) { - LOG.info("Received request: {}", request); - MercuryMessage message = MercuryMessage.queued(request); - repository.save(message); - rootReactor.notify(queueNameObtainer.getQueueName(request), Event.wrap(message)); - - return message; - } - - @Override - public void messageSent(MercuryMessage message) { - message.sent(); - repository.save(message); - } - - @Override - public void deliveryFailed(MercuryMessage message, String errorMessage) { - message.failed(errorMessage); - repository.save(message); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java b/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java deleted file mode 100644 index d39c8cc..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/QueueNameObtainer.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.stereotype.Component; - -/** - * Queue name needs to be known when message is sent to queue and also when listener subscribes to queue. - * QueueNameObtainer returns same queue name for object and for it's class. - * - * See {@link com.maciejwalkowiak.mercury.core.MessengerImpl} - * and {@link com.maciejwalkowiak.mercury.mail.common.MailConfiguration} - * - * @author Maciej Walkowiak - */ -@Component -public class QueueNameObtainer { - public String getQueueName(Class clazz) { - return clazz.getName(); - } - - public String getQueueName(Request request) { - return request.getClass().getName(); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java new file mode 100644 index 0000000..036d783 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java @@ -0,0 +1,43 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.core.Consumer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +import javax.annotation.PostConstruct; +import java.lang.reflect.ParameterizedType; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Component +class AsyncMessageNotifier implements MessageNotifier { + private final Map consumersMap = new HashMap<>(); + private final List consumers; + + @Autowired + AsyncMessageNotifier(List consumers) { + this.consumers = consumers; + } + + @PostConstruct + public void initConsumersMap() { + consumers.stream().forEach(c -> { + Class persistentClass = (Class) ((ParameterizedType)c.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; + + consumersMap.put(persistentClass, c); + }); + } + + @Override + @Async + public void notifyConsumers(Message message) { + Assert.notNull(message); + + if (consumersMap.containsKey(message.getRequest().getClass())) { + consumersMap.get(message.getRequest().getClass()).consume(message); + } + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java similarity index 74% rename from src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java rename to src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java index a88cc12..4fd85d3 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessage.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java @@ -1,7 +1,8 @@ -package com.maciejwalkowiak.mercury.core; +package com.maciejwalkowiak.mercury.core.message; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonView; +import com.maciejwalkowiak.mercury.core.Request; import org.springframework.data.annotation.Id; /** @@ -10,14 +11,14 @@ * - status * - and error message if sending failed * - * It's instances are saved in MongoDB by {@link com.maciejwalkowiak.mercury.core.MercuryMessageRepository} + * It's instances are saved in MongoDB by {@link MessageRepository} * * @param - request class * * @author Maciej Walkowiak */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class MercuryMessage { +public class Message { public enum Status { QUEUED, SENT, @@ -36,16 +37,16 @@ public enum Status { /** * Public no-arg controller required by Spring Hateoas */ - public MercuryMessage() { + public Message() { } - private MercuryMessage(Status status, T request) { + private Message(Status status, T request) { this.status = status; this.request = request; } - public static MercuryMessage queued(T request) { - return new MercuryMessage<>(Status.QUEUED, request); + public static Message queued(T request) { + return new Message<>(Status.QUEUED, request); } void sent() { diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java similarity index 70% rename from src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java rename to src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java index 6f58a65..537bf2a 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MercuryMessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java @@ -1,4 +1,4 @@ -package com.maciejwalkowiak.mercury.core; +package com.maciejwalkowiak.mercury.core.message; import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.api.HateoasController; @@ -26,24 +26,24 @@ */ @RestController @RequestMapping("/api/message/") -public class MercuryMessageController implements HateoasController { - private final MercuryMessageRepository mercuryMessageRepository; +public class MessageController implements HateoasController { + private final MessageRepository messageRepository; @Autowired - MercuryMessageController(MercuryMessageRepository mercuryMessageRepository) { - this.mercuryMessageRepository = mercuryMessageRepository; + MessageController(MessageRepository messageRepository) { + this.messageRepository = messageRepository; } @RequestMapping(value = "{id}", method = RequestMethod.GET) - @JsonView(MercuryMessage.View.Summary.class) + @JsonView(Message.View.Summary.class) public ResponseEntity message(@PathVariable String id) { - MercuryMessage message = mercuryMessageRepository.findOne(id); + Message message = messageRepository.findOne(id); return message != null ? new ResponseEntity<>(message, HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NOT_FOUND); } @Override public List links() { - return Arrays.asList(linkTo(methodOn(MercuryMessageController.class).message("{id}")).withRel("message")); + return Arrays.asList(linkTo(methodOn(MessageController.class).message("{id}")).withRel("message")); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java new file mode 100644 index 0000000..5b73f4a --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java @@ -0,0 +1,8 @@ +package com.maciejwalkowiak.mercury.core.message; + +import org.springframework.scheduling.annotation.Async; + +public interface MessageNotifier { + @Async + void notifyConsumers(Message message); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java new file mode 100644 index 0000000..ed540d2 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java @@ -0,0 +1,9 @@ +package com.maciejwalkowiak.mercury.core.message; + +import org.springframework.data.mongodb.repository.MongoRepository; + +/** + * MongoDB based repository for {@link Message} + */ +interface MessageRepository extends MongoRepository { +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java new file mode 100644 index 0000000..6a3a563 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java @@ -0,0 +1,19 @@ +package com.maciejwalkowiak.mercury.core.message; + +public interface MessageService { + void save(Message message); + + /** + * Invoked after message has been successfully sent. Changes it's status to "SENT" + * + * @param message sent message + */ + void messageSent(Message message); + + /** + * Invoked when sending message has failed. Changes it's status to "FAILED" and saves error details + * @param message - message that sending has failed + * @param errorMessage - error details + */ + void deliveryFailed(Message message, String errorMessage); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java new file mode 100644 index 0000000..7be811f --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java @@ -0,0 +1,38 @@ +package com.maciejwalkowiak.mercury.core.message; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +@Service +class MessageServiceImpl implements MessageService { + private final MessageRepository repository; + + @Autowired + MessageServiceImpl(MessageRepository repository) { + this.repository = repository; + } + + @Override + public void save(Message message) { + Assert.notNull(message); + + repository.save(message); + } + + @Override + public void messageSent(Message message) { + Assert.notNull(message); + + message.sent(); + repository.save(message); + } + + @Override + public void deliveryFailed(Message message, String errorMessage) { + Assert.notNull(message); + + message.failed(errorMessage); + repository.save(message); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java new file mode 100644 index 0000000..f40fd7b --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java @@ -0,0 +1,18 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.core.Request; + +/** + * Responsible for creating messages and notifying consumers about them. + * + * @author Maciej Walkowiak + */ +public interface Messenger { + /** + * Creates {@link Message} and publishes it into queue for processing + * + * @param request - request contains message details like content, recipients etc + * @return message with status "QUEUED" + */ + Message publish(Request request); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java new file mode 100644 index 0000000..bccc595 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java @@ -0,0 +1,35 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.core.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; + +@Component +class MessengerImpl implements Messenger { + private static final Logger LOG = LoggerFactory.getLogger(MessengerImpl.class); + + private final MessageService messageService; + private final MessageNotifier messageNotifier; + + @Autowired + MessengerImpl(MessageService messageService, MessageNotifier messageNotifier) { + this.messageService = messageService; + this.messageNotifier = messageNotifier; + } + + @Override + public Message publish(Request request) { + Assert.notNull(request); + + LOG.info("Received request: {}", request); + + Message message = Message.queued(request); + messageService.save(message); + messageNotifier.notifyConsumers(message); + + return message; + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java index 9004cff..fa6b3a4 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConfiguration.java @@ -1,30 +1,10 @@ package com.maciejwalkowiak.mercury.mail.common; -import com.maciejwalkowiak.mercury.core.QueueNameObtainer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import reactor.core.Reactor; - -import javax.annotation.PostConstruct; - -import static reactor.event.selector.Selectors.$; @Configuration @ComponentScan class MailConfiguration { - @Autowired - private MailConsumer mailConsumer; - - @Autowired - private Reactor reactor; - - @Autowired - private QueueNameObtainer queueNameObtainer; - - @PostConstruct - void initReactor() { - reactor.on($(queueNameObtainer.getQueueName(SendMailRequest.class)), mailConsumer); - } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index d25d92e..2bb94b7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -1,13 +1,12 @@ package com.maciejwalkowiak.mercury.mail.common; -import com.maciejwalkowiak.mercury.core.MercuryMessage; -import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.Consumer; +import com.maciejwalkowiak.mercury.core.message.Message; +import com.maciejwalkowiak.mercury.core.message.MessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import reactor.event.Event; -import reactor.function.Consumer; import java.util.Optional; @@ -15,39 +14,36 @@ * Consumes incoming {@link com.maciejwalkowiak.mercury.mail.common.SendMailRequest} based events * and sends emails through configured provider ({@link com.maciejwalkowiak.mercury.mail.common.MailingService}). * - * Based on Reactor. - * * If there is no provider configured all incoming emails will get status "FAILED" * * @author Maciej Walkowiak */ @Component -class MailConsumer implements Consumer>> { +class MailConsumer implements Consumer { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); private final Optional mailingService; - private final Messenger messenger; + private final MessageService messageService; @Autowired - public MailConsumer(Optional mailingService, Messenger messenger) { + public MailConsumer(Optional mailingService, MessageService messageService) { this.mailingService = mailingService; - this.messenger = messenger; + this.messageService = messageService; } @Override - public void accept(Event> mercuryMessageEvent) { - MercuryMessage message = mercuryMessageEvent.getData(); + public void consume(Message message) { LOG.info("Received send mail request: {}", message.getRequest()); if (mailingService.isPresent()) { try { mailingService.get().send(message.getRequest()); - messenger.messageSent(message); + messageService.messageSent(message); } catch (SendMailException e) { - messenger.deliveryFailed(message, e.getMessage()); + messageService.deliveryFailed(message, e.getMessage()); } } else { - messenger.deliveryFailed(message, "Mailing provider is not configured"); + messageService.deliveryFailed(message, "Mailing provider is not configured"); } } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index 9715d53..39f4abe 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -1,9 +1,9 @@ package com.maciejwalkowiak.mercury.mail.common; import com.fasterxml.jackson.annotation.JsonView; -import com.maciejwalkowiak.mercury.core.MercuryMessage; -import com.maciejwalkowiak.mercury.core.MercuryMessageController; -import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.message.Message; +import com.maciejwalkowiak.mercury.core.message.MessageController; +import com.maciejwalkowiak.mercury.core.message.Messenger; import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; @@ -37,13 +37,13 @@ class MailController implements HateoasController { } @RequestMapping(method = RequestMethod.POST) - @JsonView(MercuryMessage.View.Summary.class) + @JsonView(Message.View.Summary.class) public ResponseEntity send(@RequestBody @Valid SendMailRequest sendMailRequest) { - MercuryMessage message = messenger.publish(sendMailRequest); + Message message = messenger.publish(sendMailRequest); return ResponseEntity .status(HttpStatus.CREATED) - .location(linkTo(methodOn(MercuryMessageController.class).message(message.getId())).toUri()) + .location(linkTo(methodOn(MessageController.class).message(message.getId())).toUri()) .build(); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java index c945f19..67a1fd7 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConfiguration.java @@ -1,31 +1,12 @@ package com.maciejwalkowiak.mercury.slack; -import com.maciejwalkowiak.mercury.core.QueueNameObtainer; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import reactor.core.Reactor; - -import javax.annotation.PostConstruct; - -import static reactor.event.selector.Selectors.$; @Configuration @ComponentScan @ConditionalOnProperty(name = "slack.hook.url") class SlackConfiguration { - @Autowired - private SlackConsumer slackConsumer; - - @Autowired - private Reactor reactor; - - @Autowired - private QueueNameObtainer queueNameObtainer; - @PostConstruct - void initReactor() { - reactor.on($(queueNameObtainer.getQueueName(SlackRequest.class)), slackConsumer); - } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java index 39af05d..9815ac3 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java @@ -1,35 +1,34 @@ package com.maciejwalkowiak.mercury.slack; -import com.maciejwalkowiak.mercury.core.MercuryMessage; -import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.Consumer; +import com.maciejwalkowiak.mercury.core.message.Message; +import com.maciejwalkowiak.mercury.core.message.MessageService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import reactor.event.Event; -import reactor.function.Consumer; @Component -class SlackConsumer implements Consumer>> { +class SlackConsumer implements Consumer { private static final Logger LOG = LoggerFactory.getLogger(SlackConsumer.class); private final SlackService slackService; - private final Messenger messenger; + private final MessageService messageService; @Autowired - public SlackConsumer(SlackService slackService, Messenger messenger) { + public SlackConsumer(SlackService slackService, MessageService messageService) { this.slackService = slackService; - this.messenger = messenger; + this.messageService = messageService; } @Override - public void accept(Event> event) { + public void consume(Message message) { try { - slackService.send(event.getData().getRequest()); - messenger.messageSent(event.getData()); + slackService.send(message.getRequest()); + messageService.messageSent(message); } catch (Exception e) { LOG.error("Sending Slack message failed", e); - messenger.deliveryFailed(event.getData(), e.getMessage()); + messageService.deliveryFailed(message, e.getMessage()); } } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java index e185783..f6c727f 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -1,9 +1,9 @@ package com.maciejwalkowiak.mercury.slack; import com.fasterxml.jackson.annotation.JsonView; -import com.maciejwalkowiak.mercury.core.MercuryMessage; -import com.maciejwalkowiak.mercury.core.MercuryMessageController; -import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.message.Message; +import com.maciejwalkowiak.mercury.core.message.MessageController; +import com.maciejwalkowiak.mercury.core.message.Messenger; import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; @@ -32,13 +32,13 @@ public SlackController(Messenger messenger) { } @RequestMapping(method = RequestMethod.POST) - @JsonView(MercuryMessage.View.Summary.class) + @JsonView(Message.View.Summary.class) public ResponseEntity send(@RequestBody @Valid SlackRequest slackRequest) { - MercuryMessage message = messenger.publish(slackRequest); + Message message = messenger.publish(slackRequest); return ResponseEntity .status(HttpStatus.CREATED) - .location(linkTo(methodOn(MercuryMessageController.class).message(message.getId())).toUri()) + .location(linkTo(methodOn(MessageController.class).message(message.getId())).toUri()) .build(); } diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java deleted file mode 100644 index 4ca0242..0000000 --- a/src/test/java/com/maciejwalkowiak/mercury/core/MessengerImplTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; -import reactor.core.Reactor; -import reactor.event.Event; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class MessengerImplTest { - @InjectMocks - private MessengerImpl messenger; - @Mock - private MercuryMessageRepository repository; - @Mock - private QueueNameObtainer queueNameObtainer; - @Mock - private Reactor rootReactor; - - private final SendMailRequest request = new SendMailRequestBuilder() - .to("foo@bar.com") - .subject("subject") - .text("content") - .build(); - - @Test - public void shouldSaveMessage() { - messenger.publish(request); - - ArgumentCaptor captor = ArgumentCaptor.forClass(MercuryMessage.class); - - verify(repository).save(captor.capture()); - assertThat(captor.getValue().getRequest()).isEqualTo(request); - assertThat(captor.getValue().getStatus()).isEqualTo(MercuryMessage.Status.QUEUED); - } - - @Test - public void shouldPublishMessage() { - // given - when(queueNameObtainer.getQueueName(eq(request))).thenReturn("queue"); - - // when - messenger.publish(request); - - // then - ArgumentCaptor captor = ArgumentCaptor.forClass(Event.class); - - verify(rootReactor).notify(eq("queue"), captor.capture()); - - MercuryMessage message = (MercuryMessage) captor.getValue().getData(); - - assertThat(message.getRequest()).isEqualTo(request); - } - - @Test - public void shouldChangeStatusAndSaveWhenSent() { - // given - MercuryMessage message = MercuryMessage.queued(request); - - // when - messenger.messageSent(message); - - // then - assertThat(message.getStatus()).isEqualTo(MercuryMessage.Status.SENT); - verify(repository).save(eq(message)); - } - - @Test - public void shouldChangeStatusAndSaveWhenFailed() { - // given - MercuryMessage message = MercuryMessage.queued(request); - String errorMessage = "some error"; - - // when - messenger.deliveryFailed(message, errorMessage); - - // then - assertThat(message.getStatus()).isEqualTo(MercuryMessage.Status.FAILED); - assertThat(message.getErrorMessage()).isEqualTo(errorMessage); - verify(repository).save(eq(message)); - } -} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java deleted file mode 100644 index 417c6d0..0000000 --- a/src/test/java/com/maciejwalkowiak/mercury/core/QueueNameObtainerTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; -import org.junit.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -public class QueueNameObtainerTest { - final String QUEUE_NAME = "com.maciejwalkowiak.mercury.mail.common.SendMailRequest"; - private QueueNameObtainer queueNameObtainer = new QueueNameObtainer(); - - @Test - public void testQueueNameForClass() { - assertThat(queueNameObtainer.getQueueName(SendMailRequest.class)).isEqualTo(QUEUE_NAME); - } - - @Test - public void testQueueNameForInstance() { - SendMailRequest sendMailRequest = new SendMailRequestBuilder() - .to("foo@bar.com") - .subject("subject") - .text("content") - .build(); - assertThat(queueNameObtainer.getQueueName(sendMailRequest)).isEqualTo(QUEUE_NAME); - } - -} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java new file mode 100644 index 0000000..579d820 --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java @@ -0,0 +1,59 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class MessageServiceImplTest { + @InjectMocks + private MessageServiceImpl service; + + @Mock + private MessageNotifier messageNotifier; + + @Mock + private MessageRepository repository; + + private final SendMailRequest request = new SendMailRequestBuilder() + .to("foo@bar.com") + .subject("subject") + .text("content") + .build(); + + @Test + public void shouldChangeStatusAndSaveWhenSent() { + // given + Message message = Message.queued(request); + + // when + service.messageSent(message); + + // then + assertThat(message.getStatus()).isEqualTo(Message.Status.SENT); + verify(repository).save(eq(message)); + } + + @Test + public void shouldChangeStatusAndSaveWhenFailed() { + // given + Message message = Message.queued(request); + String errorMessage = "some error"; + + // when + service.deliveryFailed(message, errorMessage); + + // then + assertThat(message.getStatus()).isEqualTo(Message.Status.FAILED); + assertThat(message.getErrorMessage()).isEqualTo(errorMessage); + verify(repository).save(eq(message)); + } +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java new file mode 100644 index 0000000..fde47bf --- /dev/null +++ b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java @@ -0,0 +1,55 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; +import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class MessengerImplTest { + @InjectMocks + private MessengerImpl messenger; + @Mock + private MessageService messageService; + @Mock + private MessageNotifier messageNotifier; + + private final SendMailRequest request = new SendMailRequestBuilder() + .to("foo@bar.com") + .subject("subject") + .text("content") + .build(); + + @Test + public void shouldSaveMessage() { + messenger.publish(request); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + + verify(messageService).save(captor.capture()); + assertThat(captor.getValue().getRequest()).isEqualTo(request); + assertThat(captor.getValue().getStatus()).isEqualTo(Message.Status.QUEUED); + } + + @Test + public void shouldPublishMessage() { + // when + messenger.publish(request); + + // then + ArgumentCaptor captor = ArgumentCaptor.forClass(Message.class); + + verify(messageNotifier).notifyConsumers(captor.capture()); + + Message message = captor.getValue(); + + assertThat(message.getRequest()).isEqualTo(request); + } +} \ No newline at end of file diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java index 12bacab..9e7d215 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/common/MailConsumerTest.java @@ -1,12 +1,11 @@ package com.maciejwalkowiak.mercury.mail.common; -import com.maciejwalkowiak.mercury.core.MercuryMessage; -import com.maciejwalkowiak.mercury.core.Messenger; +import com.maciejwalkowiak.mercury.core.message.Message; +import com.maciejwalkowiak.mercury.core.message.MessageService; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import reactor.event.Event; import java.util.Optional; @@ -20,42 +19,43 @@ public class MailConsumerTest { @Mock private MailingService mailingService; + @Mock - private Messenger messenger; + private MessageService messageService; @Test public void shouldFailWhenMailingServiceNotPresent() { - mailConsumer = new MailConsumer(Optional.empty(), messenger); + mailConsumer = new MailConsumer(Optional.empty(), messageService); - MercuryMessage message = mock(MercuryMessage.class); + Message message = mock(Message.class); - mailConsumer.accept(new Event<>(message)); + mailConsumer.consume(message); - verify(messenger).deliveryFailed(eq(message), anyString()); + verify(messageService).deliveryFailed(eq(message), anyString()); } @Test public void shouldSendMessage() throws SendMailException { - mailConsumer = new MailConsumer(Optional.of(mailingService), messenger); + mailConsumer = new MailConsumer(Optional.of(mailingService), messageService); - MercuryMessage message = mock(MercuryMessage.class); + Message message = mock(Message.class); - mailConsumer.accept(new Event<>(message)); + mailConsumer.consume(message); verify(mailingService).send(eq(message.getRequest())); - verify(messenger).messageSent(eq(message)); + verify(messageService).messageSent(eq(message)); } @Test public void shouldFailWhenMailingServiceFailed() throws SendMailException { - mailConsumer = new MailConsumer(Optional.of(mailingService), messenger); + mailConsumer = new MailConsumer(Optional.of(mailingService), messageService); - MercuryMessage message = mock(MercuryMessage.class); + Message message = mock(Message.class); doThrow(new SendMailException("some message")).when(mailingService).send(any()); - mailConsumer.accept(new Event<>(message)); + mailConsumer.consume(message); - verify(messenger).deliveryFailed(eq(message), eq("some message")); + verify(messageService).deliveryFailed(eq(message), eq("some message")); } From 309d81625ee290baa08fc107b7869d39efaf2974 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 02:21:44 +0100 Subject: [PATCH 40/55] - simplified SendGrid functionality - cleanup --- .../mercury/core/AsyncMessagePublisher.java | 39 --------------- .../mercury/core/CoreConfiguration.java | 7 ++- .../mercury/core/MessagePublisher.java | 8 ---- .../mail/sendgrid/SendGridConfiguration.java | 28 +---------- .../sendgrid/SendGridHealthIndicator.java | 2 +- .../mail/sendgrid/SendGridProperties.java | 47 ------------------- .../mercury/slack/SlackService.java | 5 +- 7 files changed, 11 insertions(+), 125 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java b/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java deleted file mode 100644 index 3b863ea..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/AsyncMessagePublisher.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.lang.reflect.ParameterizedType; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Component -class AsyncMessagePublisher implements MessagePublisher { - private final Map consumersMap = new HashMap<>(); - private final List consumers; - - @Autowired - AsyncMessagePublisher(List consumers) { - this.consumers = consumers; - } - - @PostConstruct - public void initConsumersMap() { - consumers.stream().forEach(c -> { - Class persistentClass = (Class) ((ParameterizedType)c.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; - - consumersMap.put(persistentClass, c); - }); - } - - @Override - @Async - public void notifyConsumers(MercuryMessage message) { - if (consumersMap.containsKey(message.getRequest().getClass())) { - consumersMap.get(message.getRequest().getClass()).consume(message); - } - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index bccf99a..e7eb5d1 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -1,10 +1,15 @@ package com.maciejwalkowiak.mercury.core; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; @Configuration @ComponentScan class CoreConfiguration { - + @Bean + RestTemplate restTemplate() { + return new RestTemplate(); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java b/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java deleted file mode 100644 index b827438..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/MessagePublisher.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import org.springframework.scheduling.annotation.Async; - -public interface MessagePublisher { - @Async - void notifyConsumers(MercuryMessage message); -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java index 67a47f1..84a0c94 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java @@ -26,32 +26,6 @@ class SendGridConfiguration { @Bean public SendGrid sendGrid() { - SendGrid sendGrid = new SendGrid(properties.getUsername(), properties.getPassword()); - - if (properties.isProxyConfigured()) { - HttpHost proxy = new HttpHost(properties.getProxy().getHost(), properties.getProxy().getPort()); - CloseableHttpClient http = HttpClientBuilder.create() - .setProxy(proxy) - .setUserAgent("sendgrid/" + sendGrid.getVersion() + ";java") - .build(); - - sendGrid.setClient(http); - } - - return sendGrid; - } - - @Bean(name = "sendGridRestTemplate") - public RestTemplate restTemplate() { - if (properties.isProxyConfigured()) { - SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); - - Proxy proxy= new Proxy(Proxy.Type.HTTP, new InetSocketAddress(properties.getProxy().getHost(), properties.getProxy().getPort())); - requestFactory.setProxy(proxy); - - return new RestTemplate(requestFactory); - } else { - return new RestTemplate(); - } + return new SendGrid(properties.getUsername(), properties.getPassword()); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java index b4d8d72..bcd4ced 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java @@ -25,7 +25,7 @@ class SendGridHealthIndicator extends AbstractHealthIndicator { private final SendGridProperties sendGridProperties; @Autowired - SendGridHealthIndicator(@Qualifier("sendGridRestTemplate") RestTemplate restTemplate, SendGridProperties sendGridProperties) { + SendGridHealthIndicator(RestTemplate restTemplate, SendGridProperties sendGridProperties) { this.restTemplate = restTemplate; this.sendGridProperties = sendGridProperties; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java index e59b0e1..4815a7c 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridProperties.java @@ -14,11 +14,6 @@ class SendGridProperties { */ private String password; - /** - * Proxy configuration - */ - private Proxy proxy; - public String getUsername() { return username; } @@ -34,46 +29,4 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } - - public Proxy getProxy() { - return proxy; - } - - public void setProxy(Proxy proxy) { - this.proxy = proxy; - } - - public boolean isProxyConfigured() { - return proxy != null - && proxy.getHost() != null - && proxy.getPort() != null; - } - - public static class Proxy { - /** - * SendGrid proxy host - */ - private String host; - - /** - * SendGrid proxy port - */ - private Integer port; - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java index 396e181..7296778 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackService.java @@ -7,11 +7,12 @@ @Service class SlackService { - private final RestTemplate restTemplate = new RestTemplate(); + private final RestTemplate restTemplate; private final String slackWebHookUrl; @Autowired - public SlackService(@Value("${slack.hook.url}") String slackWebHookUrl) { + public SlackService(RestTemplate restTemplate, @Value("${slack.hook.url}") String slackWebHookUrl) { + this.restTemplate = restTemplate; this.slackWebHookUrl = slackWebHookUrl; } From 87239e4a8626d0acc10cc0164654cbdcd02e73a9 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 02:27:22 +0100 Subject: [PATCH 41/55] - simplified SendGrid functionality - fixed async messages processing - got rid of simplified message view - all message details are now shown to user --- .../mercury/core/CoreConfiguration.java | 2 ++ .../mercury/core/message/AsyncMessageNotifier.java | 9 ++++++++- .../maciejwalkowiak/mercury/core/message/Message.java | 7 ------- .../mercury/core/message/MessageController.java | 1 - .../mercury/core/message/MessageNotifier.java | 8 +++++--- .../mercury/core/message/MessageService.java | 10 +++++++++- .../mercury/mail/common/MailController.java | 1 - .../maciejwalkowiak/mercury/slack/SlackController.java | 1 - 8 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index e7eb5d1..3e7a96a 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -3,10 +3,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.web.client.RestTemplate; @Configuration @ComponentScan +@EnableAsync class CoreConfiguration { @Bean RestTemplate restTemplate() { diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java index 036d783..656a646 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java @@ -12,8 +12,15 @@ import java.util.List; import java.util.Map; +/** + * Notifies consumers about incoming message in asynchronous manner + * + * Has to be public because of @Async & JDK Proxies + * + * @author Maciej Walkowiak + */ @Component -class AsyncMessageNotifier implements MessageNotifier { +public class AsyncMessageNotifier implements MessageNotifier { private final Map consumersMap = new HashMap<>(); private final List consumers; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java index 4fd85d3..e0ab3f0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java @@ -26,11 +26,8 @@ public enum Status { } @Id - @JsonView(View.Summary.class) private String id; - @JsonView(View.Summary.class) private Status status; - @JsonView(View.Summary.class) private String errorMessage; private T request; @@ -73,8 +70,4 @@ public String getId() { public String getErrorMessage() { return errorMessage; } - - public static class View { - public interface Summary {} - } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java index 537bf2a..d2ed650 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java @@ -35,7 +35,6 @@ public class MessageController implements HateoasController { } @RequestMapping(value = "{id}", method = RequestMethod.GET) - @JsonView(Message.View.Summary.class) public ResponseEntity message(@PathVariable String id) { Message message = messageRepository.findOne(id); diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java index 5b73f4a..99411c0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageNotifier.java @@ -1,8 +1,10 @@ package com.maciejwalkowiak.mercury.core.message; -import org.springframework.scheduling.annotation.Async; - +/** + * Notifies consumers about incoming messages + * + * @author Maciej Walkowiak + */ public interface MessageNotifier { - @Async void notifyConsumers(Message message); } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java index 6a3a563..eeab0cb 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java @@ -1,12 +1,20 @@ package com.maciejwalkowiak.mercury.core.message; +/** + * @author Maciej Walkowiak + */ public interface MessageService { + /** + * Saves message in datastore + * + * @param message - message to save + */ void save(Message message); /** * Invoked after message has been successfully sent. Changes it's status to "SENT" * - * @param message sent message + * @param message - sent message */ void messageSent(Message message); diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index 39f4abe..744aaec 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -37,7 +37,6 @@ class MailController implements HateoasController { } @RequestMapping(method = RequestMethod.POST) - @JsonView(Message.View.Summary.class) public ResponseEntity send(@RequestBody @Valid SendMailRequest sendMailRequest) { Message message = messenger.publish(sendMailRequest); diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java index f6c727f..13a2efd 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -32,7 +32,6 @@ public SlackController(Messenger messenger) { } @RequestMapping(method = RequestMethod.POST) - @JsonView(Message.View.Summary.class) public ResponseEntity send(@RequestBody @Valid SlackRequest slackRequest) { Message message = messenger.publish(slackRequest); From 58619d5f6cfb1e6635afe205f74c71e8c7183897 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 02:31:06 +0100 Subject: [PATCH 42/55] Fixed unit test --- .../com/maciejwalkowiak/mercury/MercuryApplicationTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index b30b3e6..09f1a3e 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -46,7 +46,6 @@ public void setup() { public void testSendingMailRequest() throws Exception { this.mockMvc.perform(post("/api/mail").content("{ \"to\":[\"foo@bar.com\"],\"text\":\"bar\", \"subject\":\"subject\" }").contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isCreated()) - .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andExpect(header().string("Location", NotNull.NOT_NULL)); } } From 591ce8bbfe4fc0c0fbcbb81104928c31e425b4e3 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 02:47:37 +0100 Subject: [PATCH 43/55] Cleanup and refactoring --- .../mercury/core/api/ApiController.java | 15 +++++++ .../mercury/core/api/BracketsLink.java | 17 -------- .../mercury/core/message/Message.java | 1 - .../core/message/MessageController.java | 1 - .../mercury/core/message/MessageService.java | 37 ++++++++++++++--- .../core/message/MessageServiceImpl.java | 38 ----------------- .../mercury/core/message/Messenger.java | 31 +++++++++++++- .../mercury/core/message/MessengerImpl.java | 35 ---------------- .../mercury/mail/common/MailController.java | 3 +- .../mercury/mail/common/SendMailRequest.java | 37 +++++++++++++++++ .../mail/common/SendMailRequestBuilder.java | 41 ------------------- .../mail/sendgrid/SendGridConfiguration.java | 8 ---- .../sendgrid/SendGridHealthIndicator.java | 1 - .../mercury/slack/SlackController.java | 3 +- .../mercury/MercuryApplicationTests.java | 3 +- ...eImplTest.java => MessageServiceTest.java} | 7 ++-- ...sengerImplTest.java => MessengerTest.java} | 7 ++-- .../javamail/JavaMailMailingServiceTest.java | 3 +- 18 files changed, 124 insertions(+), 164 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java rename src/test/java/com/maciejwalkowiak/mercury/core/message/{MessageServiceImplTest.java => MessageServiceTest.java} (85%) rename src/test/java/com/maciejwalkowiak/mercury/core/message/{MessengerImplTest.java => MessengerTest.java} (86%) diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java index cd2afad..d6807d0 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/api/ApiController.java @@ -1,6 +1,7 @@ package com.maciejwalkowiak.mercury.core.api; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.Link; import org.springframework.hateoas.ResourceSupport; import org.springframework.http.HttpEntity; import org.springframework.stereotype.Controller; @@ -49,4 +50,18 @@ public HttpEntity links() { private static class ApiResource extends ResourceSupport { } + + /** + * Spring HATEOAS performs URL encoding and replaces characters "{" and "}" that are useful to show templated URL. + * + * BracketsLink is a hacky class that takes {@link org.springframework.hateoas.Link} + * from Spring HATEOAS package and brings back brackets "{" and "}" + * + * @author Maciej Walkowiak + */ + private static class BracketsLink extends Link { + public BracketsLink(Link link) { + super(link.getHref().replaceAll("%7B", "{").replaceAll("%7D", "}"), link.getRel()); + } + } } \ No newline at end of file diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java b/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java deleted file mode 100644 index 46bc7a3..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/api/BracketsLink.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.maciejwalkowiak.mercury.core.api; - -import org.springframework.hateoas.Link; - -/** - * Spring HATEOAS performs URL encoding and replaces characters "{" and "}" that are useful to show templated URL. - * - * {@link com.maciejwalkowiak.mercury.core.api.BracketsLink} is a hacky class that takes {@link org.springframework.hateoas.Link} - * from Spring HATEOAS package and brings back brackets "{" and "}" - * - * @author Maciej Walkowiak - */ -class BracketsLink extends Link { - public BracketsLink(Link link) { - super(link.getHref().replaceAll("%7B", "{").replaceAll("%7D", "}"), link.getRel()); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java index e0ab3f0..dc71daf 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/Message.java @@ -1,7 +1,6 @@ package com.maciejwalkowiak.mercury.core.message; import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.Request; import org.springframework.data.annotation.Id; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java index d2ed650..1427df3 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageController.java @@ -1,6 +1,5 @@ package com.maciejwalkowiak.mercury.core.message; -import com.fasterxml.jackson.annotation.JsonView; import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java index eeab0cb..dc2a9fb 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageService.java @@ -1,27 +1,54 @@ package com.maciejwalkowiak.mercury.core.message; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + /** * @author Maciej Walkowiak */ -public interface MessageService { +@Service +public class MessageService { + private final MessageRepository repository; + + @Autowired + MessageService(MessageRepository repository) { + this.repository = repository; + } + /** - * Saves message in datastore + * Saves message in data store * * @param message - message to save */ - void save(Message message); + public void save(Message message) { + Assert.notNull(message); + + repository.save(message); + } /** * Invoked after message has been successfully sent. Changes it's status to "SENT" * * @param message - sent message */ - void messageSent(Message message); + public void messageSent(Message message) { + Assert.notNull(message); + + message.sent(); + repository.save(message); + } /** * Invoked when sending message has failed. Changes it's status to "FAILED" and saves error details + * * @param message - message that sending has failed * @param errorMessage - error details */ - void deliveryFailed(Message message, String errorMessage); + public void deliveryFailed(Message message, String errorMessage) { + Assert.notNull(message); + + message.failed(errorMessage); + repository.save(message); + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java deleted file mode 100644 index 7be811f..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.maciejwalkowiak.mercury.core.message; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.Assert; - -@Service -class MessageServiceImpl implements MessageService { - private final MessageRepository repository; - - @Autowired - MessageServiceImpl(MessageRepository repository) { - this.repository = repository; - } - - @Override - public void save(Message message) { - Assert.notNull(message); - - repository.save(message); - } - - @Override - public void messageSent(Message message) { - Assert.notNull(message); - - message.sent(); - repository.save(message); - } - - @Override - public void deliveryFailed(Message message, String errorMessage) { - Assert.notNull(message); - - message.failed(errorMessage); - repository.save(message); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java index f40fd7b..e6e1c93 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/Messenger.java @@ -1,18 +1,45 @@ package com.maciejwalkowiak.mercury.core.message; import com.maciejwalkowiak.mercury.core.Request; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.Assert; /** * Responsible for creating messages and notifying consumers about them. * * @author Maciej Walkowiak */ -public interface Messenger { +@Component +public class Messenger { + private static final Logger LOG = LoggerFactory.getLogger(Messenger.class); + + private final MessageService messageService; + private final MessageNotifier messageNotifier; + + @Autowired + Messenger(MessageService messageService, MessageNotifier messageNotifier) { + this.messageService = messageService; + this.messageNotifier = messageNotifier; + } + /** * Creates {@link Message} and publishes it into queue for processing * * @param request - request contains message details like content, recipients etc * @return message with status "QUEUED" */ - Message publish(Request request); + public Message publish(Request request) { + Assert.notNull(request); + + LOG.info("Received request: {}", request); + + Message message = Message.queued(request); + messageService.save(message); + messageNotifier.notifyConsumers(message); + + return message; + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java deleted file mode 100644 index bccc595..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessengerImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.maciejwalkowiak.mercury.core.message; - -import com.maciejwalkowiak.mercury.core.Request; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.springframework.util.Assert; - -@Component -class MessengerImpl implements Messenger { - private static final Logger LOG = LoggerFactory.getLogger(MessengerImpl.class); - - private final MessageService messageService; - private final MessageNotifier messageNotifier; - - @Autowired - MessengerImpl(MessageService messageService, MessageNotifier messageNotifier) { - this.messageService = messageService; - this.messageNotifier = messageNotifier; - } - - @Override - public Message publish(Request request) { - Assert.notNull(request); - - LOG.info("Received request: {}", request); - - Message message = Message.queued(request); - messageService.save(message); - messageNotifier.notifyConsumers(message); - - return message; - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java index 744aaec..06bc1d2 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailController.java @@ -1,10 +1,9 @@ package com.maciejwalkowiak.mercury.mail.common; -import com.fasterxml.jackson.annotation.JsonView; +import com.maciejwalkowiak.mercury.core.api.HateoasController; import com.maciejwalkowiak.mercury.core.message.Message; import com.maciejwalkowiak.mercury.core.message.MessageController; import com.maciejwalkowiak.mercury.core.message.Messenger; -import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java index e121de7..d52687c 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequest.java @@ -67,4 +67,41 @@ public String toString() { ", subject='" + subject + '\'' + '}'; } + + public static class Builder { + private List to = new ArrayList<>(); + private List cc = new ArrayList<>(); + private List bcc = new ArrayList<>(); + private String subject; + private String text; + + public Builder to(String to) { + this.to.add(to); + return this; + } + + public Builder cc(String cc) { + this.cc.add(cc); + return this; + } + + public Builder bcc(String bcc) { + this.bcc.add(bcc); + return this; + } + + public Builder subject(String subject) { + this.subject = subject; + return this; + } + + public Builder text(String text) { + this.text = text; + return this; + } + + public SendMailRequest build() { + return new SendMailRequest(to, cc, bcc, subject, text); + } + } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java deleted file mode 100644 index 6b1ff63..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/SendMailRequestBuilder.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.maciejwalkowiak.mercury.mail.common; - -import java.util.ArrayList; -import java.util.List; - -public class SendMailRequestBuilder { - private List to = new ArrayList<>(); - private List cc = new ArrayList<>();; - private List bcc = new ArrayList<>();; - private String subject; - private String text; - - public SendMailRequestBuilder to(String to) { - this.to.add(to); - return this; - } - - public SendMailRequestBuilder cc(String cc) { - this.cc.add(cc); - return this; - } - - public SendMailRequestBuilder bcc(String bcc) { - this.bcc.add(bcc); - return this; - } - - public SendMailRequestBuilder subject(String subject) { - this.subject = subject; - return this; - } - - public SendMailRequestBuilder text(String text) { - this.text = text; - return this; - } - - public SendMailRequest build() { - return new SendMailRequest(to, cc, bcc, subject, text); - } -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java index 84a0c94..eb0c586 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridConfiguration.java @@ -1,20 +1,12 @@ package com.maciejwalkowiak.mercury.mail.sendgrid; import com.sendgrid.SendGrid; -import org.apache.http.HttpHost; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -import java.net.InetSocketAddress; -import java.net.Proxy; @Configuration @ConditionalOnProperty(name = "sendgrid.username") diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java index bcd4ced..28efe59 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; import org.springframework.stereotype.Component; diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java index 13a2efd..5f12d4a 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackController.java @@ -1,10 +1,9 @@ package com.maciejwalkowiak.mercury.slack; -import com.fasterxml.jackson.annotation.JsonView; +import com.maciejwalkowiak.mercury.core.api.HateoasController; import com.maciejwalkowiak.mercury.core.message.Message; import com.maciejwalkowiak.mercury.core.message.MessageController; import com.maciejwalkowiak.mercury.core.message.Messenger; -import com.maciejwalkowiak.mercury.core.api.HateoasController; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.hateoas.Link; import org.springframework.http.HttpStatus; diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index 09f1a3e..694a832 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -18,7 +18,8 @@ import org.springframework.web.context.WebApplicationContext; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MercuryApplication.class) diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceTest.java similarity index 85% rename from src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java rename to src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceTest.java index 579d820..6cf324d 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceImplTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessageServiceTest.java @@ -1,7 +1,6 @@ package com.maciejwalkowiak.mercury.core.message; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -13,9 +12,9 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) -public class MessageServiceImplTest { +public class MessageServiceTest { @InjectMocks - private MessageServiceImpl service; + private MessageService service; @Mock private MessageNotifier messageNotifier; @@ -23,7 +22,7 @@ public class MessageServiceImplTest { @Mock private MessageRepository repository; - private final SendMailRequest request = new SendMailRequestBuilder() + private final SendMailRequest request = new SendMailRequest.Builder() .to("foo@bar.com") .subject("subject") .text("content") diff --git a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerTest.java similarity index 86% rename from src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java rename to src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerTest.java index fde47bf..e2fffad 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerImplTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/core/message/MessengerTest.java @@ -1,7 +1,6 @@ package com.maciejwalkowiak.mercury.core.message; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -13,15 +12,15 @@ import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) -public class MessengerImplTest { +public class MessengerTest { @InjectMocks - private MessengerImpl messenger; + private Messenger messenger; @Mock private MessageService messageService; @Mock private MessageNotifier messageNotifier; - private final SendMailRequest request = new SendMailRequestBuilder() + private final SendMailRequest request = new SendMailRequest.Builder() .to("foo@bar.com") .subject("subject") .text("content") diff --git a/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java index 20eca96..7009910 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java +++ b/src/test/java/com/maciejwalkowiak/mercury/mail/javamail/JavaMailMailingServiceTest.java @@ -2,7 +2,6 @@ import com.maciejwalkowiak.mercury.mail.common.SendMailException; import com.maciejwalkowiak.mercury.mail.common.SendMailRequest; -import com.maciejwalkowiak.mercury.mail.common.SendMailRequestBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -24,7 +23,7 @@ public class JavaMailMailingServiceTest { @Mock private MailSender mailSender; - private final SendMailRequest request = new SendMailRequestBuilder() + private final SendMailRequest request = new SendMailRequest.Builder() .to("foo@bar.com") .subject("subject") .text("content") From f1a9383d7fb22da9528a9c53e078a09f47e578d3 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:00:40 +0100 Subject: [PATCH 44/55] Update README.adoc --- README.adoc | 63 +++++++++++++++++++---------------------------------- 1 file changed, 23 insertions(+), 40 deletions(-) diff --git a/README.adoc b/README.adoc index 48e766c..3d6553c 100644 --- a/README.adoc +++ b/README.adoc @@ -58,26 +58,31 @@ For example if properties `slack.url` is not set handler for URL `http://: == Configuration options -=== MongoDB configuration +*Mercury* has following configuration properties. ------------------------------------------------------------------------ -spring.data.mongodb.uri=mongodb://localhost/test # connection URL -spring.data.mongodb.database= -spring.data.mongodb.username= -spring.data.mongodb.password= +# Mercury configuration + +# MongoDB - required +#spring.data.mongodb.uri=mongodb://localhost/test # connection URL +#spring.data.mongodb.database= +#spring.data.mongodb.username= +#spring.data.mongodb.password= + +# JavaMail configuration - optional +#spring.mail.host= +#spring.mail.port= +#spring.mail.username= +#spring.mail.password= + +# SendGrid configuration - optional +#sendgrid.username= +#sendgrid.password= + +# Slack configuration +#slack.hook.url= ------------------------------------------------------------------------ -=== Java Mail configuration - -Put following properties to `application.properties` file: - ---------------------- -spring.mail.host= -spring.mail.port= -spring.mail.username= -spring.mail.password= ---------------------- - To configure additional Java Mail properties use `spring.mail.properties` prefix. @@ -92,31 +97,9 @@ spring.mail.properties.mail.smtp.auth=true spring.mail.properties.mail.smtp.starttls.enable=true ----------------------------------------------------- -=== SendGrid configuration - -Put following properties to `application.properties` file: - ------------------- -sendgrid.username= -sendgrid.password= ------------------- - -If you need to access SendGrid through proxy server add: - --------------------- -sendgrid.proxy.host= -sendgrid.proxy.port= --------------------- - *IMPORTANT:* if both Java Mail *and* SendGrid configuration is provided - all emails will be sent using SendGrid. -=== Slack configuration - -Put following properties to `application.properties` file: - ------------------- -slack.hook.url= ------------------- - Learn more about Slack webhooks at https://api.slack.com/ + +Additionally, since *Mercury* is based on *Spring Boot* you can use pretty much any of configuration property described in Boot docs: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html From d4eded903ac02316686477de1d9980afe7c18163 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:01:24 +0100 Subject: [PATCH 45/55] Update README.adoc --- README.adoc | 1 + 1 file changed, 1 insertion(+) diff --git a/README.adoc b/README.adoc index 3d6553c..714eb39 100644 --- a/README.adoc +++ b/README.adoc @@ -55,6 +55,7 @@ For example if properties `slack.url` is not set handler for URL `http://: * https://github.com/maciejwalkowiak/contest/releases/[Download latest release] * configure `config/application.properties` * run with `java -jar mercury-.jar` +* go to `http://localhost:8080/api` == Configuration options From d381e2d6945c89860591d0876a655a8309d91006 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:01:50 +0100 Subject: [PATCH 46/55] Minor refactoring --- .../com/maciejwalkowiak/mercury/core/Consumer.java | 7 ------- .../mercury/core/message/AsyncMessageNotifier.java | 7 +++---- .../mercury/core/message/MessageConsumer.java | 11 +++++++++++ .../mercury/mail/common/MailConsumer.java | 4 ++-- .../maciejwalkowiak/mercury/slack/SlackConsumer.java | 4 ++-- 5 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageConsumer.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java b/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java deleted file mode 100644 index 565ce7c..0000000 --- a/src/main/java/com/maciejwalkowiak/mercury/core/Consumer.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.maciejwalkowiak.mercury.core; - -import com.maciejwalkowiak.mercury.core.message.Message; - -public interface Consumer { - void consume(Message message); -} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java index 656a646..c71ee69 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/AsyncMessageNotifier.java @@ -1,6 +1,5 @@ package com.maciejwalkowiak.mercury.core.message; -import com.maciejwalkowiak.mercury.core.Consumer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; @@ -21,11 +20,11 @@ */ @Component public class AsyncMessageNotifier implements MessageNotifier { - private final Map consumersMap = new HashMap<>(); - private final List consumers; + private final Map consumersMap = new HashMap<>(); + private final List consumers; @Autowired - AsyncMessageNotifier(List consumers) { + AsyncMessageNotifier(List consumers) { this.consumers = consumers; } diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageConsumer.java new file mode 100644 index 0000000..e84a11e --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageConsumer.java @@ -0,0 +1,11 @@ +package com.maciejwalkowiak.mercury.core.message; + +import com.maciejwalkowiak.mercury.core.Request; +import com.maciejwalkowiak.mercury.core.message.Message; + +/** + * @author Maciej Walkowiak + */ +public interface MessageConsumer { + void consume(Message message); +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java index 2bb94b7..9575283 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/common/MailConsumer.java @@ -1,6 +1,6 @@ package com.maciejwalkowiak.mercury.mail.common; -import com.maciejwalkowiak.mercury.core.Consumer; +import com.maciejwalkowiak.mercury.core.message.MessageConsumer; import com.maciejwalkowiak.mercury.core.message.Message; import com.maciejwalkowiak.mercury.core.message.MessageService; import org.slf4j.Logger; @@ -19,7 +19,7 @@ * @author Maciej Walkowiak */ @Component -class MailConsumer implements Consumer { +class MailConsumer implements MessageConsumer { private static final Logger LOG = LoggerFactory.getLogger(MailConsumer.class); private final Optional mailingService; diff --git a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java index 9815ac3..a470c5a 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java +++ b/src/main/java/com/maciejwalkowiak/mercury/slack/SlackConsumer.java @@ -1,6 +1,6 @@ package com.maciejwalkowiak.mercury.slack; -import com.maciejwalkowiak.mercury.core.Consumer; +import com.maciejwalkowiak.mercury.core.message.MessageConsumer; import com.maciejwalkowiak.mercury.core.message.Message; import com.maciejwalkowiak.mercury.core.message.MessageService; import org.slf4j.Logger; @@ -9,7 +9,7 @@ import org.springframework.stereotype.Component; @Component -class SlackConsumer implements Consumer { +class SlackConsumer implements MessageConsumer { private static final Logger LOG = LoggerFactory.getLogger(SlackConsumer.class); private final SlackService slackService; From e4ee237e573592a6a3cae36dd4af0496d695efb8 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:08:35 +0100 Subject: [PATCH 47/55] Added optional configuration for Fongo for user that do not care if messages are stored in persistent storage --- pom.xml | 11 +++++------ .../mercury/core/CoreConfiguration.java | 12 ++++++++++++ .../mercury/MercuryApplicationTests.java | 13 +------------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index 629c0be..85a7d0c 100644 --- a/pom.xml +++ b/pom.xml @@ -40,6 +40,11 @@ org.springframework.boot spring-boot-starter-data-mongodb + + com.github.fakemongo + fongo + 1.5.9 + org.springframework.hateoas spring-hateoas @@ -64,12 +69,6 @@ 1.7.0 test - - com.github.fakemongo - fongo - 1.5.9 - test - diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java index 3e7a96a..529620c 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/CoreConfiguration.java @@ -1,5 +1,8 @@ package com.maciejwalkowiak.mercury.core; +import com.github.fakemongo.Fongo; +import com.mongodb.Mongo; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -10,6 +13,15 @@ @ComponentScan @EnableAsync class CoreConfiguration { + @Configuration + @ConditionalOnProperty(name = "mercury.db.inMemory", havingValue = "true") + static class FongoConfig { + @Bean + Mongo mongo() { + return new Fongo("mongo").getMongo(); + } + } + @Bean RestTemplate restTemplate() { return new RestTemplate(); diff --git a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java index 694a832..2d9bb44 100644 --- a/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java +++ b/src/test/java/com/maciejwalkowiak/mercury/MercuryApplicationTests.java @@ -1,7 +1,5 @@ package com.maciejwalkowiak.mercury; -import com.github.fakemongo.Fongo; -import com.mongodb.Mongo; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -9,8 +7,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.WebIntegrationTest; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; @@ -23,15 +19,8 @@ @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = MercuryApplication.class) -@WebIntegrationTest(randomPort = true) +@WebIntegrationTest(value = "mercury.db.inMemory=true", randomPort = true) public class MercuryApplicationTests { - @Configuration - static class Config { - @Bean - Mongo mongo() { - return new Fongo("mongo").getMongo(); - } - } @Autowired private WebApplicationContext wac; From 67903eb262931e60e2936cb673c63c8407c9409c Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:16:56 +0100 Subject: [PATCH 48/55] Update README.adoc --- README.adoc | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/README.adoc b/README.adoc index 714eb39..4a20589 100644 --- a/README.adoc +++ b/README.adoc @@ -59,17 +59,32 @@ For example if properties `slack.url` is not set handler for URL `http://: == Configuration options -*Mercury* has following configuration properties. +All *Mercury* configuration is stored in `config/application.properties` that you find in distribution package. ------------------------------------------------------------------------- -# Mercury configuration +=== Database configuration -# MongoDB - required -#spring.data.mongodb.uri=mongodb://localhost/test # connection URL -#spring.data.mongodb.database= -#spring.data.mongodb.username= -#spring.data.mongodb.password= +*Mercury* can persist messages with their statuses to MongoDB or save them into in-memory database that disappears when application goes down. +For in-memory configuration make sure configuration contains following line: + +---- +mercury.db.inMemory=true +---- + +For MongoDB configuration make sure that property *mercury.db.inMemory* does not exist or is set to *false* and put following configuration properties: + +---- +spring.data.mongodb.uri=mongodb://localhost/test # connection URL +spring.data.mongodb.database= +spring.data.mongodb.username= +spring.data.mongodb.password= +---- + +=== Services configuration + +In the same place you can configure configuration to services. Configuration properties presence activates related service (make sure to delete *#* at the beginning of each property you wish to use): + +---- # JavaMail configuration - optional #spring.mail.host= #spring.mail.port= @@ -80,9 +95,9 @@ For example if properties `slack.url` is not set handler for URL `http://: #sendgrid.username= #sendgrid.password= -# Slack configuration +# Slack configuration - optional #slack.hook.url= ------------------------------------------------------------------------- +---- To configure additional Java Mail properties use `spring.mail.properties` prefix. From f1090ef554086e4224ab273e517628bae507b8de Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:46:00 +0100 Subject: [PATCH 49/55] Update README.adoc --- README.adoc | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/README.adoc b/README.adoc index 4a20589..8f962d2 100644 --- a/README.adoc +++ b/README.adoc @@ -43,11 +43,120 @@ Upcoming features: Exposed API is dynamic - meaning that depending on what notifications provider is configured - corresponding endpoint is available. For example if properties `slack.url` is not set handler for URL `http://:/api/slack` is not created. -* `GET http://:/api/` - lists all available API methods in current configuration -* `GET http://:/api/message/{id}` - get message status -* `POST http://:/api/mail` - sends mail message - available only if *Java Mail* or *SendGrid* is configured -* `POST http://:/api/slack` - sends slack message - available only if *Slack* is configured +=== List API methods +---- +GET,OPTIONS /api/ +---- + +==== Response + +---- +Status: 200 OK + +{ + "links":[ + { + "rel":"self", + "href":"http://localhost:8082/api" + }, + { + "rel":"message", + "href":"http://localhost:8082/api/message/{id}" + }, + { + "rel":"mail", + "href":"http://localhost:8082/api/mail" + }, { + "rel":"slack", + "href":"http://localhost:8082/api/slack" + } + ] +} +---- + +=== Get message + +---- +GET /api/message/{id} +---- + +==== Response + +---- +Status: 200 OK + +{ + "id":"54b992023004a2bf10dcaf98", + "status":"FAILED", + "errorMessage":"Mailing provider is not configured", + "request":{ + "to":[ + "john.doe@mercuryapp.com" + ], + "cc":[ + + ], + "bcc":[ + + ], + "text":"Hello John", + "subject":"It's been a long time" + } +} +---- + +=== Send email + +TIP: available only if *Java Mail* or *SendGrid* is configured + +---- +POST /api/mail +---- + +==== Request + +---- +{ + "to":[ + "john.doe@mercuryapp.com" + ], + "text":"Hello John", + "subject":"It's been a long time" +} +---- + +==== Response + +---- +Status: 201 CREATED +Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 +---- + +=== Send Slack message + +TIP: available only if *Slack* is configured + +---- +POST /api/slack +---- + +==== Request + +---- +{ + "text":"hello from slack!", + "icon_emoji":":chart_with_upwards_trend:", + "channel":"#urgent" +} +---- + +==== Response + +---- +Status: 201 CREATED +Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 +---- == How to run From 675266676a3049511e5df094cd32e4ec62d269f6 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Fri, 16 Jan 2015 23:56:47 +0100 Subject: [PATCH 50/55] Update README.adoc --- README.adoc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.adoc b/README.adoc index 8f962d2..0a8e003 100644 --- a/README.adoc +++ b/README.adoc @@ -51,6 +51,7 @@ GET,OPTIONS /api/ ==== Response +[source,json] ---- Status: 200 OK @@ -83,6 +84,7 @@ GET /api/message/{id} ==== Response +[source,json] ---- Status: 200 OK @@ -116,6 +118,7 @@ POST /api/mail ==== Request +[source,json] ---- { "to":[ @@ -137,12 +140,14 @@ Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 TIP: available only if *Slack* is configured +[source,json] ---- POST /api/slack ---- ==== Request +[source,json] ---- { "text":"hello from slack!", @@ -153,6 +158,7 @@ POST /api/slack ==== Response +[source,json] ---- Status: 201 CREATED Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 @@ -176,12 +182,14 @@ All *Mercury* configuration is stored in `config/application.properties` that yo For in-memory configuration make sure configuration contains following line: +[source,json] ---- mercury.db.inMemory=true ---- For MongoDB configuration make sure that property *mercury.db.inMemory* does not exist or is set to *false* and put following configuration properties: +[source,json] ---- spring.data.mongodb.uri=mongodb://localhost/test # connection URL spring.data.mongodb.database= @@ -193,6 +201,7 @@ spring.data.mongodb.password= In the same place you can configure configuration to services. Configuration properties presence activates related service (make sure to delete *#* at the beginning of each property you wish to use): +[source,json] ---- # JavaMail configuration - optional #spring.mail.host= @@ -213,6 +222,7 @@ To configure additional Java Mail properties use Sample configuration for Gmail account: +[source,json] ----------------------------------------------------- spring.mail.host=smtp.gmail.com spring.mail.port=587 From de7f47520cae476bb13259165e451d79f1195a66 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 17 Jan 2015 00:55:16 +0100 Subject: [PATCH 51/55] Added metrics --- .../mercury/core/message/MessageMetrics.java | 32 +++++++++++++++++++ .../core/message/MessageRepository.java | 3 ++ 2 files changed, 35 insertions(+) create mode 100644 src/main/java/com/maciejwalkowiak/mercury/core/message/MessageMetrics.java diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageMetrics.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageMetrics.java new file mode 100644 index 0000000..fc3c143 --- /dev/null +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageMetrics.java @@ -0,0 +1,32 @@ +package com.maciejwalkowiak.mercury.core.message; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.endpoint.PublicMetrics; +import org.springframework.boot.actuate.metrics.Metric; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * Exposes metrics for number of messages by status + * + * @author Maciej Walkowiak + */ +@Component +class MessageMetrics implements PublicMetrics { + private final MessageRepository messageRepository; + + @Autowired + MessageMetrics(MessageRepository messageRepository) { + this.messageRepository = messageRepository; + } + + @Override + public Collection> metrics() { + return Arrays.stream(Message.Status.values()) + .map(s -> new Metric<>("message." + s.name().toLowerCase(), messageRepository.countByStatus(s))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java index ed540d2..7bf9d25 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java +++ b/src/main/java/com/maciejwalkowiak/mercury/core/message/MessageRepository.java @@ -1,9 +1,12 @@ package com.maciejwalkowiak.mercury.core.message; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; /** * MongoDB based repository for {@link Message} */ interface MessageRepository extends MongoRepository { + @Query(count = true, value = "{ 'status' : ?0 }") + long countByStatus(Message.Status status); } From 4272c39919116956d9e756d7f6264af246a25508 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 17 Jan 2015 01:56:05 +0100 Subject: [PATCH 52/55] Documentation update --- README.adoc | 232 +--------------------------------- doc/index.adoc | 336 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+), 228 deletions(-) create mode 100644 doc/index.adoc diff --git a/README.adoc b/README.adoc index 0a8e003..a43c871 100644 --- a/README.adoc +++ b/README.adoc @@ -6,235 +6,11 @@ ____ In Roman mythology *Mercury* is the patron god of messages and communication. ____ -*Mercury* is an application initially created for _Learning Spring Boot_ -contest. +*Mercury* is an application initially created for http://blog.greglturnquist.com/2014/12/announcing-learningspringboot-contest-cc-packtpub-springcentral.html[_Learning Spring Boot_] contest. +Source code is available at GitHub: https://github.com/maciejwalkowiak/contest/ -TIP: If you like the application *star* this Github repository and -*tweet* about it with #mercuryapp hashtag. +TIP: If you like the application *star* this Github repository and *tweet* about it with #mercuryapp hashtag. Mercury goal is to provide single **HTTP API** to send notifications in a company with internal applications in mind. -== Use case - -Quite often there are many custom internal applications used in companies: HR, accounting, business trips, bug trackers and so on. -Each one of them sends some notification to employees and developers have to solve same problems: - -* mail server connection configuration -* sending emails in async manner -* queue - -If mail server location or account changes it's details each application has to be reconfigured. - -== Features - -* sending email through *Java Mail* -* sending email through *https://sendgrid.com/[SendGrid]* -* sending messages to *https://slack.com[Slack]* -* asynchronous message processing - all incoming requests are saved in queue and processed by consumers -* messages are stored in *MongoDB* - you can anytime check if notification has been sent or not and what's the error message if there is any -* health checks - you can easily check if mail server/SendGrid connection is not misconfigured - -Upcoming features: - -* metrics - -== API - -Exposed API is dynamic - meaning that depending on what notifications provider is configured - corresponding endpoint is available. -For example if properties `slack.url` is not set handler for URL `http://:/api/slack` is not created. - -=== List API methods - ----- -GET,OPTIONS /api/ ----- - -==== Response - -[source,json] ----- -Status: 200 OK - -{ - "links":[ - { - "rel":"self", - "href":"http://localhost:8082/api" - }, - { - "rel":"message", - "href":"http://localhost:8082/api/message/{id}" - }, - { - "rel":"mail", - "href":"http://localhost:8082/api/mail" - }, { - "rel":"slack", - "href":"http://localhost:8082/api/slack" - } - ] -} ----- - -=== Get message - ----- -GET /api/message/{id} ----- - -==== Response - -[source,json] ----- -Status: 200 OK - -{ - "id":"54b992023004a2bf10dcaf98", - "status":"FAILED", - "errorMessage":"Mailing provider is not configured", - "request":{ - "to":[ - "john.doe@mercuryapp.com" - ], - "cc":[ - - ], - "bcc":[ - - ], - "text":"Hello John", - "subject":"It's been a long time" - } -} ----- - -=== Send email - -TIP: available only if *Java Mail* or *SendGrid* is configured - ----- -POST /api/mail ----- - -==== Request - -[source,json] ----- -{ - "to":[ - "john.doe@mercuryapp.com" - ], - "text":"Hello John", - "subject":"It's been a long time" -} ----- - -==== Response - ----- -Status: 201 CREATED -Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 ----- - -=== Send Slack message - -TIP: available only if *Slack* is configured - -[source,json] ----- -POST /api/slack ----- - -==== Request - -[source,json] ----- -{ - "text":"hello from slack!", - "icon_emoji":":chart_with_upwards_trend:", - "channel":"#urgent" -} ----- - -==== Response - -[source,json] ----- -Status: 201 CREATED -Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 ----- - -== How to run - -* install required software: Java 8 and MongoDB -* https://github.com/maciejwalkowiak/contest/releases/[Download latest release] -* configure `config/application.properties` -* run with `java -jar mercury-.jar` -* go to `http://localhost:8080/api` - -== Configuration options - -All *Mercury* configuration is stored in `config/application.properties` that you find in distribution package. - -=== Database configuration - -*Mercury* can persist messages with their statuses to MongoDB or save them into in-memory database that disappears when application goes down. - -For in-memory configuration make sure configuration contains following line: - -[source,json] ----- -mercury.db.inMemory=true ----- - -For MongoDB configuration make sure that property *mercury.db.inMemory* does not exist or is set to *false* and put following configuration properties: - -[source,json] ----- -spring.data.mongodb.uri=mongodb://localhost/test # connection URL -spring.data.mongodb.database= -spring.data.mongodb.username= -spring.data.mongodb.password= ----- - -=== Services configuration - -In the same place you can configure configuration to services. Configuration properties presence activates related service (make sure to delete *#* at the beginning of each property you wish to use): - -[source,json] ----- -# JavaMail configuration - optional -#spring.mail.host= -#spring.mail.port= -#spring.mail.username= -#spring.mail.password= - -# SendGrid configuration - optional -#sendgrid.username= -#sendgrid.password= - -# Slack configuration - optional -#slack.hook.url= ----- - -To configure additional Java Mail properties use -`spring.mail.properties` prefix. - -Sample configuration for Gmail account: - -[source,json] ------------------------------------------------------ -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username= -spring.mail.password= -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true ------------------------------------------------------ - -*IMPORTANT:* if both Java Mail *and* SendGrid configuration is provided -- all emails will be sent using SendGrid. - -Learn more about Slack webhooks at https://api.slack.com/ - -Additionally, since *Mercury* is based on *Spring Boot* you can use pretty much any of configuration property described in Boot docs: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +Read more on project website: https://maciejwalkowiak.github.io/contest/ \ No newline at end of file diff --git a/doc/index.adoc b/doc/index.adoc new file mode 100644 index 0000000..d3956e2 --- /dev/null +++ b/doc/index.adoc @@ -0,0 +1,336 @@ +:toc: right +:source-highlighter: coderay + +image::https://raw.githubusercontent.com/maciejwalkowiak/contest/gh-pages/mercury.png[] + +____ +In Roman mythology *Mercury* is the patron god of messages and communication. +____ + +*Mercury* is an application initially created for http://blog.greglturnquist.com/2014/12/announcing-learningspringboot-contest-cc-packtpub-springcentral.html[_Learning Spring Boot_] contest. Source code is available at GitHub: https://github.com/maciejwalkowiak/contest/ + +TIP: If you like the application *star* this Github repository and *tweet* about it with #mercuryapp hashtag. + +Mercury goal is to provide single **HTTP API** to send notifications in a company with internal applications in mind. + +== Use case + +*Mercury* is handy in following use case: + +Quite often there are many custom internal applications used in companies: HR, accounting, business trips, bug trackers and so on. +Each one of them sends some notification to employees and developers have to solve same problems: + +* mail server connection configuration +* sending emails in async manner +* queue + +If mail server location or account changes it's details each application has to be reconfigured. + +== Features + +* Sending emails - HTTP API for sending emails. Supported mailing services: + * *Java Mail* + * *https://sendgrid.com/[SendGrid]* +* Sending messages to *https://slack.com[Slack]* +* asynchronous message processing - all incoming requests are saved in queue and processed by consumers +* optionally - messages are stored in *MongoDB* - you can anytime check if notification has been sent or not and what's the error message if there is any + +=== Health checks + +`GET /health` gets health information from all configured services. If all application components are running fine, metrics will reply with: + +[source,json] +---- +{"status":"UP"} +---- + +But for example is mail server goes down or provided credentials are incorrect: + +[source,json] +---- +{"status":"DOWN"} +---- + +==== Metrics + +`GET /metrics` gets metrics information with number of messages in each state. + +[source,json] +---- +{ + ... + "message.queued":1, + "message.sent":34, + "message.failed":2, + ... +} +---- + +== API + +Exposed API is dynamic - meaning that depending on what notifications provider is configured - corresponding endpoint is available. +For example if properties `slack.url` is not set handler for URL `http://:/api/slack` is not created. + +=== List API methods + +---- +GET,OPTIONS /api/ +---- + +==== Response + +[source,json] +---- +Status: 200 OK + +{ + "links":[ + { + "rel":"self", + "href":"http://localhost:8082/api" + }, + { + "rel":"message", + "href":"http://localhost:8082/api/message/{id}" + }, + { + "rel":"mail", + "href":"http://localhost:8082/api/mail" + }, { + "rel":"slack", + "href":"http://localhost:8082/api/slack" + } + ] +} +---- + +=== Get message + +---- +GET /api/message/{id} +---- + +==== Response + +[source,json] +---- +Status: 200 OK + +{ + "id":"54b992023004a2bf10dcaf98", + "status":"FAILED", + "errorMessage":"Mailing provider is not configured", + "request":{ + "to":[ + "john.doe@mercuryapp.com" + ], + "cc":[ + + ], + "bcc":[ + + ], + "text":"Hello John", + "subject":"It's been a long time" + } +} +---- + +=== Send email + +TIP: available only if *Java Mail* or *SendGrid* is configured + +---- +POST /api/mail +---- + +==== Request + +[source,json] +---- +{ + "to":[ + "john.doe@mercuryapp.com" + ], + "cc":[ + "jane.smith@mercuryapp.com", + "will.moore@mercuryapp.com" + ], + "bcc":[ + "secret@mercuryapp.com" + ], + "text":"Hello John", + "subject":"It's been a long time" +} +---- + +==== Response + +---- +Status: 201 CREATED +Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 +---- + +=== Send Slack message + +TIP: available only if *Slack* is configured + +[source,json] +---- +POST /api/slack +---- + +==== Request + +[source,json] +---- +{ + "username":"Mercury", + "text":"hello from slack!", + "icon_emoji":":chart_with_upwards_trend:", + "icon_url":"https://raw.githubusercontent.com/maciejwalkowiak/contest/gh-pages/mercury.png", + "channel":"#urgent" +} +---- + +==== Response + +[source,json] +---- +Status: 201 CREATED +Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 +---- + +== How to run + +* install required software: *Java 8* and *MongoDB* (optional) +* https://github.com/maciejwalkowiak/contest/releases/[Download latest release] +* configure `config/application.properties` +* run with `java -jar mercury-.jar` +* go to `http://localhost:8080/api` + +== Configuration options + +All *Mercury* configuration is stored in `config/application.properties` that you find in distribution package. + +=== Database configuration + +*Mercury* can persist messages with their statuses to MongoDB or save them into in-memory database that disappears when application goes down. + +For in-memory configuration make sure configuration contains following line: + +[source,json] +---- +mercury.db.inMemory=true +---- + +For MongoDB configuration make sure that property *mercury.db.inMemory* does not exist or is set to *false* and put following configuration properties: + +[source,json] +---- +spring.data.mongodb.uri=mongodb://localhost/test # connection URL +spring.data.mongodb.database= +spring.data.mongodb.username= +spring.data.mongodb.password= +---- + +=== Services configuration + +In the same place you can configure configuration to services. Configuration properties presence activates related service. + +==== JavaMail + +[source,json] +---- +spring.mail.host= +spring.mail.port= +spring.mail.username= +spring.mail.password= +---- + +To configure additional Java Mail properties use +`spring.mail.properties` prefix. + +Sample configuration for *Gmail* account: + +[source,json] +----------------------------------------------------- +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username= +spring.mail.password= +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +----------------------------------------------------- + +==== SendGrid + +[source,json] +---- +sendgrid.username= +sendgrid.password= +---- + +*IMPORTANT:* if both *Java Mail* and *SendGrid* configuration is provided +- all emails will be sent using *SendGrid*. + +==== Slack + +[source,json] +---- +slack.hook.url= +---- + +Learn more about *Slack* WebHooks at https://api.slack.com/ + +==== Spring Boot configuration + +*Mercury* is based on *Spring Boot* so you can use pretty much any of configuration property described in Boot docs: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html + +For example in order to run *Mercury* on port *8082* add property: + +---- +server.port=8082 +---- + +== Technology stack + +* coded in *Java 8* +* built on the top of http://projects.spring.io/spring-boot/[Spring Boot] +* http://projects.spring.io/spring-data-mongodb/[Spring Data MongoDB] and https://github.com/fakemongo/fongo[Fongo] for storing data +* https://github.com/sendgrid/sendgrid-java[SendGrid-Java] for SendGrid integration +* documentation generated with http://asciidoctor.org[Asciidoctor] + +++++ + +
+
Created by
+
+ +
+
Maciej Walkowiak
+ + +
+++++ From 63df3d695e5054e5e61c42e00f5cb215b53838af Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 17 Jan 2015 02:19:52 +0100 Subject: [PATCH 53/55] removed docs --- doc/index.adoc | 336 ------------------------------------------------- 1 file changed, 336 deletions(-) delete mode 100644 doc/index.adoc diff --git a/doc/index.adoc b/doc/index.adoc deleted file mode 100644 index d3956e2..0000000 --- a/doc/index.adoc +++ /dev/null @@ -1,336 +0,0 @@ -:toc: right -:source-highlighter: coderay - -image::https://raw.githubusercontent.com/maciejwalkowiak/contest/gh-pages/mercury.png[] - -____ -In Roman mythology *Mercury* is the patron god of messages and communication. -____ - -*Mercury* is an application initially created for http://blog.greglturnquist.com/2014/12/announcing-learningspringboot-contest-cc-packtpub-springcentral.html[_Learning Spring Boot_] contest. Source code is available at GitHub: https://github.com/maciejwalkowiak/contest/ - -TIP: If you like the application *star* this Github repository and *tweet* about it with #mercuryapp hashtag. - -Mercury goal is to provide single **HTTP API** to send notifications in a company with internal applications in mind. - -== Use case - -*Mercury* is handy in following use case: - -Quite often there are many custom internal applications used in companies: HR, accounting, business trips, bug trackers and so on. -Each one of them sends some notification to employees and developers have to solve same problems: - -* mail server connection configuration -* sending emails in async manner -* queue - -If mail server location or account changes it's details each application has to be reconfigured. - -== Features - -* Sending emails - HTTP API for sending emails. Supported mailing services: - * *Java Mail* - * *https://sendgrid.com/[SendGrid]* -* Sending messages to *https://slack.com[Slack]* -* asynchronous message processing - all incoming requests are saved in queue and processed by consumers -* optionally - messages are stored in *MongoDB* - you can anytime check if notification has been sent or not and what's the error message if there is any - -=== Health checks - -`GET /health` gets health information from all configured services. If all application components are running fine, metrics will reply with: - -[source,json] ----- -{"status":"UP"} ----- - -But for example is mail server goes down or provided credentials are incorrect: - -[source,json] ----- -{"status":"DOWN"} ----- - -==== Metrics - -`GET /metrics` gets metrics information with number of messages in each state. - -[source,json] ----- -{ - ... - "message.queued":1, - "message.sent":34, - "message.failed":2, - ... -} ----- - -== API - -Exposed API is dynamic - meaning that depending on what notifications provider is configured - corresponding endpoint is available. -For example if properties `slack.url` is not set handler for URL `http://:/api/slack` is not created. - -=== List API methods - ----- -GET,OPTIONS /api/ ----- - -==== Response - -[source,json] ----- -Status: 200 OK - -{ - "links":[ - { - "rel":"self", - "href":"http://localhost:8082/api" - }, - { - "rel":"message", - "href":"http://localhost:8082/api/message/{id}" - }, - { - "rel":"mail", - "href":"http://localhost:8082/api/mail" - }, { - "rel":"slack", - "href":"http://localhost:8082/api/slack" - } - ] -} ----- - -=== Get message - ----- -GET /api/message/{id} ----- - -==== Response - -[source,json] ----- -Status: 200 OK - -{ - "id":"54b992023004a2bf10dcaf98", - "status":"FAILED", - "errorMessage":"Mailing provider is not configured", - "request":{ - "to":[ - "john.doe@mercuryapp.com" - ], - "cc":[ - - ], - "bcc":[ - - ], - "text":"Hello John", - "subject":"It's been a long time" - } -} ----- - -=== Send email - -TIP: available only if *Java Mail* or *SendGrid* is configured - ----- -POST /api/mail ----- - -==== Request - -[source,json] ----- -{ - "to":[ - "john.doe@mercuryapp.com" - ], - "cc":[ - "jane.smith@mercuryapp.com", - "will.moore@mercuryapp.com" - ], - "bcc":[ - "secret@mercuryapp.com" - ], - "text":"Hello John", - "subject":"It's been a long time" -} ----- - -==== Response - ----- -Status: 201 CREATED -Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 ----- - -=== Send Slack message - -TIP: available only if *Slack* is configured - -[source,json] ----- -POST /api/slack ----- - -==== Request - -[source,json] ----- -{ - "username":"Mercury", - "text":"hello from slack!", - "icon_emoji":":chart_with_upwards_trend:", - "icon_url":"https://raw.githubusercontent.com/maciejwalkowiak/contest/gh-pages/mercury.png", - "channel":"#urgent" -} ----- - -==== Response - -[source,json] ----- -Status: 201 CREATED -Location: http://localhost:8082/api/message/54b992023004a2bf10dcaf98 ----- - -== How to run - -* install required software: *Java 8* and *MongoDB* (optional) -* https://github.com/maciejwalkowiak/contest/releases/[Download latest release] -* configure `config/application.properties` -* run with `java -jar mercury-.jar` -* go to `http://localhost:8080/api` - -== Configuration options - -All *Mercury* configuration is stored in `config/application.properties` that you find in distribution package. - -=== Database configuration - -*Mercury* can persist messages with their statuses to MongoDB or save them into in-memory database that disappears when application goes down. - -For in-memory configuration make sure configuration contains following line: - -[source,json] ----- -mercury.db.inMemory=true ----- - -For MongoDB configuration make sure that property *mercury.db.inMemory* does not exist or is set to *false* and put following configuration properties: - -[source,json] ----- -spring.data.mongodb.uri=mongodb://localhost/test # connection URL -spring.data.mongodb.database= -spring.data.mongodb.username= -spring.data.mongodb.password= ----- - -=== Services configuration - -In the same place you can configure configuration to services. Configuration properties presence activates related service. - -==== JavaMail - -[source,json] ----- -spring.mail.host= -spring.mail.port= -spring.mail.username= -spring.mail.password= ----- - -To configure additional Java Mail properties use -`spring.mail.properties` prefix. - -Sample configuration for *Gmail* account: - -[source,json] ------------------------------------------------------ -spring.mail.host=smtp.gmail.com -spring.mail.port=587 -spring.mail.username= -spring.mail.password= -spring.mail.properties.mail.smtp.auth=true -spring.mail.properties.mail.smtp.starttls.enable=true ------------------------------------------------------ - -==== SendGrid - -[source,json] ----- -sendgrid.username= -sendgrid.password= ----- - -*IMPORTANT:* if both *Java Mail* and *SendGrid* configuration is provided -- all emails will be sent using *SendGrid*. - -==== Slack - -[source,json] ----- -slack.hook.url= ----- - -Learn more about *Slack* WebHooks at https://api.slack.com/ - -==== Spring Boot configuration - -*Mercury* is based on *Spring Boot* so you can use pretty much any of configuration property described in Boot docs: http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html - -For example in order to run *Mercury* on port *8082* add property: - ----- -server.port=8082 ----- - -== Technology stack - -* coded in *Java 8* -* built on the top of http://projects.spring.io/spring-boot/[Spring Boot] -* http://projects.spring.io/spring-data-mongodb/[Spring Data MongoDB] and https://github.com/fakemongo/fongo[Fongo] for storing data -* https://github.com/sendgrid/sendgrid-java[SendGrid-Java] for SendGrid integration -* documentation generated with http://asciidoctor.org[Asciidoctor] - -++++ - -
-
Created by
-
- -
-
Maciej Walkowiak
- - -
-++++ From 66c74dabf34183f18462a12186bbc6cc25ebeee9 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 17 Jan 2015 02:52:00 +0100 Subject: [PATCH 54/55] Added logs, updated default config --- etc/application.properties | 6 ++++-- .../javamail/MailServerHealthIndicator.java | 5 +++++ .../sendgrid/SendGridHealthIndicator.java | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/etc/application.properties b/etc/application.properties index 287688c..d51ea99 100644 --- a/etc/application.properties +++ b/etc/application.properties @@ -1,6 +1,8 @@ # Mercury configuration -# MongoDB - required +mercury.db.inMemory=true + +# MongoDB - optional #spring.data.mongodb.uri=mongodb://localhost/test # connection URL #spring.data.mongodb.database= #spring.data.mongodb.username= @@ -16,5 +18,5 @@ #sendgrid.username= #sendgrid.password= -# Slack configuration +# Slack configuration - optional #slack.hook.url= \ No newline at end of file diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java index 2469c4c..3db79ba 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/javamail/MailServerHealthIndicator.java @@ -1,5 +1,7 @@ package com.maciejwalkowiak.mercury.mail.javamail; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; @@ -23,6 +25,8 @@ */ @Component class MailServerHealthIndicator extends AbstractHealthIndicator { + private static final Logger LOG = LoggerFactory.getLogger(MailServerHealthIndicator.class); + private final MailProperties mailProperties; @Autowired @@ -44,6 +48,7 @@ protected void doHealthCheck(Health.Builder builder) { builder.up(); } catch (MessagingException e) { + LOG.error("JavaMail connection is down", e); builder.down(e); } } diff --git a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java index 28efe59..311fb4d 100644 --- a/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java +++ b/src/main/java/com/maciejwalkowiak/mercury/mail/sendgrid/SendGridHealthIndicator.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.Health; @@ -18,6 +20,7 @@ */ @Component class SendGridHealthIndicator extends AbstractHealthIndicator { + private static final Logger LOG = LoggerFactory.getLogger(SendGridHealthIndicator.class); private static final String GET_PROFILE_URL = "https://api.sendgrid.com/api/profile.get.json"; private final RestTemplate restTemplate; @@ -36,6 +39,7 @@ protected void doHealthCheck(Health.Builder builder) throws Exception { getAuthenticationProperties()); if (sendGridResponse.error != null) { + LOG.error("SendGrid connection is down: {}", sendGridResponse.error); builder.down().withDetail("message", sendGridResponse.error.message); } else { builder.up(); @@ -71,6 +75,21 @@ public ErrorDetails(@JsonProperty("code") String code, @JsonProperty("message") this.code = code; this.message = message; } + + @Override + public String toString() { + return "ErrorDetails{" + + "code='" + code + '\'' + + ", message='" + message + '\'' + + '}'; + } + } + + @Override + public String toString() { + return "SendGridResponse{" + + "error=" + error + + '}'; } } From 07e1ee90524037739d0d51b18be3c0946a2c1214 Mon Sep 17 00:00:00 2001 From: Maciej Walkowiak Date: Sat, 17 Jan 2015 02:55:09 +0100 Subject: [PATCH 55/55] Version 0.0.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 85a7d0c..0961403 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.maciejwalkowiak mercury - 0.0.2-SNAPSHOT + 0.0.2 jar Hermes Messenger