refactor: email notifications better default config
DELETE
distro/wildfly/src/main/resources/overlay/standalone/configuration/email-notification-templates.json
-{
- "en": {
- "apiman.account.signup.approval.request": {
- "subject": "Action required: New account signup {event.userId} needs activation",
- "htmlBody": "Hi {notification.recipient.fullName}",
- "plainBody": "Hi {notification.recipient.fullName}",
- "category": "USER_ADMINISTRATION"
- }
- }
-}

ADD
distro/wildfly/src/main/resources/overlay/standalone/configuration/email-notification-templates.json5
+{
+ "en": {
+ "apiman.account.approval.request": {
+ subject: "Action required: New developer account signup {notification.payload.userId} needs activation",
+ htmlBody: "Hi {notification.recipient.fullName}, \
+ \
+ A user '{notification.recipient.fullName}' has signed up to Apiman and their account requires approval.",
+ plainBody: "Hi {notification.recipient.fullName}",
+ category: "USER_ADMINISTRATION"
+ },
+ "apiman.client.contract.approval.request": {
+ subject: "Action required: API signup approval request",
+ htmlBody: "Hi {notification.recipient.fullName}, \
+\
+A developer is requesting approval to sign up to an API, and you are able to approve this. \
+\
+Information: \
+\
+The API approval is requested for: {notification.payload.apiOrgId} / {notification.payload.apiId} / {notification.payload.apiVersion} with plan {notification.payload.planId} / {notification.payload.planVersion} \
+\
+The user who initiated initiated the approval request: {notification.payload.user.username} / {notification.payload.user.email} \
+\
+The client the approval request was issued from: {notification.payload.clientOrgId} / {notification.payload.clientId} / {notification.payload.clientVersion} \
+\
+Actions: \
+\
+If you wish to approve the sign up request you can visit: \
+<a href='apiman.approvalAdminUrl/orgs/{notification.payload.apiOrgId}/apis/{notification.payload.apiId}/{notification.payload.apiVersion}/contracts'>the Apiman approvals page</a> for this API.",
+ plainBody: "",
+ category: "API_ADMINISTRATION"
+ },
+ "apiman.client.contract.approval.granted": {
+ subject: "Information: Your API signup request for {notification.payload.clientId} {notifications.payload.clientVersion} has been approved.",
+ htmlBody: "Hi {notification.recipient.fullName}, \
+\
+Your API signup request for {notification.payload.clientId} {notifications.payload.clientVersion} has been approved! \
+\
+To view your API key and other technical information, please visit: apiman.approvedApiUrl \
+",
+ plainBody: "",
+ category: "API_ADMINISTRATION"
+ }
+ }
+}

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/SimpleMailNotificationService.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/SimpleMailNotificationService.java
@@ -16,16 +16,21 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Locale.LanguageRange;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.trie.PatriciaTrie;
+import org.apache.commons.lang3.ObjectUtils;
+import org.elasticsearch.common.util.LocaleUtils;
import org.jetbrains.annotations.NotNull;
/**
@@ -166,8 +171,21 @@ public class SimpleMailNotificationService {
} else {
LOGGER.debug("Found template for reason {0} as {1}, shorter matching reasons templates "
+ "may also exist", selected.getKey(), reasonKey);
- return Optional.ofNullable(selected.getValue())
- .map(localeMap -> localeMap.get(locale));
+
+ Map<Locale, EmailNotificationTemplate> localeMap = selected.getValue();
+ if (localeMap == null) {
+ return Optional.empty();
+ } else {
+ List<Locale> bestMatchingLocale = List.of(
+ Locale.lookup(LanguageRange.parse(locale.toLanguageTag()), supportedLocales),
+ Locale.getDefault(),
+ new Locale("en") // TODO(msavy): config-based default language?
+ );
+ return bestMatchingLocale.stream()
+ .map(localeMap::get)
+ .filter(Objects::nonNull)
+ .findFirst();
+ }
}
}
@@ -204,33 +222,8 @@ public class SimpleMailNotificationService {
return results;
}
- // /**
- // * Get first template for a reason category (generally more coarse-grained).
- // *
- // * @param category the category to get a template for
- // * @return template, if a suitable one is found, otherwise empty
- // */
- // public Optional<EmailNotificationTemplate> findTemplateFor(@NotNull NotificationCategory category, @NotNull Locale locale) {
- // if (categoryToTemplateMap.containsKey(category)) {
- // return Optional.ofNullable(categoryToTemplateMap.get(category))
- // .map(list -> list.get(0))
- // .map(localeMap -> localeMap.get(0));
- // }
- // return Optional.empty();
- // }
-
- // /**
- // * Get all templates for a reason category (generally more coarse-grained).
- // *
- // * @param category the category to get a template for
- // * @return template, if a suitable one is found, otherwise empty
- // */
- // public List<Map<Locale, EmailNotificationTemplate>> findAllTemplatesFor(@NotNull NotificationCategory category) {
- // return categoryToTemplateMap.get(category);
- // }
-
private void readEmailNotificationTemplatesFromFile() {
- Path file = config.getConfigDirectory().resolve("email-notification-templates.json");
+ Path file = config.getConfigDirectory().resolve("email-notification-templates.json5");
if (Files.notExists(file)) {
LOGGER.warn("No email notification templates found at {0}", file);
return;

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/AccountSignupApprovalEmailNotification.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/AccountSignupApprovalEmailNotification.java
@@ -12,6 +12,7 @@ import io.apiman.manager.api.providers.eager.EagerLoaded;
import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
import javax.inject.Inject;
/**

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/ContractApprovalEmailNotification.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/ContractApprovalEmailNotification.java
@@ -12,12 +12,14 @@ import io.apiman.manager.api.providers.eager.EagerLoaded;
import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.event.Observes;
import javax.inject.Inject;
/**
* @author Marc Savy {@literal <marc@blackparrotlabs.io>}
*/
-@EagerLoaded
+//@EagerLoaded
@ApplicationScoped
public class ContractApprovalEmailNotification implements INotificationHandler {
@@ -28,8 +30,12 @@ public class ContractApprovalEmailNotification implements INotificationHandler {
this.mailNotificationService = mailNotificationService;
}
+ public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
+ // no-op to force eager initialization
+ }
+
@Override
- public void handle(NotificationDto<? extends IVersionedApimanEvent> notif) {
+ public void handle(@Observes NotificationDto<?> notif) {
NotificationDto<ContractApprovalEvent> notification = (NotificationDto<ContractApprovalEvent>) notif;
Map<String, Object> templateMap = buildTemplateMap(notification);
UserDto recipient = notification.getRecipient();

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/ContractApprovalRequestEmailNotification.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/email/handlers/ContractApprovalRequestEmailNotification.java
@@ -12,6 +12,8 @@ import io.apiman.manager.api.providers.eager.EagerLoaded;
import java.util.Map;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.event.Observes;
import javax.inject.Inject;
/**
@@ -31,8 +33,12 @@ public class ContractApprovalRequestEmailNotification implements INotificationHa
public ContractApprovalRequestEmailNotification() {
}
+ public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
+ // no-op to force eager initialization
+ }
+
@Override
- public void handle(NotificationDto<? extends IVersionedApimanEvent> rawNotification) {
+ public void handle(@Observes NotificationDto<?> rawNotification) {
@SuppressWarnings("unchecked")
NotificationDto<ContractCreatedEvent> signupNotification = (NotificationDto<ContractCreatedEvent>) rawNotification;
ContractCreatedEvent event = signupNotification.getPayload();

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/producers/ContractApprovalNotificationProducer.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/producers/ContractApprovalNotificationProducer.java
@@ -14,6 +14,7 @@ import io.apiman.manager.api.service.NotificationService;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
@@ -39,6 +40,10 @@ public class ContractApprovalNotificationProducer implements INotificationProduc
public ContractApprovalNotificationProducer() {}
+ public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
+ // no-op to force eager initialization
+ }
+
public void processEvent(@Observes ContractApprovalEvent approvalEvent) {
LOGGER.debug("Processing contract approval event: {0}", approvalEvent);
String orgId = approvalEvent.getApiOrgId();

MODIFY
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/producers/ContractApprovalRequestNotificationProducer.java
manager/api/rest-impl/src/main/java/io/apiman/manager/api/notifications/producers/ContractApprovalRequestNotificationProducer.java
@@ -14,6 +14,7 @@ import io.apiman.manager.api.service.NotificationService;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Initialized;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
@@ -37,6 +38,10 @@ public class ContractApprovalRequestNotificationProducer implements INotificatio
public ContractApprovalRequestNotificationProducer() {}
+ public void init(@Observes @Initialized(ApplicationScoped.class) Object init) {
+ // no-op to force eager initialization
+ }
+
public void processEvent(@Observes ContractCreatedEvent signupEvent) {
LOGGER.debug("Processing signup event {0}", signupEvent);
if (signupEvent.isApprovalRequired()) {

MODIFY
manager/test/api/src/test/java/io/apiman/manager/api/notifications/email/SimpleMailNotificationServiceTest.java
manager/test/api/src/test/java/io/apiman/manager/api/notifications/email/SimpleMailNotificationServiceTest.java
@@ -34,7 +34,7 @@ import static org.assertj.core.api.Assertions.tuple;
*/
public class SimpleMailNotificationServiceTest {
// Pull templates from resources
- File file = new File(getClass().getClassLoader().getResource("apiman/config/email-notification-templates.json").getFile());
+ File file = new File(getClass().getClassLoader().getResource("apiman/config/email-notification-templates.json5").getFile());
// Override default config with our test config
ApiManagerConfig config = new WarApiManagerConfig() {

RENAME
manager/test/api/src/test/resources/apiman/config/email-notification-templates.json
manager/test/api/src/test/resources/apiman/config/email-notification-templates.json5
refactor: email notifications better default config