/** * Copyright © MyCollab * <p> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p> * This program 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 Affero General Public License for more details. * <p> * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.mycollab.module.page.service.impl; import com.mycollab.common.i18n.WikiI18nEnum; import com.mycollab.core.MyCollabException; import com.mycollab.core.utils.StringUtils; import com.mycollab.module.ecm.ContentException; import com.mycollab.module.ecm.NodesUtil; import com.mycollab.module.page.domain.Folder; import com.mycollab.module.page.domain.Page; import com.mycollab.module.page.domain.PageResource; import com.mycollab.module.page.domain.PageVersion; import com.mycollab.module.page.service.PageService; import org.apache.jackrabbit.commons.JcrUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.extensions.jcr.JcrCallback; import org.springframework.extensions.jcr.JcrTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.nodetype.NodeType; import javax.jcr.version.Version; import javax.jcr.version.VersionHistory; import javax.jcr.version.VersionIterator; import javax.jcr.version.VersionManager; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.List; /** * @author MyCollab Ltd. * @since 4.4.0 */ @Repository @Transactional public class PageServiceImpl implements PageService { private static final Logger LOG = LoggerFactory.getLogger(PageServiceImpl.class); @Qualifier("pageJcrTemplate") @Autowired private JcrTemplate jcrTemplate; @Override public void savePage(final Page page, final String createdUser) { jcrTemplate.execute((JcrCallback) session -> { page.setCreatedTime(new GregorianCalendar()); page.setCreatedUser(createdUser); page.setLastUpdatedTime(new GregorianCalendar()); page.setLastUpdatedUser(createdUser); Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, page.getPath()); // forward to current path if (node != null) { if (isNodeFolder(node)) { String errorStr = String.format("Resource is existed. Search node is not a folder. It has path %s and type is %s", node.getPath(), node.getPrimaryNodeType().getName()); throw new ContentException(errorStr); } else if (isNodePage(node)) { LOG.debug("Found existing resource. Override"); VersionManager vm = session.getWorkspace().getVersionManager(); vm.checkout("/" + page.getPath()); convertPageToNode(node, page, createdUser); session.save(); vm.checkin("/" + page.getPath()); } else { String errorStr = String.format("Resource is existed. But its node type is not mycollab:content. It has path %s and type is %s", node.getPath(), node.getPrimaryNodeType().getName()); throw new ContentException(errorStr); } } else { try { String path = page.getPath(); String[] pathStr = path.split("/"); Node parentNode = rootNode; // create folder note for (int i = 0; i < pathStr.length - 1; i++) { // move to lastest node of the path Node childNode = JcrUtils.getNodeIfExists(parentNode, pathStr[i]); if (childNode != null) { if (!isNodeFolder(childNode)) { // node must is folder String errorString = "Invalid path. User want to create a content has path %s but there is a folder has path %s"; throw new ContentException(String.format(errorString, page.getPath(), childNode.getPath())); } } else { // add node childNode = parentNode.addNode(pathStr[i], "{http://www.esofthead.com/wiki}folder"); childNode.setProperty("wiki:createdUser", createdUser); childNode.setProperty("wiki:name", pathStr[i]); childNode.setProperty("wiki:description", ""); } parentNode = childNode; } Node addNode = parentNode.addNode(pathStr[pathStr.length - 1], "{http://www.esofthead.com/wiki}page"); convertPageToNode(addNode, page, createdUser); session.save(); } catch (Exception e) { LOG.error("error in convertToNode Method", e); throw new MyCollabException(e); } } return null; }); } @Override public Page getPage(final String path, final String requestedUser) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { if (isNodePage(node)) { if (isAccessible(node, requestedUser)) { return convertNodeToPage(node); } else { return null; } } } return null; }); } @Override public Folder getFolder(final String path) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { if (isNodeFolder(node)) { return convertNodeToFolder(node); } } return null; }); } private static boolean isNodeFolder(Node node) { try { return node.isNodeType("wiki:folder"); } catch (RepositoryException e) { return false; } } private static boolean isNodePage(Node node) { try { return node.isNodeType("wiki:page"); } catch (RepositoryException e) { return false; } } @Override public List<PageVersion> getPageVersions(final String path) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { VersionManager vm = session.getWorkspace().getVersionManager(); VersionHistory history = vm.getVersionHistory("/" + path); List<PageVersion> versions = new ArrayList<>(); for (VersionIterator it = history.getAllVersions(); it.hasNext(); ) { Version version = (Version) it.next(); if (!"jcr:rootVersion".equals(version.getName())) { versions.add(convertNodeToPageVersion(version)); } } return versions; } else { return null; } }); } @Override public Page restorePage(final String path, final String versionName) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { VersionManager vm = session.getWorkspace().getVersionManager(); try { vm.restore("/" + path, versionName, true); node = JcrUtils.getNodeIfExists(rootNode, path); return convertNodeToPage(node); } catch (Exception e) { LOG.error("Error when restore document {} to version {}", path, versionName, e); } } return null; }); } private PageVersion convertNodeToPageVersion(Version node) { try { PageVersion version = new PageVersion(); version.setName(node.getName()); version.setIndex(node.getIndex()); version.setCreatedTime(node.getCreated()); return version; } catch (Exception e) { LOG.error("Error while get detail node version"); throw new MyCollabException(e); } } @Override public Page getPageByVersion(final String path, final String versionName) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { VersionManager vm = session.getWorkspace().getVersionManager(); VersionHistory history = vm.getVersionHistory("/" + path); Version version = history.getVersion(versionName); if (version != null) { Node frozenNode = version.getFrozenNode(); return convertNodeToPage(frozenNode); } else { return null; } } else { return null; } }); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public void removeResource(final String path) { jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); if ("".equals(path) || "/".equals(path)) { NodeIterator nodes = rootNode.getNodes(); while (nodes.hasNext()) { Node node = nodes.nextNode(); if (isNodeFolder(node) || isNodePage(node)) { node.remove(); } } session.save(); } else { Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null && (isNodeFolder(node) || isNodePage(node))) { node.remove(); session.save(); } } return null; }); } @Override public List<Page> getPages(final String path, final String requestedUser) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { if (isNodeFolder(node)) { List<Page> pages = new ArrayList<>(); NodeIterator childNodes = node.getNodes(); while (childNodes.hasNext()) { Node childNode = childNodes.nextNode(); if (isNodePage(childNode)) { if (isAccessible(childNode, requestedUser)) { Page page = convertNodeToPage(childNode); pages.add(page); } } } return pages; } else { throw new ContentException(String.format("Do not support any node type except mycollab:folder. The current node has type: %s and its path is %s", node.getPrimaryNodeType().getName(), path)); } } return new ArrayList<Page>(); }); } @Override public List<PageResource> getResources(final String path, final String requestedUser) { return jcrTemplate.execute(session -> { Node rootNode = session.getRootNode(); Node node = JcrUtils.getNodeIfExists(rootNode, path); if (node != null) { if (isNodeFolder(node)) { List<PageResource> resources = new ArrayList<>(); NodeIterator childNodes = node.getNodes(); while (childNodes.hasNext()) { Node childNode = childNodes.nextNode(); if (isNodeFolder(childNode)) { Folder subFolder = PageServiceImpl.this.convertNodeToFolder(childNode); resources.add(subFolder); } else if (isNodePage(childNode)) { if (isAccessible(childNode, requestedUser)) { Page page = PageServiceImpl.this.convertNodeToPage(childNode); resources.add(page); } } else { String errorString = "Node %s has type not mycollab:content or mycollab:folder"; LOG.error(String.format(errorString, childNode.getPath())); } } return resources; } else { throw new ContentException( "Do not support any node type except mycollab:folder. The current node has type " + node.getPrimaryNodeType().getName()); } } LOG.debug("There is no resource in path {}", path); return new ArrayList<PageResource>(); }); } @SuppressWarnings({"rawtypes", "unchecked"}) @Override public void createFolder(final Folder folder, final String createdUser) { jcrTemplate.execute(session -> { try { Node rootNode = session.getRootNode(); String folderPath = folder.getPath(); String[] pathStr = folderPath.split("/"); Node parentNode = rootNode; // create folder note for (int i = 0; i < pathStr.length; i++) { if ("".equals(pathStr[i])) { continue; } // move to lastest node of the path Node childNode = JcrUtils.getNodeIfExists(parentNode, pathStr[i]); if (childNode != null) { LOG.debug("Found node with path {} in sub node ", pathStr[i], parentNode.getPath()); if (!isNodeFolder(childNode)) { // node must be the folder String errorString = "Invalid path. User want to create folder has path %s but there is a content has path %s"; throw new ContentException(String.format(errorString, folderPath, childNode.getPath())); } else { LOG.debug("Found folder node {}", childNode.getPath()); if (i == pathStr.length - 1) { childNode.setProperty("wiki:createdUser", createdUser); childNode.setProperty("wiki:description", StringUtils.getStrOptionalNullValue(folder.getDescription())); childNode.setProperty("wiki:name", folder.getName()); session.save(); } } } else { // add node LOG.debug("Create new folder {} of sub node {}", pathStr[i], parentNode.getPath()); childNode = parentNode.addNode(pathStr[i], "{http://www.esofthead.com/wiki}folder"); childNode.setProperty("wiki:createdUser", createdUser); childNode.setProperty("wiki:description", StringUtils.getStrOptionalNullValue(folder.getDescription())); childNode.setProperty("wiki:name", folder.getName()); session.save(); } parentNode = childNode; } LOG.debug("Node path {} is existed {}", folderPath, (JcrUtils.getNodeIfExists(rootNode, folderPath) != null)); } catch (Exception e) { String errorString = "Error while create folder with path %s"; throw new MyCollabException(String.format(errorString, folder.getPath()), e); } return null; }); } private static Node convertPageToNode(Node node, Page page, String createdUser) { try { node.addMixin(NodeType.MIX_VERSIONABLE); node.setProperty("wiki:subject", page.getSubject()); node.setProperty("wiki:content", page.getContent()); node.setProperty("wiki:status", page.getStatus()); node.setProperty("wiki:category", page.getCategory()); node.setProperty("wiki:isLock", page.isLock()); node.setProperty("wiki:createdUser", createdUser); return node; } catch (Exception e) { throw new MyCollabException(e); } } private static boolean isAccessible(Node node, String requestedUser) { String status = NodesUtil.getString(node, "wiki:status"); String createdUser = NodesUtil.getString(node, "wiki:createdUser"); return !WikiI18nEnum.status_private.name().equals(status) || (requestedUser.equals(createdUser)); } private Page convertNodeToPage(Node node) { try { Page page = new Page(); String contentPath = node.getPath(); if (contentPath.startsWith("/")) { contentPath = contentPath.substring(1); } page.setPath(contentPath); page.setSubject(NodesUtil.getString(node, "wiki:subject")); page.setContent(NodesUtil.getString(node, "wiki:content")); page.setLock(node.getProperty("wiki:isLock").getBoolean()); page.setStatus(NodesUtil.getString(node, "wiki:status")); page.setCategory(NodesUtil.getString(node, "wiki:category")); page.setCreatedTime(node.getProperty("jcr:created").getDate()); page.setCreatedUser(NodesUtil.getString(node, "wiki:createdUser")); page.setNew(false); page.setLastUpdatedTime(page.getCreatedTime()); page.setLastUpdatedUser(page.getCreatedUser()); return page; } catch (Exception e) { throw new MyCollabException(e); } } private Folder convertNodeToFolder(Node node) { try { Folder folder = new Folder(); folder.setCreatedTime(node.getProperty("jcr:created").getDate()); folder.setCreatedUser(node.getProperty("wiki:createdUser").getString()); if (node.hasProperty("wiki:description")) { folder.setDescription(node.getProperty("wiki:description").getString()); } else { folder.setDescription(""); } folder.setName(node.getProperty("wiki:name").getString()); String folderPath = node.getPath(); if (folderPath.startsWith("/")) { folderPath = folderPath.substring(1); } folder.setPath(folderPath); return folder; } catch (Exception e) { throw new MyCollabException(e); } } }