/* 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.services.elements; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.mail.internet.InternetAddress; import nl.strohalm.cyclos.access.AdminMemberPermission; import nl.strohalm.cyclos.access.MemberPermission; import nl.strohalm.cyclos.access.OperatorPermission; import nl.strohalm.cyclos.dao.members.MessageDAO; import nl.strohalm.cyclos.entities.Entity; import nl.strohalm.cyclos.entities.Relationship; import nl.strohalm.cyclos.entities.accounts.SystemAccountOwner; import nl.strohalm.cyclos.entities.accounts.transactions.TransferType; import nl.strohalm.cyclos.entities.customization.fields.MemberCustomField; import nl.strohalm.cyclos.entities.exceptions.LockingException; import nl.strohalm.cyclos.entities.exceptions.UnexpectedEntityException; import nl.strohalm.cyclos.entities.groups.MemberGroup; import nl.strohalm.cyclos.entities.groups.MemberGroupSettings; 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.Message.Direction; import nl.strohalm.cyclos.entities.members.messages.Message.Type; import nl.strohalm.cyclos.entities.members.messages.MessageQuery; import nl.strohalm.cyclos.entities.sms.MemberSmsStatus; import nl.strohalm.cyclos.entities.sms.SmsLog; import nl.strohalm.cyclos.entities.sms.SmsLog.ErrorType; import nl.strohalm.cyclos.services.InitializingService; import nl.strohalm.cyclos.services.elements.exceptions.MemberWontReceiveNotificationException; import nl.strohalm.cyclos.services.fetch.FetchServiceLocal; import nl.strohalm.cyclos.services.permissions.PermissionServiceLocal; import nl.strohalm.cyclos.services.preferences.MessageChannel; import nl.strohalm.cyclos.services.preferences.PreferenceServiceLocal; import nl.strohalm.cyclos.services.settings.SettingsServiceLocal; import nl.strohalm.cyclos.services.sms.ISmsContext; import nl.strohalm.cyclos.services.sms.SmsLogServiceLocal; import nl.strohalm.cyclos.services.transactions.PaymentServiceLocal; import nl.strohalm.cyclos.services.transactions.TransferDTO; import nl.strohalm.cyclos.services.transactions.exceptions.MaxAmountPerDayExceededException; import nl.strohalm.cyclos.services.transactions.exceptions.NotEnoughCreditsException; import nl.strohalm.cyclos.services.transactions.exceptions.UpperCreditLimitReachedException; import nl.strohalm.cyclos.utils.DateHelper; import nl.strohalm.cyclos.utils.LinkGenerator; import nl.strohalm.cyclos.utils.MailHandler; import nl.strohalm.cyclos.utils.TimePeriod; import nl.strohalm.cyclos.utils.TransactionHelper; import nl.strohalm.cyclos.utils.WorkerThreads; import nl.strohalm.cyclos.utils.access.LoggedUser; import nl.strohalm.cyclos.utils.notifications.AdminNotificationHandler; import nl.strohalm.cyclos.utils.sms.SmsSender; import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData; import nl.strohalm.cyclos.utils.transaction.TransactionCommitListener; import nl.strohalm.cyclos.utils.transaction.TransactionEndListener; import nl.strohalm.cyclos.utils.validation.InvalidError; import nl.strohalm.cyclos.utils.validation.PropertyValidation; import nl.strohalm.cyclos.utils.validation.RequiredValidation; import nl.strohalm.cyclos.utils.validation.ValidationError; import nl.strohalm.cyclos.utils.validation.ValidationException; import nl.strohalm.cyclos.utils.validation.Validator; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.mutable.MutableObject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; /** * Implementation for message service * @author luis */ public class MessageServiceImpl implements MessageServiceLocal, DisposableBean, InitializingService { public static class RequiredWhenFromAdminValidation implements PropertyValidation { private static final long serialVersionUID = -3593591846871843393L; @Override public ValidationError validate(final Object object, final Object property, final Object value) { if (LoggedUser.hasUser() && LoggedUser.isAdministrator()) { return RequiredValidation.instance().validate(object, property, value); } return null; } } /** * Ensure the message receiver is not the same logged user * @author luis */ public static class SameFromAndToValidation implements PropertyValidation { private static final long serialVersionUID = -3649308826565152361L; @Override public ValidationError validate(final Object object, final Object property, final Object value) { final SendMessageToMemberDTO dto = (SendMessageToMemberDTO) object; final Member loggedMember = (Member) (LoggedUser.hasUser() && LoggedUser.isMember() ? LoggedUser.element() : null); final Member toMember = dto.getToMember(); if (loggedMember != null && loggedMember.equals(toMember)) { return new InvalidError(); } return null; } } private class SmsSenderThreads extends WorkerThreads<SendSmsDTO> { public SmsSenderThreads(final String name, final int threadCount) { super(name, threadCount); } @Override protected void process(final SendSmsDTO params) { sendSms(params); } } private static final Log LOG = LogFactory.getLog(MessageServiceImpl.class); private PermissionServiceLocal permissionService; private MessageDAO messageDao; private FetchServiceLocal fetchService; private MemberServiceLocal memberService; private PaymentServiceLocal paymentService; private PreferenceServiceLocal preferenceService; private SettingsServiceLocal settingsService; private SmsLogServiceLocal smsLogService; private LinkGenerator linkGenerator; private MailHandler mailHandler; private SmsSender smsSender; private TransactionHelper transactionHelper; private int maxSmsThreads; private SmsSenderThreads smsSenderThreads; private AdminNotificationHandler adminNotificationHandler; @Override public boolean canManage(Message message) { message = checkMessageOwner(message); if (message == null) { return false; } return permissionService.permission() .admin(AdminMemberPermission.MESSAGES_MANAGE) .member(MemberPermission.MESSAGES_MANAGE) .operator(OperatorPermission.MESSAGES_MANAGE) .hasPermission(); } @Override public boolean canSendToAdmin() { return permissionService.permission() .member(MemberPermission.MESSAGES_SEND_TO_ADMINISTRATION) .operator(OperatorPermission.MESSAGES_SEND_TO_ADMINISTRATION) .hasPermission(); } @Override public boolean canSendToMember(final Member member) { return permissionService.permission() .admin(AdminMemberPermission.MESSAGES_SEND_TO_MEMBER) .member(MemberPermission.MESSAGES_SEND_TO_MEMBER) .operator(OperatorPermission.MESSAGES_SEND_TO_MEMBER) .hasPermission() && permissionService.relatesTo(member); } @Override public Message checkMessageOwner(Message message) { if (message == null) { return null; } message = fetchService.fetch(message, Message.Relationships.FROM_MEMBER, Message.Relationships.TO_MEMBER); final Member loggedMember = (Member) (LoggedUser.hasUser() && !LoggedUser.isAdministrator() ? LoggedUser.accountOwner() : null); final Member owner = message.getOwner(); if ((loggedMember == null && owner != null) || (loggedMember != null && !loggedMember.equals(owner))) { return null; } return message; } @Override public void destroy() throws Exception { if (smsSenderThreads != null) { smsSenderThreads.interrupt(); smsSenderThreads = null; } } @Override public void initializeService() { purgeExpiredMessagesOnTrash(Calendar.getInstance()); } @Override public Message load(final Long id, final Relationship... fetch) { return messageDao.load(id, fetch); } @Override public Message nextToSend() { return messageDao.nextToSend(); } @Override public void performAction(final MessageAction action, final Long... ids) { for (final Long id : ids) { final Message message = messageDao.load(id); if (action == MessageAction.DELETE) { messageDao.delete(message.getId()); } else { switch (action) { case MOVE_TO_TRASH: message.setRemovedAt(Calendar.getInstance()); break; case RESTORE: message.setRemovedAt(null); break; case MARK_AS_READ: message.setRead(true); break; case MARK_AS_UNREAD: message.setRead(false); break; } messageDao.update(message); } } } @Override public void purgeExpiredMessagesOnTrash(final Calendar time) { final TimePeriod timePeriod = settingsService.getLocalSettings().getDeleteMessagesOnTrashAfter(); if (timePeriod == null || timePeriod.getNumber() <= 0) { return; } final Calendar limit = timePeriod.remove(DateHelper.truncate(time)); messageDao.removeMessagesOnTrashBefore(limit); } @Override public Message read(final Long id, final Relationship... fetch) { final Message message = load(id, fetch); message.setRead(true); return message; } @Override public List<Message> search(final MessageQuery query) { return messageDao.search(query); } @Override public Message send(final SendMessageDTO message) { if (message instanceof SendMessageToGroupDTO || message instanceof SendMessageFromBrokerToMembersDTO) { return doSendBulk(message); } else if (message instanceof SendMessageToAdminDTO) { Message sentMessage = doSendSingle(message); adminNotificationHandler.notifyMessage(sentMessage); return sentMessage; } else if (message instanceof SendDirectMessageToMemberDTO) { return doSendSingle(message); } return doSendSingle(message); } @Override public void sendEmailIfNeeded(final Message message) { Member member = message.getToMember(); Set<MessageChannel> receivedChannels = preferenceService.receivedChannels(member, message.getType()); if (receivedChannels.contains(MessageChannel.EMAIL)) { InternetAddress replyTo = mailHandler.getInternetAddress(message.getFromMember()); InternetAddress to = mailHandler.getInternetAddress(member); mailHandler.send(message.getSubject(), replyTo, to, message.getBody(), message.isHtml()); } } @Override public void sendFromSystem(final SendMessageFromSystemDTO message) { final Entity entity = message.getEntity(); String link = ""; if (entity != null && linkGenerator != null) { link = linkGenerator.generateLinkFor(message.getToMember(), entity); } final String body = StringUtils.replace(message.getBody(), "#link#", link); message.setBody(body); message.setHtml(true); doSendSingle(message); } /** * This methods runs a new transaction for handling the sms status, charging, etc... Then, the log is always persisted in another transaction * (with a {@link TransactionEndListener}). So, even if the sending fails, the log is persisted */ @Override public SmsLog sendSms(final SendSmsDTO params) { final MutableObject result = new MutableObject(); transactionHelper.runInNewTransaction(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { // The returned log is transient... SmsLog log; try { log = doSendSms(params); } catch (LockingException e) { throw e; } catch (Exception e) { LOG.error("Unknown error sending sms", e); log = newSmsLog(params, ErrorType.SEND_ERROR, false); } if (log.getErrorType() != null) { status.setRollbackOnly(); } // ... we can only actually persist it after this transaction ends, because we don't know if it will be committed final SmsLog toSave = log; CurrentTransactionData.addTransactionEndListener(new TransactionEndListener() { @Override protected void onTransactionEnd(final boolean commit) { transactionHelper.runInCurrentThread(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(final TransactionStatus status) { result.setValue(smsLogService.save(toSave)); } }); } }); } }); return (SmsLog) result.getValue(); } @Override public synchronized void sendSmsAfterTransactionCommit(final SendSmsDTO params) { if (smsSenderThreads == null) { smsSenderThreads = new SmsSenderThreads("SMS sender for " + settingsService.getLocalSettings().getApplicationName(), maxSmsThreads); } CurrentTransactionData.addTransactionCommitListener(new TransactionCommitListener() { @Override public void onTransactionCommit() { smsSenderThreads.enqueue(params); } }); } public void setAdminNotificationHandler(final AdminNotificationHandler adminNotificationHandler) { this.adminNotificationHandler = adminNotificationHandler; } 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 setMaxSmsThreads(final int maxSmsThreads) { this.maxSmsThreads = maxSmsThreads; } public void setMemberServiceLocal(final MemberServiceLocal memberService) { this.memberService = memberService; } public void setMessageDao(final MessageDAO messageDao) { this.messageDao = messageDao; } public void setPaymentServiceLocal(final PaymentServiceLocal paymentService) { this.paymentService = paymentService; } public final 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; } public void setSmsLogServiceLocal(final SmsLogServiceLocal smsLogService) { this.smsLogService = smsLogService; } public void setSmsSender(final SmsSender smsSender) { this.smsSender = smsSender; } public void setTransactionHelper(final TransactionHelper transactionHelper) { this.transactionHelper = transactionHelper; } @Override public void validate(final SendMessageDTO message) throws ValidationException { getValidator(message.getClass()).validate(message); } private Validator basicToMemberValidator() { final Validator validator = basicValidator(); validator.property("toMember").required(); return validator; } private Validator basicValidator() { final Validator validator = new Validator("message"); validator.property("subject").required(); validator.property("body").required(); return validator; } /** * Returns a Message instance, filled with data from the given DTO */ private Message buildFromDTO(final SendMessageDTO dto, final Message.Direction direction) { return buildFromDTO(dto, dto, direction); } /** * Returns a Message instance, filled with data from the given DTO */ private Message buildFromDTO(final SendMessageDTO original, final SendMessageDTO dto, final Message.Direction direction) { final Message message = new Message(); message.setDate(Calendar.getInstance()); message.setHtml(dto.isHtml()); message.setSubject(dto.getSubject()); message.setBody(dto.getBody()); if (!(dto instanceof SendMessageFromSystemDTO)) { message.setFromMember((Member) (LoggedUser.hasUser() && !LoggedUser.isAdministrator() ? LoggedUser.accountOwner() : null)); } message.setType(dto.getType()); message.setDirection(direction); if (direction == Message.Direction.OUTGOING) { message.setRead(true); message.setEmailSent(true); } if (dto instanceof SendMessageToMemberDTO) { message.setToMember(((SendMessageToMemberDTO) dto).getToMember()); } else if (dto instanceof SendMessageToGroupDTO) { message.setToGroups(new ArrayList<MemberGroup>(((SendMessageToGroupDTO) dto).getToGroups())); } if (message.isFromAMember() && message.isToAMember()) { message.setCategory(null); } else { message.setCategory(dto.getCategory()); } return message; } private TransferDTO buildSmsChargeDto(final Member member, final ISmsContext smsContext) { final MemberGroupSettings memberSettings = member.getMemberGroup().getMemberSettings(); final TransferType smsChargeTransferType = memberSettings.getSmsChargeTransferType(); final BigDecimal smsChargeAmount = smsContext.getAdditionalChargeAmount(member); // There are no charge settings, so don't charge if (smsChargeTransferType == null || smsChargeAmount == null) { return null; } // Build charge DTO final TransferDTO transferDto = new TransferDTO(); if (smsChargeTransferType.isFromMember()) { transferDto.setFromOwner(member); } else { transferDto.setFromOwner(SystemAccountOwner.instance()); } transferDto.setToOwner(SystemAccountOwner.instance()); transferDto.setTransferType(smsChargeTransferType); transferDto.setDescription(smsChargeTransferType.getDescription()); transferDto.setAmount(smsChargeAmount); transferDto.setAutomatic(true); return transferDto; } private Message doSendBulk(final SendMessageDTO message) { // Validate the message parameters validate(message); // Insert the sender copy only. The BulkMessageSendingPollingTask will actually deliver each copy return insertSenderCopy(message); } private Message doSendSingle(final SendMessageDTO message) { // Validate the message parameters validate(message); Set<MessageChannel> messageChannels = null; if (message instanceof SendMessageToMemberDTO) { // If is a message to member, get the received channels final SendMessageToMemberDTO toMemberMessage = (SendMessageToMemberDTO) message; messageChannels = preferenceService.receivedChannels(toMemberMessage.getToMember(), message.getType()); if (toMemberMessage.requiresMemberToReceive() && CollectionUtils.isEmpty(messageChannels)) { // The message dto requires the member to receive and his preferences say he doesn't - throw an exception throw new MemberWontReceiveNotificationException(); } } // Then insert the sender copy final Message senderCopy = insertSenderCopy(message); final Message toSend = buildFromDTO(message, Direction.INCOMING); String sms = null; String smsTraceData = null; if (message instanceof SendMessageFromSystemDTO) { SendMessageFromSystemDTO messageFromSystem = (SendMessageFromSystemDTO) message; sms = messageFromSystem.getSms(); smsTraceData = messageFromSystem.getSmsTraceData(); } send(toSend, sms, smsTraceData, messageChannels); return senderCopy; } private SmsLog doSendSms(final SendSmsDTO params) { final Member target = params.getTargetMember(); final MemberCustomField customField = settingsService.getSmsCustomField(); if (customField == null || !memberService.hasValueForField(target, customField)) { // Either no custom field, or the member didn't have value for the mobile phone return newSmsLog(params, ErrorType.NO_PHONE, false); } Member charged = params.getChargedMember(); final boolean freeMailing = params.getSmsMailing() != null && params.getSmsMailing().isFree(); ErrorType errorType = null; boolean boughtNewMessages = false; MemberSmsStatus memberSmsStatus = null; int additionalChargedSms = 0; boolean statusChanged = false; boolean freeBaseUsed = false; ISmsContext smsContext = null; if (!freeMailing) { // Charge the SMS if (charged == null) { charged = target; params.setChargedMember(charged); } charged = fetchService.reload(charged, Element.Relationships.GROUP); smsContext = memberService.getSmsContext(charged); memberSmsStatus = memberService.getSmsStatus(charged, true); additionalChargedSms = smsContext.getAdditionalChargedSms(charged); final int freeSms = smsContext.getFreeSms(charged); if (memberSmsStatus.getFreeSmsSent() < freeSms) { // There are free messages left memberSmsStatus.setFreeSmsSent(memberSmsStatus.getFreeSmsSent() + 1); freeBaseUsed = true; statusChanged = true; } else if (memberSmsStatus.getPaidSmsLeft() > 0) { // There are paid messages left memberSmsStatus.setPaidSmsLeft(memberSmsStatus.getPaidSmsLeft() - 1); statusChanged = true; } else { // Check if paid messages are enabled if (additionalChargedSms > 0) { // Paid messages are enabled if (memberSmsStatus.isAllowChargingSms()) { // The member allows charge final TransferDTO chargeDTO = buildSmsChargeDto(charged, smsContext); try { if (chargeDTO == null) { throw new UnexpectedEntityException(); } paymentService.insertWithoutNotification(chargeDTO); // The status will only be updated if the SMS sending is successful, to avoid updating the status and having to undo later boughtNewMessages = true; } catch (final NotEnoughCreditsException e) { errorType = ErrorType.NOT_ENOUGH_FUNDS; } catch (final MaxAmountPerDayExceededException e) { errorType = ErrorType.NOT_ENOUGH_FUNDS; } catch (final UpperCreditLimitReachedException e) { errorType = ErrorType.NOT_ENOUGH_FUNDS; } catch (final UnexpectedEntityException e) { ValidationException exc = new ValidationException("The SMS charging is not well configured. Please, check the charging transfer type."); exc.setShowDetailMessage(true); throw exc; } } else { // The member have disallowed charging errorType = ErrorType.ALLOW_CHARGING_DISABLED; } } else { if (freeSms == 0) { ValidationException exc = new ValidationException("SMS cannot be sent becasue both free messages and aditional messages are zero for member: " + charged.getUsername()); exc.setShowDetailMessage(true); throw exc; } else { errorType = ErrorType.NO_SMS_LEFT; } } } } // Send the message itself if no error so far if (errorType == null) { try { if (!smsSender.send(target, params.getText(), params.getTraceData())) { throw new Exception("Sms sender returns false when sending a sms (trace=" + params.getTraceData() + ")"); } // The message was sent. Update the member sms status if (boughtNewMessages) { final int left = additionalChargedSms - 1; Calendar expiration = null; if (left > 0) { TimePeriod additionalChargedPeriod = smsContext.getAdditionalChargedPeriod(charged); if (additionalChargedPeriod == null) { additionalChargedPeriod = TimePeriod.ONE_MONTH; } expiration = additionalChargedPeriod.add(Calendar.getInstance()); } memberSmsStatus.setPaidSmsLeft(left); memberSmsStatus.setPaidSmsExpiration(expiration); statusChanged = true; } } catch (final Exception e) { LOG.error("Unknown error sending sms", e); errorType = ErrorType.SEND_ERROR; // Ensure the status won't be changed on errors statusChanged = false; } } // Update the SMS status if it has changed if (statusChanged) { memberService.updateSmsStatus(memberSmsStatus); } // Generate the SMS log return newSmsLog(params, errorType, freeBaseUsed); } private Validator getValidator(final Class<? extends SendMessageDTO> type) { if (type == SendDirectMessageToMemberDTO.class) { final Validator toMember = basicToMemberValidator(); toMember.property("toMember").add(new SameFromAndToValidation()); toMember.property("category").add(new RequiredWhenFromAdminValidation()); return toMember; } else if (type == SendMessageToAdminDTO.class) { final Validator toAdmin = basicValidator(); toAdmin.property("category").required(); return toAdmin; } else if (type == SendMessageFromBrokerToMembersDTO.class) { final Validator toRegisteredMembers = basicValidator(); return toRegisteredMembers; } else if (type == SendMessageToGroupDTO.class) { final Validator toGroup = basicValidator(); toGroup.property("toGroups").required(); toGroup.property("category").add(new RequiredWhenFromAdminValidation()); return toGroup; } else if (type == SendMessageFromSystemDTO.class) { final Validator fromSystem = basicToMemberValidator(); fromSystem.property("type").required(); return fromSystem; } else { throw new IllegalArgumentException("Unexpected type " + type); } } private Message insertSenderCopy(final SendMessageDTO dto) throws MemberWontReceiveNotificationException { // Validate the message being replied final Message inReplyTo = checkMessageOwner(dto.getInReplyTo()); // Update the original message as replied if (inReplyTo != null) { markAsReplied(inReplyTo); } if (dto instanceof SendMessageFromSystemDTO) { // There is no sender copy for messages from system (a.k.a notifications) return null; } // Insert the sender copy Message message = buildFromDTO(dto, Message.Direction.OUTGOING); message = messageDao.insert(message); // If the message is bulk, assign all members which should receive the messages if (dto instanceof SendMessageToGroupDTO) { final SendMessageToGroupDTO toGroup = (SendMessageToGroupDTO) dto; messageDao.assignPendingToSendByGroups(message, toGroup.getToGroups()); } else if (dto instanceof SendMessageFromBrokerToMembersDTO) { final Member broker = LoggedUser.element(); messageDao.assignPendingToSendByBroker(message, broker); } return message; } /** * Marks a given message as replied */ private void markAsReplied(final Message message) { if (message != null) { message.setReplied(true); messageDao.update(message); } } private SmsLog newSmsLog(final SendSmsDTO params, final ErrorType errorType, final boolean freeBaseUsed) { final SmsLog newLog = new SmsLog(); newLog.setDate(Calendar.getInstance()); newLog.setTargetMember(params.getTargetMember()); newLog.setChargedMember(params.getChargedMember()); newLog.setErrorType(errorType); newLog.setFreeBaseUsed(freeBaseUsed); newLog.setMessageType(params.getMessageType()); newLog.setSmsMailing(params.getSmsMailing()); newLog.setSmsType(params.getSmsType()); newLog.setSmsTypeArgs(params.getSmsTypeArgs()); return newLog; } /** * Sends the message, returning a set containing the channels actually delivered */ private Set<MessageChannel> send(final Message message, final String smsMessage, final String smsTraceData, Set<MessageChannel> messageChannels) { final Member toMember = fetchService.fetch(message.getToMember(), Element.Relationships.GROUP); message.setCategory(fetchService.fetch(message.getCategory())); final Set<MessageChannel> result = EnumSet.noneOf(MessageChannel.class); if (toMember != null) { final MemberGroup group = toMember.getMemberGroup(); final Type type = message.getType(); // Check which message channels will be used if (messageChannels == null) { messageChannels = preferenceService.receivedChannels(toMember, type); } if (CollectionUtils.isEmpty(messageChannels)) { // Nothing to send for this member return result; } // Send an e-mail if needed final String email = toMember.getEmail(); if (messageChannels.contains(MessageChannel.EMAIL) && StringUtils.isNotEmpty(email)) { InternetAddress replyTo = mailHandler.getInternetAddress(message.getFromMember()); InternetAddress to = mailHandler.getInternetAddress(toMember); mailHandler.sendAfterTransactionCommit(message.getSubject(), replyTo, to, message.getBody(), message.isHtml()); message.setEmailSent(true); result.add(MessageChannel.EMAIL); } // Check if we need to send a SMS if (StringUtils.isNotEmpty(smsMessage) && messageChannels.contains(MessageChannel.SMS) && group.getSmsMessages().contains(type)) { // Send the SMS final SendSmsDTO sendDTO = new SendSmsDTO(); sendDTO.setTargetMember(toMember); sendDTO.setMessageType(type); sendDTO.setText(smsMessage); sendDTO.setTraceData(smsTraceData); sendSmsAfterTransactionCommit(sendDTO); result.add(MessageChannel.SMS); } // If the user does not want the internal message, return now if (!messageChannels.contains(MessageChannel.MESSAGE)) { // If not, return now return result; } result.add(MessageChannel.MESSAGE); } // Insert the internal message messageDao.insert(message); return result; } }