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.ModifiableDataHolder; 036import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableDataHolder; 037import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 038import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 039import org.ametys.plugins.workspaces.project.objects.Project; 040import org.ametys.web.repository.SiteAwareAmetysObject; 041import org.ametys.web.repository.site.Site; 042 043/** 044 * Implementation of {@link Resource} used for document module of workspaces and can handle comments, backed by a JCR node.<br> 045 */ 046public class File extends JCRResource<FileFactory> implements CommentableAmetysObject<Comment>, SiteAwareAmetysObject 047{ 048 049 /** Tokens node */ 050 public static final String TOKENS_NODE = RepositoryConstants.NAMESPACE_PREFIX + ":tokens"; 051 /** Token node */ 052 public static final String TOKEN_NODE = RepositoryConstants.NAMESPACE_PREFIX + ":token"; 053 /** Token id */ 054 public static final String TOKEN_ID = "tokenId"; 055 /** Token id property */ 056 public static final String TOKEN_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":" + TOKEN_ID; 057 /** Creation date property for a token */ 058 public static final String TOKEN_CREATION_DATE_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":creationDate"; 059 /** File id property for a token */ 060 public static final String TOKEN_FILE_ID_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX + ":fileId"; 061 062 /** 063 * Creates an {@link File}. 064 * @param node the node backing this {@link AmetysObject} 065 * @param parentPath the parentPath in the Ametys hierarchy 066 * @param factory the DefaultAmetysObjectFactory which created the AmetysObject 067 */ 068 public File(Node node, String parentPath, FileFactory factory) 069 { 070 super(node, parentPath, factory); 071 } 072 073 /** 074 * Get the file description 075 * @return the description 076 */ 077 public String getDescription() 078 { 079 try 080 { 081 Node fileNode = getNode(); 082 return fileNode.hasProperty("ametys:description") 083 ? fileNode.getProperty("ametys:description").getString() 084 : null; 085 } 086 catch (RepositoryException e) 087 { 088 throw new AmetysRepositoryException("Cannot get description for file " + this.getName() + " (" + this.getId() + ")", e); 089 } 090 } 091 092 /** 093 * Set the file description 094 * @param description the description to set 095 */ 096 public void setDescription (String description) 097 { 098 try 099 { 100 Node fileNode = getNode(); 101 fileNode.setProperty("ametys:description", description); 102 } 103 catch (RepositoryException e) 104 { 105 throw new AmetysRepositoryException("Cannot set description for file " + this.getName() + " (" + this.getId() + ")", e); 106 } 107 } 108 /** 109 * <code>true</code> if the description was automatically generated, <code>false</code> otherwise. 110 * @return <code>true</code> if the description was automatically generated, <code>false</code> otherwise. 111 */ 112 public boolean isAutomaticallyGeneratedDescription() 113 { 114 try 115 { 116 Node fileNode = getNode(); 117 if (fileNode.hasProperty("ametys:isAutoGeneratedDescription")) 118 { 119 return fileNode.getProperty("ametys:isAutoGeneratedDescription").getBoolean(); 120 } 121 else 122 { 123 return false; 124 } 125 } 126 catch (RepositoryException e) 127 { 128 throw new AmetysRepositoryException("Cannot get automatic description status for file " + this.getName() + " (" + this.getId() + ")", e); 129 } 130 } 131 /** 132 * Set to <code>true</code> if the description was automatically generated, <code>false</code> otherwise. 133 * @param isAutoGeneratedDescription <code>true</code> if the description was automatically generated 134 */ 135 public void setIsAutoGeneratedDescription(boolean isAutoGeneratedDescription) 136 { 137 try 138 { 139 Node fileNode = getNode(); 140 fileNode.setProperty("ametys:isAutoGeneratedDescription", isAutoGeneratedDescription); 141 } 142 catch (RepositoryException e) 143 { 144 throw new AmetysRepositoryException("Cannot set automatic description status for file " + this.getName() + " (" + this.getId() + ")", e); 145 } 146 } 147 148 public Comment createComment() 149 { 150 return new Comment(_getCommentsDataHolder()); 151 } 152 153 public Comment createComment(String commentId, ZonedDateTime creationDate) 154 { 155 return new Comment(_getCommentsDataHolder(), commentId, creationDate); 156 } 157 158 public Comment getComment(String commentId) throws AmetysRepositoryException 159 { 160 return Comment.getComment(_getCommentsDataHolder(), commentId); 161 } 162 163 public List<Comment> getComments(boolean includeNotValidatedComments, boolean includeValidatedComments) throws AmetysRepositoryException 164 { 165 return Comment.getComments(_getCommentsDataHolder(), includeNotValidatedComments, includeValidatedComments); 166 } 167 168 private ModifiableDataHolder _getCommentsDataHolder() 169 { 170 try 171 { 172 Node baseNode = getBaseNode(); 173 if (!baseNode.hasNode("ametys:comments")) 174 { 175 baseNode.addNode("ametys:comments", "ametys:compositeMetadata"); 176 } 177 Node commentsNode = baseNode.getNode("ametys:comments"); 178 179 baseNode.getSession().save(); 180 181 ModifiableRepositoryData repositoryData = new JCRRepositoryData(commentsNode); 182 return new DefaultModifiableDataHolder(_getFactory().getUnversionedDataTypeExtensionPoint(), repositoryData); 183 } 184 catch (RepositoryException e) 185 { 186 throw new AmetysRepositoryException(e); 187 } 188 } 189 190 /** 191 * Token class representing a composite object with id, creation date, and file id. 192 * @param id the token id 193 * @param creationDate the creation date 194 * @param fileId the file id 195 */ 196 public record PublicLinkToken(String id, ZonedDateTime creationDate, String fileId) { /* empty */ } 197 198 // FIXME We only handle single token for now, so we just return the first one 199 /** 200 * Retrieves the file token. The JCR can handle multiple tokens, but only the first one is returned for now. 201 * @return the file token. 202 */ 203 public PublicLinkToken getToken() 204 { 205 List<PublicLinkToken> tokens = new ArrayList<>(); 206 try 207 { 208 Node fileNode = getNode(); 209 if (fileNode.hasNode(TOKENS_NODE)) 210 { 211 Node tokensNode = fileNode.getNode(TOKENS_NODE); 212 NodeIterator tokenNodes = tokensNode.getNodes(); 213 while (tokenNodes.hasNext()) 214 { 215 Node tokenNode = tokenNodes.nextNode(); 216 String id = tokenNode.getProperty(TOKEN_ID_PROPERTY).getString(); 217 ZonedDateTime creationDate = DateUtils.asZonedDateTime(tokenNode.getProperty(TOKEN_CREATION_DATE_PROPERTY).getDate()); 218 String fileId = tokenNode.getProperty(TOKEN_FILE_ID_PROPERTY).getString(); 219 tokens.add(new PublicLinkToken(id, creationDate, fileId)); 220 } 221 } 222 } 223 catch (RepositoryException e) 224 { 225 throw new AmetysRepositoryException("Cannot get tokens for file " + this.getName() + " (" + this.getId() + ")", e); 226 } 227 228 if (tokens.isEmpty()) 229 { 230 return null; 231 } 232 PublicLinkToken token = tokens.get(0); 233 234 if (!this.getId().equals(token.fileId())) 235 { 236 return null; 237 } 238 239 return token; 240 } 241 242 /** 243 * Stores the list of tokens. 244 * @param tokens the list of tokens. 245 */ 246 public void setTokens(List<PublicLinkToken> tokens) 247 { 248 try 249 { 250 Node fileNode = getNode(); 251 Node tokensNode; 252 if (fileNode.hasNode(TOKENS_NODE)) 253 { 254 tokensNode = fileNode.getNode(TOKENS_NODE); 255 tokensNode.remove(); 256 } 257 tokensNode = fileNode.addNode(TOKENS_NODE, "ametys:compositeMetadata"); 258 259 for (PublicLinkToken token : tokens) 260 { 261 Node tokenNode = tokensNode.addNode(TOKEN_NODE, "ametys:compositeMetadata"); 262 tokenNode.setProperty(TOKEN_ID_PROPERTY, token.id()); 263 tokenNode.setProperty(TOKEN_CREATION_DATE_PROPERTY, DateUtils.asCalendar(token.creationDate())); 264 tokenNode.setProperty(TOKEN_FILE_ID_PROPERTY, token.fileId()); 265 } 266 } 267 catch (RepositoryException e) 268 { 269 throw new AmetysRepositoryException("Cannot set tokens for file " + this.getName() + " (" + this.getId() + ")", e); 270 } 271 } 272 273 /** 274 * Creates a new token with the current date as the creation date. 275 * @return the created token. 276 */ 277 public PublicLinkToken createToken() 278 { 279 String tokenId = UUID.randomUUID().toString(); 280 ZonedDateTime creationDate = ZonedDateTime.now(); 281 PublicLinkToken newToken = new PublicLinkToken(tokenId, creationDate, this.getId()); 282 283 // Create a new list of tokens for idempotence, this may change in the future if we want to handle multiple tokens 284 List<PublicLinkToken> tokens = new ArrayList<>(); 285 tokens.add(newToken); 286 setTokens(tokens); 287 288 return newToken; 289 } 290 291 public String getSiteName() throws AmetysRepositoryException 292 { 293 return getSite().getName(); 294 } 295 296 public Site getSite() throws AmetysRepositoryException 297 { 298 Project project = _getFactory().getProjectManager().getParentProject(this); 299 return project.getSite(); 300 } 301}