001/* 002 * Copyright 2024 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.documents.jcr; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.List; 021import java.util.UUID; 022 023import javax.jcr.Node; 024import javax.jcr.NodeIterator; 025import javax.jcr.RepositoryException; 026 027import org.ametys.cms.repository.CommentableAmetysObject; 028import org.ametys.cms.repository.comment.Comment; 029import org.ametys.core.util.DateUtils; 030import org.ametys.plugins.explorer.resources.Resource; 031import org.ametys.plugins.explorer.resources.jcr.JCRResource; 032import org.ametys.plugins.repository.AmetysObject; 033import org.ametys.plugins.repository.AmetysRepositoryException; 034import org.ametys.plugins.repository.RepositoryConstants; 035import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 036import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder; 037import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 038import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 039 040/** 041 * Implementation of {@link Resource} used for document module of workspaces and can handle comments, backed by a JCR node.<br> 042 */ 043public class File extends JCRResource<FileFactory> implements CommentableAmetysObject<Comment> 044{ 045 046 /** Tokens node */ 047 public static final String TOKENS_NODE = RepositoryConstants.NAMESPACE_PREFIX + ":tokens"; 048 /** Token node */ 049 public static final String TOKEN_NODE = RepositoryConstants.NAMESPACE_PREFIX + ":token"; 050 /** Token id */ 051 public static final String TOKEN_ID = "tokenId"; 052 /** Token id property */ 053 public static final String TOKEN_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":" + TOKEN_ID; 054 /** Creation date property for a token */ 055 public static final String TOKEN_CREATION_DATE_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":creationDate"; 056 /** File id property for a token */ 057 public static final String TOKEN_FILE_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":fileId"; 058 059 /** 060 * Creates an {@link File}. 061 * @param node the node backing this {@link AmetysObject} 062 * @param parentPath the parentPath in the Ametys hierarchy 063 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 064 */ 065 public File(Node node, String parentPath, FileFactory factory) 066 { 067 super(node, parentPath, factory); 068 } 069 070 public Comment createComment() 071 { 072 return new Comment(_getCommentsDataHolder()); 073 } 074 075 public Comment createComment(String commentId, ZonedDateTime creationDate) 076 { 077 return new Comment(_getCommentsDataHolder(), commentId, creationDate); 078 } 079 080 public Comment getComment(String commentId) throws AmetysRepositoryException 081 { 082 return Comment.getComment(_getCommentsDataHolder(), commentId); 083 } 084 085 public List<Comment> getComments(boolean includeNotValidatedComments, boolean includeValidatedComments) throws AmetysRepositoryException 086 { 087 return Comment.getComments(_getCommentsDataHolder(), includeNotValidatedComments, includeValidatedComments); 088 } 089 090 private ModifiableModelLessDataHolder _getCommentsDataHolder() 091 { 092 try 093 { 094 Node baseNode = getBaseNode(); 095 if (!baseNode.hasNode("ametys:comments")) 096 { 097 baseNode.addNode("ametys:comments", "ametys:compositeMetadata"); 098 } 099 Node commentsNode = baseNode.getNode("ametys:comments"); 100 101 baseNode.getSession().save(); 102 103 ModifiableRepositoryData repositoryData = new JCRRepositoryData(commentsNode); 104 return new DefaultModifiableModelLessDataHolder(_getFactory().getUnversionedDataTypeExtensionPoint(), repositoryData); 105 } 106 catch (RepositoryException e) 107 { 108 throw new AmetysRepositoryException(e); 109 } 110 } 111 112 /** 113 * Token class representing a composite object with id, creation date, and file id. 114 * @param id the token id 115 * @param creationDate the creation date 116 * @param fileId the file id 117 */ 118 public record PublicLinkToken(String id, ZonedDateTime creationDate, String fileId) { /* empty */ } 119 120 // FIXME We only handle single token for now, so we just return the first one 121 /** 122 * Retrieves the file token. The JCR can handle multiple tokens, but only the first one is returned for now. 123 * @return the file token. 124 */ 125 public PublicLinkToken getToken() 126 { 127 List<PublicLinkToken> tokens = new ArrayList<>(); 128 try 129 { 130 Node fileNode = getNode(); 131 if (fileNode.hasNode(TOKENS_NODE)) 132 { 133 Node tokensNode = fileNode.getNode(TOKENS_NODE); 134 NodeIterator tokenNodes = tokensNode.getNodes(); 135 while (tokenNodes.hasNext()) 136 { 137 Node tokenNode = tokenNodes.nextNode(); 138 String id = tokenNode.getProperty(TOKEN_ID_PROPERTY).getString(); 139 ZonedDateTime creationDate = DateUtils.asZonedDateTime(tokenNode.getProperty(TOKEN_CREATION_DATE_PROPERTY).getDate()); 140 String fileId = tokenNode.getProperty(TOKEN_FILE_ID_PROPERTY).getString(); 141 tokens.add(new PublicLinkToken(id, creationDate, fileId)); 142 } 143 } 144 } 145 catch (RepositoryException e) 146 { 147 throw new AmetysRepositoryException("Cannot get tokens for file " + this.getName() + " (" + this.getId() + ")", e); 148 } 149 150 if (tokens.isEmpty()) 151 { 152 return null; 153 } 154 PublicLinkToken token = tokens.get(0); 155 156 if (!this.getId().equals(token.fileId())) 157 { 158 return null; 159 } 160 161 return token; 162 } 163 164 /** 165 * Stores the list of tokens. 166 * @param tokens the list of tokens. 167 */ 168 public void setTokens(List<PublicLinkToken> tokens) 169 { 170 try 171 { 172 Node fileNode = getNode(); 173 Node tokensNode; 174 if (fileNode.hasNode(TOKENS_NODE)) 175 { 176 tokensNode = fileNode.getNode(TOKENS_NODE); 177 tokensNode.remove(); 178 } 179 tokensNode = fileNode.addNode(TOKENS_NODE, "ametys:compositeMetadata"); 180 181 for (PublicLinkToken token : tokens) 182 { 183 Node tokenNode = tokensNode.addNode(TOKEN_NODE, "ametys:compositeMetadata"); 184 tokenNode.setProperty(TOKEN_ID_PROPERTY, token.id()); 185 tokenNode.setProperty(TOKEN_CREATION_DATE_PROPERTY, DateUtils.asCalendar(token.creationDate())); 186 tokenNode.setProperty(TOKEN_FILE_ID_PROPERTY, token.fileId()); 187 } 188 } 189 catch (RepositoryException e) 190 { 191 throw new AmetysRepositoryException("Cannot set tokens for file " + this.getName() + " (" + this.getId() + ")", e); 192 } 193 } 194 195 /** 196 * Creates a new token with the current date as the creation date. 197 * @return the created token. 198 */ 199 public PublicLinkToken createToken() 200 { 201 String tokenId = UUID.randomUUID().toString(); 202 ZonedDateTime creationDate = ZonedDateTime.now(); 203 PublicLinkToken newToken = new PublicLinkToken(tokenId, creationDate, this.getId()); 204 205 // Create a new list of tokens for idempotence, this may change in the future if we want to handle multiple tokens 206 List<PublicLinkToken> tokens = new ArrayList<>(); 207 tokens.add(newToken); 208 setTokens(tokens); 209 210 return newToken; 211 } 212}