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.search.module; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.Map; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.cocoon.ProcessingException; 029import org.apache.cocoon.environment.ObjectModelHelper; 030import org.apache.cocoon.environment.Request; 031import org.apache.cocoon.generation.ServiceableGenerator; 032import org.apache.cocoon.xml.AttributesImpl; 033import org.apache.cocoon.xml.XMLUtils; 034import org.apache.commons.lang3.StringUtils; 035import org.xml.sax.SAXException; 036 037import org.ametys.core.right.RightManager; 038import org.ametys.core.user.CurrentUserProvider; 039import org.ametys.core.user.UserIdentity; 040import org.ametys.core.util.LambdaUtils; 041import org.ametys.plugins.core.user.UserHelper; 042import org.ametys.plugins.repository.AmetysObject; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.workspaces.categories.Category; 045import org.ametys.plugins.workspaces.categories.CategoryHelper; 046import org.ametys.plugins.workspaces.categories.CategoryProviderExtensionPoint; 047import org.ametys.plugins.workspaces.members.ProjectMemberManager; 048import org.ametys.plugins.workspaces.project.ProjectManager; 049import org.ametys.plugins.workspaces.project.modules.WorkspaceModule; 050import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 051import org.ametys.plugins.workspaces.project.objects.Project; 052import org.ametys.web.WebConstants; 053import org.ametys.web.WebHelper; 054import org.ametys.web.repository.site.SiteManager; 055 056/** 057 * Abstract generator for search modules 058 * 059 */ 060public abstract class AbstractSearchModuleGenerator extends ServiceableGenerator 061{ 062 /** The ametys object resolver */ 063 protected AmetysObjectResolver _resolver; 064 /** The project manager */ 065 protected ProjectManager _projectManager; 066 /** The current user provider */ 067 protected CurrentUserProvider _currentUserProvider; 068 /** The project member manager */ 069 protected ProjectMemberManager _projectMembers; 070 /** CategoryProviderExtensionPoint */ 071 protected CategoryProviderExtensionPoint _categoryProviderEP; 072 /** The category helper */ 073 protected CategoryHelper _categoryHelper; 074 /** The user helper */ 075 protected UserHelper _userHelper; 076 /** The site manager */ 077 protected SiteManager _siteManager; 078 /** WorkspaceModuleExtensionPoint */ 079 protected WorkspaceModuleExtensionPoint _workspaceModuleEP; 080 /** Right manager */ 081 protected RightManager _rightManager; 082 083 @Override 084 public void service(ServiceManager smanager) throws ServiceException 085 { 086 super.service(smanager); 087 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 088 _projectManager = (ProjectManager) smanager.lookup(ProjectManager.ROLE); 089 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 090 _projectMembers = (ProjectMemberManager) smanager.lookup(ProjectMemberManager.ROLE); 091 _categoryProviderEP = (CategoryProviderExtensionPoint) smanager.lookup(CategoryProviderExtensionPoint.ROLE); 092 _categoryHelper = (CategoryHelper) smanager.lookup(CategoryHelper.ROLE); 093 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 094 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 095 _workspaceModuleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE); 096 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 097 } 098 099 public void generate() throws IOException, SAXException, ProcessingException 100 { 101 Request request = ObjectModelHelper.getRequest(objectModel); 102 103 String siteName = WebHelper.getSiteName(request); 104 String lang = request.getParameter("lang"); 105 String textfield = request.getParameter("textfield"); 106 107 request.setAttribute(WebConstants.REQUEST_ATTR_SITEMAP_NAME, lang); 108 request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, _siteManager.getSite(siteName).getSkinId()); 109 110 int offset = parameters.getParameterAsInteger("offset", 0); 111 int limit = parameters.getParameterAsInteger("limit", 10); 112 int minLimit = parameters.getParameterAsInteger("minLimit", 10); 113 114 contentHandler.startDocument(); 115 116 saxHits(siteName, lang, textfield, request, offset, limit, minLimit); 117 118 contentHandler.endDocument(); 119 } 120 121 /** 122 * Sax the results after search 123 * @param results list of results 124 * @param lang the current language 125 * @param offset the start of search 126 * @param limit the max number of results 127 * @param minLimit the min number of results 128 * @param totalCount total count available 129 * @throws SAXException if an error occurred while saxing 130 */ 131 protected void saxHits(List<? extends AmetysObject> results, String lang, int offset, int limit, int minLimit, long totalCount) throws SAXException 132 { 133 AttributesImpl attrs = new AttributesImpl(); 134 attrs.addCDATAAttribute("count", String.valueOf(results.size())); 135 attrs.addCDATAAttribute("total", String.valueOf(totalCount)); 136 attrs.addCDATAAttribute("offset", String.valueOf(offset)); 137 attrs.addCDATAAttribute("limit", String.valueOf(limit)); 138 attrs.addCDATAAttribute("minLimit", String.valueOf(minLimit)); 139 140 XMLUtils.startElement(contentHandler, "hits", attrs); 141 142 for (AmetysObject result : results) 143 { 144 try 145 { 146 saxHit(result, lang); 147 } 148 catch (Exception e) 149 { 150 getLogger().error("Unable to sax result for object of id '" + result.getId() + "'", e); 151 } 152 } 153 154 XMLUtils.endElement(contentHandler, "hits"); 155 } 156 157 /** 158 * Get the project holding the object. 159 * Be careful, use only for objects that belongs to a project (thread, task, event, ...) 160 * @param ao the Ametys object 161 * @return the project or null if nor found 162 */ 163 protected Project getProject(AmetysObject ao) 164 { 165 AmetysObject parent = ao.getParent(); 166 167 while (parent != null && !(parent instanceof Project)) 168 { 169 parent = parent.getParent(); 170 } 171 172 return parent != null ? (Project) parent : null; 173 } 174 175 /** 176 * SAX a project 177 * @param project the project 178 * @throws SAXException if an error occurred while saxing 179 */ 180 protected void saxProject(Project project) throws SAXException 181 { 182 if (project != null) 183 { 184 AttributesImpl attrs = new AttributesImpl(); 185 attrs.addCDATAAttribute("name", project.getName()); 186 XMLUtils.startElement(contentHandler, "project", attrs); 187 XMLUtils.createElement(contentHandler, "title", project.getTitle()); 188 Category category = project.getCategories() 189 .stream() 190 .findFirst() 191 .map(c -> _categoryProviderEP.getTag(c, null)) 192 .orElse(null); 193 saxCategory(category); 194 XMLUtils.endElement(contentHandler, "project"); 195 196 } 197 } 198 199 /** 200 * SAX category 201 * @param category the category 202 * @throws SAXException if an error occured while saxing 203 */ 204 protected void saxCategory(Category category) throws SAXException 205 { 206 if (category != null) 207 { 208 AttributesImpl attrs = new AttributesImpl(); 209 attrs.addCDATAAttribute("name", category.getName()); 210 XMLUtils.startElement(contentHandler, "category", attrs); 211 category.getTitle().toSAX(contentHandler, "title"); 212 saxCategoryColor(category); 213 XMLUtils.endElement(contentHandler, "category"); 214 } 215 } 216 217 /** 218 * SAX the category color 219 * @param category the category 220 * @throws SAXException if an error occured while saxing 221 */ 222 protected void saxCategoryColor(Category category) throws SAXException 223 { 224 Map<String, String> colors = _categoryHelper.getCategoryColor(category); 225 226 XMLUtils.startElement(contentHandler, "color"); 227 colors.entrySet().stream() 228 .forEach(LambdaUtils.wrapConsumer(entry -> XMLUtils.createElement(contentHandler, entry.getKey(), entry.getValue()))); 229 XMLUtils.endElement(contentHandler, "color"); 230 } 231 232 /** 233 * Get the project names targeted by the search 234 * @param request the request 235 * @param userOnly true to get user's projects only 236 * @return the project names 237 */ 238 public List<Project> getProjects(Request request, boolean userOnly) 239 { 240 List<String> projectNames = Arrays.asList(request.getParameter("project").split(",")) 241 .stream() 242 .filter(StringUtils::isNotEmpty) 243 .collect(Collectors.toList()); 244 245 List<String> filteredCategories = getCategories(request); 246 247 if (projectNames.size() > 0) 248 { 249 return projectNames 250 .stream() 251 .map(p -> _projectManager.getProject(p)) 252 .filter(p -> !Collections.disjoint(p.getCategories(), filteredCategories)) 253 .collect(Collectors.toList()); 254 } 255 else if (userOnly) 256 { 257 // Get user projects 258 UserIdentity user = _currentUserProvider.getUser(); 259 260 return _projectManager.getUserProjects(user, filteredCategories) 261 .keySet() 262 .stream() 263 .collect(Collectors.toList()); 264 } 265 else 266 { 267 // All projects 268 return _projectManager.getProjects(filteredCategories); 269 } 270 } 271 272 /** 273 * Filter a list of projects to return only those where the module is available 274 * @param projects list of projects 275 * @param moduleId id of the module to search for read access 276 * @return a filtered list of projects 277 */ 278 protected List<Project> filterProjectsForModule(List<Project> projects, String moduleId) 279 { 280 WorkspaceModule module = _workspaceModuleEP.getModule(moduleId); 281 282 if (module == null || projects == null || projects.isEmpty()) 283 { 284 return Collections.EMPTY_LIST; 285 } 286 287 List<Project> filteredProjects = projects.stream() 288 .filter(project -> _projectManager.isModuleActivated(project, module.getId())) 289 .filter(project -> _rightManager.currentUserHasReadAccess(module.getModuleRoot(project, false))) 290 .collect(Collectors.toList()); 291 292 return filteredProjects; 293 } 294 295 /** 296 * Get the category names targeted by the search 297 * @param request the request 298 * @return the category names 299 */ 300 protected List<String> getCategories(Request request) 301 { 302 List<String> categorieNames = Arrays.asList(request.getParameter("category").split(",")) 303 .stream() 304 .filter(StringUtils::isNotEmpty) 305 .collect(Collectors.toList()); 306 307 if (categorieNames.isEmpty()) 308 { 309 // Get all leaf categories 310 return _categoryHelper.getLeafCategories() 311 .stream() 312 .map(Category::getName) 313 .collect(Collectors.toList()); 314 } 315 else 316 { 317 List<String> filteredCategories = new ArrayList<>(); 318 for (String categoryName : categorieNames) 319 { 320 Category category = _categoryProviderEP.getTag(categoryName, Collections.EMPTY_MAP); 321 filteredCategories.addAll(_categoryHelper.getLeafCategories(category) 322 .stream() 323 .map(Category::getName) 324 .collect(Collectors.toList())); 325 } 326 327 return filteredCategories; 328 } 329 } 330 331 /** 332 * Sax the results 333 * @param siteName the current site name 334 * @param lang the current language 335 * @param textfield the search inputs 336 * @param request the request 337 * @param offset the start of search 338 * @param limit the max number of results 339 * @param minLimit the min number of results 340 * @throws SAXException if an error occurred while saxing 341 * @throws ProcessingException if the search failed 342 */ 343 protected abstract void saxHits(String siteName, String lang, String textfield, Request request, int offset, int limit, int minLimit) throws SAXException, ProcessingException; 344 345 /** 346 * Sax the content hit 347 * @param object the AmetysObject 348 * @param lang the language 349 * @throws Exception if an error occurred while saxing result 350 */ 351 protected abstract void saxHit(AmetysObject object, String lang) throws Exception; 352 353 /** 354 * SAX a user identity 355 * @param userIdentity the user identity 356 * @param tagName the tag name 357 * @throws SAXException if an error occurred while saxing 358 */ 359 protected void saxUser(UserIdentity userIdentity, String tagName) throws SAXException 360 { 361 if (userIdentity != null) 362 { 363 _userHelper.saxUserIdentity(userIdentity, contentHandler, tagName); 364 } 365 } 366 367}