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; 017 018import java.time.ZonedDateTime; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027import java.util.Objects; 028import java.util.Set; 029import java.util.function.Function; 030import java.util.stream.Collectors; 031import java.util.stream.Stream; 032import java.util.stream.StreamSupport; 033 034import javax.jcr.Node; 035import javax.jcr.PathNotFoundException; 036import javax.jcr.Property; 037import javax.jcr.RepositoryException; 038import javax.jcr.Value; 039 040import org.apache.avalon.framework.activity.Initializable; 041import org.apache.avalon.framework.component.Component; 042import org.apache.avalon.framework.context.Context; 043import org.apache.avalon.framework.context.ContextException; 044import org.apache.avalon.framework.context.Contextualizable; 045import org.apache.avalon.framework.logger.AbstractLogEnabled; 046import org.apache.avalon.framework.service.ServiceException; 047import org.apache.avalon.framework.service.ServiceManager; 048import org.apache.avalon.framework.service.Serviceable; 049import org.apache.commons.collections.CollectionUtils; 050import org.apache.commons.lang.ArrayUtils; 051import org.apache.commons.lang3.StringUtils; 052import org.apache.commons.lang3.tuple.Pair; 053 054import org.ametys.cms.repository.ContentDAO.TagMode; 055import org.ametys.cms.tag.Tag; 056import org.ametys.core.cache.AbstractCacheManager; 057import org.ametys.core.cache.AbstractCacheManager.CacheType; 058import org.ametys.core.cache.Cache; 059import org.ametys.core.observation.Event; 060import org.ametys.core.observation.ObservationManager; 061import org.ametys.core.ui.Callable; 062import org.ametys.core.user.CurrentUserProvider; 063import org.ametys.core.user.UserIdentity; 064import org.ametys.core.util.I18nUtils; 065import org.ametys.core.util.LambdaUtils; 066import org.ametys.plugins.core.user.UserHelper; 067import org.ametys.plugins.explorer.ExplorerNode; 068import org.ametys.plugins.repository.AmetysObject; 069import org.ametys.plugins.repository.AmetysObjectIterable; 070import org.ametys.plugins.repository.AmetysObjectResolver; 071import org.ametys.plugins.repository.AmetysRepositoryException; 072import org.ametys.plugins.repository.ModifiableAmetysObject; 073import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 074import org.ametys.plugins.repository.RepositoryConstants; 075import org.ametys.plugins.repository.UnknownAmetysObjectException; 076import org.ametys.plugins.repository.jcr.JCRAmetysObject; 077import org.ametys.plugins.repository.jcr.NodeTypeHelper; 078import org.ametys.plugins.repository.query.expression.Expression; 079import org.ametys.plugins.repository.query.expression.Expression.Operator; 080import org.ametys.plugins.workspaces.ObservationConstants; 081import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint; 082import org.ametys.plugins.workspaces.members.ProjectMemberManager; 083import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 084import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 085import org.ametys.plugins.workspaces.project.objects.Project; 086import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper; 087import org.ametys.plugins.workspaces.tags.ProjectTagProviderExtensionPoint; 088import org.ametys.runtime.config.Config; 089import org.ametys.runtime.i18n.I18nizableText; 090import org.ametys.runtime.plugin.component.PluginAware; 091import org.ametys.web.repository.page.Page; 092import org.ametys.web.repository.page.PageQueryHelper; 093import org.ametys.web.repository.site.Site; 094import org.ametys.web.repository.site.SiteDAO; 095import org.ametys.web.repository.site.SiteManager; 096import org.ametys.web.repository.sitemap.Sitemap; 097import org.ametys.web.site.SiteConfigurationManager; 098import org.ametys.web.tags.TagExpression; 099 100import com.google.common.collect.ImmutableMap; 101import com.google.common.collect.Iterables; 102 103/** 104 * Helper component for managing project workspaces 105 */ 106public class ProjectManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable, PluginAware, Initializable 107{ 108 /** Avalon Role */ 109 public static final String ROLE = ProjectManager.class.getName(); 110 111 /** Workspaces plugin node name */ 112 private static final String __WORKSPACES_PLUGIN_NODE_NAME = "workspaces"; 113 114 /** Workspaces plugin node name */ 115 private static final String __WORKSPACES_PLUGIN_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 116 117 /** The name of the projects root node */ 118 private static final String __PROJECTS_ROOT_NODE_NAME = "projects"; 119 120 /** The type of the projects root node */ 121 private static final String __PROJECTS_ROOT_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 122 123 /** Constants for tags metadata */ 124 private static final String __PROJECTS_TAGS_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":tags"; 125 126 /** Constants for places metadata */ 127 private static final String __PROJECTS_PLACES_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":places"; 128 129 /** Ametys object resolver */ 130 protected AmetysObjectResolver _resolver; 131 132 /** The i18n utils. */ 133 protected I18nUtils _i18nUtils; 134 135 /** Site manager */ 136 protected SiteManager _siteManager; 137 138 /** Site DAO */ 139 protected SiteDAO _siteDao; 140 141 /** Site configuration manager */ 142 protected SiteConfigurationManager _siteConfigurationManager; 143 144 /** Module Managers EP */ 145 protected WorkspaceModuleExtensionPoint _moduleManagerEP; 146 147 /** Avalon context */ 148 protected Context _context; 149 150 private ObservationManager _observationManager; 151 152 private CurrentUserProvider _currentUserProvider; 153 154 private ProjectMemberManager _projectMembers; 155 156 private String _pluginName; 157 158 private ProjectRightHelper _projectRightHelper; 159 160 private ProjectTagProviderExtensionPoint _projectTagProviderEP; 161 162 private CategoryProviderExtensionPoint _categoryProviderEP; 163 164 private UserHelper _userHelper; 165 166 private AbstractCacheManager _cacheManager; 167 168 169 @Override 170 public void contextualize(Context context) throws ContextException 171 { 172 _context = context; 173 } 174 175 @Override 176 public void service(ServiceManager manager) throws ServiceException 177 { 178 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 179 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 180 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 181 _siteDao = (SiteDAO) manager.lookup(SiteDAO.ROLE); 182 _siteConfigurationManager = (SiteConfigurationManager) manager.lookup(SiteConfigurationManager.ROLE); 183 _projectMembers = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 184 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 185 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 186 _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 187 _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE); 188 _projectTagProviderEP = (ProjectTagProviderExtensionPoint) manager.lookup(ProjectTagProviderExtensionPoint.ROLE); 189 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 190 _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE); 191 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 192 } 193 194 public void initialize() throws Exception 195 { 196 _cacheManager.createCache(ROLE, 197 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_LABEL"), 198 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_DESCRIPTION"), 199 CacheType.MEMORY, 200 true); 201 } 202 203 @Override 204 public void setPluginInfo(String pluginName, String featureName, String id) 205 { 206 _pluginName = pluginName; 207 } 208 209 /** 210 * Retrieves all projects 211 * @return the projects 212 */ 213 public AmetysObjectIterable<Project> getProjects() 214 { 215 String jcrQuery = "//element(*, ametys:project)"; 216 return _resolver.query(jcrQuery); 217 } 218 219 /** 220 * Retrieves all projects for client side 221 * @return the projects 222 */ 223 @Callable 224 public List<Map<String, Object>> getProjectsForClientSide() 225 { 226 return getProjects() 227 .stream() 228 .map(p -> getProjectProperties(p)) 229 .collect(Collectors.toList()); 230 } 231 232 233 /** 234 * Retrieves a project by its name 235 * @param name The project name 236 * @return the project or <code>null</code> if not found 237 */ 238 public Project getProject(String name) 239 { 240 String jcrQuery = "//element(" + name + ", ametys:project)"; 241 AmetysObjectIterable<Project> sites = _resolver.query(jcrQuery); 242 Iterator<Project> it = sites.iterator(); 243 244 if (it.hasNext()) 245 { 246 return it.next(); 247 } 248 249 return null; 250 } 251 252 /** 253 * Get the user's projects 254 * @param user the user 255 * @return the user's projects 256 */ 257 public List<Project> getUserProjects(UserIdentity user) 258 { 259 List<Project> userProjects = new ArrayList<>(); 260 261 AmetysObjectIterable<Project> projects = getProjects(); 262 263 for (Project project : projects) 264 { 265 if (_projectMembers.isProjectMember(project, user)) 266 { 267 userProjects.add(project); 268 } 269 } 270 271 return userProjects; 272 } 273 274 275 /** 276 * Returns true if the given project exists. 277 * @param projectName the project name. 278 * @return true if the given project exists. 279 */ 280 public boolean hasProject(String projectName) 281 { 282 return getProject(projectName) != null; 283 } 284 285 /** 286 * Retrieves the mapping of all the projects name with their title on which the current user has access 287 * @return the map (projectName, projectTitle) for all projects 288 */ 289 @Callable 290 public List<Map<String, String>> getUserProjectsData() 291 { 292 return getUserProjects(_currentUserProvider.getUser()) 293 .stream() 294 .map(p -> ImmutableMap.of("title", p.getTitle(), "name", p.getName())) 295 .collect(Collectors.toList()); 296 } 297 298 /** 299 * Retrieves the mapping of all the projects name with their title (regarless user rights) 300 * @return the map (projectName, projectTitle) for all projects 301 */ 302 @Callable 303 public List<Map<String, String>> getProjectsData() 304 { 305 return getProjects() 306 .stream() 307 .map(p -> ImmutableMap.of("title", p.getTitle(), "name", p.getName())) 308 .collect(Collectors.toList()); 309 } 310 311 /** 312 * Retrieves the project names 313 * @return the project names 314 */ 315 @Callable 316 public Collection<String> getProjectNames() 317 { 318 return getProjects() 319 .stream() 320 .map(Project::getName) 321 .collect(Collectors.toList()); 322 } 323 324 /** 325 * Return the root for projects 326 * The root node will be created if necessary 327 * @return The root for projects 328 */ 329 public ModifiableTraversableAmetysObject getProjectsRoot() 330 { 331 try 332 { 333 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins"); 334 ModifiableTraversableAmetysObject workspacesPluginNode = _getOrCreateObject(pluginsNode, __WORKSPACES_PLUGIN_NODE_NAME, __WORKSPACES_PLUGIN_NODE_TYPE); 335 return _getOrCreateObject(workspacesPluginNode, __PROJECTS_ROOT_NODE_NAME, __PROJECTS_ROOT_NODE_TYPE); 336 } 337 catch (AmetysRepositoryException e) 338 { 339 throw new AmetysRepositoryException("Error getting the projects root node.", e); 340 } 341 } 342 343 /** 344 * Retrieves the standard information of a project 345 * @param projectId Identifier of the project 346 * @return The map of information 347 */ 348 @Callable 349 public Map<String, Object> getProjectProperties(String projectId) 350 { 351 return getProjectProperties((Project) _resolver.resolveById(projectId)); 352 } 353 354 /** 355 * Retrieves the standard information of a project 356 * @param project The project 357 * @return The map of information 358 */ 359 public Map<String, Object> getProjectProperties(Project project) 360 { 361 Map<String, Object> info = new HashMap<>(); 362 363 info.put("id", project.getId()); 364 info.put("name", project.getName()); 365 info.put("type", "project"); 366 367 info.put("title", project.getTitle()); 368 info.put("description", project.getDescription()); 369 info.put("mailingList", project.getMailingList()); 370 info.put("inscriptionStatus", project.getInscriptionStatus().toString()); 371 info.put("defaultProfile", project.getDefaultProfile()); 372 373 info.put("creationDate", project.getCreationDate()); 374 375 // check if the project workspace configuration is valid 376 Collection<Site> sites = project.getSites(); 377 boolean valid = sites.size() > 0; 378 if (valid) 379 { 380 Iterator<Site> siteIterator = sites.iterator(); 381 while (valid && siteIterator.hasNext()) 382 { 383 Site site = siteIterator.next(); 384 valid = _siteConfigurationManager.isSiteConfigurationValid(site); 385 } 386 } 387 388 Set<String> categories = project.getCategories(); 389 info.put("categories", categories.stream() 390 .map(c -> _tag2json(_categoryProviderEP.getTag(c, new HashMap<>()))) 391 .collect(Collectors.toList())); 392 393 Set<String> tags = project.getTags(); 394 info.put("tags", tags.stream() 395 .map(c -> _tag2json(_projectTagProviderEP.getTag(c, new HashMap<>()))) 396 .collect(Collectors.toList())); 397 398 info.put("valid", valid); 399 400 UserIdentity[] managers = project.getManagers(); 401 info.put("managers", Arrays.stream(managers) 402 .map(u -> _userHelper.user2json(u)) 403 .collect(Collectors.toList())); 404 405 // sites is a list of map entry with id ,name, title and url property 406 // { id: site id, name: site name, title: site title, url: site url } 407 info.put("sites", sites.stream().map(site -> 408 { 409 Map<String, String> siteProps = new HashMap<>(); 410 siteProps.put("id", site.getId()); 411 siteProps.put("name", site.getName()); 412 siteProps.put("title", site.getTitle()); 413 siteProps.put("url", site.getUrl()); 414 return siteProps; 415 }).collect(Collectors.toList())); 416 417 return info; 418 } 419 420 private Map<String, Object> _tag2json(Tag tag) 421 { 422 Map<String, Object> json = new HashMap<>(); 423 json.put("id", tag.getId()); 424 json.put("name", tag.getName()); 425 json.put("title", tag.getTitle()); 426 return json; 427 } 428 429 /** 430 * Get the availables project URLs. 431 * @param project The project 432 * @return The availables project URLs, can be empty. 433 */ 434 public Set<String> getProjectUrls(Project project) 435 { 436 return _getProjectNonEmptyElements(project, Site::getUrl); 437 } 438 439 /** 440 * Get the availables project names. 441 * @param project The project 442 * @return The availables project names, can be empty. 443 */ 444 public Set<String> getProjectNames(Project project) 445 { 446 return _getProjectNonEmptyElements(project, Site::getName); 447 } 448 449 private Set<String> _getProjectNonEmptyElements(Project project, Function<? super Site, ? extends String> function) 450 { 451 return project.getSites() // Get the sites of the project 452 .stream() // Build it as a stream 453 .map(function) // Get the element of each site 454 .filter(StringUtils::isNotEmpty) // Filter empty strings 455 .collect(Collectors.toSet()); // Get only the first value 456 } 457 458 /** 459 * Create a project 460 * @param name The project name 461 * @param title The project title 462 * @param description The project description 463 * @param emailList Project mailing list 464 * @param inscriptionStatus The inscription status of the project 465 * @param defaultProfile The default profile for new members 466 * @return A map containing the id of the new project or an error key. 467 */ 468 @Callable 469 public Map<String, Object> createProject(String name, String title, String description, String emailList, String inscriptionStatus, String defaultProfile) 470 { 471 Map<String, Object> result = new HashMap<>(); 472 List<String> errors = new ArrayList<>(); 473 474 Map<String, Object> additionalValues = new HashMap<>(); 475 additionalValues.put("description", description); 476 additionalValues.put("emailList", emailList); 477 additionalValues.put("inscriptionStatus", inscriptionStatus); 478 additionalValues.put("defaultProfile", defaultProfile); 479 480 Project project = createProject(name, title, additionalValues, null, errors); 481 482 if (CollectionUtils.isEmpty(errors)) 483 { 484 result.put("id", project.getId()); 485 } 486 else 487 { 488 result.put("error", errors.get(0)); 489 } 490 491 return result; 492 } 493 494 /** 495 * Create a project 496 * @param name The project name 497 * @param title The project title 498 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags and keywords 499 * @param modulesIds The list of modules to activate. Can be null to activate all modules 500 * @param errors A list that will be populated with the encountered errors. If null, errors will not be tracked. 501 * @return The id of the new project 502 */ 503 public Project createProject(String name, String title, Map<String, Object> additionalValues, Set<String> modulesIds, List<String> errors) 504 { 505 if (StringUtils.isEmpty(title)) 506 { 507 throw new IllegalArgumentException(String.format("Cannot create project. Title is mandatory")); 508 } 509 510 ModifiableTraversableAmetysObject projectsRoot = getProjectsRoot(); 511 512 // Project name should be unique 513 if (hasProject(name)) 514 { 515 if (getLogger().isWarnEnabled()) 516 { 517 getLogger().warn(String.format("A project with the name '%s' already exists", name)); 518 } 519 520 if (errors != null) 521 { 522 errors.add("project-exists"); 523 } 524 525 return null; 526 } 527 528 Project project = projectsRoot.createChild(name, Project.NODE_TYPE); 529 project.setTitle(title); 530 String description = (String) additionalValues.getOrDefault("description", null); 531 if (StringUtils.isNotEmpty(description)) 532 { 533 project.setDescription(description); 534 } 535 String mailingList = (String) additionalValues.getOrDefault("emailList", null); 536 if (StringUtils.isNotEmpty(mailingList)) 537 { 538 project.setMailingList(mailingList); 539 } 540 String inscriptionStatus = (String) additionalValues.getOrDefault("inscriptionStatus", null); 541 if (StringUtils.isNotEmpty(inscriptionStatus)) 542 { 543 project.setInscriptionStatus(inscriptionStatus); 544 } 545 String defaultProfile = (String) additionalValues.getOrDefault("defaultProfile", null); 546 if (StringUtils.isNotEmpty(defaultProfile)) 547 { 548 project.setDefaultProfile(defaultProfile); 549 } 550 551 @SuppressWarnings("unchecked") 552 List<String> tags = (List<String>) additionalValues.getOrDefault("tags", null); 553 if (tags != null) 554 { 555 project.setTags(tags); 556 } 557 @SuppressWarnings("unchecked") 558 List<String> categoryTags = (List<String>) additionalValues.getOrDefault("categoryTags", null); 559 if (categoryTags != null) 560 { 561 project.setCategoryTags(categoryTags); 562 } 563 564 @SuppressWarnings("unchecked") 565 List<String> keywords = (List<String>) additionalValues.getOrDefault("keywords", null); 566 if (keywords != null) 567 { 568 project.setKeywords(keywords.toArray(new String[keywords.size()])); 569 } 570 571 project.setCreationDate(ZonedDateTime.now()); 572 573 // Create the project workspace = a site + a set of pages 574 _createProjectWorkspace(project, errors); 575 576 activateModules(project, modulesIds); 577 578 if (CollectionUtils.isEmpty(errors)) 579 { 580 project.saveChanges(); 581 582 // Notify observers 583 Map<String, Object> eventParams = new HashMap<>(); 584 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 585 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_ADDED, _currentUserProvider.getUser(), eventParams)); 586 587 } 588 else 589 { 590 deleteProject(project); 591 } 592 593 return project; 594 } 595 596 /** 597 * Edit a project 598 * @param id The project identifier 599 * @param title The title to set 600 * @param description The description to set 601 * @param mailingList Project mailing list 602 * @param inscriptionStatus The inscription status of the project 603 * @param defaultProfile The default profile for new members 604 */ 605 @Callable 606 public void editProject(String id, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 607 { 608 Project project = _resolver.resolveById(id); 609 editProject(project, title, description, mailingList, inscriptionStatus, defaultProfile); 610 } 611 612 /** 613 * Edit a project 614 * @param project The project 615 * @param title The title to set 616 * @param description The description to set 617 * @param mailingList Project mailing list 618 * @param inscriptionStatus The inscription status of the project 619 * @param defaultProfile The default profile for new members 620 */ 621 public void editProject(Project project, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 622 { 623 project.setTitle(title); 624 625 if (StringUtils.isNotEmpty(description)) 626 { 627 project.setDescription(description); 628 } 629 else 630 { 631 project.removeDescription(); 632 } 633 634 if (StringUtils.isNotEmpty(mailingList)) 635 { 636 project.setMailingList(mailingList); 637 } 638 else 639 { 640 project.removeMailingList(); 641 } 642 643 project.setInscriptionStatus(inscriptionStatus); 644 project.setDefaultProfile(defaultProfile); 645 646 project.saveChanges(); 647 648 // Notify observers 649 Map<String, Object> eventParams = new HashMap<>(); 650 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 651 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_UPDATED, _currentUserProvider.getUser(), eventParams)); 652 } 653 654 /** 655 * Delete a list of project. 656 * @param ids The ids of projects to delete 657 * @return The ids of the deleted projects, unknowns projects and the deleted sites 658 */ 659 @Callable 660 public Map<String, Object> deleteProjectsByIds(List<String> ids) 661 { 662 Map<String, Object> result = new HashMap<>(); 663 List<Map<String, Object>> deleted = new ArrayList<>(); 664 List<String> unknowns = new ArrayList<>(); 665 666 for (String id : ids) 667 { 668 try 669 { 670 Project project = _resolver.resolveById(id); 671 672 Map<String, Object> projectInfo = new HashMap<>(); 673 projectInfo.put("id", id); 674 projectInfo.put("title", project.getTitle()); 675 projectInfo.put("sites", deleteProject(project)); 676 677 deleted.add(projectInfo); 678 } 679 catch (UnknownAmetysObjectException e) 680 { 681 getLogger().warn(String.format("Unable to delete the definition of id '%s', because it does not exist.", id), e); 682 unknowns.add(id); 683 } 684 } 685 686 result.put("deleted", deleted); 687 result.put("unknowns", unknowns); 688 689 return result; 690 } 691 692 /** 693 * Delete a project. 694 * @param projects The list of projects to delete 695 * @return list of deleted sites (each list entry contains a data map with 696 * the id and the name of the delete site). 697 */ 698 public List<Map<String, String>> deleteProject(List<Project> projects) 699 { 700 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 701 702 for (Project project : projects) 703 { 704 deletedSitesInfo.addAll(deleteProject(project)); 705 } 706 707 return deletedSitesInfo; 708 } 709 710 /** 711 * Delete a project and its sites 712 * @param project The project to delete 713 * @return list of deleted sites (each list entry contains a data map with 714 * the id and the name of the delete site). 715 */ 716 public List<Map<String, String>> deleteProject(Project project) 717 { 718 ModifiableAmetysObject parent = project.getParent(); 719 720 Collection<Site> sites = project.getSites(); 721 722 // list of map entry with id, name and title property 723 // { id: site id, name: site name } 724 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 725 726 sites.forEach(site -> 727 { 728 try 729 { 730 Map<String, String> siteProps = new HashMap<>(); 731 siteProps.put("id", site.getId()); 732 siteProps.put("name", site.getName()); 733 734 _siteDao.deleteSite(site.getId()); 735 deletedSitesInfo.add(siteProps); 736 } 737 catch (RepositoryException e) 738 { 739 String errorMsg = String.format("Error while trying to delete the site '%s' for the project '%s'.", site.getName(), project.getName()); 740 getLogger().error(errorMsg, e); 741 } 742 }); 743 744 String projectId = project.getId(); 745 project.remove(); 746 parent.saveChanges(); 747 748 // Notify observers 749 Map<String, Object> eventParams = new HashMap<>(); 750 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 751 eventParams.put(ObservationConstants.ARGS_PROJECT_ID, projectId); 752 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_DELETED, _currentUserProvider.getUser(), eventParams)); 753 754 return deletedSitesInfo; 755 } 756 757 /** 758 * Utility method to get or create an ametys object 759 * @param <A> A sub class of AmetysObject 760 * @param parent The parent object 761 * @param name The ametys object name 762 * @param type The ametys object type 763 * @return ametys object 764 * @throws AmetysRepositoryException if an repository error occurs 765 */ 766 private <A extends AmetysObject> A _getOrCreateObject(ModifiableTraversableAmetysObject parent, String name, String type) throws AmetysRepositoryException 767 { 768 A object; 769 770 if (parent.hasChild(name)) 771 { 772 object = parent.getChild(name); 773 } 774 else 775 { 776 object = parent.createChild(name, type); 777 parent.saveChanges(); 778 } 779 780 return object; 781 } 782 783 /** 784 * Get the project of an ametys object inside a project. 785 * It can be an explorer node, or any type of resource in a module. 786 * @param id The identifier of the ametys object 787 * @return the project or null if not found 788 */ 789 public Project getParentProject(String id) 790 { 791 return getParentProject(_resolver.<AmetysObject>resolveById(id)); 792 } 793 794 /** 795 * Get the project of an ametys object inside a project. 796 * It can be an explorer node, or any type of resource in a module. 797 * @param object The ametys object 798 * @return the project or null if not found 799 */ 800 public Project getParentProject(AmetysObject object) 801 { 802 AmetysObject ametysObject = object; 803 // Go back to the local explorer root. 804 do 805 { 806 ametysObject = ametysObject.getParent(); 807 } 808 while (ametysObject instanceof ExplorerNode); 809 810 if (!(ametysObject instanceof Project)) 811 { 812 getLogger().warn(String.format("No project found for ametys object with id '%s'", ametysObject.getId())); 813 return null; 814 } 815 816 return (Project) ametysObject; 817 } 818 819 /** 820 * Get the list of project names for a given site 821 * @param siteName The site name 822 * @return the list of project names 823 */ 824 @Callable 825 public List<String> getProjectsForSite(String siteName) 826 { 827 Cache<String, List<Pair<String, String>>> cache = _getCache(); 828 if (cache.hasKey(siteName)) 829 { 830 return cache.get(siteName).stream() 831 .map(p -> p.getRight()) 832 .collect(Collectors.toList()); 833 } 834 else 835 { 836 List<String> projectNames = new ArrayList<>(); 837 838 if (_siteManager.hasSite(siteName)) 839 { 840 Site site = _siteManager.getSite(siteName); 841 getProjectsForSite(site) 842 .stream() 843 .map(Project::getName) 844 .forEach(projectNames::add); 845 } 846 847 return projectNames; 848 } 849 } 850 851 /** 852 * Get the list of project for a given site 853 * @param site The site 854 * @return the list of project 855 */ 856 public List<Project> getProjectsForSite(Site site) 857 { 858 Cache<String, List<Pair<String, String>>> cache = _getCache(); 859 if (cache.hasKey(site.getName())) 860 { 861 cache.get(site.getName()).stream() 862 .map(p -> _resolver.resolveById(p.getLeft())) 863 .collect(Collectors.toList()); 864 } 865 866 try 867 { 868 // Stream over the weak reference properties pointing to this 869 // node to find referencing projects 870 Iterator<Property> propertyIterator = site.getNode().getWeakReferences(); 871 Iterable<Property> propertyIterable = () -> propertyIterator; 872 873 List<Project> projects = StreamSupport.stream(propertyIterable.spliterator(), false) 874 .map(p -> 875 { 876 try 877 { 878 // Parent should be a composite with name "ametys:sites" 879 Node parent = p.getParent(); 880 if (NodeTypeHelper.isNodeType(parent, "ametys:compositeMetadata") && (RepositoryConstants.NAMESPACE_PREFIX + ":" + Project.DATA_SITES).equals(parent.getName())) 881 { 882 // Parent should be the project 883 parent = parent.getParent(); 884 if (NodeTypeHelper.isNodeType(parent, "ametys:project")) 885 { 886 Project project = _resolver.resolve(parent, false); 887 return project; 888 } 889 } 890 } 891 catch (Exception e) 892 { 893 if (getLogger().isWarnEnabled()) 894 { 895 // this weak reference is not from a project 896 String propertyPath = null; 897 try 898 { 899 propertyPath = p.getPath(); 900 } 901 catch (Exception e2) 902 { 903 // ignore 904 } 905 906 String warnMsg = String.format("Site '%s' is pointed by a weak reference '%s' which is not representing a relation with project. This reference is ignored.", site.getName(), propertyPath); 907 getLogger().warn(warnMsg); 908 } 909 } 910 911 return null; 912 }) 913 .filter(Objects::nonNull) 914 .collect(Collectors.toList()); 915 916 List<Pair<String, String>> projectsPairs = projects.stream().map(p -> Pair.of(p.getId(), p.getName())).collect(Collectors.toList()); 917 cache.put(site.getName(), projectsPairs); 918 return projects; 919 } 920 catch (RepositoryException e) 921 { 922 getLogger().error(String.format("Unable to find projects for site '%s'", site.getName()), e); 923 } 924 925 return new ArrayList<>(); 926 } 927 928 /** 929 * Create the project workspace for a given project. 930 * @param project The project for which the workspace must be created 931 * @param errors A list of possible errors to populate. Can be null if the caller is not interested in error tracking. 932 * @return The site created for this workspace 933 */ 934 protected Site _createProjectWorkspace(Project project, List<String> errors) 935 { 936 String initialSiteName = project.getName(); 937 Site site = null; 938 939 Map<String, Object> result = _siteDao.createSite(null, initialSiteName, ProjectWorkspaceSiteType.TYPE_ID, true); 940 941 String siteId = (String) result.get("id"); 942 String siteName = (String) result.get("name"); 943 if (StringUtils.isNotEmpty(siteId)) 944 { 945 // Creation success 946 site = _siteManager.getSite(siteName); 947 948 I18nizableText i18nSiteTitle = new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_DEFAULT_PROJECT_WORKSPACE_TITLE", Arrays.asList(project.getTitle())); 949 site.setTitle(_i18nUtils.translate(i18nSiteTitle)); 950 951 // Add site to project 952 project.setSites(Arrays.asList(site.getName())); 953 954 site.saveChanges(); 955 } 956 957 return site; 958 } 959 960 /** 961 * Get the project's tags 962 * @return The project's tags 963 */ 964 @Callable 965 public List<String> getTags() 966 { 967 AmetysObject projectsRootNode = getProjectsRoot(); 968 if (projectsRootNode instanceof JCRAmetysObject) 969 { 970 Node node = ((JCRAmetysObject) projectsRootNode).getNode(); 971 972 try 973 { 974 return Arrays.stream(node.getProperty(__PROJECTS_TAGS_PROPERTY).getValues()) 975 .map(LambdaUtils.wrap(Value::getString)) 976 .collect(Collectors.toList()); 977 } 978 catch (PathNotFoundException e) 979 { 980 // property is not set, empty list will be returned. 981 } 982 catch (RepositoryException e) 983 { 984 throw new AmetysRepositoryException(e); 985 } 986 } 987 988 return new ArrayList<>(); 989 } 990 991 /** 992 * Set the tags 993 * @param tags The tags to set 994 */ 995 @Callable 996 public synchronized void setTags(List<String> tags) 997 { 998 AmetysObject projectsRootNode = getProjectsRoot(); 999 if (projectsRootNode instanceof JCRAmetysObject) 1000 { 1001 JCRAmetysObject jcrProjectsRootNode = (JCRAmetysObject) projectsRootNode; 1002 1003 if (CollectionUtils.isNotEmpty(tags)) 1004 { 1005 String[] tagsArray = tags.stream() 1006 .map(String::trim) 1007 .map(String::toLowerCase) 1008 .filter(StringUtils::isNotEmpty) 1009 .distinct() 1010 .toArray(String[]::new); 1011 1012 try 1013 { 1014 jcrProjectsRootNode.getNode().setProperty(__PROJECTS_TAGS_PROPERTY, tagsArray); 1015 jcrProjectsRootNode.saveChanges(); 1016 } 1017 catch (RepositoryException e) 1018 { 1019 throw new AmetysRepositoryException(e); 1020 } 1021 } 1022 else 1023 { 1024 Node node = jcrProjectsRootNode.getNode(); 1025 try 1026 { 1027 if (node.hasProperty(__PROJECTS_TAGS_PROPERTY)) 1028 { 1029 node.getProperty(__PROJECTS_TAGS_PROPERTY).remove(); 1030 jcrProjectsRootNode.saveChanges(); 1031 } 1032 } 1033 catch (RepositoryException e) 1034 { 1035 throw new AmetysRepositoryException(e); 1036 } 1037 } 1038 } 1039 } 1040 1041 /** 1042 * Add project's tags 1043 * @param newTags The new tags to add 1044 */ 1045 public synchronized void addTags(Collection<String> newTags) 1046 { 1047 if (CollectionUtils.isNotEmpty(newTags)) 1048 { 1049 AmetysObject projectsRootNode = getProjectsRoot(); 1050 if (projectsRootNode instanceof JCRAmetysObject) 1051 { 1052 // Concat existing tags with new lowercased tags 1053 String[] tags = Stream.concat(getTags().stream(), newTags.stream().map(String::trim).map(String::toLowerCase).filter(StringUtils::isNotEmpty)) 1054 .distinct() 1055 .toArray(String[]::new); 1056 1057 try 1058 { 1059 ((JCRAmetysObject) projectsRootNode).getNode().setProperty(__PROJECTS_TAGS_PROPERTY, tags); 1060 } 1061 catch (RepositoryException e) 1062 { 1063 throw new AmetysRepositoryException(e); 1064 } 1065 } 1066 } 1067 } 1068 1069 /** 1070 * Get the project's places 1071 * @return The project's places 1072 */ 1073 @Callable 1074 public List<String> getPlaces() 1075 { 1076 AmetysObject projectsRootNode = getProjectsRoot(); 1077 if (projectsRootNode instanceof JCRAmetysObject) 1078 { 1079 Node node = ((JCRAmetysObject) projectsRootNode).getNode(); 1080 1081 try 1082 { 1083 return Arrays.stream(node.getProperty(__PROJECTS_PLACES_PROPERTY).getValues()) 1084 .map(LambdaUtils.wrap(Value::getString)) 1085 .collect(Collectors.toList()); 1086 } 1087 catch (PathNotFoundException e) 1088 { 1089 // property is not set, empty list will be returned. 1090 } 1091 catch (RepositoryException e) 1092 { 1093 throw new AmetysRepositoryException(e); 1094 } 1095 } 1096 1097 return new ArrayList<>(); 1098 } 1099 1100 /** 1101 * Add project's places 1102 * @param newPlaces The new places to add 1103 */ 1104 public synchronized void addPlaces(Collection<String> newPlaces) 1105 { 1106 if (CollectionUtils.isNotEmpty(newPlaces)) 1107 { 1108 AmetysObject projectsRootNode = getProjectsRoot(); 1109 if (projectsRootNode instanceof JCRAmetysObject) 1110 { 1111 Set<String> lowercasedPlaces = new HashSet<>(); 1112 1113 // Concat existing places with new places 1114 String[] places = Stream.concat(getPlaces().stream(), newPlaces.stream().map(String::trim).filter(StringUtils::isNotEmpty)) 1115 // duplicates are filtered out 1116 .filter(p -> lowercasedPlaces.add(p.toLowerCase())) 1117 .toArray(String[]::new); 1118 1119 try 1120 { 1121 ((JCRAmetysObject) projectsRootNode).getNode().setProperty(__PROJECTS_PLACES_PROPERTY, places); 1122 } 1123 catch (RepositoryException e) 1124 { 1125 throw new AmetysRepositoryException(e); 1126 } 1127 } 1128 } 1129 } 1130 1131 /** 1132 * Set the places 1133 * @param places The places to set 1134 */ 1135 @Callable 1136 public synchronized void setPlaces(List<String> places) 1137 { 1138 AmetysObject projectsRootNode = getProjectsRoot(); 1139 if (projectsRootNode instanceof JCRAmetysObject) 1140 { 1141 JCRAmetysObject jcrProjectsRootNode = (JCRAmetysObject) projectsRootNode; 1142 1143 if (CollectionUtils.isNotEmpty(places)) 1144 { 1145 Set<String> lowercasedPlaces = new HashSet<>(); 1146 1147 String[] placesArray = places.stream() 1148 .map(String::trim) 1149 .filter(StringUtils::isNotEmpty) 1150 // duplicates are filtered out 1151 .filter(p -> lowercasedPlaces.add(p.toLowerCase())) 1152 .toArray(String[]::new); 1153 1154 try 1155 { 1156 jcrProjectsRootNode.getNode().setProperty(__PROJECTS_PLACES_PROPERTY, placesArray); 1157 jcrProjectsRootNode.saveChanges(); 1158 } 1159 catch (RepositoryException e) 1160 { 1161 throw new AmetysRepositoryException(e); 1162 } 1163 } 1164 else 1165 { 1166 Node node = jcrProjectsRootNode.getNode(); 1167 try 1168 { 1169 if (node.hasProperty(__PROJECTS_PLACES_PROPERTY)) 1170 { 1171 node.getProperty(__PROJECTS_PLACES_PROPERTY).remove(); 1172 jcrProjectsRootNode.saveChanges(); 1173 } 1174 } 1175 catch (RepositoryException e) 1176 { 1177 throw new AmetysRepositoryException(e); 1178 } 1179 } 1180 } 1181 } 1182 1183 /** 1184 * Get the list of activated modules for a project 1185 * @param project The project 1186 * @return The list of activated modules 1187 */ 1188 public List<WorkspaceModule> getModules(Project project) 1189 { 1190 return _moduleManagerEP.getModules().stream() 1191 .filter(module -> isModuleActivated(project, module.getId())) 1192 .collect(Collectors.toList()); 1193 } 1194 1195 /** 1196 * Retrieves the page of the module for all available languages 1197 * @param project The project 1198 * @param moduleId The project module id 1199 * @param language the sitemap language or <code>null</code> for all sitemap languages. 1200 * @return the page or null if not found 1201 */ 1202 public AmetysObjectIterable<Page> getModulePages(Project project, String moduleId, String language) 1203 { 1204 if (_moduleManagerEP.hasExtension(moduleId)) 1205 { 1206 WorkspaceModule moduleManager = _moduleManagerEP.getExtension(moduleId); 1207 return moduleManager.getModulePages(project, language); 1208 } 1209 return null; 1210 } 1211 1212 /** 1213 * Get a page in the site of a given project with a specific tag 1214 * @param project The project 1215 * @param tagName The name of the tag 1216 * @param language the sitemap language or <code>null</code> for all sitemap languages. 1217 * @return The module's pages 1218 */ 1219 public AmetysObjectIterable<Page> getProjectPages(Project project, String tagName, String language) 1220 { 1221 String siteName = Iterables.getFirst(getProjectNames(project), null); 1222 if (StringUtils.isEmpty(siteName)) 1223 { 1224 return null; 1225 } 1226 1227 Expression expression = new TagExpression(Operator.EQ, tagName); 1228 String query = PageQueryHelper.getPageXPathQuery(siteName, language, null, expression, null); 1229 1230 return _resolver.query(query); 1231 } 1232 1233 1234 /** 1235 * Get the dashboard page in the site of a given project 1236 * @param project The project 1237 * @param language the sitemap language or <code>null</code> for all sitemap languages. 1238 * @return The module's dashboard pages 1239 */ 1240 public AmetysObjectIterable<Page> getProjectDashboardPage(Project project, String language) 1241 { 1242 String siteName = Iterables.getFirst(getProjectNames(project), null); 1243 if (StringUtils.isEmpty(siteName)) 1244 { 1245 return null; 1246 } 1247 1248 String query = "//element(" + siteName + ", ametys:site)/ametys-internal:sitemaps/" 1249 + (language == null ? "*" : language) 1250 + "//element(index, ametys:page)"; 1251 1252 return _resolver.query(query); 1253 } 1254 1255 /** 1256 * Activate the list of module of the project 1257 * @param project The project 1258 * @param moduleIds The list of modules. Can be null to activate all modules 1259 */ 1260 public void activateModules(Project project, Set<String> moduleIds) 1261 { 1262 Set<String> modules = moduleIds == null ? _moduleManagerEP.getExtensionsIds() : moduleIds; 1263 1264 for (String moduleId : modules) 1265 { 1266 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1267 if (module != null && !isModuleActivated(project, moduleId)) 1268 { 1269 module.activateModule(project); 1270 project.addModule(moduleId); 1271 } 1272 } 1273 1274 project.saveChanges(); 1275 } 1276 1277 /** 1278 * Initialize the sitemap with the active module of the project 1279 * @param project The project 1280 * @param sitemap The sitemap 1281 */ 1282 public void initializeModulesSitemap(Project project, Sitemap sitemap) 1283 { 1284 Set<String> modules = _moduleManagerEP.getExtensionsIds(); 1285 1286 for (String moduleId : modules) 1287 { 1288 if (_moduleManagerEP.hasExtension(moduleId)) 1289 { 1290 WorkspaceModule module = _moduleManagerEP.getExtension(moduleId); 1291 1292 if (isModuleActivated(project, moduleId)) 1293 { 1294 module.initializeSitemap(sitemap); 1295 } 1296 } 1297 } 1298 } 1299 1300 /** 1301 * Determines if a module is activated 1302 * @param project The project 1303 * @param moduleId The id of module 1304 * @return true if the module the currently activated 1305 */ 1306 public boolean isModuleActivated(Project project, String moduleId) 1307 { 1308 return ArrayUtils.contains(project.getModules(), moduleId); 1309 } 1310 1311 /** 1312 * Remove the explorer root node of the project module, remove all events 1313 * related to that module and set it to deactivated 1314 * @param project The project 1315 * @param moduleIds The id of module to activate 1316 */ 1317 public void deactivateModules(Project project, Set<String> moduleIds) 1318 { 1319 for (String moduleId : moduleIds) 1320 { 1321 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1322 if (module != null && isModuleActivated(project, moduleId)) 1323 { 1324 module.deactivateModule(project); 1325 project.removeModule(moduleId); 1326 } 1327 } 1328 1329 project.saveChanges(); 1330 } 1331 1332 1333 /** 1334 * Get the list of profiles configured for the workspaces' projects 1335 * @return The list of profiles as JSON 1336 */ 1337 @Callable 1338 public Map<String, Object> getProjectProfiles() 1339 { 1340 Map<String, Object> result = new HashMap<>(); 1341 List<Map<String, Object>> profiles = _projectRightHelper.getProfileList().stream().map(p -> p.toJSON()).collect(Collectors.toList()); 1342 result.put("profiles", profiles); 1343 return result; 1344 } 1345 1346 /** 1347 * Get the tags from the projects 1348 * @param projectIds The ids of the projects 1349 * @return the tags of the projects 1350 */ 1351 @Callable 1352 public Set<String> getTags(List<String> projectIds) 1353 { 1354 Set<String> tags = new HashSet<>(); 1355 1356 for (String projectId : projectIds) 1357 { 1358 Project project = _resolver.resolveById(projectId); 1359 tags.addAll(project.getTags()); 1360 } 1361 1362 return tags; 1363 } 1364 1365 /** 1366 * Tag the projects 1367 * @param projectIds the project ids 1368 * @param tagNames the tag names 1369 * @param contextualParameters the contextuals parameters 1370 * @return results 1371 */ 1372 @Callable 1373 public Map<String, Object> tag(List<String> projectIds, List<String> tagNames, Map<String, Object> contextualParameters) 1374 { 1375 return tag(projectIds, tagNames, TagMode.REPLACE.toString(), contextualParameters); 1376 } 1377 1378 /** 1379 * Tag the projects 1380 * @param projectIds the project ids 1381 * @param tagNames the tag names 1382 * @param mode the mode The mode for updating tags: 'REPLACE' to replace tags, 'INSERT' to add tags or 'REMOVE' to remove tags. 1383 * @param contextualParameters the contextual parameters 1384 * @return results 1385 */ 1386 @Callable 1387 public Map<String, Object> tag(List<String> projectIds, List<String> tagNames, String mode, Map<String, Object> contextualParameters) 1388 { 1389 Map<String, Object> result = new HashMap<>(); 1390 1391 result.put("invalid-tags", new ArrayList<String>()); 1392 result.put("allright-projects", new ArrayList<Map<String, Object>>()); 1393 1394 for (String projectId : projectIds) 1395 { 1396 Project project = _resolver.resolveById(projectId); 1397 1398 Map<String, Object> project2json = new HashMap<>(); 1399 project2json.put("id", project.getId()); 1400 project2json.put("title", project.getTitle()); 1401 1402 TagMode tagMode = TagMode.valueOf(mode); 1403 1404 Set<String> oldTags = project.getTags(); 1405 if (TagMode.REPLACE.equals(tagMode)) 1406 { 1407 // First delete old tags 1408 for (String tagName : oldTags) 1409 { 1410 project.untag(tagName); 1411 } 1412 } 1413 1414 // Then set new tags 1415 for (String tagName : tagNames) 1416 { 1417 if (isTagValid(tagName)) 1418 { 1419 if (TagMode.REMOVE.equals(tagMode)) 1420 { 1421 project.untag(tagName); 1422 } 1423 else if (TagMode.REPLACE.equals(tagMode) || !oldTags.contains(tagName)) 1424 { 1425 project.tag(tagName); 1426 } 1427 } 1428 else 1429 { 1430 @SuppressWarnings("unchecked") 1431 List<String> invalidTags = (List<String>) result.get("invalid-tags"); 1432 invalidTags.add(tagName); 1433 } 1434 } 1435 1436 project.saveChanges(); 1437 1438 project2json.put("tags", project.getTags()); 1439 @SuppressWarnings("unchecked") 1440 List<Map<String, Object>> allRightProjects = (List<Map<String, Object>>) result.get("allright-projects"); 1441 allRightProjects.add(project2json); 1442 1443 if (!oldTags.equals(project.getTags())) 1444 { 1445 // Notify observers that the project has been tagged 1446 Map<String, Object> eventParams = new HashMap<>(); 1447 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1448 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_UPDATED, _currentUserProvider.getUser(), eventParams)); 1449 } 1450 } 1451 1452 return result; 1453 } 1454 1455 /** 1456 * Test if a tag is valid 1457 * @param tagName The tag name 1458 * @return True if the tag is valid 1459 */ 1460 public boolean isTagValid (String tagName) 1461 { 1462 Map<String, Object> params = new HashMap<>(); 1463 Tag tag = _projectTagProviderEP.getTag(tagName, params); 1464 1465 return tag != null; 1466 } 1467 1468 /** 1469 * Get the site name holding the catalog of projects 1470 * @return the catalog's site name 1471 */ 1472 public String getCatalogSiteName() 1473 { 1474 return Config.getInstance().getValue("workspaces.catalog.site.name"); 1475 } 1476 1477 /** 1478 * Get the site name holding the users directory 1479 * @return the users directory's site name 1480 */ 1481 public String getUsersDirectorySiteName() 1482 { 1483 return Config.getInstance().getValue("workspaces.member.userdirectory.site.name"); 1484 } 1485 1486 private Cache<String, List<Pair<String, String>>> _getCache() 1487 { 1488 return this._cacheManager.get(ROLE); 1489 } 1490}