/*
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.controls.members.messages;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import nl.strohalm.cyclos.access.AdminMemberPermission;
import nl.strohalm.cyclos.access.BrokerPermission;
import nl.strohalm.cyclos.access.MemberPermission;
import nl.strohalm.cyclos.access.OperatorPermission;
import nl.strohalm.cyclos.annotations.Inject;
import nl.strohalm.cyclos.controls.ActionContext;
import nl.strohalm.cyclos.controls.BaseFormAction;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.groups.GroupQuery;
import nl.strohalm.cyclos.entities.groups.MemberGroup;
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.Type;
import nl.strohalm.cyclos.entities.members.messages.MessageCategory;
import nl.strohalm.cyclos.entities.members.messages.MessageCategoryQuery;
import nl.strohalm.cyclos.entities.members.preferences.NotificationPreference;
import nl.strohalm.cyclos.entities.settings.LocalSettings;
import nl.strohalm.cyclos.exceptions.PermissionDeniedException;
import nl.strohalm.cyclos.services.elements.MessageCategoryService;
import nl.strohalm.cyclos.services.elements.MessageService;
import nl.strohalm.cyclos.services.elements.SendDirectMessageToMemberDTO;
import nl.strohalm.cyclos.services.elements.SendMessageDTO;
import nl.strohalm.cyclos.services.elements.SendMessageFromBrokerToMembersDTO;
import nl.strohalm.cyclos.services.elements.SendMessageToAdminDTO;
import nl.strohalm.cyclos.services.elements.SendMessageToGroupDTO;
import nl.strohalm.cyclos.services.elements.exceptions.MemberWontReceiveNotificationException;
import nl.strohalm.cyclos.services.preferences.PreferenceService;
import nl.strohalm.cyclos.utils.ActionHelper;
import nl.strohalm.cyclos.utils.TextFormat;
import nl.strohalm.cyclos.utils.binding.BeanBinder;
import nl.strohalm.cyclos.utils.binding.DataBinder;
import nl.strohalm.cyclos.utils.binding.PropertyBinder;
import nl.strohalm.cyclos.utils.binding.SimpleCollectionBinder;
import nl.strohalm.cyclos.utils.conversion.CoercionHelper;
import nl.strohalm.cyclos.utils.conversion.HtmlConverter;
import nl.strohalm.cyclos.utils.conversion.StringTrimmerConverter;
import nl.strohalm.cyclos.utils.transaction.CurrentTransactionData;
import nl.strohalm.cyclos.utils.validation.ValidationException;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.apache.struts.action.ActionForward;
/**
* Action used to send a message
* @author luis
*/
public class SendMessageAction extends BaseFormAction {
/**
* An enum indicating where to send a message
* @author luis
*/
public static enum SendTo {
MEMBER, ADMIN, GROUP, BROKERED_MEMBERS
}
private static final int WRAP_SIZE = 50;
private MessageService messageService;
private MessageCategoryService messageCategoryService;
private PreferenceService preferenceService;
private Map<Class<? extends SendMessageDTO>, DataBinder<? extends SendMessageDTO>> dataBindersByType;
@Inject
public void setMessageCategoryService(final MessageCategoryService messageCategoryService) {
this.messageCategoryService = messageCategoryService;
}
@Inject
public void setMessageService(final MessageService messageService) {
this.messageService = messageService;
}
@Inject
public void setPreferenceService(final PreferenceService preferenceService) {
this.preferenceService = preferenceService;
}
@Override
protected ActionForward handleSubmit(final ActionContext context) throws Exception {
final SendMessageForm form = context.getForm();
final long toMemberId = form.getToMemberId();
// Send the message
final SendMessageDTO dto = resolveDTO(context);
// Call the correct service method
try {
String key = "message.sent";
messageService.send(dto);
if (dto instanceof SendDirectMessageToMemberDTO) {
final SendDirectMessageToMemberDTO sendDirectMessageToMemberDTO = (SendDirectMessageToMemberDTO) dto;
Type type = null;
if (context.isAdmin()) {
type = Message.Type.FROM_ADMIN_TO_MEMBER;
} else {
type = Message.Type.FROM_MEMBER;
}
if (CurrentTransactionData.hasMailError()) {
final Member member = sendDirectMessageToMemberDTO.getToMember();
final NotificationPreference np = preferenceService.load(member, type);
if (np.isMessage()) {
key = "message.warning.messageNotReceivedByEmail";
} else {
return context.sendError("message.error.emailNotSent");
}
}
}
context.sendMessage(key);
} catch (final MemberWontReceiveNotificationException e) {
return context.sendError("message.error.memberWontReceiveNotification");
}
// Go back to the correct location
if (dto.getInReplyTo() == null && toMemberId > 0L) {
return ActionHelper.redirectWithParam(context.getRequest(), context.findForward("backToProfile"), "memberId", toMemberId);
}
return context.findForward("backToList");
}
@Override
protected void prepareForm(final ActionContext context) throws Exception {
final SendMessageForm form = context.getForm();
final HttpServletRequest request = context.getRequest();
final Member toMember = resolveToMember(context);
final Message inReplyTo = resolveInReplyTo(context);
if (toMember == null) {
final List<SendTo> sendTo = new ArrayList<SendTo>();
if (context.isAdmin()) {
// An admin may send to a group, so, we must get the groups
if (inReplyTo == null) {
final GroupQuery gq = new GroupQuery();
gq.setNatures(Group.Nature.MEMBER, Group.Nature.BROKER);
gq.setStatus(Group.Status.NORMAL);
request.setAttribute("groups", groupService.search(gq));
if (permissionService.hasPermission(AdminMemberPermission.MESSAGES_SEND_TO_MEMBER)) {
sendTo.add(SendTo.MEMBER);
}
if (permissionService.hasPermission(AdminMemberPermission.MESSAGES_SEND_TO_GROUP)) {
sendTo.add(SendTo.GROUP);
}
}
} else {
if (form.isToBrokeredMembers()) {
if (context.isBroker() && permissionService.hasPermission(BrokerPermission.MESSAGES_SEND_TO_MEMBERS)) {
sendTo.add(SendTo.BROKERED_MEMBERS);
request.setAttribute("toBrokeredMembers", SendTo.BROKERED_MEMBERS);
}
} else if (inReplyTo == null) {
if (context.isMember() && permissionService.hasPermission(MemberPermission.MESSAGES_SEND_TO_MEMBER) || context.isOperator() && permissionService.hasPermission(OperatorPermission.MESSAGES_SEND_TO_MEMBER)) {
sendTo.add(SendTo.MEMBER);
}
if (context.isBroker() && permissionService.hasPermission(BrokerPermission.MESSAGES_SEND_TO_MEMBERS)) {
sendTo.add(SendTo.BROKERED_MEMBERS);
}
// A member may send to admin, so we must get the categories
final MessageCategoryQuery query = new MessageCategoryQuery();
query.setFromElement((Member) context.getAccountOwner());
final List<MessageCategory> categories = messageCategoryService.search(query);
request.setAttribute("categories", categories);
if (CollectionUtils.isNotEmpty(categories) && (context.isMember() && permissionService.hasPermission(MemberPermission.MESSAGES_SEND_TO_ADMINISTRATION) || context.isOperator() && permissionService.hasPermission(OperatorPermission.MESSAGES_SEND_TO_ADMINISTRATION))) {
sendTo.add(SendTo.ADMIN);
}
}
}
if (inReplyTo == null && CollectionUtils.isEmpty(sendTo)) {
throw new PermissionDeniedException();
}
request.setAttribute("sendTo", sendTo);
} else {
final MessageCategoryQuery query = new MessageCategoryQuery();
query.setFromElement((Element) (context.isOperator() ? context.getAccountOwner() : context.getElement()));
query.setToElement(toMember);
request.setAttribute("categories", messageCategoryService.search(query));
}
// Message reply
final LocalSettings localSettings = settingsService.getLocalSettings();
TextFormat messageFormat = localSettings.getMessageFormat();
if (inReplyTo != null) {
form.setMessage("subject", context.message("message.reply.subject", inReplyTo.getSubject()));
String body;
if (inReplyTo.isHtml()) {
body = "<br><br><div style='padding-left:40px;border-left:1px solid black'>" + inReplyTo.getBody() + "</div>";
messageFormat = TextFormat.RICH;
} else {
body = " \n\n> " + StringUtils.replace(WordUtils.wrap(inReplyTo.getBody(), WRAP_SIZE), "\n", "\n> ");
messageFormat = TextFormat.PLAIN;
}
request.setAttribute("body", body);
form.setMessage("html", inReplyTo.isHtml());
if (inReplyTo.getCategory() != null) {
form.setMessage("category", inReplyTo.getCategory().getId());
if (inReplyTo.getToMember() != null) {
// Reply to a member
request.setAttribute("categoryName", inReplyTo.getCategory().getName());
request.setAttribute("categoryEditable", false);
} else {
// Reply to administration
final MessageCategoryQuery query = new MessageCategoryQuery();
query.setFromElement((Element) (context.isOperator() ? context.getAccountOwner() : context.getElement()));
request.setAttribute("categories", messageCategoryService.search(query));
request.setAttribute("categoryId", inReplyTo.getCategory().getId());
}
}
}
form.setMessage("html", messageFormat == TextFormat.RICH);
request.setAttribute("inReplyTo", inReplyTo);
request.setAttribute("toMember", toMember);
request.setAttribute("messageFormat", messageFormat);
}
@Override
protected void validateForm(final ActionContext context) {
final SendMessageDTO dto = resolveDTO(context);
messageService.validate(dto);
}
private <T extends SendMessageDTO> BeanBinder<T> basicDataBinderFor(final Class<T> type) {
final BeanBinder<T> binder = BeanBinder.instance(type);
// The body is not read here, as it can be either plain text or html
binder.registerBinder("category", PropertyBinder.instance(MessageCategory.class, "category"));
binder.registerBinder("subject", PropertyBinder.instance(String.class, "subject"));
binder.registerBinder("inReplyTo", PropertyBinder.instance(Message.class, "inReplyTo"));
binder.registerBinder("html", PropertyBinder.instance(Boolean.TYPE, "html"));
return binder;
}
@SuppressWarnings("unchecked")
private <T extends SendMessageDTO> DataBinder<T> getDataBinderFor(final Class<T> type) {
if (dataBindersByType == null) {
dataBindersByType = new HashMap<Class<? extends SendMessageDTO>, DataBinder<? extends SendMessageDTO>>();
final BeanBinder<SendDirectMessageToMemberDTO> toMemberBinder = basicDataBinderFor(SendDirectMessageToMemberDTO.class);
toMemberBinder.registerBinder("toMember", PropertyBinder.instance(Member.class, "toMember"));
dataBindersByType.put(SendDirectMessageToMemberDTO.class, toMemberBinder);
final BeanBinder<SendMessageToAdminDTO> toAdminBinder = basicDataBinderFor(SendMessageToAdminDTO.class);
dataBindersByType.put(SendMessageToAdminDTO.class, toAdminBinder);
final BeanBinder<SendMessageFromBrokerToMembersDTO> toBrokeredBinder = basicDataBinderFor(SendMessageFromBrokerToMembersDTO.class);
dataBindersByType.put(SendMessageFromBrokerToMembersDTO.class, toBrokeredBinder);
final BeanBinder<SendMessageToGroupDTO> toGroupBinder = basicDataBinderFor(SendMessageToGroupDTO.class);
toGroupBinder.registerBinder("toGroups", SimpleCollectionBinder.instance(MemberGroup.class, "toGroups"));
dataBindersByType.put(SendMessageToGroupDTO.class, toGroupBinder);
}
return (DataBinder<T>) dataBindersByType.get(type);
}
/**
* Resolve a send message dto
*/
private SendMessageDTO resolveDTO(final ActionContext context) {
final SendMessageForm form = context.getForm();
Class<? extends SendMessageDTO> dtoClass = null;
final SendTo sendTo = CoercionHelper.coerce(SendTo.class, form.getSendTo());
if (sendTo == null) {
throw new ValidationException();
}
// Test and validate who to send the message
switch (sendTo) {
case MEMBER:
dtoClass = SendDirectMessageToMemberDTO.class;
break;
case GROUP:
if (!context.isAdmin()) {
throw new ValidationException();
}
dtoClass = SendMessageToGroupDTO.class;
break;
case BROKERED_MEMBERS:
if (!context.isBroker()) {
throw new ValidationException();
}
dtoClass = SendMessageFromBrokerToMembersDTO.class;
break;
case ADMIN:
if (!(context.isMember() || context.isOperator())) {
throw new ValidationException();
}
dtoClass = SendMessageToAdminDTO.class;
break;
default:
throw new ValidationException();
}
final SendMessageDTO dto = getDataBinderFor(dtoClass).readFromString(form.getMessage());
if (dto.isHtml()) {
dto.setBody(HtmlConverter.instance().valueOf("" + form.getMessage("body")));
} else {
dto.setBody(StringTrimmerConverter.instance().valueOf("" + form.getMessage("body")));
}
return dto;
}
private Message resolveInReplyTo(final ActionContext context) {
final SendMessageForm form = context.getForm();
final long inReplyToId = form.getInReplyTo();
if (inReplyToId <= 0L) {
return null;
}
final Message inReplyTo = messageService.load(inReplyToId, Message.Relationships.TO_MEMBER);
if ((context.isAdmin() && inReplyTo.getToMember() != null) || (context.isMember() && !context.getAccountOwner().equals(inReplyTo.getToMember()))) {
throw new PermissionDeniedException();
}
return inReplyTo;
}
/**
* Resolve the member to send to, if any
*/
private Member resolveToMember(final ActionContext context) {
final SendMessageForm form = context.getForm();
final long toMemberId = form.getToMemberId();
Member toMember = null;
// Load the member to send to, if any
if (toMemberId > 0L) {
final Element loggedElement = (Element) (context.isOperator() ? context.getAccountOwner() : context.getElement());
// Cannot send to self
if (toMemberId == loggedElement.getId()) {
throw new ValidationException();
}
// Ensure a member
final Element element = elementService.load(toMemberId, Element.Relationships.USER);
if (!(element instanceof Member)) {
throw new ValidationException();
}
toMember = (Member) element;
}
return toMember;
}
}