001/* 002 * Copyright 2016 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.workspaces.project.generators; 017 018import java.io.IOException; 019import java.util.Arrays; 020import java.util.Comparator; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Optional; 027import java.util.Set; 028import java.util.function.Consumer; 029import java.util.function.Function; 030import java.util.stream.Stream; 031 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.cocoon.ProcessingException; 035import org.apache.cocoon.environment.ObjectModelHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.cocoon.generation.ServiceableGenerator; 038import org.apache.cocoon.xml.AttributesImpl; 039import org.apache.cocoon.xml.XMLUtils; 040import org.apache.commons.lang3.StringUtils; 041import org.xml.sax.SAXException; 042 043import org.ametys.cms.tag.AbstractTagProviderExtensionPoint; 044import org.ametys.cms.tag.DefaultTag; 045import org.ametys.cms.tag.TagProvider; 046import org.ametys.core.user.CurrentUserProvider; 047import org.ametys.core.user.UserIdentity; 048import org.ametys.core.user.UserManager; 049import org.ametys.core.user.population.PopulationContextHelper; 050import org.ametys.core.util.LambdaUtils; 051import org.ametys.core.util.LambdaUtils.ThrowingConsumer; 052import org.ametys.plugins.core.user.UserHelper; 053import org.ametys.plugins.workspaces.categories.Category; 054import org.ametys.plugins.workspaces.categories.CategoryHelper; 055import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint; 056import org.ametys.plugins.workspaces.documents.WorkspaceExplorerResourceDAO; 057import org.ametys.plugins.workspaces.members.ProjectMemberManager; 058import org.ametys.plugins.workspaces.project.ProjectManager; 059import org.ametys.plugins.workspaces.project.objects.Project; 060import org.ametys.plugins.workspaces.project.objects.Project.InscriptionStatus; 061import org.ametys.plugins.workspaces.tags.ProjectTagProviderExtensionPoint; 062import org.ametys.plugins.workspaces.tasks.WorkspaceTaskDAO; 063import org.ametys.plugins.workspaces.threads.WorkspaceThreadDAO; 064import org.ametys.web.WebConstants; 065import org.ametys.web.repository.page.ZoneItem; 066import org.ametys.web.repository.site.Site; 067 068/** 069 * Generator used to gather informations on all of the projects of the application for the 070 * projects' catalogue service 071 */ 072public class SimpleProjectsCatalogueGenerator extends ServiceableGenerator 073{ 074 /** The project manager component */ 075 private ProjectManager _projectManager; 076 077 /** The project member manager */ 078 private ProjectMemberManager _projectMemberManager; 079 080 /** The user manager */ 081 private UserManager _userManager; 082 083 /** The current user provider */ 084 private CurrentUserProvider _currentUserProvider; 085 086 private UserHelper _userHelper; 087 088 /** The population context helper */ 089 private PopulationContextHelper _populationContextHelper; 090 091 /** The documents module DAO */ 092 private WorkspaceExplorerResourceDAO _workspaceExplorerResourceDAO; 093 094 /** The tasks module DAO */ 095 private WorkspaceTaskDAO _workspaceTaskDAO; 096 097 /** Workspace threads DAO */ 098 private WorkspaceThreadDAO _workspaceThreadDAO; 099 100 private CategoryProviderExtensionPoint _categoryProviderEP; 101 private ProjectTagProviderExtensionPoint _projectTagProviderEP; 102 private CategoryHelper _categoryHelper; 103 104 @Override 105 public void service(ServiceManager serviceManager) throws ServiceException 106 { 107 super.service(serviceManager); 108 109 _projectManager = (ProjectManager) serviceManager.lookup(ProjectManager.ROLE); 110 _projectMemberManager = (ProjectMemberManager) serviceManager.lookup(ProjectMemberManager.ROLE); 111 _userManager = (UserManager) serviceManager.lookup(UserManager.ROLE); 112 _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE); 113 _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE); 114 _populationContextHelper = (PopulationContextHelper) serviceManager.lookup(PopulationContextHelper.ROLE); 115 _workspaceExplorerResourceDAO = (WorkspaceExplorerResourceDAO) serviceManager.lookup(WorkspaceExplorerResourceDAO.ROLE); 116 _workspaceTaskDAO = (WorkspaceTaskDAO) serviceManager.lookup(WorkspaceTaskDAO.ROLE); 117 _workspaceThreadDAO = (WorkspaceThreadDAO) serviceManager.lookup(WorkspaceThreadDAO.ROLE); 118 _projectTagProviderEP = (ProjectTagProviderExtensionPoint) manager.lookup(ProjectTagProviderExtensionPoint.ROLE); 119 _categoryProviderEP = (CategoryProviderExtensionPoint) serviceManager.lookup(CategoryProviderExtensionPoint.ROLE); 120 _categoryHelper = (CategoryHelper) serviceManager.lookup(CategoryHelper.ROLE); 121 } 122 123 @Override 124 public void generate() throws IOException, SAXException, ProcessingException 125 { 126 int max = parameters.getParameterAsInteger("max-results", 0); 127 128 Request request = ObjectModelHelper.getRequest(objectModel); 129 130 ZoneItem zoneItem = (ZoneItem) request.getAttribute(WebConstants.REQUEST_ATTR_ZONEITEM); 131 boolean memberOnly = zoneItem.getServiceParameters().getValue("memberOnly", false, false); 132 133 String[] filterCategoriesArray = zoneItem.getServiceParameters().getValue("filterCategories", false, new String[0]); 134 List<String> filterCategories = Arrays.asList(filterCategoriesArray); 135 136 contentHandler.startDocument(); 137 AttributesImpl attrs = new AttributesImpl(); 138 attrs.addCDATAAttribute("zoneItemId", zoneItem.getId()); 139 XMLUtils.startElement(contentHandler, "projects", attrs); 140 141 if (_currentUserProvider.getUser() != null) 142 { 143 // Normalized, case insensitive comparator on project titles. 144 Function<Project, String> getProjectTitle = Project::getTitle; 145 Comparator<Project> projectTitleComparator = Comparator.comparing(getProjectTitle.andThen(StringUtils::stripAccents), String.CASE_INSENSITIVE_ORDER); 146 147 Stream<Project> projectStream; 148 if (memberOnly) 149 { 150 projectStream = _projectManager.getUserProjects(_currentUserProvider.getUser(), filterCategories) 151 .keySet() 152 .stream() 153 .sorted(projectTitleComparator); 154 } 155 else 156 { 157 projectStream = _projectManager.getProjects().stream() 158 .filter(project -> !project.getInscriptionStatus().equals(InscriptionStatus.PRIVATE) || _projectMemberManager.isProjectMember(project, _currentUserProvider.getUser())) 159 .filter(project -> _filterCategories(project, filterCategories)) 160 .sorted(projectTitleComparator); 161 } 162 163 // limit 164 if (max > 0) 165 { 166 projectStream = projectStream.limit(max); 167 } 168 169 // SAX the projects 170 projectStream.forEach(p -> _saxProject(p)); 171 } 172 173 XMLUtils.endElement(contentHandler, "projects"); 174 contentHandler.endDocument(); 175 } 176 177 private boolean _filterCategories(Project project, List<String> filterCategories) 178 { 179 if (filterCategories.isEmpty()) 180 { 181 return true; 182 } 183 else 184 { 185 Set<String> categories = project.getCategories(); 186 for (String category : categories) 187 { 188 if (filterCategories.contains(category)) 189 { 190 // At least one category matches 191 return true; 192 } 193 } 194 195 return false; 196 } 197 198 } 199 200 private void _saxProject(Project project) 201 { 202 try 203 { 204 Iterator<Site> projectSitesIterator = project.getSites().iterator(); 205 Site site = projectSitesIterator.hasNext() ? projectSitesIterator.next() : null; 206 if (site == null) 207 { 208 // log and exit prematurely 209 if (getLogger().isWarnEnabled()) 210 { 211 getLogger().warn(String.format("The project '%s' does not have any associated site.", project.getTitle())); 212 } 213 214 return; 215 } 216 217 UserIdentity userIdentity = _currentUserProvider.getUser(); 218 boolean hasAccess = _projectMemberManager.isProjectMember(project, userIdentity); 219 220 AttributesImpl attrs = new AttributesImpl(); 221 attrs.addCDATAAttribute("id", project.getId()); 222 attrs.addCDATAAttribute("name", project.getName()); 223 attrs.addCDATAAttribute("siteName", site.getName()); 224 attrs.addCDATAAttribute("hasAccess", Boolean.toString(hasAccess)); 225 InscriptionStatus inscriptionStatus = project.getInscriptionStatus(); 226 attrs.addCDATAAttribute("inscriptionStatus", inscriptionStatus.toString()); 227 if (!hasAccess && !inscriptionStatus.equals(InscriptionStatus.PRIVATE) && userIdentity != null) 228 { 229 boolean inPopulations = _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), false).contains(userIdentity.getPopulationId()) 230 || _populationContextHelper.getUserPopulationsOnContext("/sites-fo/" + site.getName(), false).contains(userIdentity.getPopulationId()); 231 attrs.addCDATAAttribute("inPopulations", Boolean.toString(inPopulations)); 232 } 233 234 XMLUtils.startElement(contentHandler, "project", attrs); 235 236 XMLUtils.createElement(contentHandler, "title", project.getTitle()); 237 String siteUrl = site.getUrl(); 238 if (siteUrl != null) 239 { 240 XMLUtils.createElement(contentHandler, "url", site.getUrl()); 241 } 242 XMLUtils.createElement(contentHandler, "description", StringUtils.defaultString(project.getDescription())); 243 244 site.illustrationToSAX(contentHandler); 245 project.coverImageToSAX(contentHandler); 246 247 // Managers 248 XMLUtils.startElement(contentHandler, "managers"); 249 Arrays.stream(project.getManagers()) 250 .map(_userManager::getUser) 251 .filter(Objects::nonNull) 252 .forEach(LambdaUtils.wrapConsumer(m -> _userHelper.saxUser(m, contentHandler, "manager"))); 253 XMLUtils.endElement(contentHandler, "managers"); 254 255 // Tags 256 XMLUtils.startElement(contentHandler, "projectTags"); 257 project.getTags().stream() 258 .forEach(_saxDefaultTag(_projectTagProviderEP, "projectTag")); 259 XMLUtils.endElement(contentHandler, "projectTags"); 260 261 // Keywords 262 XMLUtils.startElement(contentHandler, "keywords"); 263 Arrays.stream(project.getKeywords()) 264 .forEach(LambdaUtils.wrapConsumer(keyword -> XMLUtils.createElement(contentHandler, "keyword", keyword))); 265 XMLUtils.endElement(contentHandler, "keywords"); 266 267 // Categories 268 XMLUtils.startElement(contentHandler, "categories"); 269 project.getCategories().stream() 270 .forEach(_saxCategory("category")); 271 XMLUtils.endElement(contentHandler, "categories"); 272 273 // Stats 274 XMLUtils.startElement(contentHandler, "statistics"); 275 _saxProjectStatistic("nbProjectMember", _projectMemberManager.getMembersCount(project)); 276 _saxProjectStatistic("nbProjectDocuments", _workspaceExplorerResourceDAO.getDocumentsCount(project)); 277 _saxProjectStatistic("nbProjectTasks", _workspaceTaskDAO.getTasksCount(project)); 278 _saxProjectStatistic("nbProjectThreads", _workspaceThreadDAO.getThreadsCount(project)); 279 XMLUtils.endElement(contentHandler, "statistics"); 280 281 XMLUtils.endElement(contentHandler, "project"); 282 } 283 catch (SAXException | IOException e) 284 { 285 throw new RuntimeException(String.format("Unable to SAX project with id '%s'", project.getId()), e); 286 } 287 } 288 289 private Consumer<String> _saxCategory(String tagName) 290 { 291 Map<String, Object> contextualParameter = new HashMap<>(); 292 Function<String, Function<TagProvider<Category>, Category>> getTagFromExtension = categoryId -> extension -> extension.getTag(categoryId, contextualParameter); 293 Consumer<Category> saxCategory = LambdaUtils.wrapConsumer(category -> 294 { 295 XMLUtils.startElement(contentHandler, tagName); 296 category.getTitle().toSAX(contentHandler, "title"); 297 _saxCategoryColor(category); 298 XMLUtils.endElement(contentHandler, tagName); 299 }); 300 301 return categoryId -> _categoryProviderEP.getExtensionsIds().stream() 302 .map(_categoryProviderEP::getExtension) 303 .map(getTagFromExtension.apply(categoryId)) 304 .filter(Objects::nonNull) 305 .findFirst() 306 .ifPresent(saxCategory); 307 } 308 309 private Consumer<String> _saxDefaultTag(AbstractTagProviderExtensionPoint<DefaultTag> provider, String tagName) 310 { 311 Map<String, Object> contextualParameter = new HashMap<>(); 312 Function<String, Function<TagProvider<DefaultTag>, DefaultTag>> getTagFromExtension = keywordId -> extension -> extension.getTag(keywordId, contextualParameter); 313 Consumer<DefaultTag> saxTag = LambdaUtils.wrapConsumer(tag -> tag.getTitle().toSAX(contentHandler, tagName)); 314 315 return keywordId -> provider.getExtensionsIds().stream() 316 .map(provider::getExtension) 317 .map(getTagFromExtension.apply(keywordId)) 318 .filter(Objects::nonNull) 319 .findFirst() 320 .ifPresent(saxTag); 321 } 322 323 private void _saxProjectStatistic(String elementName, Long count) 324 { 325 ThrowingConsumer<Long> saxElement = value -> XMLUtils.createElement(contentHandler, elementName, Long.toString(value)); 326 327 Optional.ofNullable(count) 328 .ifPresent(LambdaUtils.wrapConsumer(saxElement)); 329 } 330 331 private void _saxCategoryColor(Category category) throws SAXException 332 { 333 Map<String, String> colors = _categoryHelper.getCategoryColor(category); 334 335 XMLUtils.startElement(contentHandler, "color"); 336 colors.entrySet().stream() 337 .forEach(LambdaUtils.wrapConsumer(entry -> XMLUtils.createElement(contentHandler, entry.getKey(), entry.getValue()))); 338 XMLUtils.endElement(contentHandler, "color"); 339 } 340 341}