001/* 002 * Copyright 2012 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.cms.repository; 017 018import java.time.ZonedDateTime; 019import java.util.Calendar; 020import java.util.List; 021import java.util.Locale; 022import java.util.Map; 023 024import javax.jcr.Node; 025import javax.jcr.RepositoryException; 026 027import org.apache.avalon.framework.component.Component; 028 029import org.ametys.cms.content.references.OutgoingReferences; 030import org.ametys.cms.data.type.ModelItemTypeConstants; 031import org.ametys.core.user.UserIdentity; 032import org.ametys.core.util.DateUtils; 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.repositorydata.ModifiableRepositoryData; 037import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData; 038import org.ametys.plugins.repository.lock.LockableAmetysObject; 039import org.ametys.runtime.model.ElementDefinition; 040import org.ametys.runtime.model.ModelItem; 041 042 043/** 044 * Provides helper methods to use the {@link ModifiableContent} API on {@link DefaultContent}s. 045 */ 046public class ModifiableContentHelper implements Component 047{ 048 /** The Avalon role */ 049 public static final String ROLE = ModifiableContentHelper.class.getName(); 050 051 /** Constants for the root of comments */ 052 public static final String METADATA_COMMENTS = "comments"; 053 /** Constants for the root of contributor comments */ 054 public static final String METADATA_CONTRIBUTOR_COMMENTS = "contributor-comments"; 055 056 /** 057 * Set a {@link DefaultContent} title for the given locale. 058 * @param content the {@link DefaultContent} to set. 059 * @param title the title to set. 060 * @param locale The locale. Can be null if the content is not a multilingual content. 061 * @throws AmetysRepositoryException if an error occurs. 062 */ 063 public void setTitle(DefaultContent content, String title, Locale locale) throws AmetysRepositoryException 064 { 065 ModelItem titleDefinition = content.getDefinition(Content.ATTRIBUTE_TITLE); 066 ModifiableRepositoryData repositoryData = new JCRRepositoryData(content.getNode()); 067 if (titleDefinition instanceof ElementDefinition && ModelItemTypeConstants.MULTILINGUAL_STRING_ELEMENT_TYPE_ID.equals(((ElementDefinition) titleDefinition).getType().getId())) 068 { 069 if (locale == null) 070 { 071 throw new IllegalArgumentException("Cannot set a title with null locale on a multilingual title"); 072 } 073 074 _addLockToken(content); 075 ModifiableRepositoryData titleRepositoryData; 076 if (repositoryData.hasValue(Content.ATTRIBUTE_TITLE)) 077 { 078 titleRepositoryData = repositoryData.getRepositoryData(Content.ATTRIBUTE_TITLE); 079 } 080 else 081 { 082 titleRepositoryData = repositoryData.addRepositoryData(Content.ATTRIBUTE_TITLE, RepositoryConstants.MULTILINGUAL_STRING_METADATA_NODETYPE); 083 } 084 titleRepositoryData.setValue(locale.toString(), title); 085 } 086 else 087 { 088 _addLockToken(content); 089 repositoryData.setValue(Content.ATTRIBUTE_TITLE, title); 090 } 091 } 092 093 /** 094 * Set the title of non-multilingual {@link DefaultContent}. 095 * Be careful! Use only if content's title is not a multilingual string. If not sure use {@link #setTitle(DefaultContent, String, Locale)} instead. 096 * @param content the {@link DefaultContent} to set. 097 * @param title the title to set. 098 * @throws AmetysRepositoryException if an error occurs. 099 */ 100 public void setTitle(DefaultContent content, String title) throws AmetysRepositoryException 101 { 102 setTitle(content, title, null); 103 } 104 105 /** 106 * Copy the title of the source content to the target content 107 * @param srcContent The source content 108 * @param targetContent The target content 109 * @throws AmetysRepositoryException if an error occurs. 110 */ 111 public void copyTitle(Content srcContent, ModifiableContent targetContent) throws AmetysRepositoryException 112 { 113 targetContent.setValue(Content.ATTRIBUTE_TITLE, srcContent.getValue(Content.ATTRIBUTE_TITLE)); 114 } 115 116 /** 117 * Set a {@link DefaultContent} user. 118 * @param content the {@link DefaultContent} to set. 119 * @param user the user to set. 120 * @throws AmetysRepositoryException if an error occurs. 121 */ 122 public void setCreator(DefaultContent content, UserIdentity user) throws AmetysRepositoryException 123 { 124 _storeUserMetadata(content, DefaultContent.METADATA_CREATOR, user); 125 } 126 127 /** 128 * Set a {@link DefaultContent} creation date. 129 * @param content the {@link DefaultContent} to set. 130 * @param creationDate the creation date to set. 131 * @throws AmetysRepositoryException if an error occurs. 132 */ 133 public void setCreationDate(DefaultContent content, ZonedDateTime creationDate) throws AmetysRepositoryException 134 { 135 _storeDatetimeMetadata(content, DefaultContent.METADATA_CREATION, creationDate); 136 } 137 138 /** 139 * Set a {@link DefaultContent} contributor. 140 * @param content the {@link DefaultContent} to set. 141 * @param user the contributor to set. 142 * @throws AmetysRepositoryException if an error occurs. 143 */ 144 public void setLastContributor(DefaultContent content, UserIdentity user) throws AmetysRepositoryException 145 { 146 _storeUserMetadata(content, DefaultContent.METADATA_CONTRIBUTOR, user); 147 } 148 149 /** 150 * Set a {@link DefaultContent} last modification date. 151 * @param content the {@link DefaultContent} to set. 152 * @param lastModified the last modification date to set. 153 * @throws AmetysRepositoryException if an error occurs. 154 */ 155 public void setLastModified(DefaultContent content, ZonedDateTime lastModified) throws AmetysRepositoryException 156 { 157 _storeDatetimeMetadata(content, DefaultContent.METADATA_MODIFIED, lastModified); 158 } 159 160 /** 161 * Set a {@link DefaultContent} first validator. 162 * @param content the {@link DefaultContent} to set. 163 * @param user the validator to set. 164 * @throws AmetysRepositoryException if an error occurs. 165 */ 166 public void setFirstValidator(DefaultContent content, UserIdentity user) throws AmetysRepositoryException 167 { 168 _storeUserMetadata(content, DefaultContent.METADATA_FIRST_VALIDATOR, user); 169 } 170 171 /** 172 * Set a {@link DefaultContent} first validation date. 173 * @param content the {@link DefaultContent} to set. 174 * @param validationDate the first validation date. 175 * @throws AmetysRepositoryException if an error occurs. 176 */ 177 public void setFirstValidationDate(DefaultContent content, ZonedDateTime validationDate) throws AmetysRepositoryException 178 { 179 _storeDatetimeMetadata(content, DefaultContent.METADATA_FIRST_VALIDATION, validationDate); 180 } 181 182 /** 183 * Set a {@link DefaultContent} last validator. 184 * @param content the {@link DefaultContent} to set. 185 * @param user the validator to set. 186 * @throws AmetysRepositoryException if an error occurs. 187 */ 188 public void setLastValidator(DefaultContent content, UserIdentity user) throws AmetysRepositoryException 189 { 190 _storeUserMetadata(content, DefaultContent.METADATA_LAST_VALIDATOR, user); 191 } 192 193 /** 194 * Set a {@link DefaultContent} last validation date. 195 * @param content the {@link DefaultContent} to set. 196 * @param validationDate the last validation date. 197 * @throws AmetysRepositoryException if an error occurs. 198 */ 199 public void setLastValidationDate(DefaultContent content, ZonedDateTime validationDate) throws AmetysRepositoryException 200 { 201 _storeDatetimeMetadata(content, DefaultContent.METADATA_LAST_VALIDATION, validationDate); 202 } 203 204 /** 205 * Set a {@link DefaultContent} last major validator. 206 * @param content the {@link DefaultContent} to set. 207 * @param user the validator to set. 208 * @throws AmetysRepositoryException if an error occurs. 209 */ 210 public void setLastMajorValidator(DefaultContent content, UserIdentity user) throws AmetysRepositoryException 211 { 212 _storeUserMetadata(content, DefaultContent.METADATA_LAST_MAJOR_VALIDATOR, user); 213 } 214 215 /** 216 * Set a {@link DefaultContent} last major validation date. 217 * @param content the {@link DefaultContent} to set. 218 * @param validationDate the last major validation date. 219 * @throws AmetysRepositoryException if an error occurs. 220 */ 221 public void setLastMajorValidationDate(DefaultContent content, ZonedDateTime validationDate) throws AmetysRepositoryException 222 { 223 _storeDatetimeMetadata(content, DefaultContent.METADATA_LAST_MAJORVALIDATION, validationDate); 224 } 225 226 /** 227 * Store the outgoing references on the content. 228 * @param content The content concerned by these outgoing references. 229 * @param references A non null map of outgoing references grouped by metadata (key are metadata path) 230 * @throws AmetysRepositoryException if an error occurs. 231 */ 232 public void setOutgoingReferences(DefaultContent content, Map<String, OutgoingReferences> references) throws AmetysRepositoryException 233 { 234 try 235 { 236 Node contentNode = content.getNode(); 237 if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_ROOT_OUTGOING_REFERENCES)) 238 { 239 contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_ROOT_OUTGOING_REFERENCES).remove(); 240 } 241 242 if (references.size() != 0) 243 { 244 Node rootOutgoingRefsNode = contentNode.addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_ROOT_OUTGOING_REFERENCES); 245 for (String path : references.keySet()) 246 { 247 // Outgoing references node creation (for the given path) 248 Node outgoingRefsNode = rootOutgoingRefsNode.addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_OUTGOING_REFERENCES); 249 outgoingRefsNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + DefaultContent.METADATA_OUTGOING_REFERENCES_PATH_PROPERTY, path); 250 251 // Reference nodes per type 252 OutgoingReferences outgoingReferences = references.get(path); 253 254 for (String type : outgoingReferences.keySet()) 255 { 256 List<String> referenceValues = outgoingReferences.get(type); 257 if (referenceValues != null && !referenceValues.isEmpty()) 258 { 259 Node outgoingReferenceNode = outgoingRefsNode.addNode(type, RepositoryConstants.NAMESPACE_PREFIX + ':' + DefaultContent.METADATA_OUTGOING_REFERENCE_NODETYPE); 260 outgoingReferenceNode.setProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + DefaultContent.METADATA_OUTGOING_REFERENCE_PROPERTY, referenceValues.toArray(new String[referenceValues.size()])); 261 } 262 } 263 } 264 } 265 } 266 catch (RepositoryException e) 267 { 268 throw new AmetysRepositoryException(e); 269 } 270 } 271 272 /** 273 * Store a metadata of type datetime in the content 274 * @param content the content described by the metadata 275 * @param metadataName the name of the metadata 276 * @param date the value to set 277 * @throws AmetysRepositoryException when an error occurred 278 */ 279 protected void _storeDatetimeMetadata(DefaultContent content, String metadataName, ZonedDateTime date) 280 { 281 _addLockToken(content); 282 283 ModifiableRepositoryData repositoryData = new JCRRepositoryData(content.getNode()); 284 Calendar calendar = DateUtils.asCalendar(date); 285 repositoryData.setValue(metadataName, calendar); 286 } 287 288 /** 289 * Store a metadata of type user in the content 290 * @param content the content described by the metadata 291 * @param metadataName the name of the metadata 292 * @param user the value to set 293 * @throws AmetysRepositoryException when an error occurred 294 */ 295 protected void _storeUserMetadata(DefaultContent content, String metadataName, UserIdentity user) throws AmetysRepositoryException 296 { 297 try 298 { 299 _addLockToken(content); 300 301 // TODO CMS-9336 All the metatadata here should be stored using types 302 ModifiableRepositoryData repositoryData = new JCRRepositoryData(content.getNode()); 303 ModifiableRepositoryData creatorRepositoryData; 304 if (repositoryData.hasValue(metadataName)) 305 { 306 creatorRepositoryData = repositoryData.getRepositoryData(metadataName); 307 } 308 else 309 { 310 creatorRepositoryData = repositoryData.addRepositoryData(metadataName, RepositoryConstants.USER_NODETYPE); 311 } 312 creatorRepositoryData.setValue("login", user.getLogin()); 313 creatorRepositoryData.setValue("population", user.getPopulationId()); 314 } 315 catch (AmetysRepositoryException e) 316 { 317 throw new AmetysRepositoryException("Error setting the metadata '" + metadataName + "' for content '" + content.getId() + "' with value '" + UserIdentity.userIdentityToString(user) + "'.", e); 318 } 319 } 320 321 private void _addLockToken(DefaultContent content) 322 { 323 if (content instanceof LockableAmetysObject lockableContent) 324 { 325 lockableContent.setLockInfoOnCurrentContext(); 326 } 327 } 328 329 /** 330 * Retrieves the data holder for the given content's comments 331 * @param content the content 332 * @param createNew <code>true</code> to create the comments' data holder if it does not already exist, <code>false</code> otherwise 333 * @return the data holder for content's comments, or <code>null</code> if the data holder does not already exist and createNew is set to <code>false</code> 334 */ 335 public ModifiableModelLessDataHolder getCommentsDataHolder(DefaultContent content, boolean createNew) 336 { 337 return content.getUnversionedDataHolder().getComposite(METADATA_COMMENTS, createNew); 338 } 339 340 /** 341 * Retrieves the data holder for the given content's contributor comments 342 * @param content the content 343 * @param createNew <code>true</code> to create the contributor comments' data holder if it does not already exist, <code>false</code> otherwise 344 * @return the data holder for content's contributor comments, or <code>null</code> if the data holder does not already exist and createNew is set to <code>false</code> 345 */ 346 public ModifiableModelLessDataHolder getContributorCommentsDataHolder(DefaultContent content, boolean createNew) 347 { 348 return content.getUnversionedDataHolder().getComposite(METADATA_CONTRIBUTOR_COMMENTS, createNew); 349 } 350}