001/* 002 * Copyright 2020 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.Collections; 023import java.util.HashMap; 024import java.util.HashSet; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Objects; 030import java.util.Optional; 031import java.util.Set; 032import java.util.function.Predicate; 033import java.util.regex.Pattern; 034import java.util.stream.Collectors; 035import java.util.stream.Stream; 036import java.util.stream.StreamSupport; 037 038import javax.jcr.Node; 039import javax.jcr.PathNotFoundException; 040import javax.jcr.Property; 041import javax.jcr.RepositoryException; 042import javax.jcr.Session; 043import javax.jcr.Value; 044 045import org.apache.avalon.framework.activity.Initializable; 046import org.apache.avalon.framework.component.Component; 047import org.apache.avalon.framework.context.Context; 048import org.apache.avalon.framework.context.ContextException; 049import org.apache.avalon.framework.context.Contextualizable; 050import org.apache.avalon.framework.service.ServiceException; 051import org.apache.avalon.framework.service.ServiceManager; 052import org.apache.avalon.framework.service.Serviceable; 053import org.apache.cocoon.components.ContextHelper; 054import org.apache.cocoon.environment.Request; 055import org.apache.commons.collections.CollectionUtils; 056import org.apache.commons.lang3.ArrayUtils; 057import org.apache.commons.lang3.StringUtils; 058import org.apache.commons.lang3.tuple.Pair; 059 060import org.ametys.cms.tag.Tag; 061import org.ametys.core.cache.AbstractCacheManager; 062import org.ametys.core.cache.AbstractCacheManager.CacheType; 063import org.ametys.core.cache.Cache; 064import org.ametys.core.cache.CacheException; 065import org.ametys.core.group.GroupDirectoryContextHelper; 066import org.ametys.core.group.GroupIdentity; 067import org.ametys.core.observation.Event; 068import org.ametys.core.observation.ObservationManager; 069import org.ametys.core.observation.Observer; 070import org.ametys.core.right.RightManager; 071import org.ametys.core.right.RightManager.RightResult; 072import org.ametys.core.ui.Callable; 073import org.ametys.core.user.CurrentUserProvider; 074import org.ametys.core.user.UserIdentity; 075import org.ametys.core.user.population.PopulationContextHelper; 076import org.ametys.core.util.I18nUtils; 077import org.ametys.core.util.LambdaUtils; 078import org.ametys.plugins.core.impl.cache.AbstractCacheKey; 079import org.ametys.plugins.core.search.UserAndGroupSearchManager; 080import org.ametys.plugins.core.user.UserHelper; 081import org.ametys.plugins.explorer.ExplorerNode; 082import org.ametys.plugins.explorer.resources.ModifiableResourceCollection; 083import org.ametys.plugins.repository.AmetysObject; 084import org.ametys.plugins.repository.AmetysObjectIterable; 085import org.ametys.plugins.repository.AmetysObjectResolver; 086import org.ametys.plugins.repository.AmetysRepositoryException; 087import org.ametys.plugins.repository.CollectionIterable; 088import org.ametys.plugins.repository.ModifiableAmetysObject; 089import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 090import org.ametys.plugins.repository.RepositoryConstants; 091import org.ametys.plugins.repository.UnknownAmetysObjectException; 092import org.ametys.plugins.repository.jcr.JCRAmetysObject; 093import org.ametys.plugins.repository.jcr.NodeTypeHelper; 094import org.ametys.plugins.repository.provider.AbstractRepository; 095import org.ametys.plugins.repository.provider.JackrabbitRepository; 096import org.ametys.plugins.repository.provider.WorkspaceSelector; 097import org.ametys.plugins.repository.query.expression.Expression; 098import org.ametys.plugins.repository.query.expression.Expression.Operator; 099import org.ametys.plugins.repository.query.expression.StringExpression; 100import org.ametys.plugins.workspaces.ObservationConstants; 101import org.ametys.plugins.workspaces.catalog.CatalogSiteType; 102import org.ametys.plugins.workspaces.categories.Category; 103import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint; 104import org.ametys.plugins.workspaces.keywords.KeywordProviderExtensionPoint; 105import org.ametys.plugins.workspaces.members.JCRProjectMember; 106import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType; 107import org.ametys.plugins.workspaces.members.ProjectMemberManager; 108import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember; 109import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 110import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 111import org.ametys.plugins.workspaces.project.objects.Project; 112import org.ametys.plugins.workspaces.project.objects.Project.InscriptionStatus; 113import org.ametys.plugins.workspaces.project.rights.ProjectRightHelper; 114import org.ametys.plugins.workspaces.util.StatisticColumn; 115import org.ametys.plugins.workspaces.util.StatisticsColumnType; 116import org.ametys.runtime.authentication.AccessDeniedException; 117import org.ametys.runtime.config.Config; 118import org.ametys.runtime.i18n.I18nizableText; 119import org.ametys.runtime.plugin.component.AbstractLogEnabled; 120import org.ametys.runtime.plugin.component.PluginAware; 121import org.ametys.web.repository.page.ModifiablePage; 122import org.ametys.web.repository.page.Page; 123import org.ametys.web.repository.page.PageQueryHelper; 124import org.ametys.web.repository.page.SitemapElement; 125import org.ametys.web.repository.page.ZoneItem; 126import org.ametys.web.repository.site.Site; 127import org.ametys.web.repository.site.SiteDAO; 128import org.ametys.web.repository.site.SiteException; 129import org.ametys.web.repository.site.SiteException.Type; 130import org.ametys.web.repository.site.SiteManager; 131import org.ametys.web.repository.sitemap.Sitemap; 132import org.ametys.web.site.SiteConfigurationManager; 133 134/** 135 * Helper component for managing project workspaces 136 */ 137public class ProjectManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable, PluginAware, Initializable, Observer 138{ 139 /** Avalon Role */ 140 public static final String ROLE = ProjectManager.class.getName(); 141 142 /** Constant for the {@link Cache} id (the {@link Cache} is in {@link CacheType#REQUEST REQUEST} attribute) for the {@link Project}s objects 143 * in cache by {@link RequestProjectCacheKey} (composition of project name and workspace name). */ 144 public static final String REQUEST_PROJECTBYID_CACHE = ProjectManager.class.getName() + "$ProjectById"; 145 146 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by project name (for whole application). */ 147 public static final String MEMORY_PROJECTIDBYNAMECACHE = ProjectManager.class.getName() + "$UUID"; 148 149 /** Constant for the {@link Cache} id (the {@link Cache} is in {@link CacheType#REQUEST REQUEST} attribute) for the {@link Page}s objects 150 * in cache by {@link RequestModuleCacheKey} (composition of project name and module name). */ 151 public static final String REQUEST_PAGESBYPROJECTANDMODULE_CACHE = ProjectManager.class.getName() + "$PagesByModule"; 152 153 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by project name (for whole application). */ 154 public static final String MEMORY_PAGESBYIDCACHE = ProjectManager.class.getName() + "$PageUUID"; 155 156 /** Constant for the {@link Cache} id for the {@link Project} ids (as {@link String}s) in cache by site name (for whole application). */ 157 public static final String MEMORY_SITEASSOCIATION_CACHE = ProjectManager.class.getName() + "$SiteAssociation"; 158 159 /** Workspaces plugin node name */ 160 private static final String __WORKSPACES_PLUGIN_NODE_NAME = "workspaces"; 161 162 /** Workspaces plugin node name */ 163 private static final String __WORKSPACES_PLUGIN_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 164 165 /** The name of the projects root node */ 166 private static final String __PROJECTS_ROOT_NODE_NAME = "projects"; 167 168 /** The type of the projects root node */ 169 private static final String __PROJECTS_ROOT_NODE_TYPE = RepositoryConstants.NAMESPACE_PREFIX + ":unstructured"; 170 171 /** Constants for tags metadata */ 172 private static final String __PROJECTS_TAGS_PROPERTY = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":tags"; 173 174 private static final String __PAGE_MODULES_VALUE = "workspaces-modules"; 175 176 private static final String __IS_CACHE_FILLED = "###iscachefilled###"; 177 178 /** Ametys object resolver */ 179 protected AmetysObjectResolver _resolver; 180 181 /** The i18n utils. */ 182 protected I18nUtils _i18nUtils; 183 184 /** Site manager */ 185 protected SiteManager _siteManager; 186 187 /** Site DAO */ 188 protected SiteDAO _siteDao; 189 190 /** Site configuration manager */ 191 protected SiteConfigurationManager _siteConfigurationManager; 192 193 /** Module Managers EP */ 194 protected WorkspaceModuleExtensionPoint _moduleManagerEP; 195 196 /** Helper for user population */ 197 protected PopulationContextHelper _populationContextHelper; 198 199 /** Helper for group directory's context */ 200 protected GroupDirectoryContextHelper _groupDirectoryContextHelper; 201 202 /** The project members' manager */ 203 protected ProjectMemberManager _projectMemberManager; 204 205 /** Avalon context */ 206 protected Context _context; 207 208 private ObservationManager _observationManager; 209 210 private CurrentUserProvider _currentUserProvider; 211 212 private ProjectMemberManager _projectMembers; 213 214 private String _pluginName; 215 216 private ProjectRightHelper _projectRightHelper; 217 218 private CategoryProviderExtensionPoint _categoryProviderEP; 219 220 private KeywordProviderExtensionPoint _keywordProviderEP; 221 222 private UserHelper _userHelper; 223 224 private AbstractCacheManager _cacheManager; 225 226 private UserAndGroupSearchManager _userAndGroupSearchManager; 227 228 private JackrabbitRepository _repository; 229 230 private WorkspaceSelector _workspaceSelector; 231 232 private RightManager _rightManager; 233 234 private GroupDirectoryContextHelper _directoryContextHelper; 235 236 @Override 237 public void contextualize(Context context) throws ContextException 238 { 239 _context = context; 240 } 241 242 @Override 243 public void service(ServiceManager manager) throws ServiceException 244 { 245 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 246 _repository = (JackrabbitRepository) manager.lookup(AbstractRepository.ROLE); 247 _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE); 248 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 249 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 250 _siteDao = (SiteDAO) manager.lookup(SiteDAO.ROLE); 251 _siteConfigurationManager = (SiteConfigurationManager) manager.lookup(SiteConfigurationManager.ROLE); 252 _projectMembers = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 253 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 254 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 255 _moduleManagerEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 256 _projectRightHelper = (ProjectRightHelper) manager.lookup(ProjectRightHelper.ROLE); 257 _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE); 258 _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE); 259 _keywordProviderEP = (KeywordProviderExtensionPoint) manager.lookup(KeywordProviderExtensionPoint.ROLE); 260 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 261 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 262 _groupDirectoryContextHelper = (GroupDirectoryContextHelper) manager.lookup(GroupDirectoryContextHelper.ROLE); 263 _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 264 _userAndGroupSearchManager = (UserAndGroupSearchManager) manager.lookup(UserAndGroupSearchManager.ROLE); 265 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 266 _directoryContextHelper = (GroupDirectoryContextHelper) manager.lookup(GroupDirectoryContextHelper.ROLE); 267 } 268 269 public void initialize() throws Exception 270 { 271 _createCaches(); 272 _observationManager.registerObserver(this); 273 } 274 275 @Override 276 public void setPluginInfo(String pluginName, String featureName, String id) 277 { 278 _pluginName = pluginName; 279 } 280 281 /** 282 * Enumeration for the profile to assign for new module 283 */ 284 public enum ProfileForNewModule 285 { 286 /** No profile assigned for new modules */ 287 NONE, 288 289 /** Default profile assigned for new modules */ 290 DEFAULT_MEMBER_PROFILE 291 292 } 293 294 /** 295 * Retrieves all projects 296 * @return the projects 297 */ 298 public AmetysObjectIterable<Project> getProjects() 299 { 300 return getProjects(true); 301 } 302 303 /** 304 * Retrieves all projects 305 * @param onlyWorking true to retrieve only working projects with non null sites 306 * @return the projects 307 */ 308 public AmetysObjectIterable<Project> getProjects(boolean onlyWorking) 309 { 310 // As cache is computed from default JCR workspace, we need to filter on sites that exist into the current JCR workspace 311 Set<Project> projects = _getUUIDCache().values().stream() 312 .filter(_resolver::hasAmetysObjectForId) 313 .map(_resolver::<Project>resolveById) 314 // If needed, check if the site is not null, as it can happen if site was deleted from admin side 315 .filter(project -> !onlyWorking || Objects.nonNull(project.getSite())) 316 .collect(Collectors.toSet()); 317 318 return new CollectionIterable<>(projects); 319 } 320 321 /** 322 * Retrieves projects filtered by categories 323 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 324 * @return the projects 325 */ 326 public List<Project> getProjects(Set<String> filteredCategories) 327 { 328 return getProjects(filteredCategories, null, null, true); 329 } 330 331 /** 332 * Retrieves projects filtered by categories and/or keywords 333 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 334 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 335 * @param anyMatch true to get projects matching categories OR keywords OR pattern 336 * @return the projects 337 */ 338 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, boolean anyMatch) 339 { 340 return getProjects(filteredCategories, filteredKeywords, null, anyMatch); 341 } 342 343 /** 344 * Retrieves projects filtered by categories and/or keywords and/or pattern 345 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 346 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 347 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 348 * @param anyMatch true to get projects matching categories OR keywords OR pattern 349 * @return the projects 350 */ 351 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch) 352 { 353 return getProjects(filteredCategories, filteredKeywords, null, anyMatch, false); 354 } 355 356 /** 357 * Retrieves projects filtered by categories and/or keywords and/or pattern 358 * @param filteredCategories the filtered categories. Can be null or empty to no filter by categories. 359 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 360 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 361 * @param anyMatch true to get projects matching categories OR keywords OR pattern 362 * @param excludePrivate true to exclude private projects 363 * @return the projects 364 */ 365 public List<Project> getProjects(Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch, boolean excludePrivate) 366 { 367 List<Predicate<Project>> filters = new ArrayList<>(); 368 if (filteredCategories != null && !filteredCategories.isEmpty()) 369 { 370 filters.add(p -> !Collections.disjoint(p.getCategories(), filteredCategories)); 371 } 372 373 if (filteredKeywords != null && !filteredKeywords.isEmpty()) 374 { 375 filters.add(p -> !Collections.disjoint(Arrays.asList(p.getKeywords()), filteredKeywords)); 376 } 377 378 Pattern patternFilter = StringUtils.isNotEmpty(pattern) ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) : null; 379 if (patternFilter != null) 380 { 381 filters.add(p -> p.getTitle() != null && patternFilter.matcher(p.getTitle()).find() || p.getDescription() != null && patternFilter.matcher(p.getDescription()).find()); 382 } 383 384 Predicate<Project> fullMatch = filters.stream().reduce(anyMatch ? Predicate::or : Predicate::and).orElse(p -> Boolean.TRUE); 385 Predicate<Project> matchStatus = p -> !excludePrivate || p.getInscriptionStatus() != InscriptionStatus.PRIVATE; 386 387 return getProjects() 388 .stream() 389 .filter(matchStatus) 390 .filter(fullMatch) 391 .collect(Collectors.toList()); 392 } 393 394 /** 395 * Get the projects categories 396 * @return the projects categories 397 */ 398 public Set<Category> getProjectsCategories() 399 { 400 return getProjects().stream() 401 .map(Project::getCategories) 402 .flatMap(Collection::stream) 403 .map(id -> _categoryProviderEP.getTag(id, null)) 404 .filter(Objects::nonNull) 405 .collect(Collectors.toSet()); 406 } 407 408 /** 409 * Get the user's projects categories 410 * @param user the user 411 * @return the user's projects categories 412 */ 413 public Set<Category> getUserProjectsCategories(UserIdentity user) 414 { 415 return getUserProjects(user).keySet() 416 .stream() 417 .map(Project::getCategories) 418 .flatMap(Collection::stream) 419 .map(id -> _categoryProviderEP.getTag(id, null)) 420 .filter(Objects::nonNull) 421 .collect(Collectors.toSet()); 422 } 423 424 /** 425 * Get the projects modules 426 * @return the projects modules 427 */ 428 public Set<WorkspaceModule> getProjectsModules() 429 { 430 return getProjects().stream() 431 .map(Project::getModules) 432 .flatMap(Arrays::stream) 433 .map(id -> _moduleManagerEP.<WorkspaceModule>getModule(id)) 434 .filter(Objects::nonNull) 435 .collect(Collectors.toSet()); 436 } 437 438 /** 439 * Get the projects modules for new members (depending on the project configuration) 440 * @param project the project 441 * @return the project modules 442 */ 443 public Set<WorkspaceModule> getProjectModulesForNewMembers(Project project) 444 { 445 Site projectSite = project.getSite(); 446 447 // Check if project site overrides catalog configuration 448 String membersModulesPolicy = projectSite.getValue(ProjectWorkspaceSiteType.PROJECT_OVERRIDE_MEMBERS_MODULES_POLICY_SITE_PARAM, false, ProjectWorkspaceSiteType.MembersModulesPolicy.GLOBAL.name()); 449 450 String membersModulesAsString = null; 451 if (membersModulesPolicy.equals(ProjectWorkspaceSiteType.MembersModulesPolicy.MEMBERS_MODULES_BY_PROJECT.name())) 452 { 453 membersModulesAsString = projectSite.getValue(ProjectWorkspaceSiteType.PROJECT_MEMBERS_MODULES_PROJECT_SITE_PARAM); 454 } 455 else 456 { 457 Site catalogSite = _siteManager.getSite(getCatalogSiteName()); 458 // Use #hasValueOrEmpty because we can't use #getValue with a default value. Indeed, the #getValue use the default value for null value or empty value. 459 membersModulesAsString = catalogSite.hasValueOrEmpty(CatalogSiteType.PROJECT_MEMBERS_MODULES_SITE_PARAM) 460 ? catalogSite.getValue(CatalogSiteType.PROJECT_MEMBERS_MODULES_SITE_PARAM) 461 : String.join(",", _moduleManagerEP.getExtensionsIds()); 462 } 463 464 String[] membersModuleIds = StringUtils.split(StringUtils.defaultString(membersModulesAsString), ","); 465 466 Set<WorkspaceModule> modules = new HashSet<>(); 467 for (String moduleId : membersModuleIds) 468 { 469 if (StringUtils.isNotBlank(moduleId)) 470 { 471 WorkspaceModule module = _moduleManagerEP.getModule(moduleId.trim()); 472 modules.add(module); 473 } 474 } 475 return modules; 476 } 477 478 /** 479 * Get the projects modules 480 * @param user the user 481 * @return the projects modules 482 */ 483 public Set<WorkspaceModule> getUserProjectsModules(UserIdentity user) 484 { 485 return getUserProjects(user).keySet() 486 .stream() 487 .map(Project::getModules) 488 .flatMap(Arrays::stream) 489 .map(id -> _moduleManagerEP.<WorkspaceModule>getModule(id)) 490 .filter(Objects::nonNull) 491 .collect(Collectors.toSet()); 492 } 493 494 /** 495 * Retrieves all projects for client side 496 * @return the projects 497 */ 498 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 499 public List<Map<String, Object>> getProjectsForClientSide() 500 { 501 return getProjects(false) 502 .stream() 503 .map(p -> getProjectProperties(p)) 504 .collect(Collectors.toList()); 505 } 506 507 508 /** 509 * Retrieves a project by its name 510 * @param projectName The project name 511 * @return the project or <code>null</code> if not found 512 */ 513 public Project getProject(String projectName) 514 { 515 if (StringUtils.isBlank(projectName)) 516 { 517 return null; 518 } 519 520 Request request = _getRequest(); 521 if (request == null) 522 { 523 // There is no request to store cache 524 return _computeProject(projectName); 525 } 526 527 Cache<RequestProjectCacheKey, Project> projectsCache = _getRequestProjectCache(); 528 529 // The site key in the cache is of the form {site + workspace}. 530 String currentWorkspace = _workspaceSelector.getWorkspace(); 531 RequestProjectCacheKey projectKey = RequestProjectCacheKey.of(projectName, currentWorkspace); 532 533 try 534 { 535 Project project = projectsCache.get(projectKey, __ -> _computeProject(projectName)); 536 return project; 537 } 538 catch (CacheException e) 539 { 540 if (e.getCause() instanceof UnknownAmetysObjectException) 541 { 542 throw new UnknownAmetysObjectException(e.getMessage()); 543 } 544 else 545 { 546 throw e; 547 } 548 } 549 } 550 551 /** 552 * Get the user's projects 553 * @param user the user 554 * @return the user's projects 555 */ 556 public Map<Project, MemberType> getUserProjects(UserIdentity user) 557 { 558 return getUserProjects(user, Set.of()); 559 } 560 561 /** 562 * Get the user's projects filtered by categories 563 * @param user the user 564 * @param filteredCategories the filtered categories. Can be empty to no filter by categories 565 * @return the user's projects 566 */ 567 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories) 568 { 569 return getUserProjects(user, filteredCategories, null, null, true); 570 } 571 572 /** 573 * Get the user's projects filtered by categories OR keywords 574 * @param user the user 575 * @param filteredCategories the filtered categories. Can be empty to no filter by categories 576 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords 577 * @return the user's projects 578 */ 579 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords) 580 { 581 return getUserProjects(user, filteredCategories, filteredKeywords, null, true); 582 } 583 584 /** 585 * Get the user's projects filtered by categories, keywords and/or pattern 586 * @param user the user 587 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 588 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 589 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 590 * @param anyMatch true to get projects matching categories OR keywords OR pattern 591 * @return the user's projects 592 */ 593 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch) 594 { 595 return getUserProjects(user, filteredCategories, filteredKeywords, pattern, true, false); 596 } 597 598 /** 599 * Get the user's projects filtered by categories, keywords and/or pattern 600 * @param user the user 601 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 602 * @param filteredKeywords the filtered keywords. Can be empty to no filter by keywords. 603 * @param pattern to filter on pattern. Can be null or empty to no filter on pattern 604 * @param anyMatch true to get projects matching categories OR keywords OR pattern 605 * @param excludePrivate true to exclude private projects 606 * @return the user's projects 607 */ 608 public Map<Project, MemberType> getUserProjects(UserIdentity user, Set<String> filteredCategories, Set<String> filteredKeywords, String pattern, boolean anyMatch, boolean excludePrivate) 609 { 610 Map<Project, MemberType> userProjects = new HashMap<>(); 611 612 List<Project> projects = getProjects(filteredCategories, filteredKeywords, pattern, anyMatch, excludePrivate); 613 for (Project project : projects) 614 { 615 ProjectMember member = _projectMembers.getProjectMember(project, user); 616 if (member != null) 617 { 618 userProjects.put(project, member.getType()); 619 } 620 } 621 622 return userProjects; 623 } 624 625 /** 626 * Get the projects managed by the user 627 * @param user the user 628 * @return the projects for which the user is a manager 629 */ 630 public List<Project> getManagedProjects(UserIdentity user) 631 { 632 return getManagedProjects(user, Set.of()); 633 } 634 635 /** 636 * Get the projects managed by the user 637 * @param user the user 638 * @param filteredCategories the filtered categories. Can be empty to no filter by categories. 639 * @return the projects for which the user is a manager 640 */ 641 public List<Project> getManagedProjects(UserIdentity user, Set<String> filteredCategories) 642 { 643 return getProjects(filteredCategories) 644 .stream() 645 .filter(p -> ArrayUtils.contains(p.getManagers(), user)) 646 .collect(Collectors.toList()); 647 } 648 649 /** 650 * Returns true if the given project exists. 651 * @param projectName the project name. 652 * @return true if the given project exists. 653 */ 654 public boolean hasProject(String projectName) 655 { 656 Map<String, String> uuidCache = _getUUIDCache(); 657 if (uuidCache.containsKey(projectName)) 658 { 659 // As cache is computed from default JCR workspace, we need to check if the project exists into the current JCR workspace 660 return _resolver.hasAmetysObjectForId(uuidCache.get(projectName)); 661 } 662 return false; 663 } 664 665 /** 666 * Get all managers 667 * @return the managers 668 */ 669 public Set<UserIdentity> getManagers() 670 { 671 return getProjects() 672 .stream() 673 .map(Project::getManagers) 674 .flatMap(Arrays::stream) 675 .collect(Collectors.toSet()); 676 } 677 678 /** 679 * Determines if the current user is a manager of at least one project 680 * @return true if the user is a manager 681 */ 682 public boolean isManager() 683 { 684 return isManager(_currentUserProvider.getUser()); 685 } 686 687 /** 688 * Determines if the user is a manager of at least one project 689 * @param user the user 690 * @return true if the user is a manager 691 */ 692 public boolean isManager(UserIdentity user) 693 { 694 AmetysObjectIterable<Project> projects = getProjects(); 695 for (Project project : projects) 696 { 697 if (isManager(project, user)) 698 { 699 return true; 700 } 701 } 702 return false; 703 } 704 705 /** 706 * Determines if the user is a manager of the project 707 * @param projectName the project name 708 * @param user the user 709 * @return true if the user is a manager 710 */ 711 public boolean isManager(String projectName, UserIdentity user) 712 { 713 Project project = getProject(projectName); 714 if (project != null) 715 { 716 return isManager(project, user); 717 } 718 return false; 719 } 720 721 /** 722 * Determines if the user is a manager of the project 723 * @param project the project 724 * @param user the user 725 * @return true if the user is a manager 726 */ 727 public boolean isManager(Project project, UserIdentity user) 728 { 729 return ArrayUtils.contains(project.getManagers(), user); 730 } 731 732 /** 733 * Can the current user access backoffice on the site of the current project 734 * @param project The non null project to analyse 735 * @return true if the user can access to the backoffice 736 */ 737 public boolean canAccessBO(Project project) 738 { 739 Site site = project.getSite(); 740 if (site == null) 741 { 742 return false; 743 } 744 745 Request request = ContextHelper.getRequest(_context); 746 String currentSiteName = (String) request.getAttribute("siteName"); 747 try 748 { 749 request.setAttribute("siteName", site.getName()); // Setting temporarily this attribute to check user rights on any object on this site 750 return !_rightManager.getUserRights(_currentUserProvider.getUser(), "/cms").isEmpty(); 751 } 752 finally 753 { 754 request.setAttribute("siteName", currentSiteName); 755 } 756 } 757 758 /** 759 * Can the current user leave the project 760 * @param project The non null project to analyse 761 * @return true if the user can leave the project 762 */ 763 public boolean canLeaveProject(Project project) 764 { 765 if (project == null) 766 { 767 return false; 768 } 769 770 Site site = project.getSite(); 771 if (site == null) 772 { 773 return false; 774 } 775 776 UserIdentity userIdentity = _currentUserProvider.getUser(); 777 778 // As a user in a group can't leave the project, check if he is registered as User member 779 Set<ProjectMember> members = _projectMemberManager.getProjectMembers(project, false); 780 ProjectMember projectMember = members.stream() 781 .filter(member -> MemberType.USER == member.getType()) 782 .filter(member -> userIdentity.equals(member.getUser().getIdentity())) 783 .findFirst() 784 .orElse(null); 785 786 // The user is either on a group, or has not been found 787 if (projectMember == null) 788 { 789 return false; 790 } 791 792 // The user is the only remaining manager, and therefore can not leave the project 793 if (_projectMemberManager.isOnlyManager(project, userIdentity)) 794 { 795 return false; 796 } 797 798 return true; 799 } 800 801 /** 802 * Retrieves the mapping of all the projects name with their title on which the current user has access 803 * @return the map (projectName, projectTitle) for all projects 804 */ 805 @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION) 806 public List<Map<String, Object>> getUserProjectsData() 807 { 808 return getUserProjects(_currentUserProvider.getUser()) 809 .keySet() 810 .stream() 811 .map(p -> _project2json(p)) 812 .collect(Collectors.toList()); 813 } 814 815 /** 816 * Get the user modules on the given project. 817 * @param projectName the project name 818 * @return The maps of modules with the read access information 819 */ 820 @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION) 821 public Map<String, Object> getUserModules(String projectName) 822 { 823 Project project = this.getProject(projectName); 824 UserIdentity currentUser = _currentUserProvider.getUser(); 825 826 return Arrays.stream(project.getModules()) 827 .collect(Collectors.toMap( 828 mId -> mId, 829 mId -> _projectRightHelper.hasReadAccessOnModule(project, mId, currentUser))); 830 } 831 832 /** 833 * Retrieves the users that have not been yet added to a project with a given criteria 834 * @param projectName the project name 835 * @param limit limit of request 836 * @param criteria the criteria of the search 837 * @param previousSearchData the previous search data to compute offset. Null if first search 838 * @return list of users 839 */ 840 @SuppressWarnings("unchecked") 841 @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION) 842 public Map<String, Object> searchUserByProject(String projectName, int limit, String criteria, Map<String, Object> previousSearchData) 843 { 844 845 Project project = this.getProject(projectName); 846 847 if (!_projectRightHelper.canAddMember(project)) 848 { 849 throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to do read operation without convenient right"); 850 } 851 852 Map<String, Object> results = new HashMap<>(); 853 Site site = project.getSite(); 854 855 Set<String> projectMemberList = _projectMemberManager.getProjectMembers(project, false) 856 .stream() 857 .map(member -> 858 { 859 if (member.getType() == MemberType.USER) 860 { 861 return UserIdentity.userIdentityToString(member.getUser().getIdentity()); 862 } 863 else 864 { 865 GroupIdentity groupIdentityAsString = member.getGroup().getIdentity(); 866 return GroupIdentity.groupIdentityToString(groupIdentityAsString); 867 } 868 }) 869 .collect(Collectors.toSet()); 870 871 Set<String> contexts = new HashSet<>(Arrays.asList("/sites/" + site.getName(), "/sites-fo/" + site.getName())); 872 873 Map<String, Object> params = new HashMap<>(); 874 params.put("pattern", criteria); 875 876 Map<String, Object> result = new HashMap<>(); 877 Map<String, Object> searchData = previousSearchData; 878 List<Map<String, Object>> memberList = new ArrayList<>(); 879 880 Set<String> groupDirectories = _directoryContextHelper.getGroupDirectoriesOnContexts(contexts); 881 Set<String> userPopulations = getPopulation(site, true); 882 883 do 884 { 885 result = _userAndGroupSearchManager.searchUsersAndGroup(userPopulations, groupDirectories, limit - memberList.size(), searchData, params, true); 886 List<Map<String, Object>> filteredMembers = ((List<Map<String, Object>>) result.get("results")) 887 .stream() 888 .filter(member -> 889 { 890 return !projectMemberList.contains(member.get("login") + "#" + member.get("populationId")) && !projectMemberList.contains(member.get("id") + "#" + member.get("groupDirectory")); 891 }).collect(Collectors.toList()); 892 searchData = (Map<String, Object>) result.get("searchData"); 893 memberList.addAll(filteredMembers); 894 } 895 while (!result.containsKey("finished") && memberList.size() < limit); 896 897 results.put("searchData", searchData); 898 results.put("memberList", memberList); 899 return results; 900 } 901 902 /** 903 * Get the populations of the project 904 * @param site the project site 905 * @param excludeConfigurationPopulations true to exclude populations configured in the catalog site or in the project site 906 * @return the populations of the project 907 */ 908 public Set<String> getPopulation(Site site, boolean excludeConfigurationPopulations) 909 { 910 Set<String> contexts = new HashSet<>(Arrays.asList("/sites/" + site.getName(), "/sites-fo/" + site.getName())); 911 912 Set<String> userPopulations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, true); 913 914 if (excludeConfigurationPopulations) 915 { 916 Site catalogSite = _siteManager.getSite(getCatalogSiteName()); 917 918 // Get the excluded population configuration 919 String excludedPopulationsString = catalogSite.getValue(CatalogSiteType.PROJECT_EXCLUDED_POPULATIONS_SITE_PARAM); 920 921 // Check if project site overrides catalog configuration 922 if (site.getValue(ProjectWorkspaceSiteType.PROJECT_OVERRIDE_EXCLUDED_POPULATIONS_SITE_PARAM, false, false)) 923 { 924 excludedPopulationsString = site.getValue(ProjectWorkspaceSiteType.PROJECT_EXCLUDED_POPULATIONS_PROJECT_SITE_PARAM); 925 } 926 927 List<String> excludedPopulationsList = Arrays.asList(StringUtils.split(StringUtils.defaultString(excludedPopulationsString), ",")); 928 929 if (!excludedPopulationsList.isEmpty()) 930 { 931 userPopulations.removeAll(excludedPopulationsList); 932 } 933 } 934 935 return userPopulations; 936 } 937 938 /** 939 * Retrieves the mapping of all the projects name with their title (regarless user rights) 940 * @return the map (projectName, projectTitle) for all projects 941 */ 942 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 943 public List<Map<String, Object>> getProjectsData() 944 { 945 return getProjects() 946 .stream() 947 .map(p -> _project2json(p)) 948 .collect(Collectors.toList()); 949 } 950 951 /** 952 * Get the project's main properties as json object 953 * @param project the project 954 * @return the json representation of project 955 */ 956 protected Map<String, Object> _project2json(Project project) 957 { 958 Map<String, Object> json = new HashMap<>(); 959 960 json.put("id", project.getId()); 961 json.put("name", project.getName()); 962 json.put("title", project.getTitle()); 963 json.put("url", getProjectUrl(project, StringUtils.EMPTY)); 964 965 return json; 966 } 967 /** 968 * Retrieves the project names 969 * @return the project names 970 */ 971 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 972 public Collection<String> getProjectNames() 973 { 974 // As cache is computed from default JCR workspace, we need to filter on sites that exist into the current JCR workspace 975 return _getUUIDCache().entrySet().stream() 976 .filter(e -> _resolver.hasAmetysObjectForId(e.getValue())) 977 .map(Map.Entry::getKey) 978 .collect(Collectors.toList()); 979 } 980 981 /** 982 * Return the root for projects 983 * The root node will be created if necessary 984 * @return The root for projects 985 */ 986 public ModifiableTraversableAmetysObject getProjectsRoot() 987 { 988 try 989 { 990 ModifiableTraversableAmetysObject pluginsNode = _resolver.resolveByPath("/ametys:plugins"); 991 ModifiableTraversableAmetysObject workspacesPluginNode = _getOrCreateObject(pluginsNode, __WORKSPACES_PLUGIN_NODE_NAME, __WORKSPACES_PLUGIN_NODE_TYPE); 992 return _getOrCreateObject(workspacesPluginNode, __PROJECTS_ROOT_NODE_NAME, __PROJECTS_ROOT_NODE_TYPE); 993 } 994 catch (AmetysRepositoryException e) 995 { 996 throw new AmetysRepositoryException("Error getting the projects root node.", e); 997 } 998 } 999 1000 /** 1001 * Retrieves the standard information of a project 1002 * @param projectId Identifier of the project 1003 * @return The map of information 1004 */ 1005 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 1006 public Map<String, Object> getProjectProperties(String projectId) 1007 { 1008 return getProjectProperties((Project) _resolver.resolveById(projectId)); 1009 } 1010 1011 /** 1012 * Retrieves the standard information of a project 1013 * @param project The project 1014 * @return The map of information 1015 */ 1016 public Map<String, Object> getProjectProperties(Project project) 1017 { 1018 Map<String, Object> info = new HashMap<>(); 1019 1020 info.put("id", project.getId()); 1021 info.put("name", project.getName()); 1022 info.put("type", "project"); 1023 1024 info.put("title", project.getTitle()); 1025 info.put("description", project.getDescription()); 1026 info.put("inscriptionStatus", project.getInscriptionStatus().toString()); 1027 info.put("defaultProfile", project.getDefaultProfile()); 1028 1029 info.put("creationDate", project.getCreationDate()); 1030 1031 // check if the project workspace configuration is valid 1032 Site site = project.getSite(); 1033 boolean valid = site != null && _siteConfigurationManager.isSiteConfigurationValid(site); 1034 1035 Set<String> categories = project.getCategories(); 1036 info.put("categories", categories.stream() 1037 .map(c -> _categoryProviderEP.getTag(c, new HashMap<>())) 1038 .filter(Objects::nonNull) 1039 .map(t -> _tag2json(t)) 1040 .collect(Collectors.toList())); 1041 1042 info.put("keywords", Arrays.stream(project.getKeywords()) 1043 .map(c -> _keywordProviderEP.getTag(c, new HashMap<>())) 1044 .filter(Objects::nonNull) 1045 .map(t -> _tag2json(t)) 1046 .collect(Collectors.toList())); 1047 1048 info.put("valid", valid); 1049 1050 UserIdentity[] managers = project.getManagers(); 1051 info.put("managers", Arrays.stream(managers) 1052 .map(u -> _userHelper.user2json(u)) 1053 .collect(Collectors.toList())); 1054 1055 Map<String, String> siteProps = new HashMap<>(); 1056 // site map with id ,name, title and url property 1057 // { id: site id, name: site name, title: site title, url: site url } 1058 if (site != null) 1059 { 1060 siteProps.put("id", site.getId()); 1061 siteProps.put("name", site.getName()); 1062 siteProps.put("title", site.getTitle()); 1063 siteProps.put("url", site.getUrl()); 1064 } 1065 info.put("site", siteProps); 1066 1067 return info; 1068 } 1069 1070 private Map<String, Object> _tag2json(Tag tag) 1071 { 1072 Map<String, Object> json = new HashMap<>(); 1073 json.put("id", tag.getId()); 1074 json.put("name", tag.getName()); 1075 json.put("title", tag.getTitle()); 1076 return json; 1077 } 1078 1079 /** 1080 * Get the project URL. 1081 * @param project The project 1082 * @param defaultValue The default value to use if there is no site 1083 * @return The project URL if a site is configured, otherwise return the default value. 1084 */ 1085 public String getProjectUrl(Project project, String defaultValue) 1086 { 1087 Site site = project.getSite(); 1088 if (site == null) 1089 { 1090 return defaultValue; 1091 } 1092 else 1093 { 1094 return site.getUrl(); 1095 } 1096 } 1097 1098 /** 1099 * Create a project 1100 * @param name The project name 1101 * @param title The project title 1102 * @param description The project description 1103 * @param emailList Project mailing list 1104 * @param inscriptionStatus The inscription status of the project 1105 * @param defaultProfile The default profile for new members 1106 * @return A map containing the id of the new project or an error key. 1107 */ 1108 @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION) 1109 public Map<String, Object> createProject(String name, String title, String description, String emailList, String inscriptionStatus, String defaultProfile) 1110 { 1111 checkRightsForProjectCreation(InscriptionStatus.valueOf(inscriptionStatus.toUpperCase()), null); 1112 1113 Map<String, Object> result = new HashMap<>(); 1114 List<I18nizableText> errors = new ArrayList<>(); 1115 1116 Map<String, Object> additionalValues = new HashMap<>(); 1117 additionalValues.put("description", description); 1118 additionalValues.put("emailList", emailList); 1119 additionalValues.put("inscriptionStatus", inscriptionStatus); 1120 additionalValues.put("defaultProfile", defaultProfile); 1121 1122 Project project = createProject(name, title, additionalValues, null, errors); 1123 1124 if (CollectionUtils.isEmpty(errors) && project != null) 1125 { 1126 result.put("id", project.getId()); 1127 result.put("success", true); 1128 } 1129 else 1130 { 1131 result.put("success", false); 1132 result.put("errors", errors); 1133 } 1134 1135 return result; 1136 } 1137 1138 /** 1139 * Create a project 1140 * @param name The project name 1141 * @param title The project title 1142 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 1143 * @param modulesIds The list of modules to activate. Can be null to activate all modules 1144 * @param errors A list that will be populated with the encountered errors. Cannot be null. 1145 * @return The id of the new project 1146 */ 1147 public Project createProject(String name, String title, Map<String, Object> additionalValues, Set<String> modulesIds, List<I18nizableText> errors) 1148 { 1149 if (StringUtils.isEmpty(title)) 1150 { 1151 throw new IllegalArgumentException(String.format("Cannot create project. Title is mandatory")); 1152 } 1153 1154 Project project = null; 1155 boolean hasError = true; 1156 try 1157 { 1158 ModifiableTraversableAmetysObject projectsRoot = getProjectsRoot(); 1159 1160 // Project name should be unique 1161 if (hasProject(name)) 1162 { 1163 getLogger().warn("Project creation failed because a project with the name '{}' already exists", name); 1164 errors.add(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CREATE_PROJECT_ERROR_ALREADY_EXIST")); 1165 return null; 1166 } 1167 1168 project = projectsRoot.createChild(name, Project.NODE_TYPE); 1169 project.setTitle(title); 1170 String description = (String) additionalValues.getOrDefault("description", null); 1171 if (StringUtils.isNotEmpty(description)) 1172 { 1173 project.setDescription(description); 1174 } 1175 String mailingList = (String) additionalValues.getOrDefault("emailList", null); 1176 if (StringUtils.isNotEmpty(mailingList)) 1177 { 1178 project.setMailingList(mailingList); 1179 } 1180 1181 String inscriptionStatus = (String) additionalValues.getOrDefault("inscriptionStatus", null); 1182 if (StringUtils.isNotEmpty(inscriptionStatus)) 1183 { 1184 project.setInscriptionStatus(inscriptionStatus); 1185 } 1186 1187 String defaultProfile = (String) additionalValues.getOrDefault("defaultProfile", null); 1188 if (StringUtils.isNotEmpty(defaultProfile)) 1189 { 1190 project.setDefaultProfile(defaultProfile); 1191 } 1192 1193 @SuppressWarnings("unchecked") 1194 List<String> tags = (List<String>) additionalValues.getOrDefault("tags", null); 1195 if (tags != null) 1196 { 1197 project.setTags(tags); 1198 } 1199 @SuppressWarnings("unchecked") 1200 List<String> categoryTags = (List<String>) additionalValues.getOrDefault("categoryTags", null); 1201 if (categoryTags != null) 1202 { 1203 project.setCategoryTags(categoryTags); 1204 } 1205 1206 @SuppressWarnings("unchecked") 1207 List<String> keywords = (List<String>) additionalValues.getOrDefault("keywords", null); 1208 if (keywords != null) 1209 { 1210 project.setKeywords(keywords.toArray(new String[keywords.size()])); 1211 } 1212 1213 project.setCreationDate(ZonedDateTime.now()); 1214 1215 // Create the project workspace = a site + a set of pages 1216 Site site = _createProjectWorkspace(project, errors); 1217 if (site != null && CollectionUtils.isEmpty(errors)) 1218 { 1219 activateModules(project, modulesIds, additionalValues); 1220 1221 project.saveChanges(); 1222 1223 // Notify observers 1224 Map<String, Object> eventParams = new HashMap<>(); 1225 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1226 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_ADDED, _currentUserProvider.getUser(), eventParams)); 1227 1228 hasError = false; 1229 } 1230 } 1231 catch (Exception e) 1232 { 1233 getLogger().error("Fail to create project '{}'", name, e); 1234 errors.add(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CREATE_PROJECT_ERROR")); 1235 } 1236 1237 if (hasError) 1238 { 1239 if (project != null) 1240 { 1241 getLogger().error("Project creation does not complete as expected. Delete project '{}'.", name); 1242 deleteProject(project); 1243 project = null; 1244 } 1245 else 1246 { 1247 getLogger().error("Fail to create project '{}'.", name); 1248 } 1249 } 1250 1251 clearCaches(); 1252 1253 return project; 1254 } 1255 1256 /** 1257 * Edit a project 1258 * @param id The project identifier 1259 * @param title The title to set 1260 * @param description The description to set 1261 * @param mailingList Project mailing list 1262 * @param inscriptionStatus The inscription status of the project 1263 * @param defaultProfile The default profile for new members 1264 */ 1265 @Callable(rights = ProjectConstants.RIGHT_PROJECT_EDIT, context = "/admin") 1266 public void editProject(String id, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 1267 { 1268 Project project = _resolver.resolveById(id); 1269 editProject(project, title, description, mailingList, inscriptionStatus, defaultProfile); 1270 } 1271 1272 /** 1273 * Edit a project 1274 * @param project The project 1275 * @param title The title to set 1276 * @param description The description to set 1277 * @param mailingList Project mailing list 1278 * @param inscriptionStatus The inscription status of the project 1279 * @param defaultProfile The default profile for new members 1280 */ 1281 public void editProject(Project project, String title, String description, String mailingList, String inscriptionStatus, String defaultProfile) 1282 { 1283 checkRightsForProjectEdition(project, InscriptionStatus.valueOf(inscriptionStatus.toUpperCase()), null); 1284 1285 project.setTitle(title); 1286 1287 if (StringUtils.isNotEmpty(description)) 1288 { 1289 project.setDescription(description); 1290 } 1291 else 1292 { 1293 project.removeDescription(); 1294 } 1295 1296 if (StringUtils.isNotEmpty(mailingList)) 1297 { 1298 project.setMailingList(mailingList); 1299 } 1300 else 1301 { 1302 project.removeMailingList(); 1303 } 1304 1305 project.setInscriptionStatus(inscriptionStatus); 1306 project.setDefaultProfile(defaultProfile); 1307 1308 project.saveChanges(); 1309 1310 // Notify observers 1311 Map<String, Object> eventParams = new HashMap<>(); 1312 eventParams.put(ObservationConstants.ARGS_PROJECT, project); 1313 eventParams.put(org.ametys.plugins.workspaces.ObservationConstants.ARGS_PROJECT_ID, project.getId()); 1314 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_UPDATED, _currentUserProvider.getUser(), eventParams)); 1315 } 1316 1317 /** 1318 * Delete a list of project. 1319 * @param ids The ids of projects to delete 1320 * @return The ids of the deleted projects, unknowns projects and the deleted sites 1321 */ 1322 @Callable(rights = ProjectConstants.RIGHT_PROJECT_DELETE, context = "/admin") 1323 public Map<String, Object> deleteProjectsByIds(List<String> ids) 1324 { 1325 Map<String, Object> result = new HashMap<>(); 1326 List<Map<String, Object>> deleted = new ArrayList<>(); 1327 List<String> unknowns = new ArrayList<>(); 1328 1329 for (String id : ids) 1330 { 1331 try 1332 { 1333 Project project = _resolver.resolveById(id); 1334 1335 Map<String, Object> projectInfo = new HashMap<>(); 1336 projectInfo.put("id", id); 1337 projectInfo.put("title", project.getTitle()); 1338 projectInfo.put("sites", deleteProject(project)); 1339 1340 deleted.add(projectInfo); 1341 } 1342 catch (UnknownAmetysObjectException e) 1343 { 1344 getLogger().warn(String.format("Unable to delete the definition of id '%s', because it does not exist.", id), e); 1345 unknowns.add(id); 1346 } 1347 } 1348 1349 result.put("deleted", deleted); 1350 result.put("unknowns", unknowns); 1351 1352 return result; 1353 } 1354 1355 /** 1356 * Delete a project. 1357 * @param projects The list of projects to delete 1358 * @return list of deleted sites (each list entry contains a data map with 1359 * the id and the name of the delete site). 1360 */ 1361 public List<Map<String, String>> deleteProject(List<Project> projects) 1362 { 1363 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 1364 1365 for (Project project : projects) 1366 { 1367 deletedSitesInfo.addAll(deleteProject(project)); 1368 } 1369 1370 return deletedSitesInfo; 1371 } 1372 1373 /** 1374 * Delete a project and its sites 1375 * @param project The project to delete 1376 * @return list of deleted sites (each list entry contains a data map with 1377 * the id and the name of the delete site). 1378 */ 1379 public List<Map<String, String>> deleteProject(Project project) 1380 { 1381 ModifiableAmetysObject parent = project.getParent(); 1382 1383 // list of map entry with id, name and title property 1384 // { id: site id, name: site name } 1385 List<Map<String, String>> deletedSitesInfo = new ArrayList<>(); 1386 1387 Site site = project.getSite(); 1388 if (site != null) 1389 { 1390 try 1391 { 1392 Map<String, String> siteProps = new HashMap<>(); 1393 siteProps.put("id", site.getId()); 1394 siteProps.put("name", site.getName()); 1395 1396 _siteDao.deleteSite(site.getId()); 1397 deletedSitesInfo.add(siteProps); 1398 } 1399 catch (RepositoryException e) 1400 { 1401 String errorMsg = String.format("Error while trying to delete the site '%s' for the project '%s'.", site.getName(), project.getName()); 1402 getLogger().error(errorMsg, e); 1403 } 1404 } 1405 1406 String projectId = project.getId(); 1407 Collection<ProjectMember> projectMembers = _projectMembers.getProjectMembers(project, true); 1408 project.remove(); 1409 parent.saveChanges(); 1410 1411 // Notify observers 1412 Map<String, Object> eventParams = new HashMap<>(); 1413 eventParams.put(ObservationConstants.ARGS_PROJECT_ID, projectId); 1414 eventParams.put(ObservationConstants.ARGS_PROJECT_NAME, project.getName()); 1415 eventParams.put(ObservationConstants.ARGS_PROJECT_MEMBERS, projectMembers); 1416 _observationManager.notify(new Event(ObservationConstants.EVENT_PROJECT_DELETED, _currentUserProvider.getUser(), eventParams)); 1417 1418 clearCaches(); 1419 1420 return deletedSitesInfo; 1421 } 1422 1423 /** 1424 * Utility method to get or create an ametys object 1425 * @param <A> A sub class of AmetysObject 1426 * @param parent The parent object 1427 * @param name The ametys object name 1428 * @param type The ametys object type 1429 * @return ametys object 1430 * @throws AmetysRepositoryException if an repository error occurs 1431 */ 1432 private <A extends AmetysObject> A _getOrCreateObject(ModifiableTraversableAmetysObject parent, String name, String type) throws AmetysRepositoryException 1433 { 1434 A object; 1435 1436 if (parent.hasChild(name)) 1437 { 1438 object = parent.getChild(name); 1439 } 1440 else 1441 { 1442 object = parent.createChild(name, type); 1443 parent.saveChanges(); 1444 } 1445 1446 return object; 1447 } 1448 1449 /** 1450 * Get the project of an ametys object inside a project. 1451 * It can be an explorer node, or any type of resource in a module. 1452 * @param id The identifier of the ametys object 1453 * @return the project or null if not found 1454 */ 1455 public Project getParentProject(String id) 1456 { 1457 return getParentProject(_resolver.<AmetysObject>resolveById(id)); 1458 } 1459 1460 /** 1461 * Get the project of an ametys object inside a project. 1462 * It can be an explorer node, or any type of resource in a module. 1463 * @param object The ametys object 1464 * @return the project or null if not found 1465 */ 1466 public Project getParentProject(AmetysObject object) 1467 { 1468 AmetysObject ametysObject = object; 1469 // Go back to the local explorer root. 1470 do 1471 { 1472 ametysObject = ametysObject.getParent(); 1473 } 1474 while (ametysObject instanceof ExplorerNode); 1475 1476 if (!(ametysObject instanceof Project)) 1477 { 1478 getLogger().warn(String.format("No project found for ametys object with id '%s'", ametysObject.getId())); 1479 return null; 1480 } 1481 1482 return (Project) ametysObject; 1483 } 1484 1485 /** 1486 * Get the list of project names for a given site 1487 * @param siteName The site name 1488 * @return the list of project names 1489 */ 1490 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 1491 public List<String> getProjectsForSite(String siteName) 1492 { 1493 Cache<String, List<Pair<String, String>>> cache = _getMemorySiteAssociationCache(); 1494 if (cache.hasKey(siteName)) 1495 { 1496 return cache.get(siteName).stream() 1497 .map(p -> p.getRight()) 1498 .collect(Collectors.toList()); 1499 } 1500 else 1501 { 1502 List<String> projectNames = new ArrayList<>(); 1503 1504 if (_siteManager.hasSite(siteName)) 1505 { 1506 Site site = _siteManager.getSite(siteName); 1507 getProjectsForSite(site) 1508 .stream() 1509 .map(Project::getName) 1510 .forEach(projectNames::add); 1511 } 1512 1513 return projectNames; 1514 } 1515 } 1516 1517 /** 1518 * Get the list of project for a given site 1519 * @param site The site 1520 * @return the list of project 1521 */ 1522 public List<Project> getProjectsForSite(Site site) 1523 { 1524 Cache<String, List<Pair<String, String>>> cache = _getMemorySiteAssociationCache(); 1525 if (cache.hasKey(site.getName())) 1526 { 1527 return cache.get(site.getName()).stream() 1528 .map(p -> _resolver.<Project>resolveById(p.getLeft())) 1529 .collect(Collectors.toList()); 1530 } 1531 1532 try 1533 { 1534 // Stream over the weak reference properties pointing to this 1535 // node to find referencing projects 1536 Iterator<Property> propertyIterator = site.getNode().getWeakReferences(); 1537 Iterable<Property> propertyIterable = () -> propertyIterator; 1538 1539 List<Project> projects = StreamSupport.stream(propertyIterable.spliterator(), false) 1540 .map(p -> 1541 { 1542 try 1543 { 1544 Node parent = p.getParent(); 1545 1546 // Check if the parent is a project" 1547 if (NodeTypeHelper.isNodeType(parent, "ametys:project")) 1548 { 1549 Project project = _resolver.resolve(parent, false); 1550 return project; 1551 } 1552 } 1553 catch (Exception e) 1554 { 1555 if (getLogger().isWarnEnabled()) 1556 { 1557 // this weak reference is not from a project 1558 String propertyPath = null; 1559 try 1560 { 1561 propertyPath = p.getPath(); 1562 } 1563 catch (Exception e2) 1564 { 1565 // ignore 1566 } 1567 1568 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); 1569 getLogger().warn(warnMsg); 1570 } 1571 } 1572 1573 return null; 1574 }) 1575 .filter(Objects::nonNull) 1576 .collect(Collectors.toList()); 1577 1578 List<Pair<String, String>> projectsPairs = projects.stream().map(p -> Pair.of(p.getId(), p.getName())).collect(Collectors.toList()); 1579 cache.put(site.getName(), projectsPairs); 1580 return projects; 1581 } 1582 catch (RepositoryException e) 1583 { 1584 getLogger().error(String.format("Unable to find projects for site '%s'", site.getName()), e); 1585 } 1586 1587 return new ArrayList<>(); 1588 } 1589 1590 /** 1591 * Create the project workspace for a given project. 1592 * @param project The project for which the workspace must be created 1593 * @param errors A list of possible errors to populate. Can be null if the caller is not interested in error tracking. 1594 * @return The site created for this workspace 1595 */ 1596 protected Site _createProjectWorkspace(Project project, List<I18nizableText> errors) 1597 { 1598 try 1599 { 1600 Site catalogSite = _siteManager.getSite(getCatalogSiteName()); 1601 Site site = _siteDao.createSite(project.getName(), catalogSite, ProjectWorkspaceSiteType.TYPE_ID, true); 1602 1603 setProjectSiteTitle(site, project.getTitle()); 1604 1605 // Add site to project 1606 project.setSite(site); 1607 1608 site.saveChanges(); 1609 1610 return site; 1611 } 1612 catch (UnknownCatalogSiteException e) 1613 { 1614 getLogger().error("Failed to create site for project '" + project.getName() + "' because the catalog site is unknown", e); 1615 errors.add(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CREATE_PROJECT_CREATE_SITE_UNKNOWN_CATALOG_ERROR")); 1616 return null; 1617 } 1618 catch (SiteException e) 1619 { 1620 if (e.getType() == Type.UNAUTHORIZED_SITENAME) 1621 { 1622 errors.add(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CREATE_PROJECT_ERROR_INVALID_SITE_NAME", List.of(project.getName()))); 1623 } 1624 else 1625 { 1626 getLogger().error("Failed to create site for project '" + project.getName() + "'", e); 1627 errors.add(new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CREATE_PROJECT_CREATE_SITE_ERROR")); 1628 } 1629 return null; 1630 } 1631 } 1632 1633 /** 1634 * Get the project's tags 1635 * @return The project's tags 1636 */ 1637 public List<String> getTags() 1638 { 1639 AmetysObject projectsRootNode = getProjectsRoot(); 1640 if (projectsRootNode instanceof JCRAmetysObject) 1641 { 1642 Node node = ((JCRAmetysObject) projectsRootNode).getNode(); 1643 1644 try 1645 { 1646 return Arrays.stream(node.getProperty(__PROJECTS_TAGS_PROPERTY).getValues()) 1647 .map(LambdaUtils.wrap(Value::getString)) 1648 .collect(Collectors.toList()); 1649 } 1650 catch (PathNotFoundException e) 1651 { 1652 // property is not set, empty list will be returned. 1653 } 1654 catch (RepositoryException e) 1655 { 1656 throw new AmetysRepositoryException(e); 1657 } 1658 } 1659 1660 return new ArrayList<>(); 1661 } 1662 1663 /** 1664 * Add project's tags 1665 * @param newTags The new tags to add 1666 */ 1667 public synchronized void addTags(Collection<String> newTags) 1668 { 1669 if (CollectionUtils.isNotEmpty(newTags)) 1670 { 1671 AmetysObject projectsRootNode = getProjectsRoot(); 1672 if (projectsRootNode instanceof JCRAmetysObject) 1673 { 1674 // Concat existing tags with new lowercased tags 1675 String[] tags = Stream.concat(getTags().stream(), newTags.stream().map(String::trim).map(String::toLowerCase).filter(StringUtils::isNotEmpty)) 1676 .distinct() 1677 .toArray(String[]::new); 1678 1679 try 1680 { 1681 ((JCRAmetysObject) projectsRootNode).getNode().setProperty(__PROJECTS_TAGS_PROPERTY, tags); 1682 } 1683 catch (RepositoryException e) 1684 { 1685 throw new AmetysRepositoryException(e); 1686 } 1687 } 1688 } 1689 } 1690 1691 /** 1692 * Get the list of activated modules for a project 1693 * @param project The project 1694 * @return The list of activated modules 1695 */ 1696 public List<WorkspaceModule> getModules(Project project) 1697 { 1698 return _moduleManagerEP.getModules().stream() 1699 .filter(module -> isModuleActivated(project, module.getId())) 1700 .collect(Collectors.toList()); 1701 } 1702 1703 /** 1704 * Retrieves the page of the module for all available languages 1705 * @param project The project 1706 * @param moduleId The project module id 1707 * @return the page or null if not found 1708 */ 1709 public Set<Page> getModulePages(Project project, String moduleId) 1710 { 1711 if (_moduleManagerEP.hasExtension(moduleId)) 1712 { 1713 WorkspaceModule module = _moduleManagerEP.getExtension(moduleId); 1714 return getModulePages(project, module); 1715 } 1716 return null; 1717 } 1718 1719 /** 1720 * Return the possible module roots associated to a page 1721 * @param page The given page 1722 * @return A non null set of the data of the linked modules 1723 */ 1724 public Set<ModifiableResourceCollection> pageToModuleRoot(Page page) 1725 { 1726 Set<ModifiableResourceCollection> data = new LinkedHashSet<>(); 1727 1728 Page rootPage = page; 1729 SitemapElement parent = page.getParent(); 1730 while (!(parent instanceof Sitemap) && !page.hasValue(__PAGE_MODULES_VALUE)) 1731 { 1732 rootPage = (Page) parent; 1733 parent = parent.getParent(); 1734 } 1735 1736 String[] modulesRootsIds = rootPage.getValue(__PAGE_MODULES_VALUE, new String[0]); 1737 if (modulesRootsIds.length > 0) 1738 { 1739 for (String moduleRootId : modulesRootsIds) 1740 { 1741 try 1742 { 1743 ModifiableResourceCollection moduleRoot = _resolver.resolveById(moduleRootId); 1744 data.add(moduleRoot); 1745 } 1746 catch (UnknownAmetysObjectException e) 1747 { 1748 // Ignore obsolete data 1749 } 1750 } 1751 } 1752 1753 return data; 1754 } 1755 1756 /** 1757 * Mark the given page as this module page. The modified page will not be saved. 1758 * @param page The page to change 1759 * @param moduleRoot The workspace module that use this page 1760 */ 1761 public void tagProjectPage(ModifiablePage page, ModifiableResourceCollection moduleRoot) 1762 { 1763 String[] currentModules = page.getValue(__PAGE_MODULES_VALUE, new String[0]); 1764 1765 Set<String> modules = new LinkedHashSet<>(Arrays.asList(currentModules)); 1766 modules.add(moduleRoot.getId()); 1767 1768 String[] newModules = new String[modules.size()]; 1769 modules.toArray(newModules); 1770 1771 page.setValue(__PAGE_MODULES_VALUE, newModules); 1772 } 1773 1774 /** 1775 * Remove the mark on the given page of this module. The modified page will not be saved. 1776 * @param page The page to change 1777 * @param moduleRoot The workspace module that use this page 1778 */ 1779 public void untagProjectPage(ModifiablePage page, ModifiableResourceCollection moduleRoot) 1780 { 1781 if (moduleRoot != null) 1782 { 1783 String[] currentModules = page.getValue(__PAGE_MODULES_VALUE, new String[0]); 1784 1785 Set<String> modules = new LinkedHashSet<>(Arrays.asList(currentModules)); 1786 modules.remove(moduleRoot.getId()); 1787 1788 String[] newModules = new String[modules.size()]; 1789 modules.toArray(newModules); 1790 1791 page.setValue(__PAGE_MODULES_VALUE, newModules); 1792 } 1793 } 1794 1795 /** 1796 * Get a page in the site of a given project with a specific tag 1797 * @param project The project 1798 * @param workspaceModule the module 1799 * @return The module's pages 1800 */ 1801 public Set<Page> getModulePages(Project project, WorkspaceModule workspaceModule) 1802 { 1803 Request request = _getRequest(); 1804 if (request == null) 1805 { 1806 // There is no request to store cache 1807 return _computePages(project, workspaceModule); 1808 } 1809 1810 Cache<RequestModuleCacheKey, Set<Page>> pagesCache = _getRequestPageCache(); 1811 1812 // The site key in the cache is of the form {site + workspace}. 1813 String currentWorkspace = _workspaceSelector.getWorkspace(); 1814 RequestModuleCacheKey pagesKey = RequestModuleCacheKey.of(project.getName(), workspaceModule.getId(), currentWorkspace); 1815 1816 try 1817 { 1818 return pagesCache.get(pagesKey, __ -> _computePages(project, workspaceModule)); 1819 } 1820 catch (CacheException e) 1821 { 1822 if (e.getCause() instanceof UnknownAmetysObjectException) 1823 { 1824 throw (UnknownAmetysObjectException) e.getCause(); 1825 } 1826 else 1827 { 1828 throw new RuntimeException("An error occurred while computing page of module " + workspaceModule.getModuleName() + " in project " + project.getName(), e); 1829 } 1830 } 1831 } 1832 1833 private Set<Page> _computePages(Project project, WorkspaceModule workspaceModule) 1834 { 1835 Set<String> pagesUUids = _getMemoryPageCache().get(ModuleCacheKey.of(project.getName(), workspaceModule.getId()), __ -> _computePagesIds(project, workspaceModule)); 1836 if (pagesUUids != null) 1837 { 1838 return pagesUUids.stream().map(uuid -> _resolver.<Page>resolveById(uuid)).collect(Collectors.toSet()); 1839 } 1840 else 1841 { 1842 // Project may be present in cache for 'default' workspace but does not exist in current JCR workspace 1843 throw new UnknownAmetysObjectException("There is no pages for '" + project.getName() + "', module '" + workspaceModule.getModuleName() + "'"); 1844 } 1845 } 1846 1847 private Set<String> _computePagesIds(Project project, WorkspaceModule workspaceModule) 1848 { 1849 Site site = project.getSite(); 1850 String siteName = site != null ? site.getName() : null; 1851 if (StringUtils.isEmpty(siteName)) 1852 { 1853 return null; 1854 } 1855 1856 ModifiableResourceCollection moduleRoot = workspaceModule.getModuleRoot(project, false); 1857 if (moduleRoot != null) 1858 { 1859 Expression expression = new StringExpression(__PAGE_MODULES_VALUE, Operator.EQ, moduleRoot.getId()); 1860 String query = PageQueryHelper.getPageXPathQuery(siteName, null, null, expression, null); 1861 1862 return StreamSupport.stream(_resolver.query(query).spliterator(), false) 1863 .map(page -> page.getId()) 1864 .collect(Collectors.toSet()); 1865 } 1866 else 1867 { 1868 return Set.of(); 1869 } 1870 } 1871 1872 /** 1873 * Activate the list of module of the project 1874 * @param project The project 1875 * @param moduleIds The list of modules. Can be null to activate all modules 1876 * @param additionalValues A list of optional additional values. Accepted values are : description, mailingList, inscriptionStatus, defaultProfile, tags, categoryTags, keywords and language 1877 */ 1878 public void activateModules(Project project, Set<String> moduleIds, Map<String, Object> additionalValues) 1879 { 1880 Set<String> modules = moduleIds == null ? _moduleManagerEP.getExtensionsIds() : moduleIds; 1881 1882 for (String moduleId : modules) 1883 { 1884 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1885 if (module != null && !isModuleActivated(project, moduleId)) 1886 { 1887 module.activateModule(project, additionalValues); 1888 project.addModule(moduleId); 1889 } 1890 } 1891 1892 _setDefaultProfileForMembers(project, modules); 1893 1894 project.saveChanges(); 1895 } 1896 1897 private void _setDefaultProfileForMembers(Project project, Set<String> modules) 1898 { 1899 String profileForNewModule = StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.new.module")); 1900 1901 if (profileForNewModule.equals(ProfileForNewModule.DEFAULT_MEMBER_PROFILE.name())) 1902 { 1903 Set<String> defaultProfiles = Set.of(StringUtils.defaultString(Config.getInstance().getValue("workspaces.profile.default"))); 1904 Map<JCRProjectMember, Object> projectMembers = _projectMembers.getJCRProjectMembers(project); 1905 1906 Set<WorkspaceModule> modulesForMembers = getProjectModulesForNewMembers(project); 1907 for (String moduleId : modules) 1908 { 1909 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1910 Set<String> profiles = modulesForMembers.contains(module) ? defaultProfiles : Set.of(); 1911 for (JCRProjectMember member : projectMembers.keySet()) 1912 { 1913 _projectMembers.setProfileOnModule(member, project, module, profiles); 1914 } 1915 } 1916 } 1917 } 1918 1919 /** 1920 * Initialize the sitemap with the active module of the project 1921 * @param project The project 1922 * @param sitemap The sitemap 1923 */ 1924 public void initializeModulesSitemap(Project project, Sitemap sitemap) 1925 { 1926 Set<String> modules = _moduleManagerEP.getExtensionsIds(); 1927 1928 for (String moduleId : modules) 1929 { 1930 if (_moduleManagerEP.hasExtension(moduleId)) 1931 { 1932 WorkspaceModule module = _moduleManagerEP.getExtension(moduleId); 1933 1934 if (isModuleActivated(project, moduleId)) 1935 { 1936 module.initializeSitemap(project, sitemap); 1937 } 1938 } 1939 } 1940 } 1941 1942 /** 1943 * Determines if a module is activated 1944 * @param project The project 1945 * @param moduleId The id of module 1946 * @return true if the module the currently activated 1947 */ 1948 public boolean isModuleActivated(Project project, String moduleId) 1949 { 1950 return ArrayUtils.contains(project.getModules(), moduleId); 1951 } 1952 1953 /** 1954 * Remove the explorer root node of the project module, remove all events 1955 * related to that module and set it to deactivated 1956 * @param project The project 1957 * @param moduleIds The id of module to activate 1958 */ 1959 public void deactivateModules(Project project, Set<String> moduleIds) 1960 { 1961 for (String moduleId : moduleIds) 1962 { 1963 WorkspaceModule module = _moduleManagerEP.getModule(moduleId); 1964 if (module != null && isModuleActivated(project, moduleId)) 1965 { 1966 module.deactivateModule(project); 1967 project.removeModule(moduleId); 1968 } 1969 } 1970 1971 project.saveChanges(); 1972 } 1973 1974 1975 /** 1976 * Get the list of profiles configured for the workspaces' projects 1977 * @return The list of profiles as JSON 1978 */ 1979 @Callable(rights = {ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, ProjectConstants.RIGHT_PROJECT_EDIT}, context = "/admin") 1980 public Map<String, Object> getProjectProfiles() 1981 { 1982 Map<String, Object> result = new HashMap<>(); 1983 List<Map<String, Object>> profiles = _projectRightHelper.getProfiles().stream().map(p -> p.toJSON()).collect(Collectors.toList()); 1984 result.put("profiles", profiles); 1985 return result; 1986 } 1987 1988 /** 1989 * Get the site name holding the catalog of projects 1990 * @return the catalog's site name 1991 * @throws UnknownCatalogSiteException when the config is invalid 1992 */ 1993 public String getCatalogSiteName() throws UnknownCatalogSiteException 1994 { 1995 String catalogSiteName = Config.getInstance().getValue("workspaces.catalog.site.name"); 1996 if (!_siteManager.hasSite(catalogSiteName)) 1997 { 1998 throw new UnknownCatalogSiteException("Unknown site '" + catalogSiteName + "'. The global Ametys configuration is invalid for the parameter 'workspaces.catalog.site.name'"); 1999 } 2000 return catalogSiteName; 2001 } 2002 2003 /** 2004 * Get the site name holding the users directory 2005 * @return the users directory's site name 2006 * @throws UnknownUserDirectorySiteException when the config is invalid 2007 */ 2008 public String getUsersDirectorySiteName() throws UnknownUserDirectorySiteException 2009 { 2010 String udSiteName = Config.getInstance().getValue("workspaces.member.userdirectory.site.name"); 2011 if (!_siteManager.hasSite(udSiteName)) 2012 { 2013 throw new UnknownUserDirectorySiteException("Unknown site '" + udSiteName + "'. The global Ametys configuration is invalid for the parameter 'workspaces.member.userdirectory.site.name'"); 2014 } 2015 return udSiteName; 2016 } 2017 2018 @Override 2019 public boolean supports(Event event) 2020 { 2021 return event.getId().equals(ObservationConstants.EVENT_PROJECT_DELETED) 2022 || event.getId().equals(ObservationConstants.EVENT_PROJECT_UPDATED) 2023 || event.getId().equals(ObservationConstants.EVENT_PROJECT_ADDED) 2024 || event.getId().equals(org.ametys.web.ObservationConstants.EVENT_PAGE_ADDED) 2025 || event.getId().equals(org.ametys.web.ObservationConstants.EVENT_PAGE_DELETED); 2026 } 2027 2028 public int getPriority() 2029 { 2030 return 0; 2031 } 2032 2033 public void observe(Event event, Map<String, Object> transientVars) throws Exception 2034 { 2035 clearCaches(); 2036 } 2037 2038 /** 2039 * Prefix project title 2040 * @param site the site 2041 * @param title the title 2042 */ 2043 public void setProjectSiteTitle(Site site, String title) 2044 { 2045 I18nizableText i18nSiteTitle = new I18nizableText("plugin." + _pluginName, "PLUGINS_WORKSPACES_PROJECT_DEFAULT_PROJECT_WORKSPACE_TITLE", Arrays.asList(title)); 2046 site.setTitle(_i18nUtils.translate(i18nSiteTitle)); 2047 } 2048 2049 private Project _computeProject(String projectName) 2050 { 2051 if (hasProject(projectName)) 2052 { 2053 String uuid = _getUUIDCache().get(projectName); 2054 return _resolver.<Project>resolveById(uuid); 2055 } 2056 else 2057 { 2058 // Project may be present in cache for 'default' workspace but does not exist in current JCR workspace 2059 throw new UnknownAmetysObjectException("There is no site named '" + projectName + "'"); 2060 } 2061 } 2062 2063 /** 2064 * Check rights to create project 2065 * @param inscriptionStatus the inscription status 2066 * @param zoneItem the zoneItem containing catalog service. Must be a catalog service if not null 2067 */ 2068 public void checkRightsForProjectCreation(InscriptionStatus inscriptionStatus, ZoneItem zoneItem) 2069 { 2070 SitemapElement catalogPage = zoneItem != null ? zoneItem.getZone().getSitemapElement() : null; 2071 2072 if (catalogPage != null && !_projectRightHelper.hasCatalogReadAccess(zoneItem)) 2073 { 2074 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to create project from page '" + catalogPage.getId() + "' without sufficient rights"); 2075 } 2076 2077 switch (inscriptionStatus) 2078 { 2079 case PRIVATE: 2080 boolean hasRightToCreatePrivateProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, catalogPage) == RightResult.RIGHT_ALLOW : false; 2081 if (!hasRightToCreatePrivateProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2082 { 2083 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to create private project without sufficient rights"); 2084 } 2085 break; 2086 case MODERATED: 2087 boolean hasRightToCreateModeratedProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, catalogPage) == RightResult.RIGHT_ALLOW : false; 2088 if (!hasRightToCreateModeratedProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2089 { 2090 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to create public project with moderation without sufficient rights"); 2091 } 2092 break; 2093 case OPEN: 2094 boolean hasRightToCreateOpenProjetOnPages = catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, catalogPage) == RightResult.RIGHT_ALLOW : false; 2095 if (!hasRightToCreateOpenProjetOnPages && _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, "/${WorkspaceName}") != RightResult.RIGHT_ALLOW) 2096 { 2097 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to create public project without sufficient rights"); 2098 } 2099 break; 2100 default: 2101 throw new IllegalArgumentException("Inscription status '" + inscriptionStatus.toString() + "' is unknown"); 2102 } 2103 } 2104 2105 /** 2106 * Check rights to edit project 2107 * @param project the project 2108 * @param inscriptionStatus the inscription status 2109 * @param zoneItem the zoneItem containing catalog service. Must be a catalog service if not null 2110 */ 2111 public void checkRightsForProjectEdition(Project project, InscriptionStatus inscriptionStatus, ZoneItem zoneItem) 2112 { 2113 InscriptionStatus oldInscriptionStatus = project.getInscriptionStatus(); 2114 SitemapElement catalogPage = zoneItem != null ? zoneItem.getZone().getSitemapElement() : null; 2115 2116 if (catalogPage != null && !_projectRightHelper.hasCatalogReadAccess(zoneItem)) 2117 { 2118 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit project from page '" + catalogPage.getId() + "' without sufficient rights"); 2119 } 2120 2121 if (oldInscriptionStatus != inscriptionStatus) 2122 { 2123 boolean canCreatePrivateProjet = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2124 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PRIVATE, catalogPage) == RightResult.RIGHT_ALLOW : false); 2125 boolean canCreatePublicProjetWithModeration = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2126 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_MODERATED, catalogPage) == RightResult.RIGHT_ALLOW : false); 2127 boolean canCreatePublicProjet = _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, "/${WorkspaceName}") == RightResult.RIGHT_ALLOW 2128 || (catalogPage != null ? _rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_CREATE_PUBLIC_OPENED, catalogPage) == RightResult.RIGHT_ALLOW : false); 2129 2130 switch (oldInscriptionStatus) 2131 { 2132 case PRIVATE: 2133 if (!canCreatePrivateProjet) 2134 { 2135 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit private project without sufficient rights"); 2136 } 2137 break; 2138 case MODERATED: 2139 if (!canCreatePublicProjetWithModeration) 2140 { 2141 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit public project with moderation without sufficient rights"); 2142 } 2143 break; 2144 case OPEN: 2145 if (!canCreatePublicProjet) 2146 { 2147 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit public project without sufficient rights"); 2148 } 2149 break; 2150 default: 2151 throw new IllegalArgumentException("Inscription status '" + oldInscriptionStatus.toString() + "' is unknown"); 2152 } 2153 2154 switch (inscriptionStatus) 2155 { 2156 case PRIVATE: 2157 if (!canCreatePrivateProjet) 2158 { 2159 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit project to private project without sufficient rights"); 2160 } 2161 break; 2162 case MODERATED: 2163 if (!canCreatePublicProjetWithModeration) 2164 { 2165 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit project to public project with moderation without sufficient rights"); 2166 } 2167 break; 2168 case OPEN: 2169 if (!canCreatePublicProjet) 2170 { 2171 throw new AccessDeniedException("User " + _currentUserProvider.getUser() + " tried to edit project to public project without sufficient rights"); 2172 } 2173 break; 2174 default: 2175 throw new IllegalArgumentException("Inscription status '" + inscriptionStatus.toString() + "' is unknown"); 2176 } 2177 } 2178 } 2179 2180 /** 2181 * Clear the site cache 2182 */ 2183 public void clearCaches () 2184 { 2185 _getMemorySiteAssociationCache().invalidateAll(); 2186 _getMemoryProjectCache().invalidateAll(); 2187 _getMemoryPageCache().invalidateAll(); 2188 _getRequestProjectCache().invalidateAll(); 2189 _getRequestPageCache().invalidateAll(); 2190 } 2191 2192 private Cache<String, List<Pair<String, String>>> _getMemorySiteAssociationCache() 2193 { 2194 return _cacheManager.get(MEMORY_SITEASSOCIATION_CACHE); 2195 } 2196 2197 private Cache<String, String> _getMemoryProjectCache() 2198 { 2199 return _cacheManager.get(MEMORY_PROJECTIDBYNAMECACHE); 2200 } 2201 2202 private Cache<ModuleCacheKey, Set<String>> _getMemoryPageCache() 2203 { 2204 return _cacheManager.get(MEMORY_PAGESBYIDCACHE); 2205 } 2206 2207 private Cache<RequestProjectCacheKey, Project> _getRequestProjectCache() 2208 { 2209 return _cacheManager.get(REQUEST_PROJECTBYID_CACHE); 2210 } 2211 2212 private Cache<RequestModuleCacheKey, Set<Page>> _getRequestPageCache() 2213 { 2214 return _cacheManager.get(REQUEST_PAGESBYPROJECTANDMODULE_CACHE); 2215 } 2216 2217 2218 /** 2219 * Creates the caches 2220 */ 2221 protected void _createCaches() 2222 { 2223 _cacheManager.createMemoryCache(MEMORY_SITEASSOCIATION_CACHE, 2224 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_LABEL"), 2225 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_CACHE_PROJECT_MANAGER_DESCRIPTION"), 2226 true, 2227 null); 2228 _cacheManager.createMemoryCache(MEMORY_PROJECTIDBYNAMECACHE, 2229 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_UUID_CACHE_LABEL"), 2230 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_UUID_CACHE_DESCRIPTION"), 2231 true, 2232 null); 2233 _cacheManager.createMemoryCache(MEMORY_PAGESBYIDCACHE, 2234 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEUUID_CACHE_LABEL"), 2235 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEUUID_CACHE_DESCRIPTION"), 2236 true, 2237 null); 2238 _cacheManager.createRequestCache(REQUEST_PROJECTBYID_CACHE, 2239 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_REQUEST_CACHE_LABEL"), 2240 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_REQUEST_CACHE_DESCRIPTION"), 2241 false); 2242 _cacheManager.createRequestCache(REQUEST_PAGESBYPROJECTANDMODULE_CACHE, 2243 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEREQUEST_CACHE_LABEL"), 2244 new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_MANAGER_PAGEREQUEST_CACHE_DESCRIPTION"), 2245 false); 2246 } 2247 2248 private synchronized Map<String, String> _getUUIDCache() 2249 { 2250 if (!_getMemoryProjectCache().hasKey(__IS_CACHE_FILLED)) 2251 { 2252 Session defaultSession = null; 2253 try 2254 { 2255 // Force default workspace to execute query 2256 defaultSession = _repository.login(RepositoryConstants.DEFAULT_WORKSPACE); 2257 2258 String jcrQuery = "//element(*, ametys:project)"; 2259 2260 AmetysObjectIterable<Project> projects = _resolver.query(jcrQuery, defaultSession); 2261 2262 for (Project project : projects) 2263 { 2264 _getMemoryProjectCache().put(project.getName(), project.getId()); 2265 } 2266 2267 _getMemoryProjectCache().put(__IS_CACHE_FILLED, null); 2268 } 2269 catch (RepositoryException e) 2270 { 2271 throw new AmetysRepositoryException(e); 2272 } 2273 finally 2274 { 2275 if (defaultSession != null) 2276 { 2277 defaultSession.logout(); 2278 } 2279 } 2280 } 2281 2282 Map<String, String> cacheAsMap = _getMemoryProjectCache().asMap(); 2283 cacheAsMap.remove(__IS_CACHE_FILLED); 2284 return cacheAsMap; 2285 } 2286 2287 private static final class RequestProjectCacheKey extends AbstractCacheKey 2288 { 2289 private RequestProjectCacheKey(String projectName, String workspaceName) 2290 { 2291 super(projectName, workspaceName); 2292 } 2293 2294 static RequestProjectCacheKey of(String projectName, String workspaceName) 2295 { 2296 return new RequestProjectCacheKey(projectName, workspaceName); 2297 } 2298 } 2299 2300 private static final class ModuleCacheKey extends AbstractCacheKey 2301 { 2302 private ModuleCacheKey(String projectName, String moduleId) 2303 { 2304 super(projectName, moduleId); 2305 } 2306 2307 static ModuleCacheKey of(String projectName, String moduleId) 2308 { 2309 return new ModuleCacheKey(projectName, moduleId); 2310 } 2311 } 2312 2313 private static final class RequestModuleCacheKey extends AbstractCacheKey 2314 { 2315 private RequestModuleCacheKey(String projectName, String moduleId, String workspaceName) 2316 { 2317 super(projectName, moduleId, workspaceName); 2318 } 2319 2320 static RequestModuleCacheKey of(String projectName, String moduleId, String workspaceName) 2321 { 2322 return new RequestModuleCacheKey(projectName, moduleId, workspaceName); 2323 } 2324 } 2325 2326 private Request _getRequest () 2327 { 2328 try 2329 { 2330 return (Request) _context.get(ContextHelper.CONTEXT_REQUEST_OBJECT); 2331 } 2332 catch (ContextException ce) 2333 { 2334 getLogger().info("Unable to get the request", ce); 2335 return null; 2336 } 2337 } 2338 2339 /** 2340 * Retrieves all projects for client side 2341 * @return the projects 2342 */ 2343 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 2344 public List<Map<String, Object>> getProjectsStatisticsForClientSide() 2345 { 2346 return getProjects() 2347 .stream() 2348 .map(p -> getProjectStatistics(p)) 2349 .collect(Collectors.toList()); 2350 } 2351 2352 /** 2353 * Retrieves the standard information of a project 2354 * @param project The project 2355 * @return The map of information 2356 */ 2357 public Map<String, Object> getProjectStatistics(Project project) 2358 { 2359 Map<String, Object> statistics = new HashMap<>(); 2360 2361 statistics.put("title", project.getTitle()); 2362 2363 long totalSize = 0; 2364 for (WorkspaceModule moduleManager : _moduleManagerEP.getModules()) 2365 { 2366 Map<String, Object> moduleStatistics = moduleManager.getStatistics(project); 2367 statistics.putAll(moduleStatistics); 2368 Long size = (Long) moduleStatistics.get(moduleManager.getModuleSizeKey()); 2369 totalSize += (size != null && size >= 0) ? (Long) moduleStatistics.get(moduleManager.getModuleSizeKey()) : 0; 2370 } 2371 2372 statistics.put("totalSize", totalSize); 2373 2374 ZonedDateTime creationDate = project.getCreationDate(); 2375 2376 statistics.put("creationDate", creationDate); 2377 statistics.put("managers", Arrays.stream(project.getManagers()) 2378 .map(u -> _userHelper.user2json(u)) 2379 .collect(Collectors.toList())); 2380 2381 return statistics; 2382 } 2383 2384 /** 2385 * Retrieves all projects for client side 2386 * @return the projects 2387 */ 2388 @Callable(rights = "Runtime_Rights_Admin_Access", context = "/admin") 2389 public List<Map<String, Object>> getProjectsStatisticsColumnsModel() 2390 { 2391 return getStatisticHeaders() 2392 .stream() 2393 .map(p -> p.convertToJSON()) 2394 .collect(Collectors.toList()); 2395 } 2396 2397 private List<StatisticColumn> getStatisticHeaders() 2398 { 2399 2400 List<StatisticColumn> flatStatisticHeaders = getFlatStatisticHeaders(); 2401 List<StatisticColumn> headers = new ArrayList<>(); 2402 for (StatisticColumn statisticColumn : flatStatisticHeaders) 2403 { 2404 // this column have a parent, we have to find it and attach it 2405 if (statisticColumn.getGroup() != null) 2406 { 2407 Optional<StatisticColumn> parent = flatStatisticHeaders.stream() 2408 .filter(column -> column.getId().equals(statisticColumn.getGroup())) 2409 .findAny(); 2410 if (parent.isPresent()) 2411 { 2412 parent.get().addSubColumn(statisticColumn); 2413 } 2414 } 2415 else 2416 { 2417 headers.add(statisticColumn); 2418 } 2419 } 2420 2421 return headers; 2422 } 2423 2424 private List<StatisticColumn> getFlatStatisticHeaders() 2425 { 2426 List<StatisticColumn> headers = new ArrayList<>(); 2427 headers.add(new StatisticColumn("title", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_TITLE")) 2428 .withType(StatisticsColumnType.STRING) 2429 .withWidth(200) 2430 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderTitle")); 2431 headers.add(new StatisticColumn("creationDate", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_CREATION")) 2432 .withType(StatisticsColumnType.DATE) 2433 .withWidth(150)); 2434 headers.add(new StatisticColumn("managers", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MANAGERS")) 2435 .withRenderer("Ametys.grid.GridColumnHelper.renderUser") 2436 .withFilter(false)); 2437 for (WorkspaceModule moduleManager : _moduleManagerEP.getModules()) 2438 { 2439 headers.addAll(moduleManager.getStatisticModel()); 2440 } 2441 2442 StatisticColumn elements = new StatisticColumn(WorkspaceModule.GROUP_HEADER_ELEMENTS_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_ELEMENTS")) 2443 .withFilter(false); 2444 headers.add(elements); 2445 2446 StatisticColumn activatedModules = new StatisticColumn(WorkspaceModule.GROUP_HEADER_ACTIVATED_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_ACTIVE_MODULES")) 2447 .isHidden(true) 2448 .withFilter(false); 2449 headers.add(activatedModules); 2450 2451 StatisticColumn lastActivity = new StatisticColumn(WorkspaceModule.GROUP_HEADER_LAST_ACTIVITY_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_LAST_ACTIVITY")).isHidden(true); 2452 headers.add(lastActivity); 2453 2454 StatisticColumn modulesSize = new StatisticColumn(WorkspaceModule.GROUP_HEADER_SIZE_ID, new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MODULES_SIZE")) 2455 .withFilter(false); 2456 modulesSize.addSubColumn(new StatisticColumn("totalSize", new I18nizableText("plugin.workspaces", "PLUGINS_WORKSPACES_PROJECT_STATISTICS_TOOL_COLUMN_MODULES_SIZE_TOTAL")) 2457 .withRenderer("Ametys.plugins.workspaces.project.tool.ProjectsGridHelper.renderSize") 2458 .withType(StatisticsColumnType.LONG)); 2459 2460 headers.add(modulesSize); 2461 2462 return headers; 2463 } 2464 2465 /** 2466 * Check if the user is in one of the populations of project 2467 * @param project the project 2468 * @param user the user 2469 * @return true if the user is in one of the populations of project 2470 */ 2471 public boolean isUserInProjectPopulations(Project project, UserIdentity user) 2472 { 2473 Site site = project.getSite(); 2474 2475 if (site == null) 2476 { 2477 throw new IllegalArgumentException("Cannot determine if user " + UserIdentity.userIdentityToString(user) + " can connect to the project " + project.getName() + " since the project has no associated site to determine the populations"); 2478 } 2479 String siteName = site.getName(); 2480 2481 Set<String> populations = _populationContextHelper.getUserPopulationsOnContext("/sites/" + siteName, false); 2482 Set<String> frontPopulations = _populationContextHelper.getUserPopulationsOnContext("/sites-fo/" + siteName, false); 2483 2484 return populations.contains(user.getPopulationId()) || frontPopulations.contains(user.getPopulationId()); 2485 } 2486 2487 /** 2488 * Thrown to indicate that the catalog site is unknown 2489 */ 2490 public class UnknownCatalogSiteException extends IllegalArgumentException 2491 { 2492 /** 2493 * Construct a {@code UnknownCatalogSiteException} with the specified message 2494 * @param message the message 2495 */ 2496 public UnknownCatalogSiteException(String message) 2497 { 2498 super(message); 2499 } 2500 } 2501 2502 /** 2503 * Thrown to indicate that the user directory site is unknown 2504 */ 2505 public class UnknownUserDirectorySiteException extends IllegalArgumentException 2506 { 2507 /** 2508 * Construct a {@code UnknownUserDirectorySiteException} with the specified message 2509 * @param message the message 2510 */ 2511 public UnknownUserDirectorySiteException(String message) 2512 { 2513 super(message); 2514 } 2515 } 2516}