001/*
002 *  Copyright 2025 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.activities.activitystream;
017
018import java.time.ZonedDateTime;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.cocoon.components.ContextHelper;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.lang3.StringUtils;
038
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.core.userpref.UserPreferencesException;
042import org.ametys.core.userpref.UserPreferencesManager;
043import org.ametys.core.util.DateUtils;
044import org.ametys.plugins.workspaces.project.ProjectManager;
045import org.ametys.plugins.workspaces.project.modules.WorkspaceModule;
046import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint;
047import org.ametys.plugins.workspaces.project.objects.Project;
048import org.ametys.web.WebHelper;
049
050/**
051 * Component to return a map for action to get the activities for activity stream
052 */
053public class ActivityStreamServiceComponent implements Serviceable, Component, Contextualizable
054{
055    /** The avalon role */
056    public static final String ROLE = ActivityStreamServiceComponent.class.getName();
057    
058    private ActivityStreamClientInteraction _activityStream;
059    private ProjectManager _projectManager;
060    private UserPreferencesManager _userPrefManager;
061    private CurrentUserProvider _currentUserProvider;
062    private WorkspaceModuleExtensionPoint _moduleEP;
063    private Context _context;
064    
065    public void service(ServiceManager manager) throws ServiceException
066    {
067        _activityStream = (ActivityStreamClientInteraction) manager.lookup(ActivityStreamClientInteraction.ROLE);
068        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
069        _userPrefManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE);
070        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
071        _moduleEP = (WorkspaceModuleExtensionPoint) manager.lookup(WorkspaceModuleExtensionPoint.ROLE);
072    }
073    
074    public void contextualize(Context context) throws ContextException
075    {
076        _context = context;
077    }
078    
079    /**
080     * Read many request parameters to get the activity stream
081     * @param limit The max number of results
082     * @param allActivities All activities of any project (or the current one only)
083     * @param markAsRead Mark the activities as read
084     * @return The activity stream
085     * @throws UserPreferencesException If an error occurs while marking activities as read
086     */
087    public Map<String, Object> getActivityStream(int limit, boolean allActivities, boolean markAsRead) throws UserPreferencesException
088    {
089        Request request = ContextHelper.getRequest(_context);
090        
091        // The filtered modules
092        Set<String> activityTypes = _getFilteredActivityTypes(request);
093        
094        // The filtered projects' categories
095        Set<String> categories = _getFilteredCategories(request);
096        
097        // Fultext search
098        String pattern = request.getParameter("pattern");
099        
100        // Dante range
101        ZonedDateTime fromDate = null;
102        String fromDateAsStr = request.getParameter("fromDate");
103        if (StringUtils.isNotEmpty(fromDateAsStr))
104        {
105            fromDate = DateUtils.parseZonedDateTime(fromDateAsStr);
106        }
107        ZonedDateTime untilDate = null;
108        String untilDateAsStr = request.getParameter("untilDate");
109        if (StringUtils.isNotEmpty(untilDateAsStr))
110        {
111            untilDate = DateUtils.parseZonedDateTime(untilDateAsStr);
112        }
113        
114        String siteName = WebHelper.getSiteName(request);
115        
116        List<String> projects = _projectManager.getProjectsForSite(siteName);
117        
118        List<Map<String, Object>> activities;
119        if (projects.isEmpty() || allActivities)
120        {
121            // Get activity stream of all current user's projects
122            activities = _activityStream.getActivitiesForCurrentUser(pattern, categories, activityTypes, fromDate, untilDate, limit);
123        }
124        else
125        {
126            // Get activity stream  of current project only
127            Project project = _projectManager.getProject(projects.get(0));
128            activities = _activityStream.getActivitiesForCurrentUser(Collections.singleton(project), activityTypes, fromDate, untilDate, pattern, limit);
129        }
130        
131        UserIdentity user = _currentUserProvider.getUser();
132        if (markAsRead && user != null)
133        {
134            // Mark as read in user prefs
135            _userPrefManager.setUserPreferences(user, ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_CONTEXT, Map.of(), Map.of(ActivityStreamClientInteraction.ACTIVITY_STREAM_USER_PREF_LAST_UPDATE, DateUtils.dateToString(new Date())));
136        }
137        
138        Map<String, Object> result = new HashMap<>();
139        // FIXME this should be rename activities
140        result.put("events", activities);
141        
142        return result;
143    }
144    
145    private Set<String> _getFilteredActivityTypes(Request request)
146    {
147        String[] moduleIds = request.getParameterValues("modules");
148        if (moduleIds != null)
149        {
150            return Arrays.stream(moduleIds)
151                    .filter(StringUtils::isNotEmpty)
152                    .map(id -> _moduleEP.<WorkspaceModule>getModule(id))
153                    .map(WorkspaceModule::getAllowedEventTypes)
154                    .flatMap(Set::stream)
155                    .collect(Collectors.toSet());
156        }
157        else
158        {
159            return Set.of();
160        }
161    }
162    
163    private Set<String> _getFilteredCategories(Request request)
164    {
165        String[] categoryIds = request.getParameterValues("categories");
166        if (categoryIds != null)
167        {
168            return Arrays.stream(categoryIds)
169                    .filter(StringUtils::isNotEmpty)
170                    .collect(Collectors.toSet());
171        }
172        else
173        {
174            return Set.of();
175        }
176    }
177}