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}