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.mobileapp.observer; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021import java.util.Map.Entry; 022import java.util.Objects; 023import java.util.Set; 024import java.util.function.Function; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030 031import org.ametys.core.observation.AsyncObserver; 032import org.ametys.core.observation.Event; 033import org.ametys.core.user.CurrentUserProvider; 034import org.ametys.core.user.User; 035import org.ametys.core.user.UserIdentity; 036import org.ametys.plugins.mobileapp.FeedHelper; 037import org.ametys.plugins.mobileapp.PushNotificationManager; 038import org.ametys.plugins.mobileapp.UserPreferencesHelper; 039import org.ametys.plugins.mobileapp.action.GetEventTypesAction; 040import org.ametys.plugins.repository.AmetysObjectResolver; 041import org.ametys.plugins.repository.activities.Activity; 042import org.ametys.plugins.repository.activities.ActivityType; 043import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType; 044import org.ametys.plugins.workspaces.members.ProjectMemberManager; 045import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember; 046import org.ametys.plugins.workspaces.project.ProjectManager; 047import org.ametys.plugins.workspaces.project.objects.Project; 048import org.ametys.runtime.config.Config; 049import org.ametys.runtime.plugin.PluginsManager; 050import org.ametys.runtime.plugin.component.AbstractLogEnabled; 051import org.ametys.web.renderingcontext.RenderingContext; 052import org.ametys.web.renderingcontext.RenderingContextHandler; 053/** 054 * On validation, test each query to notify impacted users 055 */ 056public class ProjectActivityObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable 057{ 058 private static final String __PROJECT_ENABLED_CONF_ID = "plugin.mobileapp.project.enabled"; 059 060 private FeedHelper _feedHelper; 061 private UserPreferencesHelper _userPreferencesHelper; 062 private PushNotificationManager _pushNotificationManager; 063 private ProjectManager _projectManager; 064 private AmetysObjectResolver _resolver; 065 private ProjectMemberManager _projectMemberHelper; 066 private CurrentUserProvider _currentUserProvider; 067 private RenderingContextHandler _renderingContextHandler; 068 069 @Override 070 public void service(ServiceManager manager) throws ServiceException 071 { 072 _feedHelper = (FeedHelper) manager.lookup(FeedHelper.ROLE); 073 _userPreferencesHelper = (UserPreferencesHelper) manager.lookup(UserPreferencesHelper.ROLE); 074 _pushNotificationManager = (PushNotificationManager) manager.lookup(PushNotificationManager.ROLE); 075 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 076 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 077 _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 078 079 if (PluginsManager.getInstance().isPluginActive("workspaces")) 080 { 081 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 082 _projectMemberHelper = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 083 } 084 } 085 086 public boolean supports(Event event) 087 { 088 return PluginsManager.getInstance().isPluginActive("workspaces") 089 && Config.getInstance().getValue(__PROJECT_ENABLED_CONF_ID, false, false) 090 && org.ametys.plugins.repository.ObservationConstants.EVENT_ACTIVITY_CREATED.contains(event.getId()); 091 } 092 093 public int getPriority() 094 { 095 return MAX_PRIORITY; 096 } 097 098 public void observe(Event event, Map<String, Object> transientVars) throws Exception 099 { 100 String activityId = (String) event.getArguments().get(org.ametys.plugins.repository.ObservationConstants.ARGS_ACTIVITY_ID); 101 Activity activity = _resolver.resolveById(activityId); 102 103 // Push notifications are only sent for allowed types 104 if (GetEventTypesAction.EVENT_TYPES.containsKey(activity.getEventType())) 105 { 106 ActivityType activityType = activity.getActivityType(); 107 if (activityType instanceof AbstractWorkspacesActivityType) 108 { 109 RenderingContext currentRenderingContext = _renderingContextHandler.getRenderingContext(); 110 try 111 { 112 // Force FRONT RenderingContext as it is reset to BACK being in an AsyncObserver 113 _renderingContextHandler.setRenderingContext(RenderingContext.FRONT); 114 Project project = _projectManager.getProject(activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME)); 115 getLogger().info("Listing push notification to send for activity on project '{}'", project.getId()); 116 117 Map<String, Object> activity2json = activity.toJSONForClient(); 118 119 Map<String, Object> projectJson = _feedHelper.projectToMap(project); 120 121 // we only look for tokens among project members 122 Set<ProjectMember> members = _projectMemberHelper.getProjectMembers(project, true); 123 List<UserIdentity> users = members.stream().map(member -> member.getUser()) 124 .filter(Objects::nonNull) 125 .map(User::getIdentity) 126 // User should not be notified by his own actions 127 .filter(user -> !user.equals(_currentUserProvider.getUser())) 128 .toList(); 129 130 // Get all map of language => tokens for all users, and generate a new big map language => tokens 131 Map<String, Map<UserIdentity, Set<String>>> langAndTokens = new HashMap<>(); 132 for (UserIdentity user : users) 133 { 134 Map<String, Set<String>> tokensForUser = _userPreferencesHelper.getUserImpactedTokens(user, project, activity.getEventType()); 135 for (Entry<String, Set<String>> tokensByLang : tokensForUser.entrySet()) 136 { 137 Map<UserIdentity, Set<String>> tokens = langAndTokens.computeIfAbsent(tokensByLang.getKey(), __ -> new HashMap<>()); 138 tokens.put(user, tokensByLang.getValue()); 139 } 140 } 141 142 if (getLogger().isDebugEnabled()) 143 { 144 getLogger().debug("Push tokens " + langAndTokens.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue().size()).collect(Collectors.joining(", "))); 145 } 146 147 // Get the activities infos for each languages 148 Map<String, Map<String, Object>> translatedActivityData = langAndTokens.keySet().stream() 149 .distinct() 150 .collect(Collectors.toMap(Function.identity(), lang -> _feedHelper.getActivityInfos(activity2json, projectJson, lang))); 151 152 for (Entry<String, Map<UserIdentity, Set<String>>> entry : langAndTokens.entrySet()) 153 { 154 String lang = entry.getKey(); 155 Map<UserIdentity, Set<String>> tokens = entry.getValue(); 156 Map<String, Object> activityData = translatedActivityData.get(lang); 157 158 try 159 { 160 @SuppressWarnings("unchecked") 161 Map<String, Object> category = (Map<String, Object>) ((Map<String, Object>) activityData.get("project")).get("category"); 162 String categoryLabel = ((org.ametys.runtime.i18n.I18nizableText) category.get("title")).getLabel(); 163 category.put("title", categoryLabel); 164 } 165 catch (Exception e) 166 { 167 // Nothing, this is just a hack do pass the notification framework jsonifier 168 } 169 170 String title = project.getTitle(); 171 String message = (String) activityData.get("short-description"); 172 173 Map<String, Object> notificationData = new HashMap<>(); 174 notificationData.put("project_name", project.getName()); 175 notificationData.put("project_id", project.getId()); 176 notificationData.put("title", title); 177 notificationData.put("date", activityData.get("date")); 178 notificationData.put("content_id", activityData.get("content_id")); 179 notificationData.put("content_url", activityData.get("content_url")); 180 181 _pushNotificationManager.pushNotifications(title, message, tokens, notificationData); 182 } 183 } 184 finally 185 { 186 _renderingContextHandler.setRenderingContext(currentRenderingContext); 187 } 188 } 189 } 190 } 191}