From f613e6ded37c60a1a0254994c93bf249913ca883 Mon Sep 17 00:00:00 2001 From: Patrick Huang Date: Tue, 25 Nov 2014 16:01:21 +1000 Subject: [PATCH] rhbz1120457 - enable email notification on language team permission change --- .../src/test/resources/conf/standalone.xml | 100 ++++++++++++ zanata-war/pom.xml | 11 ++ .../org/zanata/ApplicationConfiguration.java | 11 +- ...uageTeamPermissionChangeEmailStrategy.java | 93 +++++++++++ .../LanguageTeamPermissionChangedEvent.java | 104 ++++++++++++ .../notification/DeadLetterQueueReceiver.java | 154 ++++++++++++++++++ .../EmailQueueMessageReceiver.java | 117 +++++++++++++ ...missionChangeJmsMessagePayloadHandler.java | 82 ++++++++++ .../notification/NotificationManager.java | 73 +++++++++ .../security/AuthenticationManager.java | 16 +- .../service/impl/LanguageTeamServiceImpl.java | 30 ++++ .../src/main/resources/messages.properties | 13 +- .../language_team_permission_changed.vm | 17 ++ .../WEB-INF/classes/META-INF/components.xml | 6 + .../src/main/webapp-jboss/WEB-INF/ejb-jar.xml | 20 +++ .../main/webapp-jboss/WEB-INF/jboss-ejb3.xml | 8 + .../arquillian/standalone-arquillian.xml | 84 +++++++++- 17 files changed, 926 insertions(+), 13 deletions(-) create mode 100644 zanata-war/src/main/java/org/zanata/email/LanguageTeamPermissionChangeEmailStrategy.java create mode 100644 zanata-war/src/main/java/org/zanata/events/LanguageTeamPermissionChangedEvent.java create mode 100644 zanata-war/src/main/java/org/zanata/notification/DeadLetterQueueReceiver.java create mode 100644 zanata-war/src/main/java/org/zanata/notification/EmailQueueMessageReceiver.java create mode 100644 zanata-war/src/main/java/org/zanata/notification/LanguageTeamPermissionChangeJmsMessagePayloadHandler.java create mode 100644 zanata-war/src/main/java/org/zanata/notification/NotificationManager.java create mode 100644 zanata-war/src/main/resources/org/zanata/email/templates/language_team_permission_changed.vm create mode 100644 zanata-war/src/main/webapp-jboss/WEB-INF/ejb-jar.xml create mode 100644 zanata-war/src/main/webapp-jboss/WEB-INF/jboss-ejb3.xml diff --git a/functional-test/src/test/resources/conf/standalone.xml b/functional-test/src/test/resources/conf/standalone.xml index 0ab223d794..578c643c88 100644 --- a/functional-test/src/test/resources/conf/standalone.xml +++ b/functional-test/src/test/resources/conf/standalone.xml @@ -14,6 +14,7 @@ + @@ -134,6 +135,10 @@ + + + + + + + true + false + NIO + 102400 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + jms.queue.DLQ + jms.queue.ExpiryQueue + 5000 + 2 + 10485760 + BLOCK + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + true + + + + + + + + + provided + + org.jboss.spec.javax.jms + jboss-jms-api_1.1_spec + provided + + + org.jboss.spec.javax.ejb + jboss-ejb-api_3.1_spec + provided + + com.h2database diff --git a/zanata-war/src/main/java/org/zanata/ApplicationConfiguration.java b/zanata-war/src/main/java/org/zanata/ApplicationConfiguration.java index ecc08dae81..d94ce43056 100644 --- a/zanata-war/src/main/java/org/zanata/ApplicationConfiguration.java +++ b/zanata-war/src/main/java/org/zanata/ApplicationConfiguration.java @@ -122,6 +122,8 @@ public class ApplicationConfiguration implements Serializable { private Optional openIdProvider; // Cache the OpenId provider + private String serverPath; + @Create public void load() { log.info("Reloading configuration"); @@ -236,18 +238,21 @@ public String getRegisterPath() { public String getServerPath() { String configuredValue = databaseBackedConfig.getServerHost(); + if (configuredValue != null) { + serverPath = configuredValue; + } // Try to determine a server path if one is not configured - if (configuredValue == null) { + if (serverPath == null) { HttpServletRequest request = ServletContexts.instance().getRequest(); if (request != null) { - configuredValue = + serverPath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath(); } } - return configuredValue; + return serverPath; } public String getDocumentFileStorageLocation() { diff --git a/zanata-war/src/main/java/org/zanata/email/LanguageTeamPermissionChangeEmailStrategy.java b/zanata-war/src/main/java/org/zanata/email/LanguageTeamPermissionChangeEmailStrategy.java new file mode 100644 index 0000000000..99fb32627d --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/email/LanguageTeamPermissionChangeEmailStrategy.java @@ -0,0 +1,93 @@ +package org.zanata.email; + +import java.util.List; +import javax.mail.internet.InternetAddress; + +import org.zanata.events.LanguageTeamPermissionChangedEvent; +import org.zanata.i18n.Messages; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.googlecode.totallylazy.collections.PersistentMap; +import lombok.RequiredArgsConstructor; + +/** + * @author Patrick Huang pahuang@redhat.com + */ +@RequiredArgsConstructor +public class LanguageTeamPermissionChangeEmailStrategy extends EmailStrategy { + private final LanguageTeamPermissionChangedEvent changedEvent; + private final Messages msgs; + + @Override + public String getSubject(Messages msgs) { + return msgs.format("jsf.email.languageteam.permission.Subject", + changedEvent.getLanguage()); + } + + @Override + public String getBodyResourceName() { + return "org/zanata/email/templates/language_team_permission_changed.vm"; + } + + @Override + public Optional getReplyToAddress() { + return Optional.of(Addresses.getReplyTo( + changedEvent.getChangedByEmail(), + changedEvent.getChangedByName())); + } + + @Override + public PersistentMap makeContext( + PersistentMap genericContext, + InternetAddress[] toAddresses) { + PersistentMap context = super.makeContext( + genericContext, toAddresses); + List oldPermissions = Lists.newArrayList(); + if (changedEvent.numOfGrantedOldPermissions() == 0) { + oldPermissions + .add( + msgs.get("jsf.email.languageteam.permission.old.notInTeam")); + } else { + transformPermissionToDescription( + changedEvent.getOldPermission(), oldPermissions); + } + + List newPermissions = Lists.newArrayList(); + if (changedEvent.numOfGrantedNewPermissions() == 0) { + newPermissions.add(msgs + .get("jsf.email.languageteam.permission.new.notInTeam")); + } else { + transformPermissionToDescription( + changedEvent.getNewPermission(), newPermissions); + } + + return context + .insert("language", changedEvent.getLanguage()) + .insert("changedByName", changedEvent.getChangedByName()) + .insert("changedByEmail", changedEvent.getChangedByEmail()) + .insert("oldPermissions", oldPermissions) + .insert("newPermissions", newPermissions) + .insert("toName", toAddresses[0].getPersonal()); + } + + private void transformPermissionToDescription( + List permissionList, List permissionDescriptions) { + if (changedEvent.translatorPermissionOf( + permissionList)) { + permissionDescriptions.add( + msgs.get("jsf.email.languageteam.permission.isTranslator")); + } + if (changedEvent.reviewerPermissionOf( + permissionList)) { + permissionDescriptions.add( + msgs.get("jsf.email.languageteam.permission.isReviewer")); + } + if (changedEvent.coordinatorPermissionOf( + permissionList)) { + permissionDescriptions + .add( + msgs.get("jsf.email.languageteam.permission.isCoordinator")); + } + } +} diff --git a/zanata-war/src/main/java/org/zanata/events/LanguageTeamPermissionChangedEvent.java b/zanata-war/src/main/java/org/zanata/events/LanguageTeamPermissionChangedEvent.java new file mode 100644 index 0000000000..ab38a11125 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/events/LanguageTeamPermissionChangedEvent.java @@ -0,0 +1,104 @@ +package org.zanata.events; + +import java.io.Serializable; +import java.util.List; + +import lombok.Getter; +import lombok.ToString; + +import org.zanata.common.LocaleId; +import org.zanata.model.HLocaleMember; +import org.zanata.model.HPerson; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +/** + * @author Patrick Huang pahuang@redhat.com + */ +@Getter +@ToString +public class LanguageTeamPermissionChangedEvent implements Serializable { + public static final String LANGUAGE_TEAM_PERMISSION_CHANGED = + "languageTeamPermissionChanged"; + private static final long serialVersionUID = -1L; + + private final LocaleId language; + private final String name; + private final String email; + private final String changedByName; + private final String changedByEmail; + private List oldPermission = ImmutableList.of(false, false, false); + private List newPermission = ImmutableList.of(false, false, false); + + public LanguageTeamPermissionChangedEvent(HPerson person, LocaleId language, + HPerson doneByPerson) { + name = person.getName(); + email = person.getEmail(); + this.language = language; + changedByName = doneByPerson.getName(); + changedByEmail = doneByPerson.getEmail(); + } + + public LanguageTeamPermissionChangedEvent joiningTheTeam( + boolean isTranslator, boolean isReviewer, boolean isCoordinator) { + return this.oldPermission(false, false, false) + .newPermission(isTranslator, isReviewer, isCoordinator); + } + + public LanguageTeamPermissionChangedEvent updatingPermissions( + HLocaleMember oldMembership, boolean isTranslator, + boolean isReviewer, boolean isCoordinator) { + return this.oldPermission(oldMembership.isTranslator(), + oldMembership.isReviewer(), oldMembership.isCoordinator()) + .newPermission(isTranslator, isReviewer, isCoordinator); + } + + private LanguageTeamPermissionChangedEvent oldPermission( + boolean isTranslator, boolean isReviewer, boolean isCoordinator) { + oldPermission = + ImmutableList.of(isTranslator, isReviewer, isCoordinator); + return this; + } + + private LanguageTeamPermissionChangedEvent newPermission( + boolean isTranslator, boolean isReviewer, boolean isCoordinator) { + newPermission = + ImmutableList.of(isTranslator, isReviewer, isCoordinator); + return this; + } + + public int numOfGrantedOldPermissions() { + return Iterables.frequency(oldPermission, Boolean.TRUE); + } + + public int numOfGrantedNewPermissions() { + return Iterables.frequency(newPermission, Boolean.TRUE); + } + + public boolean translatorPermissionOf(List permissionList) { + return getPermission(permissionList, Permission.translator); + } + + public boolean reviewerPermissionOf(List permissionList) { + return getPermission(permissionList, Permission.reviewer); + } + + public boolean coordinatorPermissionOf(List permissionList) { + return getPermission(permissionList, Permission.coordinator); + } + + private static Boolean getPermission(List permissionList, + Permission permission) { + return permissionList.get(permission.ordinal()); + } + + public boolean isPermissionChanged() { + return !oldPermission.equals(newPermission); + } + + private static enum Permission { + translator, reviewer, coordinator + } +} diff --git a/zanata-war/src/main/java/org/zanata/notification/DeadLetterQueueReceiver.java b/zanata-war/src/main/java/org/zanata/notification/DeadLetterQueueReceiver.java new file mode 100644 index 0000000000..145b7711d5 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/notification/DeadLetterQueueReceiver.java @@ -0,0 +1,154 @@ +/* + * Copyright 2014, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.notification; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import javax.ejb.ActivationConfigProperty; +import javax.ejb.MessageDriven; +import javax.jms.Message; +import javax.jms.MessageListener; +import javax.jms.ObjectMessage; +import javax.jms.TextMessage; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.annotations.Name; + +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; + +/** + * @author Patrick Huang pahuang@redhat.com + */ +@MessageDriven(activationConfig = { + @ActivationConfigProperty( + propertyName = "destinationType", + propertyValue = "javax.jms.Queue" + ), + @ActivationConfigProperty( + propertyName = "destination", + propertyValue = "jms/queue/DLQ" + ), + @ActivationConfigProperty( + propertyName = "maxSession", + propertyValue = "1") +}) +@Name("deadLetterQueueReceiver") +@Slf4j +public class DeadLetterQueueReceiver implements MessageListener { + + @Override + public void onMessage(Message message) { + log.error("dead message: {}", DeadMessageUnwrapper.unwrap(message)); + } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + static class DeadMessageUnwrapper { + private final Serializable payload; + private final Map properties; + + static DeadMessageUnwrapper unwrap(final Message message) { + + Serializable payload = extractPayload(message); + Map properties = extractProperties(message); + // we may want to add more interested properties. e.g. reply address + return new DeadMessageUnwrapper(payload, properties); + } + + private static Map extractProperties(final Message message) { + List propNames = + tryDoOrNullOnException(new Callable>() { + @Override + public List call() throws Exception { + return Collections.list(message.getPropertyNames()); + } + }); + + ImmutableMap.Builder builder = + ImmutableMap.builder(); + for (String propName : propNames) { + builder.put(propName, getPropertyValue(message, propName)); + } + return builder.build(); + } + + private static Object getPropertyValue(final Message message, + final String propertyName) { + return tryDoOrNullOnException(new Callable() { + @Override + public Object call() throws Exception { + return message.getObjectProperty(propertyName); + } + }); + } + + /** + * try to extract message payload by message type. + * + * @return message payload + */ + private static Serializable extractPayload(final Message message) { + // note: this is NOT a complete list of message types + if (message instanceof ObjectMessage) { + return tryDoOrNullOnException(new Callable() { + @Override + public Serializable call() throws Exception { + return ((ObjectMessage) message).getObject(); + } + }); + } else if (message instanceof TextMessage) { + return tryDoOrNullOnException(new Callable() { + @Override + public Serializable call() throws Exception { + return ((TextMessage) message).getText(); + } + }); + } else { + // generally this is not going to give us much information + return message.toString(); + } + } + + static T tryDoOrNullOnException(Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + return null; + } + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("payload", payload) + .add("properties", properties) + .toString(); + } + } +} diff --git a/zanata-war/src/main/java/org/zanata/notification/EmailQueueMessageReceiver.java b/zanata-war/src/main/java/org/zanata/notification/EmailQueueMessageReceiver.java new file mode 100644 index 0000000000..86e7e720c9 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/notification/EmailQueueMessageReceiver.java @@ -0,0 +1,117 @@ +/* + * Copyright 2014, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.notification; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; + +import javax.ejb.ActivationConfigProperty; +import javax.ejb.MessageDriven; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageListener; +import javax.jms.ObjectMessage; + +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.zanata.events.LanguageTeamPermissionChangedEvent; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; + +import static com.google.common.base.Strings.nullToEmpty; +import static org.zanata.notification.NotificationManager.MessagePropertiesKey; + +/** + * @author Patrick Huang pahuang@redhat.com + */ +@MessageDriven(activationConfig = { + @ActivationConfigProperty( + propertyName = "destinationType", + propertyValue = "javax.jms.Queue" + ), + @ActivationConfigProperty( + propertyName = "destination", + propertyValue = "jms/queue/MailsQueue" + ) +}) +@Name("emailQueueMessageReceiver") +@Slf4j +public class EmailQueueMessageReceiver implements MessageListener { + + private static Map handlers = Collections + .emptyMap(); + + @In("languageTeamPermissionChangeJmsMessagePayloadHandler") + private LanguageTeamPermissionChangeJmsMessagePayloadHandler + languageTeamHandler; + + @Override + public void onMessage(Message message) { + if (message instanceof ObjectMessage) { + try { + String objectType = + nullToEmpty( + message.getStringProperty( + MessagePropertiesKey.objectType.name())); + + JmsMessagePayloadHandler jmsMessagePayloadHandler = + getHandlers().get(objectType); + if (jmsMessagePayloadHandler != null) { + log.debug("found handler for message object type [{}]", + objectType); + jmsMessagePayloadHandler.handle(((ObjectMessage) message) + .getObject()); + } else { + log.warn("can not find handler for message:{}", message); + } + + } catch (JMSException e) { + log.warn("error handling jms message: {}", message); + Throwables.propagate(e); + } + } + } + + public Map getHandlers() { + if (handlers.isEmpty()) { + synchronized (this) { + handlers = + ImmutableMap + . builder() + .put(LanguageTeamPermissionChangedEvent.class + .getCanonicalName(), + languageTeamHandler) + .build(); + } + log.info("email queue payload handlers: {}", handlers); + } + return handlers; + } + + public static interface JmsMessagePayloadHandler { + void handle(Serializable data); + } +} diff --git a/zanata-war/src/main/java/org/zanata/notification/LanguageTeamPermissionChangeJmsMessagePayloadHandler.java b/zanata-war/src/main/java/org/zanata/notification/LanguageTeamPermissionChangeJmsMessagePayloadHandler.java new file mode 100644 index 0000000000..8e16d15118 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/notification/LanguageTeamPermissionChangeJmsMessagePayloadHandler.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.notification; + +import java.io.Serializable; + +import javax.mail.internet.InternetAddress; + +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.AutoCreate; +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Scope; +import org.zanata.email.Addresses; +import org.zanata.email.EmailBuilder; +import org.zanata.email.LanguageTeamPermissionChangeEmailStrategy; +import org.zanata.events.LanguageTeamPermissionChangedEvent; +import org.zanata.i18n.Messages; +import lombok.extern.slf4j.Slf4j; + +/** + * We can only have application or stateless scope beans in here. + * + * @see EmailQueueMessageReceiver + * @author Patrick Huang pahuang@redhat.com + */ +@Name("languageTeamPermissionChangeJmsMessagePayloadHandler") +@Scope(ScopeType.STATELESS) +@AutoCreate +@Slf4j +public class LanguageTeamPermissionChangeJmsMessagePayloadHandler implements + EmailQueueMessageReceiver.JmsMessagePayloadHandler { + @In + private EmailBuilder emailBuilder; + + @In + private Messages msgs; + + @Override + public void handle(Serializable data) { + if (!(data instanceof LanguageTeamPermissionChangedEvent)) { + log.error("can not handle data other than type {}", + LanguageTeamPermissionChangedEvent.class); + return; + } + LanguageTeamPermissionChangedEvent changedEvent = + LanguageTeamPermissionChangedEvent.class.cast(data); + log.debug("language team permission change data:{}", changedEvent); + + String receivedReason = + msgs.format("jsf.email.languageteam.permission.ReceivedReason", + changedEvent.getLanguage()); + LanguageTeamPermissionChangeEmailStrategy emailStrategy = + new LanguageTeamPermissionChangeEmailStrategy( + changedEvent, msgs); + InternetAddress to = + Addresses.getAddress(changedEvent.getEmail(), + changedEvent.getName()); + + emailBuilder.sendMessage(emailStrategy, receivedReason, to); + } + +} diff --git a/zanata-war/src/main/java/org/zanata/notification/NotificationManager.java b/zanata-war/src/main/java/org/zanata/notification/NotificationManager.java new file mode 100644 index 0000000000..30846e2f4a --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/notification/NotificationManager.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014, Red Hat, Inc. and individual contributors as indicated by the + * @author tags. See the copyright.txt file in the distribution for a full + * listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 2.1 of the License, or (at your option) + * any later version. + * + * This software is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF + * site: http://www.fsf.org. + */ +package org.zanata.notification; + +import java.io.Serializable; + +import javax.jms.JMSException; +import javax.jms.ObjectMessage; +import javax.jms.QueueSender; +import javax.jms.QueueSession; + +import lombok.extern.slf4j.Slf4j; + +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.In; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Observer; +import org.jboss.seam.annotations.Scope; +import org.jboss.seam.annotations.Startup; +import org.zanata.events.LanguageTeamPermissionChangedEvent; + +import com.google.common.base.Throwables; + +@Name("notificationManager") +@Scope(ScopeType.APPLICATION) +@Startup +@Slf4j +public class NotificationManager implements Serializable { + private static final long serialVersionUID = -1L; + + @In + private QueueSender mailQueueSender; + + @In + private QueueSession queueSession; + + @Observer(LanguageTeamPermissionChangedEvent.LANGUAGE_TEAM_PERMISSION_CHANGED) + public void onLanguageTeamPermissionChanged( + final LanguageTeamPermissionChangedEvent event) { + try { + ObjectMessage message = + queueSession.createObjectMessage(event); + message.setObjectProperty(MessagePropertiesKey.objectType.name(), + event.getClass().getCanonicalName()); + mailQueueSender.send(message); + } + catch (JMSException e) { + throw Throwables.propagate(e); + } + } + + static enum MessagePropertiesKey { + objectType + } +} diff --git a/zanata-war/src/main/java/org/zanata/security/AuthenticationManager.java b/zanata-war/src/main/java/org/zanata/security/AuthenticationManager.java index 3d7cdaa30a..f359afebef 100644 --- a/zanata-war/src/main/java/org/zanata/security/AuthenticationManager.java +++ b/zanata-war/src/main/java/org/zanata/security/AuthenticationManager.java @@ -305,21 +305,21 @@ public void onLoginCompleted(AuthenticationType authType) { userAccountServiceImpl.runRoleAssignmentRules(authenticatedAccount, authenticatedCredentials, authType.name()); } + // make sure server path is populated. Here we are sure servlet request + // is available. In cases where it's not in database and + // there is no servlet request, the value will not be null. + // e.g. EmailBuilder triggered by JMS message + applicationConfiguration.getServerPath(); } public boolean isAccountWaitingForActivation(String username) { HAccount account = accountDAO.getByUsername(username); - if (account != null && account.getAccountActivationKey() != null) { - return true; - } - return false; + return account != null && account.getAccountActivationKey() != null; } public boolean isAccountEnabled(String username) { - if (StringUtils.isEmpty(username)) { - return false; - } - return identityStore.isUserEnabled(username); + return !StringUtils.isEmpty(username) && + identityStore.isUserEnabled(username); } public boolean isAuthenticatedAccountWaitingForActivation() { diff --git a/zanata-war/src/main/java/org/zanata/service/impl/LanguageTeamServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/LanguageTeamServiceImpl.java index 55cbc717d0..9bfdbeca5a 100644 --- a/zanata-war/src/main/java/org/zanata/service/impl/LanguageTeamServiceImpl.java +++ b/zanata-war/src/main/java/org/zanata/service/impl/LanguageTeamServiceImpl.java @@ -6,11 +6,15 @@ import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; +import org.jboss.seam.core.Events; +import org.jboss.seam.security.management.JpaIdentityStore; import org.zanata.common.LocaleId; import org.zanata.dao.LocaleDAO; import org.zanata.dao.LocaleMemberDAO; import org.zanata.dao.PersonDAO; +import org.zanata.events.LanguageTeamPermissionChangedEvent; import org.zanata.exception.ZanataServiceException; +import org.zanata.model.HAccount; import org.zanata.model.HLocale; import org.zanata.model.HLocaleMember; import org.zanata.model.HLocaleMember.HLocaleMemberPk; @@ -26,6 +30,8 @@ public class LanguageTeamServiceImpl implements LanguageTeamService { private LocaleMemberDAO localeMemberDAO; + private HAccount authenticatedAccount; + @In public void setPersonDAO(PersonDAO personDAO) { this.personDAO = personDAO; @@ -41,6 +47,12 @@ public void setLocaleMemberDAO(LocaleMemberDAO localeMemberDAO) { this.localeMemberDAO = localeMemberDAO; } + @In(value = JpaIdentityStore.AUTHENTICATED_USER, scope = ScopeType.SESSION) + public void setAuthenticatedAccount(HAccount authenticatedAccount) { + this.authenticatedAccount = authenticatedAccount; + } + + public List getLanguageMemberships(String userName) { return personDAO.getLanguageMembershipByUsername(userName); } @@ -54,6 +66,9 @@ public void joinOrUpdateRoleInLanguageTeam(String locale, Long personId, boolean alreadyJoined = localeMemberDAO.isLocaleMember(personId, localeId); HLocaleMember localeMember; + LanguageTeamPermissionChangedEvent permissionChangedEvent; + + HPerson authenticatedUser = authenticatedAccount.getPerson(); if (!alreadyJoined) { if (currentPerson.getLanguageMemberships().size() >= MAX_NUMBER_MEMBERSHIP) { throw new ZanataServiceException( @@ -66,15 +81,30 @@ public void joinOrUpdateRoleInLanguageTeam(String locale, Long personId, new HLocaleMember(currentPerson, lang, isTranslator, isReviewer, isCoordinator); lang.getMembers().add(localeMember); + permissionChangedEvent = + new LanguageTeamPermissionChangedEvent(currentPerson, + localeId, authenticatedUser) + .joiningTheTeam(isTranslator, isReviewer, + isCoordinator); } else { localeMember = localeMemberDAO.findByPersonAndLocale(personId, localeId); + permissionChangedEvent = + new LanguageTeamPermissionChangedEvent(currentPerson, + localeId, authenticatedUser) + .updatingPermissions(localeMember, + isTranslator, isReviewer, isCoordinator); localeMember.setTranslator(isTranslator); localeMember.setReviewer(isReviewer); localeMember.setCoordinator(isCoordinator); } localeMemberDAO.makePersistent(localeMember); localeMemberDAO.flush(); + if (Events.exists() && permissionChangedEvent.isPermissionChanged()) { + Events.instance().raiseTransactionSuccessEvent( + LanguageTeamPermissionChangedEvent.LANGUAGE_TEAM_PERMISSION_CHANGED, + permissionChangedEvent); + } } public boolean leaveLanguageTeam(String locale, Long personId) { diff --git a/zanata-war/src/main/resources/messages.properties b/zanata-war/src/main/resources/messages.properties index 2d11b4f702..dd2543acc0 100644 --- a/zanata-war/src/main/resources/messages.properties +++ b/zanata-war/src/main/resources/messages.properties @@ -1175,7 +1175,18 @@ jsf.email.UserMessageIntro={0} has included the following message with this requ jsf.email.JoinGroupRequest.ResponseInstructions=Click the link below to go act on the request. Please reply to {0} at {1} when you have finished processing this request. jsf.email.group.maintainer.ReceivedReason=You are maintainer in group "{0}" - +#------ language team permission changed email +jsf.email.languageteam.permission.Subject=Your permission in language team "{0}" has changed +jsf.email.languageteam.permission.ReceivedReason=You are a team member in the "{0}" language team +jsf.email.languageteam.permission.DearName=Dear {0}, +jsf.email.languageteam.permission.Changed={0}({1}) has changed your permission in language team "{2}". +jsf.email.languageteam.permission.old.prefix=You were: +jsf.email.languageteam.permission.old.notInTeam=not a team member. +jsf.email.languageteam.permission.new.prefix=You are now: +jsf.email.languageteam.permission.new.notInTeam=no longer a team member. +jsf.email.languageteam.permission.isTranslator=a translator +jsf.email.languageteam.permission.isReviewer=a reviewer +jsf.email.languageteam.permission.isCoordinator=a team coordinator diff --git a/zanata-war/src/main/resources/org/zanata/email/templates/language_team_permission_changed.vm b/zanata-war/src/main/resources/org/zanata/email/templates/language_team_permission_changed.vm new file mode 100644 index 0000000000..3928be02a4 --- /dev/null +++ b/zanata-war/src/main/resources/org/zanata/email/templates/language_team_permission_changed.vm @@ -0,0 +1,17 @@ +

$msgs.format("jsf.email.languageteam.permission.DearName", $toName)

+ +

$msgs.format("jsf.email.languageteam.permission.Changed", $changedByName, $changedByEmail, $language)

+ +

$msgs.get("jsf.email.languageteam.permission.old.prefix")

+
    + #foreach($permission in $oldPermissions) +
  • $permission
  • + #end +
+ +

$msgs.get("jsf.email.languageteam.permission.new.prefix")

+
    + #foreach($permission in $newPermissions) +
  • $permission
  • + #end +
diff --git a/zanata-war/src/main/webapp-jboss/WEB-INF/classes/META-INF/components.xml b/zanata-war/src/main/webapp-jboss/WEB-INF/classes/META-INF/components.xml index 381c396c19..61839f75b5 100644 --- a/zanata-war/src/main/webapp-jboss/WEB-INF/classes/META-INF/components.xml +++ b/zanata-war/src/main/webapp-jboss/WEB-INF/classes/META-INF/components.xml @@ -11,6 +11,7 @@ xmlns:bpm="http://jboss.org/schema/seam/bpm" xmlns:mail="http://jboss.org/schema/seam/mail" xmlns:framework="http://jboss.org/schema/seam/framework" + xmlns:jms="http://jboss.org/schema/seam/jms" xmlns:resteasy="http://jboss.org/schema/seam/resteasy" xsi:schemaLocation= "http://jboss.org/schema/seam/core http://jboss.org/schema/seam/core-2.3.xsd @@ -23,6 +24,7 @@ http://jboss.org/schema/seam/bpm http://jboss.org/schema/seam/bpm-2.3.xsd http://jboss.org/schema/seam/mail http://jboss.org/schema/seam/mail-2.3.xsd http://jboss.org/schema/seam/framework http://jboss.org/schema/seam/framework-2.3.xsd + http://jboss.org/schema/seam/jms http://jboss.org/schema/seam/jms-2.3.xsd http://jboss.org/schema/seam/resteasy http://jboss.org/schema/seam/resteasy-2.3.xsd http://jboss.org/schema/seam/components http://jboss.org/schema/seam/components-2.3.xsd"> @@ -49,6 +51,10 @@ max-request-size="5200000" url-pattern="/*" /> + + + + diff --git a/zanata-war/src/main/webapp-jboss/WEB-INF/ejb-jar.xml b/zanata-war/src/main/webapp-jboss/WEB-INF/ejb-jar.xml new file mode 100644 index 0000000000..3e458c6dcc --- /dev/null +++ b/zanata-war/src/main/webapp-jboss/WEB-INF/ejb-jar.xml @@ -0,0 +1,20 @@ + + + + + + org.jboss.seam.ejb.SeamInterceptor + + + + + + * + org.jboss.seam.ejb.SeamInterceptor + + + + diff --git a/zanata-war/src/main/webapp-jboss/WEB-INF/jboss-ejb3.xml b/zanata-war/src/main/webapp-jboss/WEB-INF/jboss-ejb3.xml new file mode 100644 index 0000000000..db77d5635c --- /dev/null +++ b/zanata-war/src/main/webapp-jboss/WEB-INF/jboss-ejb3.xml @@ -0,0 +1,8 @@ + + + diff --git a/zanata-war/src/test/resources/arquillian/standalone-arquillian.xml b/zanata-war/src/test/resources/arquillian/standalone-arquillian.xml index 82cbcbef5b..f3f478e40f 100644 --- a/zanata-war/src/test/resources/arquillian/standalone-arquillian.xml +++ b/zanata-war/src/test/resources/arquillian/standalone-arquillian.xml @@ -14,6 +14,7 @@ + @@ -142,6 +143,10 @@ + + + + - + + + true + false + NIO + 102400 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + jms.queue.DLQ + jms.queue.ExpiryQueue + 5000 + 2 + 10485760 + BLOCK + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + true + + + + + @@ -372,6 +451,9 @@ port="${jboss.http.port,env.JBOSS_HTTP_PORT:8180}" /> + + +