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