001/* 002 * Copyright 2016 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.project.objects; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.time.ZonedDateTime; 021import java.util.List; 022import java.util.Optional; 023import java.util.Set; 024 025import javax.jcr.Node; 026import javax.jcr.RepositoryException; 027 028import org.apache.commons.lang3.ArrayUtils; 029import org.xml.sax.ContentHandler; 030import org.xml.sax.SAXException; 031 032import org.ametys.cms.data.Binary; 033import org.ametys.cms.indexing.solr.SolrAclCacheUninfluentialObject; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.plugins.explorer.ExplorerNode; 036import org.ametys.plugins.repository.AmetysObject; 037import org.ametys.plugins.repository.AmetysObjectIterable; 038import org.ametys.plugins.repository.AmetysRepositoryException; 039import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 040import org.ametys.plugins.repository.RepositoryConstants; 041import org.ametys.plugins.repository.TraversableAmetysObject; 042import org.ametys.plugins.repository.activities.ActivityHolder; 043import org.ametys.plugins.repository.activities.ActivityHolderAmetysObject; 044import org.ametys.plugins.repository.activities.DefaultActivityHolder; 045import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 046import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper; 047import org.ametys.web.repository.SiteAwareAmetysObject; 048import org.ametys.web.repository.site.Site; 049import org.ametys.web.repository.site.SiteManager; 050 051/** 052 * {@link AmetysObject} for storing project informations. 053 */ 054@SolrAclCacheUninfluentialObject 055public class Project extends DefaultTraversableAmetysObject<ProjectFactory> implements ActivityHolderAmetysObject, SiteAwareAmetysObject 056{ 057 /** Project node type name. */ 058 public static final String NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":project"; 059 060 private static final String __EXPLORER_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"; 061 private static final String __DATA_TITLE = "title"; 062 private static final String __DATA_DESCRIPTION = "description"; 063 064 private static final String __DATA_MAILING_LIST = "mailingList"; 065 private static final String __DATA_CREATION = "creationDate"; 066 private static final String __DATA_MANAGERS = "managers"; 067 private static final String __DATA_MODULES = "modules"; 068 private static final String __DATA_INSCRIPTION_STATUS = "inscriptionStatus"; 069 private static final String __DATA_DEFAULT_PROFILE = "defaultProfile"; 070 private static final String __DATA_COVERIMAGE = "coverImage"; 071 private static final String __DATA_KEYWORDS = "keywords"; 072 private static final String __PLUGINS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"; 073 private static final String __DATA_CATEGORIES = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":categories"; 074 075 /** 076 * The inscription status of the project 077 */ 078 public enum InscriptionStatus 079 { 080 /** Inscriptions are opened to anyone */ 081 OPEN("open"), 082 /** Inscriptions are moderated */ 083 MODERATED("moderated"), 084 /** Inscriptions are private */ 085 PRIVATE("private"); 086 087 private String _value; 088 089 private InscriptionStatus(String value) 090 { 091 this._value = value; 092 } 093 094 @Override 095 public String toString() 096 { 097 return _value; 098 } 099 100 /** 101 * Converts a string to an Inscription 102 * @param status The status to convert 103 * @return The status corresponding to the string or null if unknown 104 */ 105 public static InscriptionStatus createsFromString(String status) 106 { 107 for (InscriptionStatus v : InscriptionStatus.values()) 108 { 109 if (v.toString().equals(status)) 110 { 111 return v; 112 } 113 } 114 return null; 115 } 116 } 117 118 /** 119 * Creates a {@link Project}. 120 * @param node the node backing this {@link AmetysObject}. 121 * @param parentPath the parent path in the Ametys hierarchy. 122 * @param factory the {@link ProjectFactory} which creates the AmetysObject. 123 */ 124 public Project(Node node, String parentPath, ProjectFactory factory) 125 { 126 super(node, parentPath, factory); 127 } 128 129 public ActivityHolder getActivityHolder() throws RepositoryException 130 { 131 // The child node is provided by the activity-holder nodetype so we are sure it is there 132 return new DefaultActivityHolder(getChild(ACTIVITIES_ROOT_NODE_NAME), _getFactory()); 133 } 134 135 /** 136 * Retrieves the title. 137 * @return the title. 138 * @throws AmetysRepositoryException if an error occurs. 139 */ 140 public String getTitle() throws AmetysRepositoryException 141 { 142 return getValue(__DATA_TITLE); 143 } 144 145 /** 146 * Set the title. 147 * @param title the title. 148 * @throws AmetysRepositoryException if an error occurs. 149 */ 150 public void setTitle(String title) throws AmetysRepositoryException 151 { 152 setValue(__DATA_TITLE, title); 153 } 154 155 /** 156 * Retrieves the description. 157 * @return the description. 158 * @throws AmetysRepositoryException if an error occurs. 159 */ 160 public String getDescription() throws AmetysRepositoryException 161 { 162 return getValue(__DATA_DESCRIPTION); 163 } 164 165 /** 166 * Set the description. 167 * @param description the description. 168 * @throws AmetysRepositoryException if an error occurs. 169 */ 170 public void setDescription(String description) throws AmetysRepositoryException 171 { 172 setValue(__DATA_DESCRIPTION, description); 173 } 174 175 /** 176 * Remove the description. 177 * @throws AmetysRepositoryException if an error occurs. 178 */ 179 public void removeDescription() throws AmetysRepositoryException 180 { 181 removeValue(__DATA_DESCRIPTION); 182 } 183 184 /** 185 * Retrieves the explorer nodes. 186 * @return the explorer nodes or an empty {@link AmetysObjectIterable}. 187 * @throws AmetysRepositoryException if an error occurs. 188 */ 189 public ExplorerNode getExplorerRootNode() throws AmetysRepositoryException 190 { 191 return (ExplorerNode) getChild(__EXPLORER_NODE_NAME); 192 } 193 194 /** 195 * Retrieves the explorer nodes. 196 * @return the explorer nodes or an empty {@link AmetysObjectIterable}. 197 * @throws AmetysRepositoryException if an error occurs. 198 */ 199 public AmetysObjectIterable<ExplorerNode> getExplorerNodes() throws AmetysRepositoryException 200 { 201 return ((TraversableAmetysObject) getChild(__EXPLORER_NODE_NAME)).getChildren(); 202 } 203 204 205 /** 206 * Retrieves the mailing list. 207 * @return the mailing list. 208 * @throws AmetysRepositoryException if an error occurs. 209 */ 210 public String getMailingList() throws AmetysRepositoryException 211 { 212 return getValue(__DATA_MAILING_LIST); 213 } 214 215 /** 216 * Set the mailing list. 217 * @param mailingList the mailing list. 218 * @throws AmetysRepositoryException if an error occurs. 219 */ 220 public void setMailingList(String mailingList) throws AmetysRepositoryException 221 { 222 setValue(__DATA_MAILING_LIST, mailingList); 223 } 224 225 /** 226 * Remove the mailing list. 227 * @throws AmetysRepositoryException if an error occurs. 228 */ 229 public void removeMailingList() throws AmetysRepositoryException 230 { 231 removeValue(__DATA_MAILING_LIST); 232 } 233 234 /** 235 * Retrieves the date of creation. 236 * @return the date of creation. 237 * @throws AmetysRepositoryException if an error occurs. 238 */ 239 public ZonedDateTime getCreationDate() throws AmetysRepositoryException 240 { 241 return getValue(__DATA_CREATION); 242 } 243 244 /** 245 * Set the date of creation. 246 * @param creationDate the date of creation 247 * @throws AmetysRepositoryException if an error occurs. 248 */ 249 public void setCreationDate(ZonedDateTime creationDate) throws AmetysRepositoryException 250 { 251 setValue(__DATA_CREATION, creationDate); 252 } 253 254 @Override 255 public String getSiteName() throws AmetysRepositoryException 256 { 257 return getValue(METADATA_SITE); 258 } 259 260 /** 261 * Get the site of the project 262 * @return The site. Can be null if the reference is broken. 263 */ 264 public Site getSite() 265 { 266 SiteManager siteManager = _getFactory()._getSiteManager(); 267 return siteManager.getSite(getValue(METADATA_SITE)); 268 } 269 270 /** 271 * Set the site of the project 272 * @param site the site to set 273 */ 274 public void setSite(Site site) 275 { 276 setValue(METADATA_SITE, site.getName()); 277 278 if (needsSave()) 279 { 280 saveChanges(); 281 } 282 } 283 284 /** 285 * Get the project managers user identities 286 * @return The managers 287 */ 288 public UserIdentity[] getManagers() 289 { 290 return getValueOrDefault(__DATA_MANAGERS, new UserIdentity[0]); 291 } 292 293 /** 294 * Set the project managers 295 * @param user The managers 296 */ 297 public void setManagers(UserIdentity[] user) 298 { 299 setValue(__DATA_MANAGERS, user); 300 } 301 302 /** 303 * Retrieve the list of activated modules for the project 304 * @return The list of modules ids 305 */ 306 public String[] getModules() 307 { 308 return getValueOrDefault(__DATA_MODULES, new String[0]); 309 } 310 311 /** 312 * Set the list of activated modules for the project 313 * @param modules The list of modules 314 */ 315 public void setModules(String[] modules) 316 { 317 setValue(__DATA_MODULES, modules); 318 } 319 320 /** 321 * Add a module to the list of activated modules 322 * @param moduleId The module id 323 */ 324 public void addModule(String moduleId) 325 { 326 String[] modules = getValue(__DATA_MODULES); 327 if (!ArrayUtils.contains(modules, moduleId)) 328 { 329 setValue(__DATA_MODULES, modules == null ? new String[]{moduleId} : ArrayUtils.add(modules, moduleId)); 330 } 331 } 332 333 /** 334 * Remove a module from the list of activated modules 335 * @param moduleId The module id 336 */ 337 public void removeModule(String moduleId) 338 { 339 String[] modules = getValue(__DATA_MODULES); 340 if (ArrayUtils.contains(modules, moduleId)) 341 { 342 setValue(__DATA_MODULES, ArrayUtils.removeElement(modules, moduleId)); 343 } 344 } 345 346 /** 347 * Get the inscription status of the project 348 * @return The inscription status 349 */ 350 public InscriptionStatus getInscriptionStatus() 351 { 352 if (hasValue(__DATA_INSCRIPTION_STATUS)) 353 { 354 return InscriptionStatus.createsFromString(getValue(__DATA_INSCRIPTION_STATUS)); 355 } 356 return InscriptionStatus.PRIVATE; 357 } 358 359 /** 360 * Set the inscription status of the project 361 * @param inscriptionStatus The inscription status 362 */ 363 public void setInscriptionStatus(String inscriptionStatus) 364 { 365 if (inscriptionStatus != null && InscriptionStatus.createsFromString(inscriptionStatus) != null) 366 { 367 setValue(__DATA_INSCRIPTION_STATUS, inscriptionStatus); 368 } 369 else 370 { 371 removeValue(__DATA_INSCRIPTION_STATUS); 372 } 373 } 374 375 /** 376 * Set the inscription status of the project 377 * @param inscriptionStatus The inscription status 378 */ 379 public void setInscriptionStatus(InscriptionStatus inscriptionStatus) 380 { 381 if (inscriptionStatus != null) 382 { 383 setValue(__DATA_INSCRIPTION_STATUS, inscriptionStatus.toString()); 384 } 385 else 386 { 387 removeValue(__DATA_INSCRIPTION_STATUS); 388 } 389 } 390 391 /** 392 * Get the default profile for new members of the project 393 * @return The default profile 394 */ 395 public String getDefaultProfile() 396 { 397 return getValue(__DATA_DEFAULT_PROFILE); 398 } 399 400 401 /** 402 * Set the default profile for the members of the project 403 * @param profileId The ID of the profile 404 */ 405 public void setDefaultProfile(String profileId) 406 { 407 if (profileId != null) 408 { 409 setValue(__DATA_DEFAULT_PROFILE, profileId); 410 } 411 else 412 { 413 removeValue(__DATA_DEFAULT_PROFILE); 414 } 415 } 416 417 /** 418 * Get the root for plugins 419 * @return the root for plugins 420 * @throws AmetysRepositoryException if an error occurs. 421 */ 422 public ModifiableTraversableAmetysObject getRootPlugins () throws AmetysRepositoryException 423 { 424 return (ModifiableTraversableAmetysObject) getChild(__PLUGINS_NODE_NAME); 425 } 426 427 /** 428 * Retrieve the list of tags 429 * @return The list of tags 430 * @throws AmetysRepositoryException if an error occurs 431 */ 432 public Set<String> getTags() throws AmetysRepositoryException 433 { 434 return TaggableAmetysObjectHelper.getTags(this); 435 } 436 437 /** 438 * Add a tag to the project 439 * @param tag The tag 440 * @throws AmetysRepositoryException if an error occurs 441 */ 442 public void tag(String tag) throws AmetysRepositoryException 443 { 444 TaggableAmetysObjectHelper.tag(this, tag); 445 } 446 447 /** 448 * Remove a tag from the project 449 * @param tag The tag 450 * @throws AmetysRepositoryException if an error occurs 451 */ 452 public void untag(String tag) throws AmetysRepositoryException 453 { 454 TaggableAmetysObjectHelper.untag(this, tag); 455 } 456 457 /** 458 * Set the project tags 459 * @param tags The list of tags 460 */ 461 public void setTags(List<String> tags) 462 { 463 Set<String> currentTags = getTags(); 464 // remove old tags not selected 465 currentTags.stream() 466 .filter(tag -> !tags.contains(tag)) 467 .forEach(tag -> untag(tag)); 468 469 // add new selected tags 470 tags.stream() 471 .filter(tag -> !currentTags.contains(tag)) 472 .forEach(tag -> tag(tag)); 473 } 474 475 476 /** 477 * Retrieve the list of categories 478 * @return The categories 479 * @throws AmetysRepositoryException if an error occurs 480 */ 481 public Set<String> getCategories() throws AmetysRepositoryException 482 { 483 return TaggableAmetysObjectHelper.getTags(this, __DATA_CATEGORIES); 484 } 485 486 /** 487 * Add a category to the project 488 * @param category The category 489 * @throws AmetysRepositoryException if an error occurs 490 */ 491 public void addCategory(String category) throws AmetysRepositoryException 492 { 493 TaggableAmetysObjectHelper.tag(this, category, __DATA_CATEGORIES); 494 } 495 496 /** 497 * Remove a category from the project 498 * @param category The category 499 * @throws AmetysRepositoryException if an error occurs 500 */ 501 public void removeCategory(String category) throws AmetysRepositoryException 502 { 503 TaggableAmetysObjectHelper.untag(this, category, __DATA_CATEGORIES); 504 } 505 506 /** 507 * Set the category tags of the project 508 * @param categoryTags The category tags 509 */ 510 public void setCategoryTags(List<String> categoryTags) 511 { 512 Set<String> currentCategories = getCategories(); 513 // remove old tags not selected 514 currentCategories.stream() 515 .filter(category -> !categoryTags.contains(category)) 516 .forEach(category -> removeCategory(category)); 517 518 // add new selected tags 519 categoryTags.stream() 520 .filter(category -> !currentCategories.contains(category)) 521 .forEach(category -> addCategory(category)); 522 } 523 524 /** 525 * Set the cover image of the site 526 * @param is The input stream of the cover image 527 * @param mimeType The mimetype of the cover image 528 * @param filename The filename of the cover image 529 * @param lastModificationDate The last modification date of the cover image 530 * @throws IOException if an error occurs while setting the cover image 531 */ 532 public void setCoverImage(InputStream is, String mimeType, String filename, ZonedDateTime lastModificationDate) throws IOException 533 { 534 if (is != null) 535 { 536 Binary coverImage = new Binary(); 537 coverImage.setInputStream(is); 538 Optional.ofNullable(mimeType).ifPresent(coverImage::setMimeType); 539 Optional.ofNullable(filename).ifPresent(coverImage::setFilename); 540 Optional.ofNullable(lastModificationDate).ifPresent(coverImage::setLastModificationDate); 541 setValue(__DATA_COVERIMAGE, coverImage); 542 } 543 else 544 { 545 removeValue(__DATA_COVERIMAGE); 546 } 547 } 548 549 /** 550 * Returns the cover image of the project 551 * @return the cover image of the project 552 */ 553 public Binary getCoverImage() 554 { 555 return getValue(__DATA_COVERIMAGE); 556 } 557 558 /** 559 * Generates SAX events for the project's cover image 560 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 561 * @throws SAXException if error occurs during the SAX events generation 562 * @throws IOException if an I/O error occurs while reading the cover image 563 */ 564 public void coverImageToSAX(ContentHandler contentHandler) throws SAXException, IOException 565 { 566 dataToSAX(contentHandler, __DATA_COVERIMAGE); 567 } 568 569 /** 570 * Retrieve the list of keywords for the project 571 * @return The list of keywords 572 */ 573 public String[] getKeywords() 574 { 575 return getValueOrDefault(__DATA_KEYWORDS, new String[0]); 576 } 577 578 /** 579 * Set the list of keywordss for the project 580 * @param keywords The list of keywords 581 */ 582 public void setKeywords(String[] keywords) 583 { 584 setValue(__DATA_KEYWORDS, keywords); 585 } 586}