/** * Copyright (C) 2011 JTalks.org Team * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jtalks.jcommune.web.controller; import org.jtalks.jcommune.model.entity.JCUser; import org.jtalks.jcommune.model.entity.PrivateMessage; import org.jtalks.jcommune.model.entity.PrivateMessageStatus; import org.jtalks.jcommune.service.PrivateMessageService; import org.jtalks.jcommune.service.UserService; import org.jtalks.jcommune.plugin.api.exceptions.NotFoundException; import org.jtalks.jcommune.service.nontransactional.BBCodeService; import org.jtalks.jcommune.web.dto.PrivateMessageDraftDto; import org.jtalks.jcommune.web.dto.PrivateMessageDto; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.propertyeditors.StringTrimmerEditor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import javax.validation.Valid; import java.util.Arrays; import java.util.List; /** * MVC controller for Private Messaging. Handles request for inbox, outbox and new private messages. * * @author Pavel Vervenko * @author Max Malakhov * @author Kirill Afonin * @author Alexandre Teterin * @author Guram Savinov */ @Controller public class PrivateMessageController { public static final String PM_IDENTIFIERS = "pmIdentifiers"; private PrivateMessageService pmService; private BBCodeService bbCodeService; private UserService userService; //constants are moved here when occurs 4 or more times, as project PMD rule states private static final String PM_FORM = "pm/pmForm"; private static final String PM_ID = "pmId"; private static final String DTO = "privateMessageDto"; /** * This method turns the trim binder on. Trim builder * removes leading and trailing spaces from the submitted fields. * So, it ensures, that all validations will be applied to * trimmed field values only. * * @param binder Binder object to be injected */ @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(String.class, new StringTrimmerEditor(true)); } /** * @param pmService for PrivateMessage-related operation * @param bbCodeService for qutes creation * @param userService to get current user */ @Autowired public PrivateMessageController(PrivateMessageService pmService, BBCodeService bbCodeService, UserService userService) { this.pmService = pmService; this.bbCodeService = bbCodeService; this.userService = userService; } /** * Render the PM page with the list of incoming messages for the /inbox URI. * * @param page the private message page number. * @return {@code ModelAndView} with added {@link Page} instance with of private messages. */ @RequestMapping(value = {"/inbox","/pm"}, method = RequestMethod.GET) public ModelAndView inboxPage(@RequestParam(value = "page", defaultValue = "1", required = false) String page) { Page<PrivateMessage> inboxPage = pmService.getInboxForCurrentUser(page); return new ModelAndView("pm/inbox") .addObject("inboxPage", inboxPage); } /** * Render the PM outbox page with the list of sent messages for the /outbox URI. * * @param page the private message page number. * @return {@code ModelAndView} with added {@link Page} instance with of private messages. */ @RequestMapping(value = "/outbox", method = RequestMethod.GET) public ModelAndView outboxPage(@RequestParam(value = "page", defaultValue = "1", required = false) String page) { Page<PrivateMessage> outboxPage = pmService.getOutboxForCurrentUser(page); return new ModelAndView("pm/outbox") .addObject("outboxPage", outboxPage); } /** * Render the PM draft page with the list of draft messages for the /outbox URI. * * @param page the private message page number. * @return {@code ModelAndView} with added {@link Page} instance with of private messages. */ @RequestMapping(value = "/drafts", method = RequestMethod.GET) public ModelAndView draftsPage(@RequestParam(value = "page", defaultValue = "1", required = false) String page) { Page<PrivateMessage> draftsPage = pmService.getDraftsForCurrentUser(page); return new ModelAndView("pm/drafts") .addObject("draftsPage", draftsPage); } /** * Render the page with a form for creation new Private Message with empty {@link PrivateMessageDto} bound. * * @return {@code ModelAndView} with the form */ @RequestMapping(value = "/pm/new", method = RequestMethod.GET) public ModelAndView newPmPage(@RequestParam(value = "recipientId", required = false) Long recipientId) throws NotFoundException { Long senderId = userService.getCurrentUser().getId(); pmService.checkPermissionsToSend(senderId); PrivateMessageDto pmDto = new PrivateMessageDto(); if (recipientId != null) { String name = userService.get(recipientId).getUsername(); pmDto.setRecipient(name); } return new ModelAndView(PM_FORM) .addObject(DTO, pmDto); } /** * Render the page with the form for the reply to original message. * The form has the next filled fields: recipient, title * * @param id {@link PrivateMessage} id * @return {@code ModelAndView} with the message having filled recipient, title fields * @throws NotFoundException when message not found */ @RequestMapping(value = "/reply/{pmId}", method = RequestMethod.GET) public ModelAndView replyPage(@PathVariable(PM_ID) Long id) throws NotFoundException { PrivateMessage pm = pmService.get(id); PrivateMessageDto object = PrivateMessageDto.getReplyDtoFor(pm); return new ModelAndView(PM_FORM).addObject(DTO, object); } /** * Render the page with the form for the reply with quoting to original message. * The form has the next filled fields: recipient, title, message * * @param id {@link PrivateMessage} id * @return {@code ModelAndView} with the message having filled recipient, title, message fields * @throws NotFoundException when message not found */ @RequestMapping(value = "/quote/{pmId}", method = RequestMethod.GET) public ModelAndView quotePage(@PathVariable(PM_ID) Long id) throws NotFoundException { PrivateMessage pm = pmService.get(id); PrivateMessageDto dto = PrivateMessageDto.getReplyDtoFor(pm); dto.setBody(bbCodeService.quote(pm.getBody(), pm.getUserFrom())); return new ModelAndView(PM_FORM).addObject(DTO, dto); } /** * Save the PrivateMessage for the filled in PrivateMessageDto. * * @param pmDto {@link PrivateMessageDto} populated in form * @param result result of {@link PrivateMessageDto} validation * @return redirect to /inbox on success or back to "/new_pm" on validation errors * @throws NotFoundException is invalid user set as recipient */ @RequestMapping(value = "/pm/new", method = RequestMethod.POST) public ModelAndView sendMessage(@Valid @ModelAttribute PrivateMessageDto pmDto, BindingResult result) throws NotFoundException { if (result.hasErrors()) { return new ModelAndView(PM_FORM) .addObject(DTO, pmDto); } JCUser userFrom = userService.getCurrentUser(); JCUser userTo = userService.getByUsername(pmDto.getRecipient()); // todo: we can easily get current user in service if (pmDto.getId() > 0) { pmService.sendDraft(pmDto.getId(), pmDto.getTitle(), pmDto.getBody(), userTo, userFrom); } else { pmService.sendMessage(pmDto.getTitle(), pmDto.getBody(), userTo, userFrom); } return new ModelAndView("redirect:/outbox"); } /** * Show page with private message details. * * @param id {@link PrivateMessage} id * @return {@code ModelAndView} with a message * @throws NotFoundException when message not found */ @RequestMapping(value = {"/pm/inbox/{pmId}", "/pm/outbox/{pmId}"}, method = RequestMethod.GET) public ModelAndView showPmPage(@PathVariable(PM_ID) Long id) throws NotFoundException { PrivateMessage pm = pmService.get(id); return new ModelAndView("pm/showPm") .addObject("pm", pm) .addObject("user", userService.getCurrentUser()); } /** * Edit private message page. * * @param id {@link PrivateMessage} id * @return private message form view and populated form dto * @throws NotFoundException when message not found */ @RequestMapping(value = "/pm/drafts/{pmId}/edit", method = RequestMethod.GET) public ModelAndView editDraftPage(@PathVariable(PM_ID) Long id) throws NotFoundException { PrivateMessage pm = pmService.get(id); if (!pm.getStatus().equals(PrivateMessageStatus.DRAFT)) { // todo: 404? we need something more meaninful here throw new NotFoundException("Edit allowed only for draft messages."); } return new ModelAndView(PM_FORM).addObject(DTO, PrivateMessageDto.getFullPmDtoFor(pm)); } /** * Save private message as draft. As draft message is not requred to be valid * * @param pmDto Dto populated in form * @param result validation result * @return redirect to "drafts" folder if saved successfully or show form with error message */ @RequestMapping(value = "/pm/save", method = {RequestMethod.POST, RequestMethod.GET}) public ModelAndView saveDraft(@Valid @ModelAttribute("privateMessageDto") PrivateMessageDraftDto pmDto, BindingResult result) { String targetView = "redirect:/drafts"; long pmDtoId = pmDto.getId(); JCUser userFrom = userService.getCurrentUser(); JCUser userTo = null; if (pmDto.getRecipient() != null) { try { userTo = userService.getByUsername(pmDto.getRecipient()); } catch (NotFoundException e) { //Catch block is empty because we don't need any logic if recipient not found. We should leave it null } } if (userTo == null && result.hasGlobalErrors()) { // The case when field "To:" filled incorrectly and fields "Title:" and "Body" are both empty . if (pmDtoId != 0) { //means that we try to edit existing draft try { pmService.delete(Arrays.asList(pmDtoId)); } catch (NotFoundException e) { // Catch block is empty because we don't need any additional logic in case if user removed // draft in separate browser tab. We should just redirect him to list of drafts } } return new ModelAndView(targetView); } if (result.hasFieldErrors()){ return new ModelAndView(PM_FORM).addObject(DTO,pmDto); } pmService.saveDraft(pmDtoId, userTo, pmDto.getTitle(), pmDto.getBody(), userFrom); return new ModelAndView(targetView); } /** * Delete private messages. * * @param ids Comma-separated identifiers of the private messages for deletion * @return redirect to folder from what request is come * @throws NotFoundException if message hasn't been found */ @RequestMapping(value = "/pm", method = {RequestMethod.DELETE}) public String deleteMessages(@RequestParam(PM_IDENTIFIERS) List<Long> ids) throws NotFoundException { String url = pmService.delete(ids); return "redirect:/" + url; } }