/**
* 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.common.service.security.SecurityContextFacade;
import org.jtalks.jcommune.model.entity.Branch;
import org.jtalks.jcommune.model.entity.JCUser;
import org.jtalks.jcommune.model.entity.Post;
import org.jtalks.jcommune.model.entity.Topic;
import org.jtalks.jcommune.plugin.api.PluginLoader;
import org.jtalks.jcommune.plugin.api.core.Plugin;
import org.jtalks.jcommune.plugin.api.core.TopicPlugin;
import org.jtalks.jcommune.plugin.api.filters.StateFilter;
import org.jtalks.jcommune.plugin.api.web.dto.CreateTopicBtnDto;
import org.jtalks.jcommune.plugin.api.filters.TypeFilter;
import org.jtalks.jcommune.service.*;
import org.jtalks.jcommune.plugin.api.exceptions.NotFoundException;
import org.jtalks.jcommune.service.nontransactional.LocationService;
import org.jtalks.jcommune.web.dto.BranchDto;
import org.jtalks.jcommune.plugin.api.web.dto.Breadcrumb;
import org.jtalks.jcommune.plugin.api.web.util.BreadcrumbBuilder;
import org.jtalks.jcommune.service.dto.EntityToDtoConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static ch.lambdaj.Lambda.on;
import static ch.lambdaj.Lambda.project;
/**
* @author Vitaliy kravchenko
* @author Kirill Afonin
* @author Alexandre Teterin
* @author Evgeniy Naumenko
* @author Eugeny Batov
*/
@Controller
public class BranchController {
public static final String PAGE = "page";
private BranchService branchService;
private PostService postService;
private TopicFetchService topicFetchService;
private LastReadPostService lastReadPostService;
private UserService userService;
private BreadcrumbBuilder breadcrumbBuilder;
private LocationService locationService;
private PluginLoader pluginLoader;
private final PermissionEvaluator aclEvaluator;
private final SecurityContextFacade securityContextFacade;
private EntityToDtoConverter converter;
/**
* Post count in the RSS for recent posts in the branch
*/
private static final int RECENT_POST_COUNT = 15;
/**
* Describes order of buttons in dropdown. Buttons will be sorted in ascending order.
* If two or more buttons have same order value they will be sorted by text in ascending too.
*/
private static final int CREATE_TOPIC_BUTTON_ORDER = 100;
private static final int CREATE_CODE_REVIEW_BUTTON_ORDER = 101;
/**
* Constructor creates MVC controller with specified BranchService
*
* @param branchService for branch-related service actions
* @param topicFetchService for topic-related service actions
* @param lastReadPostService service to retrieve unread posts information
* @param userService to get user currently logged in
* @param breadcrumbBuilder for creating breadcrumbs
* @param locationService to fetch user forum page location info
* @param postService to get separate posts
* @param securityContextFacade
*/
@Autowired
public BranchController(BranchService branchService,
TopicFetchService topicFetchService,
LastReadPostService lastReadPostService,
UserService userService,
BreadcrumbBuilder breadcrumbBuilder,
LocationService locationService,
PostService postService,
PermissionEvaluator aclEvaluator,
SecurityContextFacade securityContextFacade,
PluginLoader pluginLoader,
EntityToDtoConverter converter) {
this.branchService = branchService;
this.topicFetchService = topicFetchService;
this.lastReadPostService = lastReadPostService;
this.userService = userService;
this.breadcrumbBuilder = breadcrumbBuilder;
this.locationService = locationService;
this.postService = postService;
this.aclEvaluator = aclEvaluator;
this.securityContextFacade = securityContextFacade;
this.pluginLoader = pluginLoader;
this.converter = converter;
}
/**
* Displays to user a list of topic from the chosen branch with pagination.
*
* @param branchId branch for display
* @param page page
* @return {@code ModelAndView} with topics list and vars for pagination
* @throws org.jtalks.jcommune.plugin.api.exceptions.NotFoundException
* when branch not found
*/
@RequestMapping(value = "/branches/{branchId}", method = RequestMethod.GET)
public ModelAndView showPage(@PathVariable("branchId") long branchId,
@RequestParam(value = PAGE, defaultValue = "1", required = false) String page
) throws NotFoundException {
branchService.checkIfBranchExists(branchId);
Branch branch = branchService.get(branchId);
Page<Topic> topicsPage = topicFetchService.getTopics(branch, page);
lastReadPostService.fillLastReadPostForTopics(topicsPage.getContent());
JCUser currentUser = userService.getCurrentUser();
List<Breadcrumb> breadcrumbs = breadcrumbBuilder.getForumBreadcrumb(branch);
return new ModelAndView("topic/topicList")
.addObject("viewList", locationService.getUsersViewing(branch))
.addObject("branch", branch)
.addObject("topicsPage", converter.convertTopicPageToTopicDtoPage(topicsPage))
.addObject("breadcrumbList", breadcrumbs)
.addObject("topicTypes", getTopicTypes(branchId))
.addObject("subscribed", branch.getSubscribers().contains(currentUser));
}
/**
* Get the list of the topic types which are allowed for the current User and specified Branch
*
* @param branchId id of the branch where topic will be created
* @return list of the topic type information objects
*/
private List<CreateTopicBtnDto> getTopicTypes(long branchId) {
Authentication authentication = securityContextFacade.getContext().getAuthentication();
boolean hasTopicPermission =
aclEvaluator.hasPermission(authentication, branchId, "BRANCH", "BranchPermission.CREATE_POSTS");
boolean hasReviewPermission =
aclEvaluator.hasPermission(authentication, branchId, "BRANCH", "BranchPermission.CREATE_CODE_REVIEW");
List<CreateTopicBtnDto> topicTypes = new ArrayList<>();
if (hasTopicPermission) {
topicTypes.add(new CreateTopicBtnDto("new-topic-btn", "label.addtopic", "label.addtopic.tip",
"/topics/new?branchId=" + branchId, CREATE_TOPIC_BUTTON_ORDER));
}
if (hasReviewPermission) {
topicTypes.add(new CreateTopicBtnDto("new-code-review-btn", "label.addCodeReview", "label.addCodeReview.tip",
"/reviews/new?branchId=" + branchId, CREATE_CODE_REVIEW_BUTTON_ORDER));
}
List<Plugin> topicPlugins = pluginLoader.getPlugins(new TypeFilter(TopicPlugin.class),
new StateFilter(Plugin.State.ENABLED));
for (Plugin topicPlugin : topicPlugins) {
if (aclEvaluator.hasPermission(authentication, branchId, "BRANCH",
((TopicPlugin)topicPlugin).getCreateTopicPermission())) {
topicTypes.add(((TopicPlugin)topicPlugin).getCreateTopicBtnDto(branchId));
}
}
Collections.sort(topicTypes, new CreateTopicBtnDto.CreateTopicBtnDtoComparator());
return topicTypes;
}
/**
* Displays last messages for the branch.
*
* @return {@code ModelAndView} with post list and vars for pagination
*/
@RequestMapping("/branches/{branchId}/recent")
public ModelAndView recentBranchPostsPage(@PathVariable("branchId") long branchId) throws NotFoundException {
branchService.checkIfBranchExists(branchId);
Branch branch = branchService.get(branchId);
List<Post> posts = postService.getLastPostsFor(branch, RECENT_POST_COUNT);
return new ModelAndView("posts/recent")
.addObject("feedTitle", branch.getName())
.addObject("feedDescription", branch.getDescription())
.addObject("urlSuffix", branch.getUrlSuffix())
.addObject("posts", posts);
}
/**
* Displays topics updated during last 24 hours.
*
* @param page page
* @return {@code ModelAndView} with topics list and vars for pagination
*/
@RequestMapping("/topics/recent")
public ModelAndView recentTopicsPage(
@RequestParam(value = PAGE, defaultValue = "1", required = false) String page) {
Page<Topic> topicsPage = topicFetchService.getRecentTopics(page);
lastReadPostService.fillLastReadPostForTopics(topicsPage.getContent());
return new ModelAndView("topic/recent")
.addObject("topicsPage", converter.convertTopicPageToTopicDtoPage(topicsPage))
.addObject("topics", topicsPage.getContent()); // for rssViewer
}
/**
* Displays to user a list of topics without answers(topics which has only 1 post added during topic creation).
*
* @param page page
* @return {@code ModelAndView} with topics list and vars for pagination
*/
@RequestMapping("/topics/unanswered")
public ModelAndView unansweredTopicsPage(@RequestParam(value = PAGE, defaultValue = "1", required = false)
String page) {
Page<Topic> topicsPage = topicFetchService.getUnansweredTopics(page);
lastReadPostService.fillLastReadPostForTopics(topicsPage.getContent());
return new ModelAndView("topic/unansweredTopics")
.addObject("topicsPage", converter.convertTopicPageToTopicDtoPage(topicsPage));
}
/**
* Provides all available for move topic branches from section with given sectionId as JSON array.
*
* @param currentTopicId id of topic that we want to move
* @param sectionId id of section
* @return branches dto array
* @throws NotFoundException when section with given id not found
*/
@RequestMapping("/branches/json/{currentTopicId}/{sectionId}")
@ResponseBody
public BranchDto[] getBranchesFromSection(@PathVariable("currentTopicId") long currentTopicId,
@PathVariable("sectionId") long sectionId) throws NotFoundException {
List<Branch> branches = branchService.getAvailableBranchesInSection(sectionId, currentTopicId);
return convertBranchesListToBranchDtoArray(branches);
}
/**
* Get all available for move topic branches as JSON array.
*
* @param currentTopicId id of topic that we want to move
* @return branches dto array
*/
@RequestMapping("/branches/json/{currentTopicId}")
@ResponseBody
public BranchDto[] getAllBranches(@PathVariable("currentTopicId") long currentTopicId) {
List<Branch> branches = branchService.getAllAvailableBranches(currentTopicId);
return convertBranchesListToBranchDtoArray(branches);
}
/**
* Converts branch list into branch dto array.
*
* @param branches branch list
* @return branch dto array
*/
private BranchDto[] convertBranchesListToBranchDtoArray(List<Branch> branches) {
List<BranchDto> dtos = project(branches, BranchDto.class,
on(Branch.class).getId(),
on(Branch.class).getName());
return dtos.toArray(new BranchDto[dtos.size()]);
}
}