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