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