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}