package com.aperture_software.glados_wiki.webmvc.controllers;
import com.aperture_software.glados_wiki.entities.*;
import com.aperture_software.glados_wiki.entities.functions.page_content.PageContentToPageContentResponseFunction;
import com.aperture_software.glados_wiki.entities.page_content.PageContentResponse;
import com.aperture_software.glados_wiki.entities.page_content.PageContentVersionsResponse;
import com.aperture_software.glados_wiki.exceptions.PageAclException;
import com.aperture_software.glados_wiki.markdown.VarInLinkExpandLinkRefTransformer;
import com.aperture_software.glados_wiki.markdown.WikiLinkToMarkdownFunction;
import com.aperture_software.glados_wiki.markdown.WikiSignatureEscapeFunction;
import com.aperture_software.glados_wiki.markdown.WikiSignatureExpandFunction;
import com.aperture_software.glados_wiki.services.*;
import com.aperture_software.glados_wiki.servlets.WebappPath;
import com.aperture_software.glados_wiki.support.*;
import com.aperture_software.glados_wiki.webmvc.controllers.helpers.RedirectHelper;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.apache.shiro.authz.annotation.RequiresUser;
import org.bson.types.ObjectId;
import org.markdown4j.Markdown4jProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap;
import javax.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Created with IntelliJ IDEA.
* User: jhyun
* Date: 13. Nov. 18.
* Time: 20:39
*/
@Controller
public class PageController {
private static Logger LOG = LoggerFactory.getLogger(PageController.class);
@Autowired
private PageService pageService;
@Autowired
private PageContentService pageContentService;
@Autowired
private GroupService groupService;
@Autowired
private UserService userService;
@Autowired
private PageAclService pageAclService;
@Autowired
private TimeZoneService timeZoneService;
@Inject
private FlashAlerts flashAlerts;
@Inject
private RedirectHelper redirectHelper;
@RequiresUser
@RequestMapping(value = "/edit/{title:.+}", method = {RequestMethod.GET})
public ModelAndView editPage(@PathVariable("title") final String title
, @RequestParam(value = "origVersion", defaultValue = "", required = false) final String origVersion
, ModelMap m) throws PageAclException {
Optional<Page> p = pageService.getPageByTitle(title);
if (p.isPresent()) {
Page p2 = p.get();
m.put("page", p2);
m.put("title", p2.getTitle());
// check-acl
pageAclService.checkWritable(p2);
} else {
m.put("page", null);
m.put("title", title);
// check-acl
pageAclService.checkWritable(null);
}
//
m.put("origVersion", origVersion);
return new ModelAndView("page/edit", m);
}
@RequiresUser
@RequestMapping(value = "/post", method = {RequestMethod.POST})
public View savePage(RedirectAttributesModelMap redirectAttributesModelMap,
@RequestParam(value = "title", required = true) final String title,
@RequestParam(value = "content", required = true) final String content,
@RequestParam(value = "aclForEveryone", required = false, defaultValue = "") final String aclForEveryone,
@RequestParam(value = "aclRUser", required = false) List<String> aclRUser,
@RequestParam(value = "aclWUser", required = false) List<String> aclWUser,
@RequestParam(value = "aclRGroup", required = false) List<String> aclRGroup,
@RequestParam(value = "aclWGroup", required = false) List<String> aclWGroup,
@RequestParam(value = "tag", required = false) List<String> tags,
@RequestParam(value = "origVersion", defaultValue = "", required = false) final String origVersion
)
throws Exception {
//
boolean jumped = false;
PageContent pageContentParent = null;
Page p = null;
if (pageService.existsByTitle(title)) {
p = pageService.getPageByTitle(title).get();
// check-acl
pageAclService.checkWritable(p);
//
ObjectId origVersion_ = new ObjectId(origVersion);
if (false == p.getCurrent().getId().equals(origVersion_)) {
jumped = true;
flashAlerts.add(redirectAttributesModelMap, new FlashAlerts.FlashAlert(BootstrapAlertTypes.WARNING, "Page Content has JUMPED! (maybe there's unmerged changes, check page versions.)"));
Optional<PageContent> pageContentParent_ = pageContentService.load(origVersion_);
if (pageContentParent_.isPresent()) {
pageContentParent = pageContentParent_.get();
} else {
LOG.warn(String.format("MISSING PARENT!!! -- parent=[%s], SKIP.", origVersion_.toString()));
}
} else {
pageContentParent = p.getCurrent();
}
} else {
// check-acl
pageAclService.checkWritable(null);
//
p = pageService.createByTitle(title);
}
//
PageContent pageContent = new PageContent();
pageContent.setContent(content);
pageContent.setCtime(new Date());
pageContent.setPage(p);
if (pageContentParent != null) {
pageContent.setParent(pageContentParent);
}
Optional<User> u = SecurityUtils2.getUser(userService);
if (u.isPresent()) {
pageContent.setCreator(u.get());
}
//
WikiSignatureExpandFunction wikiSignatureExpandFunction = new WikiSignatureExpandFunction(timeZoneService, u);
pageContent.setContent(wikiSignatureExpandFunction.apply(pageContent.getContent()));
//
pageContentService.save(pageContent);
//
pageService.addContent(pageContent);
p.setCurrent(pageContent);
//
p.setAclForEveryone(aclForEveryone);
p.setReadables(gatherUserAuthentications(aclRGroup, aclRUser));
p.setWritables(gatherUserAuthentications(aclWGroup, aclWUser));
setPageTags(p, tags);
//
pageService.update(p);
//
return redirectHelper.redirectToPageView(title);
}
private void setPageTags(Page p, List<String> tags) throws Exception {
if (tags != null) {
for (String t : tags) {
if (Strings.isNullOrEmpty(t)) {
throw new Exception("EMPTY TAG IS NOT ALLOWED!");
}
}
p.setTags(tags);
} else {
p.setTags(null);
}
}
private List<UserAuthentication> gatherUserAuthentications(List<String> groupNames, List<String> userNames) {
List<UserAuthentication> results = new ArrayList<UserAuthentication>();
//
if (groupNames != null) {
for (String groupName : groupNames) {
Optional<Group> g = groupService.getByName(groupName);
if (g.isPresent()) {
results.add(g.get());
} else {
LOG.warn(String.format("group %s not found, skip.", groupName));
}
}
}
if (userNames != null) {
for (String userName : userNames) {
Optional<User> u = userService.getByUsername(userName);
if (u.isPresent()) {
results.add(u.get());
} else {
LOG.warn(String.format("user %s not found, skip.", userName));
}
}
}
//
return results;
}
@RequestMapping(value = "/pageById/{id}")
public View viewPageById(@PathVariable("id") final String id) throws Exception {
Optional<Page> p = pageService.getById(id);
if (p.isPresent()) {
return redirectHelper.redirectToPageView(p.get().getTitle());
} else {
throw new Exception(String.format("PAGE-ID [%s] NOT FOUND.", id));
}
}
@RequestMapping(value = "/pageById2/{id}/{version}")
public View viewPageById(@PathVariable("id") final String id, @PathVariable("version") final String version) throws Exception {
Optional<Page> p = pageService.getById(id);
if (p.isPresent()) {
return redirectHelper.redirectToPageView(p.get().getTitle(), version);
} else {
throw new Exception(String.format("PAGE-ID [%s] NOT FOUND.", id));
}
}
@RequestMapping(value = "/page/{title:.+}", method = {RequestMethod.GET})
public ModelAndView viewPage(@PathVariable("title") final String title,
@RequestParam(value = "version", defaultValue = "", required = false) final String versionId,
ModelMap m) throws Exception {
//
Optional<Page> page = pageService.getPageByTitle(title);
if (page.isPresent() == false) {
return new ModelAndView(redirectHelper.redirectToEditPage(title));
} else {
// check-acl
pageAclService.checkReadable(page.get());
//
m.put("page", page.get());
PageContent pageContent = getPageContent(title, versionId, page);
m.put("pageContent", pageContent);
//
String markdowned = renderMarkdown(pageContent);
m.put("markdowned", markdowned);
//
List<Page> tagAssocs = pageService.listTagAssocs(page.get(), 0, Integer.MAX_VALUE);
m.put("tagAssocs", tagAssocs);
//
boolean pageWritable = pageAclService.isWritable(page.get());
m.put("pageWritable", pageWritable);
//
return new ModelAndView("page/view", m);
}
}
private PageContent getPageContent(String title, String versionId, Optional<Page> page) throws Exception {
PageContent pageContent;
if (Strings.isNullOrEmpty(versionId)) {
return page.get().getCurrent();
} else {
Optional<PageContent> pc = getVersion(page.get(), versionId);
if (pc.isPresent()) {
return pc.get();
} else {
throw new Exception(String.format("VERSION [%s] NOT FOUND FOR PAGE [%s]", versionId, title));
}
}
}
private String renderMarkdown(PageContent pageContent) throws IOException {
Markdown4jProcessor p = new Markdown4jProcessor();
p.addLinkRefTransformer(new VarInLinkExpandLinkRefTransformer(
ImmutableMap.<String, String>builder()
.put("WEBAPP_PATH_ROOT", WebappPath.getWebappPath())
.put("FILE", String.format("%s/a/file/blob", WebappPath.getWebappPath()))
.build()));
String content = pageContent.getContent();
WikiLinkToMarkdownFunction wikiLinkToMarkdownFunction = new WikiLinkToMarkdownFunction();
String content2 = wikiLinkToMarkdownFunction.apply(content);
WikiSignatureEscapeFunction wikiSignatureEscapeFunction = new WikiSignatureEscapeFunction();
return p.process(wikiSignatureEscapeFunction.apply(content2));
}
@ResponseBody
@RequestMapping(value = "/versions/{title:.+}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public PageContentVersionsResponse listVersions(@PathVariable("title") final String title,
@RequestParam(value = "offset", defaultValue = "0", required = false) int offset,
@RequestParam(value = "limit", defaultValue = "25", required = false) int limit) throws Exception {
//
Page p2 = getPageByTitle(title);
// check-acl
pageAclService.checkReadable(p2);
//
final long tot = pageContentService.countVersions(p2);
//
Pagination pagination = new Pagination(tot, offset, limit);
List<PageContent> l = pageContentService.listVersions(p2, offset, limit);
List<PageContentResponse> l2 = Lists.transform(l, new PageContentToPageContentResponseFunction());
//
return new PageContentVersionsResponse(pagination, l2);
}
private Optional<PageContent> getVersion(final Page page, final String versionId) {
ObjectId versionId_ = new ObjectId(versionId);
for (PageContent pageContent : page.getVersions()) {
if (pageContent.getId().equals(versionId_)) {
return Optional.of(pageContent);
}
}
//
return Optional.absent();
}
@ResponseBody
@RequestMapping(value = "/raw/{title:.+}", produces = MediaType.TEXT_PLAIN_VALUE + Utf8.FOR_CONTENT_TYPE)
public String viewRawVersion(@PathVariable("title") final String title,
@RequestParam(value = "version", defaultValue = "", required = false) final String versionId) throws Exception {
//
Page p2 = getPageByTitle(title);
// check-acl
pageAclService.checkReadable(p2);
String s = null;
if (Strings.isNullOrEmpty(versionId)) {
s = p2.getCurrent().getContent();
} else {
Optional<PageContent> c = getVersion(p2, versionId);
if (c.isPresent() == false) {
throw new Exception(String.format("page content for [%s] at version [%s], NOT FOUND!", title, versionId));
}
s = c.get().getContent();
}
//
return s;
}
private Page getPageByTitle(String title) throws Exception {
Optional<Page> p = pageService.getPageByTitle(title);
if (p.isPresent() == false) {
throw new Exception(String.format("Page NOT FOUND! [%s]", title));
}
return p.get();
}
@RequestMapping(value = "/renamePage")
public View renamePage(@RequestParam(value = "pageTitle", required = true) final String pageTitle,
@RequestParam(value = "newPageTitle", required = true) final String newPageTitle) throws Exception {
Page p2 = getPageByTitle(pageTitle);
pageAclService.checkWritable(p2);
//
pageService.rename(p2, newPageTitle);
return redirectHelper.redirectToPageView(newPageTitle);
}
private void checkVersionFound(final Optional<PageContent> version, final String versionId, final Page p) throws Exception {
if (version.isPresent() == false) {
throw new Exception(String.format("VERSION [%s] FOR PAGE [%s] NOT FOUND!", versionId, p.getTitle()));
}
}
@RequestMapping(value = "/diff/{title:.+}")
public ModelAndView diffPage(ModelMap m,
@PathVariable("title") final String title,
@RequestParam(value = "versionL", required = true) final String versionL,
@RequestParam(value = "versionR", required = true) final String versionR) throws Exception {
//
Page p2 = getPageByTitle(title);
pageAclService.checkReadable(p2);
//
Optional<PageContent> versionL_ = getVersion(p2, versionL);
checkVersionFound(versionL_, title, p2);
Optional<PageContent> versionR_ = getVersion(p2, versionR);
checkVersionFound(versionR_, title, p2);
//
m.put("page", p2);
m.put("versionL", versionL_.get());
m.put("versionR", versionR_.get());
//
return new ModelAndView("page/diff", m);
}
@RequestMapping(value = "/recents")
public ModelAndView recents(ModelMap m) {
return new ModelAndView("recents/list", m);
}
}