/* This file is part of Cyclos (www.cyclos.org). A project of the Social Trade Organisation (www.socialtrade.org). Cyclos is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Cyclos 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Cyclos; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package nl.strohalm.cyclos.utils.notifications; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import nl.strohalm.cyclos.access.BasicPermission; import nl.strohalm.cyclos.entities.Entity; import nl.strohalm.cyclos.entities.Relationship; import nl.strohalm.cyclos.entities.accounts.Account; import nl.strohalm.cyclos.entities.accounts.AccountOwner; import nl.strohalm.cyclos.entities.accounts.MemberAccount; import nl.strohalm.cyclos.entities.accounts.SystemAccount; import nl.strohalm.cyclos.entities.accounts.SystemAccountType; import nl.strohalm.cyclos.entities.accounts.guarantees.Guarantee; import nl.strohalm.cyclos.entities.accounts.guarantees.GuaranteeType; import nl.strohalm.cyclos.entities.accounts.transactions.AuthorizationLevel; import nl.strohalm.cyclos.entities.accounts.transactions.Invoice; import nl.strohalm.cyclos.entities.accounts.transactions.Payment; import nl.strohalm.cyclos.entities.accounts.transactions.Transfer; import nl.strohalm.cyclos.entities.alerts.Alert; import nl.strohalm.cyclos.entities.alerts.ErrorLogEntry; import nl.strohalm.cyclos.entities.alerts.MemberAlert; import nl.strohalm.cyclos.entities.alerts.SystemAlert; import nl.strohalm.cyclos.entities.groups.MemberGroup; import nl.strohalm.cyclos.entities.members.Administrator; import nl.strohalm.cyclos.entities.members.Element; import nl.strohalm.cyclos.entities.members.Member; import nl.strohalm.cyclos.entities.members.messages.Message; import nl.strohalm.cyclos.entities.members.messages.MessageCategory; import nl.strohalm.cyclos.entities.members.preferences.AdminNotificationPreferenceQuery; import nl.strohalm.cyclos.entities.settings.LocalSettings; import nl.strohalm.cyclos.entities.settings.MessageSettings; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal; import nl.strohalm.cyclos.services.preferences.PreferenceServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.utils.EnumHelper; import nl.strohalm.cyclos.utils.LinkGenerator; import nl.strohalm.cyclos.utils.MailHandler; import nl.strohalm.cyclos.utils.MessageProcessingHelper; import nl.strohalm.cyclos.utils.MessageResolver; import nl.strohalm.cyclos.utils.RelationshipHelper; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.annotation.AfterReturning; /** * Aspect used to notify admins about events on the system * @author luis */ public class AdminNotificationHandlerImpl implements AdminNotificationHandler { private class AdminSendDTO { private Administrator admin; private SendDTO sendDTO; public Administrator getAdmin() { return admin; } public SendDTO getSendDTO() { return sendDTO; } public void setAdmin(final Administrator admin) { this.admin = admin; } public void setSendDTO(final SendDTO sendDTO) { this.sendDTO = sendDTO; } } private class QuerySendDTO { private final AdminNotificationPreferenceQuery query; private final SendDTO sendDTO; public QuerySendDTO(final AdminNotificationPreferenceQuery query, final SendDTO sendDTO) { this.query = query; this.sendDTO = sendDTO; } public AdminNotificationPreferenceQuery getQuery() { return query; } public SendDTO getSendDTO() { return sendDTO; } } private class SendDTO { private String subject; private String body; private Entity relatedEntity; private Map<String, Object> variables; private boolean isHtml; private MessageCategory category; private Member fromMember; public String getBody() { return body; } public MessageCategory getCategory() { return category; } public Member getFromMember() { return fromMember; } public Entity getRelatedEntity() { return relatedEntity; } public String getSubject() { return subject; } public Map<String, Object> getVariables() { return variables; } public boolean isHtml() { return isHtml; } public void setBody(final String body) { this.body = body; } public void setCategory(final MessageCategory category) { this.category = category; } public void setFromMember(final Member fromMember) { this.fromMember = fromMember; } public void setHtml(final boolean isHtml) { this.isHtml = isHtml; } public void setRelatedEntity(final Entity relatedEntity) { this.relatedEntity = relatedEntity; } public void setSubject(final String subject) { this.subject = subject; } public void setVariables(final Map<String, Object> variables) { this.variables = variables; } } private static final Relationship[] TRANSFER_FETCH = { RelationshipHelper.nested(Payment.Relationships.FROM, MemberAccount.Relationships.MEMBER), RelationshipHelper.nested(Payment.Relationships.FROM, Account.Relationships.TYPE), RelationshipHelper.nested(Payment.Relationships.TO, MemberAccount.Relationships.MEMBER), RelationshipHelper.nested(Payment.Relationships.TO, Account.Relationships.TYPE), Payment.Relationships.TYPE }; private PreferenceServiceLocal preferenceService; private SettingsServiceLocal settingsService; private FetchServiceLocal fetchService; private PermissionServiceLocal permissionService; private MailHandler mailHandler; private LinkGenerator linkGenerator; private MessageResolver messageResolver = new MessageResolver.NoOpMessageResolver(); /** * Notifies an alert by e-mail */ @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.alerts.AlertServiceLocal.create(..))", returning = "alert", argNames = "alert") public void notifyAlert(final Alert alert) { final boolean isMember = alert instanceof MemberAlert; final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); final String key = alert.getKey(); Map<String, Object> variables = null; final LocalSettings localSettings = settingsService.getLocalSettings(); final MessageSettings messageSettings = settingsService.getMessageSettings(); String subject; if (isMember) { final MemberAlert memberAlert = fetchService.fetch((MemberAlert) alert, RelationshipHelper.nested(MemberAlert.Relationships.MEMBER, Element.Relationships.GROUP)); final Member member = memberAlert.getMember(); query.setMemberAlert(EnumHelper.findByValue(MemberAlert.Alerts.class, key)); query.setMemberGroup(member.getMemberGroup()); variables = member.getVariableValues(localSettings); subject = messageSettings.getAdminMemberAlertSubject(); } else { query.setSystemAlert(EnumHelper.findByValue(SystemAlert.Alerts.class, key)); subject = messageSettings.getAdminSystemAlertSubject(); } final List<String> args = Arrays.asList(alert.getArg0(), alert.getArg1(), alert.getArg2(), alert.getArg3(), alert.getArg4()); final String body = messageResolver.message(key, args); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(null); sendDTO.setVariables(variables); sendDTO.setHtml(false); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); boolean enqueue = SystemAlert.Alerts.APPLICATION_SHUTDOWN != query.getSystemAlert(); send(queryDTO, enqueue); } /** * Joinpoint that notifies application errors */ @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.alerts.ErrorLogServiceLocal.insert(..))", returning = "errorLog", argNames = "errorLog") public void notifyApplicationErrors(final ErrorLogEntry errorLog) { final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setApplicationErrors(true); final MessageSettings messageSettings = settingsService.getMessageSettings(); final String subject = messageSettings.getAdminApplicationErrorSubject(); final String body = messageSettings.getAdminApplicationErrorMessage(); final Map<String, Object> variables = new HashMap<String, Object>(); variables.put("path", errorLog.getPath()); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(errorLog); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.elements.MessageServiceLocal.sendFromMemberToAdmin(..))", argNames = "message", returning = "message") public void notifyMessage(Message message) { message = fetchService.fetch(message, RelationshipHelper.nested(Message.Relationships.FROM_MEMBER, Element.Relationships.GROUP), Message.Relationships.CATEGORY); final MessageCategory category = message.getCategory(); if (category == null) { return; } final Member fromMember = message.getFromMember(); final boolean isHtml = message.isHtml(); final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setMessageCategory(category); query.setMemberGroup(fromMember.getMemberGroup()); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(message.getSubject()); sendDTO.setBody(message.getBody()); sendDTO.setRelatedEntity(null); sendDTO.setVariables(null); sendDTO.setHtml(isHtml); sendDTO.setCategory(category); sendDTO.setFromMember(fromMember); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } /** * Joinpoint that notifies new pending payments */ @Override @AfterReturning(pointcut = "(execution(* nl.strohalm.cyclos.services.transactions.PaymentServiceLocal.doPaymentAsMemberToMember(..))) || (execution(* nl.strohalm.cyclos.services.transactions.PaymentServiceLocal.doPaymentFromMemberToMember(..))) || (execution(* nl.strohalm.cyclos.services.transactions.PaymentServiceLocal.doPaymentFromSystemToMember(..))) || (execution(* nl.strohalm.cyclos.services.transactions.TransferAuthorizationServiceLocal.authorize*(..)))", argNames = "transfer", returning = "transfer") public void notifyNewPendingPayment(final Transfer transfer) { if (transfer.getProcessDate() != null) { return; } final AuthorizationLevel authorizationLevel = fetchService.fetch(transfer.getNextAuthorizationLevel(), AuthorizationLevel.Relationships.ADMIN_GROUPS); if (authorizationLevel == null || CollectionUtils.isEmpty(authorizationLevel.getAdminGroups())) { return; } final LocalSettings localSettings = settingsService.getLocalSettings(); final MessageSettings messageSettings = settingsService.getMessageSettings(); final AccountOwner fromOwner = transfer.getFromOwner(); final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); final Map<String, Object> variables = new HashMap<String, Object>(); variables.putAll(transfer.getVariableValues(localSettings)); variables.putAll(fromOwner.getVariableValues(localSettings)); if (fromOwner instanceof Member) { final Member fromMember = fetchService.fetch((Member) fromOwner, Element.Relationships.GROUP); query.setMemberGroup(fromMember.getMemberGroup()); } else { variables.put("member", localSettings.getApplicationUsername()); variables.put("login", transfer.getFrom().getOwnerName()); query.setAccountTypes(Arrays.asList((SystemAccountType) fetchService.fetch(transfer.getFrom().getType()))); } query.setAdminGroups(authorizationLevel.getAdminGroups()); query.setNewPendingPayment(transfer.getType()); final String subject = messageSettings.getAdminNewPendingPaymentSubject(); final String body = messageSettings.getAdminNewPendingPaymentMessage(); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(transfer); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } /** * Joinpoint that notifies new public registrations */ @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.elements.ElementServiceLocal.publicRegisterMember(..)) || execution(* nl.strohalm.cyclos.services.elements.ElementServiceLocal.publicValidateRegistration(..))", returning = "registeredMember", argNames = "registeredMember") public void notifyNewPublicRegistration(final Member member) { final MemberGroup group = member.getMemberGroup(); final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setNewMemberGroup(group); final LocalSettings localSettings = settingsService.getLocalSettings(); final MessageSettings messageSettings = settingsService.getMessageSettings(); final String subject = messageSettings.getAdminNewMemberSubject(); final String body = messageSettings.getAdminNewMemberMessage(); final Map<String, Object> variables = new HashMap<String, Object>(); variables.put("group", group.getName()); variables.putAll(member.getVariableValues(localSettings)); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(member); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } /** * Joinpoint that notifies system payments */ @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.transactions.PaymentServiceLocal.doPayment*(..)) || execution(* nl.strohalm.cyclos.services.transactions.PaymentServiceLocal.doSelfPayment(..))", argNames = "transfer", returning = "transfer") public void notifyPayment(Transfer transfer) { transfer = fetchService.fetch(transfer, TRANSFER_FETCH); final Account from = transfer.getFrom(); final Account to = transfer.getTo(); if (!(from instanceof SystemAccount || to instanceof SystemAccount)) { // Only notify payments from or to system return; } final LocalSettings localSettings = settingsService.getLocalSettings(); final MessageSettings messageSettings = settingsService.getMessageSettings(); String subject; String body; Member member; final Map<String, Object> variables = new HashMap<String, Object>(); final Collection<SystemAccountType> accountTypes = new HashSet<SystemAccountType>(); if (from instanceof MemberAccount) { subject = messageSettings.getAdminPaymentFromMemberToSystemSubject(); body = messageSettings.getAdminPaymentFromMemberToSystemMessage(); accountTypes.add((SystemAccountType) to.getType()); member = ((MemberAccount) from).getMember(); } else if (to instanceof MemberAccount) { subject = messageSettings.getAdminPaymentFromSystemToMemberSubject(); body = messageSettings.getAdminPaymentFromSystemToMemberMessage(); accountTypes.add((SystemAccountType) from.getType()); member = ((MemberAccount) to).getMember(); } else { subject = messageSettings.getAdminPaymentFromSystemToSystemSubject(); body = messageSettings.getAdminPaymentFromSystemToSystemMessage(); accountTypes.add((SystemAccountType) from.getType()); accountTypes.add((SystemAccountType) to.getType()); member = null; } if (member != null) { variables.putAll(member.getVariableValues(localSettings)); } variables.putAll(transfer.getVariableValues(localSettings)); final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setTransferType(transfer.getType()); query.setAccountTypes(accountTypes); if (member != null) { member = fetchService.fetch(member, Element.Relationships.GROUP); query.setMemberGroup(member.getMemberGroup()); } final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(transfer); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } @Override @AfterReturning(pointcut = "(execution(* nl.strohalm.cyclos.services.accounts.guarantees.GuaranteeServiceLocal.changeStatus(..)))", argNames = "guarantee", returning = "guarantee") public void notifyPendingGuarantee(final Guarantee guarantee) { doNotifyPendingGuarantee(guarantee); } /** * Joinpoint that notifies system invoices */ @Override @AfterReturning(pointcut = "execution(* nl.strohalm.cyclos.services.transactions.InvoiceServiceLocal.sendFromMemberToSystem(..))", returning = "invoice", argNames = "invoice") public void notifySystemInvoice(final Invoice invoice) { final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setSystemInvoices(true); final MessageSettings messageSettings = settingsService.getMessageSettings(); final String subject = messageSettings.getAdminSystemInvoiceSubject(); final String body = messageSettings.getAdminSystemInvoiceMessage(); final LocalSettings localSettings = settingsService.getLocalSettings(); final Map<String, Object> variables = new HashMap<String, Object>(); variables.putAll(invoice.getVariableValues(localSettings)); variables.putAll(invoice.getFromMember().getVariableValues(localSettings)); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(invoice); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } public void setFetchServiceLocal(final FetchServiceLocal fetchService) { this.fetchService = fetchService; } public void setLinkGenerator(final LinkGenerator linkGenerator) { this.linkGenerator = linkGenerator; } public void setMailHandler(final MailHandler mailHandler) { this.mailHandler = mailHandler; } public void setMessageResolver(final MessageResolver messageResolver) { this.messageResolver = messageResolver; } public void setPermissionServiceLocal(final PermissionServiceLocal permissionService) { this.permissionService = permissionService; } public void setPreferenceServiceLocal(final PreferenceServiceLocal preferenceService) { this.preferenceService = preferenceService; } public void setSettingsServiceLocal(final SettingsServiceLocal settingsService) { this.settingsService = settingsService; } private void doNotifyPendingGuarantee(Guarantee guarantee) { if (guarantee.getStatus() == Guarantee.Status.PENDING_ADMIN) { guarantee = fetchService.fetch(guarantee, Guarantee.Relationships.GUARANTEE_TYPE); final MessageSettings messageSettings = settingsService.getMessageSettings(); final LocalSettings localSettings = settingsService.getLocalSettings(); final AdminNotificationPreferenceQuery query = new AdminNotificationPreferenceQuery(); query.setGuaranteeType(guarantee.getGuaranteeType()); String subject = null; String body = null; if (guarantee.getGuaranteeType().getModel() == GuaranteeType.Model.WITH_BUYER_ONLY) { subject = messageSettings.getAdminPendingBuyerOnlyGuaranteeSubject(); body = messageSettings.getAdminPendingBuyerOnlyGuaranteeMessage(); } else { subject = messageSettings.getAdminPendingGuaranteeSubject(); body = messageSettings.getAdminPendingGuaranteeMessage(); } final Map<String, Object> variables = new HashMap<String, Object>(); variables.putAll(guarantee.getVariableValues(localSettings)); final SendDTO sendDTO = new SendDTO(); sendDTO.setSubject(subject); sendDTO.setBody(body); sendDTO.setRelatedEntity(guarantee); sendDTO.setVariables(variables); sendDTO.setHtml(true); sendDTO.setCategory(null); sendDTO.setFromMember(null); final QuerySendDTO queryDTO = new QuerySendDTO(query, sendDTO); send(queryDTO); } } private void send(final AdminSendDTO adminDTO, final boolean enqueue) { // Ensure we won't notify inactive admins final boolean active = permissionService.hasPermission(adminDTO.getAdmin().getGroup(), BasicPermission.BASIC_LOGIN); if (!active) { return; } // Check the mail is filled final String email = adminDTO.getAdmin().getEmail(); if (StringUtils.isEmpty(email)) { return; } final SendDTO sendDTO = adminDTO.getSendDTO(); // Fill in the link if (sendDTO.getRelatedEntity() != null && linkGenerator != null) { sendDTO.getVariables().put("link", linkGenerator.generateLinkFor(adminDTO.getAdmin(), sendDTO.getRelatedEntity())); } // Process the variables and send final String body = mailHandler.processBody(adminDTO.getAdmin(), sendDTO.getBody(), sendDTO.getFromMember(), sendDTO.getCategory(), sendDTO.isHtml()); final String processedSubject = MessageProcessingHelper.processVariables(sendDTO.getSubject(), sendDTO.getVariables()); final String processedBody = MessageProcessingHelper.processVariables(body, sendDTO.getVariables()); if (enqueue) { mailHandler.sendAfterTransactionCommit(processedSubject, null, mailHandler.getInternetAddress(adminDTO.getAdmin()), processedBody, sendDTO.isHtml()); } else { mailHandler.send(processedSubject, null, mailHandler.getInternetAddress(adminDTO.getAdmin()), processedBody, sendDTO.isHtml()); } } private void send(final QuerySendDTO queryDTO) { send(queryDTO, true); } private void send(final QuerySendDTO queryDTO, final boolean enqueue) { final List<Administrator> admins = preferenceService.listAdminsForNotification(queryDTO.getQuery()); final AdminSendDTO adminDTO = new AdminSendDTO(); adminDTO.setSendDTO(queryDTO.getSendDTO()); for (final Administrator admin : admins) { adminDTO.setAdmin(admin); send(adminDTO, enqueue); } } }