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.util.ArrayList; 019import java.util.Collection; 020import java.util.Date; 021import java.util.Iterator; 022import java.util.Objects; 023import java.util.stream.Collectors; 024import java.util.stream.StreamSupport; 025 026import javax.jcr.Node; 027import javax.jcr.NodeIterator; 028import javax.jcr.Property; 029import javax.jcr.RepositoryException; 030import javax.jcr.Value; 031import javax.jcr.ValueFactory; 032 033import org.apache.commons.lang3.ArrayUtils; 034 035import org.ametys.core.user.UserIdentity; 036import org.ametys.plugins.explorer.ExplorerNode; 037import org.ametys.plugins.repository.AmetysObject; 038import org.ametys.plugins.repository.AmetysObjectIterable; 039import org.ametys.plugins.repository.AmetysRepositoryException; 040import org.ametys.plugins.repository.RepositoryConstants; 041import org.ametys.plugins.repository.TraversableAmetysObject; 042import org.ametys.plugins.repository.events.EventHolder; 043import org.ametys.plugins.repository.events.JCREventHelper; 044import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject; 045import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 046import org.ametys.plugins.repository.metadata.UnknownMetadataException; 047import org.ametys.plugins.repository.metadata.jcr.JCRCompositeMetadata; 048import org.ametys.web.repository.site.Site; 049import org.ametys.web.repository.site.SiteManager; 050 051/** 052 * {@link AmetysObject} for storing project informations. 053 */ 054public class Project extends DefaultTraversableAmetysObject<ProjectFactory> implements ProjectsTreeNode, EventHolder 055{ 056 /** Project node type name. */ 057 public static final String NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":project"; 058 059 /** Metadata name for project 's sites */ 060 public static final String METADATA_SITES = "sites"; 061 062 private static final String __EXPLORER_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"; 063 private static final String __METADATA_TITLE = "title"; 064 private static final String __METADATA_DESCRIPTION = "description"; 065 private static final String __METADATA_URL = "url"; 066 067 private static final String __METADATA_MAILING_LIST = "mailingList"; 068 private static final String __METADATA_CREATION = "creationDate"; 069 private static final String __METADATA_MANAGER = "manager"; 070 private static final String __METADATA_MODULES = "modules"; 071 private static final String __METADATA_INSCRIPTION_STATUS = "inscriptionStatus"; 072 private static final String __METADATA_DEFAULT_PROFILE = "defaultProfile"; 073 074 /** 075 * The inscription status of the project 076 */ 077 public enum InscriptionStatus 078 { 079 /** Inscriptions are opened to anyone */ 080 OPEN("open"), 081 /** Inscriptions are moderated */ 082 MODERATED("moderated"), 083 /** Inscriptions are private */ 084 PRIVATE("private"); 085 086 private String _value; 087 088 private InscriptionStatus(String value) 089 { 090 this._value = value; 091 } 092 093 @Override 094 public String toString() 095 { 096 return _value; 097 } 098 099 /** 100 * Converts a string to an Inscription 101 * @param status The status to convert 102 * @return The status corresponding to the string or null if unknown 103 */ 104 public static InscriptionStatus createsFromString(String status) 105 { 106 for (InscriptionStatus v : InscriptionStatus.values()) 107 { 108 if (v.toString().equals(status)) 109 { 110 return v; 111 } 112 } 113 return null; 114 } 115 } 116 117 /** 118 * Creates a {@link Project}. 119 * @param node the node backing this {@link AmetysObject}. 120 * @param parentPath the parent path in the Ametys hierarchy. 121 * @param factory the {@link ProjectFactory} which creates the AmetysObject. 122 */ 123 public Project(Node node, String parentPath, ProjectFactory factory) 124 { 125 super(node, parentPath, factory); 126 } 127 128 /** 129 * Retrieves the title. 130 * @return the title. 131 * @throws AmetysRepositoryException if an error occurs. 132 */ 133 public String getTitle() throws AmetysRepositoryException 134 { 135 try 136 { 137 return getMetadataHolder().getString(__METADATA_TITLE); 138 } 139 catch (UnknownMetadataException e) 140 { 141 return null; 142 } 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 getMetadataHolder().setMetadata(__METADATA_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 try 163 { 164 return getMetadataHolder().getString(__METADATA_DESCRIPTION); 165 } 166 catch (UnknownMetadataException e) 167 { 168 return null; 169 } 170 } 171 172 /** 173 * Set the description. 174 * @param description the description. 175 * @throws AmetysRepositoryException if an error occurs. 176 */ 177 public void setDescription(String description) throws AmetysRepositoryException 178 { 179 getMetadataHolder().setMetadata(__METADATA_DESCRIPTION, description); 180 } 181 182 /** 183 * Remove the description. 184 * @throws AmetysRepositoryException if an error occurs. 185 */ 186 public void removeDescription() throws AmetysRepositoryException 187 { 188 ModifiableCompositeMetadata metadataHolder = getMetadataHolder(); 189 if (metadataHolder.hasMetadata(__METADATA_DESCRIPTION)) 190 { 191 getMetadataHolder().removeMetadata(__METADATA_DESCRIPTION); 192 } 193 } 194 195 /** 196 * Retrieves the URL. 197 * @return the URL. 198 * @throws AmetysRepositoryException if an error occurs. 199 */ 200 public String getURL() throws AmetysRepositoryException 201 { 202 try 203 { 204 return getMetadataHolder().getString(__METADATA_URL); 205 } 206 catch (UnknownMetadataException e) 207 { 208 return null; 209 } 210 } 211 212 /** 213 * Set the URL. 214 * @param url the URL. 215 * @throws AmetysRepositoryException if an error occurs. 216 */ 217 public void setURL(String url) throws AmetysRepositoryException 218 { 219 getMetadataHolder().setMetadata(__METADATA_URL, url); 220 } 221 222 /** 223 * Retrieves the explorer nodes. 224 * @return the explorer nodes or an empty {@link AmetysObjectIterable}. 225 * @throws AmetysRepositoryException if an error occurs. 226 */ 227 public ExplorerNode getExplorerRootNode() throws AmetysRepositoryException 228 { 229 return (ExplorerNode) getChild(__EXPLORER_NODE_NAME); 230 } 231 232 /** 233 * Retrieves the explorer nodes. 234 * @return the explorer nodes or an empty {@link AmetysObjectIterable}. 235 * @throws AmetysRepositoryException if an error occurs. 236 */ 237 public AmetysObjectIterable<ExplorerNode> getExplorerNodes() throws AmetysRepositoryException 238 { 239 return ((TraversableAmetysObject) getChild(__EXPLORER_NODE_NAME)).getChildren(); 240 } 241 242 243 /** 244 * Retrieves the mailing list. 245 * @return the mailing list. 246 * @throws AmetysRepositoryException if an error occurs. 247 */ 248 public String getMailingList() throws AmetysRepositoryException 249 { 250 try 251 { 252 return getMetadataHolder().getString(__METADATA_MAILING_LIST); 253 } 254 catch (UnknownMetadataException e) 255 { 256 return null; 257 } 258 } 259 260 /** 261 * Set the mailing list. 262 * @param mailingList the mailing list. 263 * @throws AmetysRepositoryException if an error occurs. 264 */ 265 public void setMailingList(String mailingList) throws AmetysRepositoryException 266 { 267 getMetadataHolder().setMetadata(__METADATA_MAILING_LIST, mailingList); 268 } 269 270 /** 271 * Remove the mailing list. 272 * @throws AmetysRepositoryException if an error occurs. 273 */ 274 public void removeMailingList() throws AmetysRepositoryException 275 { 276 ModifiableCompositeMetadata metadataHolder = getMetadataHolder(); 277 if (metadataHolder.hasMetadata(__METADATA_MAILING_LIST)) 278 { 279 getMetadataHolder().removeMetadata(__METADATA_MAILING_LIST); 280 } 281 } 282 283 /** 284 * Retrieves the date of creation. 285 * @return the date of creation. 286 * @throws AmetysRepositoryException if an error occurs. 287 */ 288 public Date getCreationDate() throws AmetysRepositoryException 289 { 290 try 291 { 292 return getMetadataHolder().getDate(__METADATA_CREATION); 293 } 294 catch (UnknownMetadataException e) 295 { 296 return null; 297 } 298 } 299 300 /** 301 * Set the date of creation. 302 * @param creationDate the date of creation 303 * @throws AmetysRepositoryException if an error occurs. 304 */ 305 public void setCreationDate(Date creationDate) throws AmetysRepositoryException 306 { 307 getMetadataHolder().setMetadata(__METADATA_CREATION, creationDate); 308 } 309 310 @Override 311 public Node getEventsRootNode() throws RepositoryException 312 { 313 return JCREventHelper.getEventsRootNode(getNode()); 314 } 315 316 @Override 317 public NodeIterator getEvents() throws RepositoryException 318 { 319 return JCREventHelper.getEvents(this); 320 } 321 322 /** 323 * Get the sites of the project 324 * @return The collection of sites 325 */ 326 public Collection<Site> getSites() 327 { 328 try 329 { 330 ModifiableCompositeMetadata metadataHolder = getMetadataHolder(); 331 332 if (!metadataHolder.hasMetadata(METADATA_SITES)) 333 { 334 return new ArrayList<>(); 335 } 336 else 337 { 338 SiteManager siteManager = _getFactory()._getSiteManager(); 339 340 // Stream over the properties to retrieves the corresponding sites. 341 JCRCompositeMetadata sitesHolder = (JCRCompositeMetadata) metadataHolder.getCompositeMetadata(METADATA_SITES); 342 Node jcrSitesNode = sitesHolder.getNode(); 343 Iterator<Property> propertyIterator = jcrSitesNode.getProperties(); 344 Iterable<Property> propertyIterable = () -> propertyIterator; 345 346 return StreamSupport.stream(propertyIterable.spliterator(), false) 347 .map(p -> 348 { 349 try 350 { 351 return p.getNode().getName(); 352 } 353 catch (Exception e) 354 { 355 // site might not exist (anymore...) 356 return null; 357 } 358 }) 359 .filter(Objects::nonNull) 360 .map(siteName -> siteManager.getSite(siteName)) 361 .collect(Collectors.toList()); 362 } 363 } 364 catch (RepositoryException e) 365 { 366 return new ArrayList<>(); 367 } 368 } 369 370 /** 371 * Set the sites of the project 372 * @param sites The names of the site 373 */ 374 public void setSites(Collection<String> sites) 375 { 376 ModifiableCompositeMetadata metadataHolder = getMetadataHolder(); 377 378 if (metadataHolder.hasMetadata(METADATA_SITES)) 379 { 380 metadataHolder.removeMetadata(METADATA_SITES); 381 } 382 383 JCRCompositeMetadata sitesHolder = (JCRCompositeMetadata) metadataHolder.getCompositeMetadata(METADATA_SITES, true); 384 Node jcrSitesNode = sitesHolder.getNode(); 385 SiteManager siteManager = _getFactory()._getSiteManager(); 386 387 // create weak references to site nodes 388 int[] propIdx = {0}; 389 sites.forEach(siteName -> 390 { 391 if (siteManager.hasSite(siteName)) 392 { 393 Site site = siteManager.getSite(siteName); 394 395 try 396 { 397 ValueFactory valueFactory = jcrSitesNode.getSession().getValueFactory(); 398 Value weakRefValue = valueFactory.createValue(site.getNode(), true); 399 400 propIdx[0]++; // increment index 401 jcrSitesNode.setProperty(Integer.toString(propIdx[0]), weakRefValue); 402 } 403 catch (RepositoryException e) 404 { 405 throw new AmetysRepositoryException("Unexpected repository exception", e); 406 } 407 } 408 }); 409 410 if (needsSave()) 411 { 412 saveChanges(); 413 } 414 } 415 416// /** 417// * Get the project path of the project 418// * The project path is composed of the project category names and the project name separated by slashes. 419// * e.g. cat1/cat2/project-name 420// * @return he project path of the project 421// */ 422// public String getProjectPath() 423// { 424// Deque<String> path = new ArrayDeque<>(); 425// path.addFirst(getName()); 426// 427// try 428// { 429// Node parentNode = getNode().getParent(); 430// 431// while (NodeTypeHelper.isNodeType(parentNode, "ametys:projectCategory")) 432// { 433// path.addFirst(parentNode.getName()); 434// parentNode = parentNode.getParent(); 435// } 436// 437// return String.join("/", path); 438// } 439// catch (RepositoryException e) 440// { 441// throw new AmetysRepositoryException("Unexpected repository exception while retrieving project path", e); 442// } 443// } 444 445 /** 446 * Get the project manager user identity 447 * @return The manager 448 */ 449 public UserIdentity getManager() 450 { 451 return getMetadataHolder().getUser(__METADATA_MANAGER, null); 452 } 453 454 /** 455 * Set the project manager 456 * @param user The manager 457 */ 458 public void setManager(UserIdentity user) 459 { 460 getMetadataHolder().setMetadata(__METADATA_MANAGER, user); 461 } 462 463 /** 464 * Retrieve the list of activated modules for the project 465 * @return The list of modules ids 466 */ 467 public String[] getModules() 468 { 469 return getMetadataHolder().getStringArray(__METADATA_MODULES, new String[]{}); 470 } 471 472 /** 473 * Set the list of activated modules for the project 474 * @param modules The list of modules 475 */ 476 public void setModules(String[] modules) 477 { 478 getMetadataHolder().setMetadata(__METADATA_MODULES, modules); 479 } 480 481 /** 482 * Add a module to the list of activated modules 483 * @param moduleId The module id 484 */ 485 public void addModule(String moduleId) 486 { 487 String[] modules = getMetadataHolder().getStringArray(__METADATA_MODULES, null); 488 if (!ArrayUtils.contains(modules, moduleId)) 489 { 490 getMetadataHolder().setMetadata(__METADATA_MODULES, modules == null ? new String[]{moduleId} : ArrayUtils.add(modules, moduleId)); 491 } 492 } 493 494 /** 495 * Remove a module from the list of activated modules 496 * @param moduleId The module id 497 */ 498 public void removeModule(String moduleId) 499 { 500 String[] modules = getMetadataHolder().getStringArray(__METADATA_MODULES, null); 501 if (ArrayUtils.contains(modules, moduleId)) 502 { 503 getMetadataHolder().setMetadata(__METADATA_MODULES, ArrayUtils.removeElement(modules, moduleId)); 504 } 505 } 506 507 /** 508 * Get the inscription status of the project 509 * @return The inscription status 510 */ 511 public InscriptionStatus getInscriptionStatus() 512 { 513 if (getMetadataHolder().hasMetadata(__METADATA_INSCRIPTION_STATUS)) 514 { 515 return InscriptionStatus.createsFromString(getMetadataHolder().getString(__METADATA_INSCRIPTION_STATUS)); 516 } 517 return InscriptionStatus.PRIVATE; 518 } 519 520 /** 521 * Set the inscription status of the project 522 * @param inscriptionStatus The inscription status 523 */ 524 public void setInscriptionStatus(String inscriptionStatus) 525 { 526 if (inscriptionStatus != null && InscriptionStatus.createsFromString(inscriptionStatus) != null) 527 { 528 getMetadataHolder().setMetadata(__METADATA_INSCRIPTION_STATUS, inscriptionStatus); 529 } 530 else if (getMetadataHolder().hasMetadata(__METADATA_INSCRIPTION_STATUS)) 531 { 532 getMetadataHolder().removeMetadata(__METADATA_INSCRIPTION_STATUS); 533 } 534 } 535 536 /** 537 * Get the default profile for new members of the project 538 * @return The default profile 539 */ 540 public String getDefaultProfile() 541 { 542 if (getMetadataHolder().hasMetadata(__METADATA_DEFAULT_PROFILE)) 543 { 544 return getMetadataHolder().getString(__METADATA_DEFAULT_PROFILE); 545 } 546 return null; 547 } 548 549 550 /** 551 * Set the default profile for the members of the project 552 * @param profileId The ID of the profile 553 */ 554 public void setDefaultProfile(String profileId) 555 { 556 if (profileId != null) 557 { 558 getMetadataHolder().setMetadata(__METADATA_DEFAULT_PROFILE, profileId); 559 } 560 else if (getMetadataHolder().hasMetadata(__METADATA_DEFAULT_PROFILE)) 561 { 562 getMetadataHolder().removeMetadata(__METADATA_DEFAULT_PROFILE); 563 } 564 } 565}