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.User; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.plugins.mobileapp.FeedHelper; 036import org.ametys.plugins.mobileapp.PushNotificationManager; 037import org.ametys.plugins.mobileapp.UserPreferencesHelper; 038import org.ametys.plugins.mobileapp.action.GetEventTypesAction; 039import org.ametys.plugins.repository.AmetysObjectResolver; 040import org.ametys.plugins.repository.activities.Activity; 041import org.ametys.plugins.repository.activities.ActivityType; 042import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType; 043import org.ametys.plugins.workspaces.members.ProjectMemberManager; 044import org.ametys.plugins.workspaces.members.ProjectMemberManager.ProjectMember; 045import org.ametys.plugins.workspaces.project.ProjectManager; 046import org.ametys.plugins.workspaces.project.objects.Project; 047import org.ametys.runtime.config.Config; 048import org.ametys.runtime.plugin.PluginsManager; 049import org.ametys.runtime.plugin.component.AbstractLogEnabled; 050/** 051 * On validation, test each query to notify impacted users 052 */ 053public class ProjectActivityObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable 054{ 055 private static final String __PROJECT_ENABLED_CONF_ID = "plugin.mobileapp.project.enabled"; 056 057 private FeedHelper _feedHelper; 058 private UserPreferencesHelper _userPreferencesHelper; 059 private PushNotificationManager _pushNotificationManager; 060 private ProjectManager _projectManager; 061 private AmetysObjectResolver _resolver; 062 private ProjectMemberManager _projectMemberHelper; 063 064 @Override 065 public void service(ServiceManager manager) throws ServiceException 066 { 067 _feedHelper = (FeedHelper) manager.lookup(FeedHelper.ROLE); 068 _userPreferencesHelper = (UserPreferencesHelper) manager.lookup(UserPreferencesHelper.ROLE); 069 _pushNotificationManager = (PushNotificationManager) manager.lookup(PushNotificationManager.ROLE); 070 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 071 072 if (PluginsManager.getInstance().isPluginActive("workspaces")) 073 { 074 _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE); 075 _projectMemberHelper = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE); 076 } 077 } 078 079 public boolean supports(Event event) 080 { 081 return PluginsManager.getInstance().isPluginActive("workspaces") 082 && Config.getInstance().getValue(__PROJECT_ENABLED_CONF_ID, false, false) 083 && org.ametys.plugins.repository.ObservationConstants.EVENT_ACTIVITY_CREATED.contains(event.getId()); 084 } 085 086 public int getPriority() 087 { 088 return MAX_PRIORITY; 089 } 090 091 public void observe(Event event, Map<String, Object> transientVars) throws Exception 092 { 093 String activityId = (String) event.getArguments().get(org.ametys.plugins.repository.ObservationConstants.ARGS_ACTIVITY_ID); 094 Activity activity = _resolver.resolveById(activityId); 095 096 // Push notifications are only sent for allowed types 097 if (GetEventTypesAction.EVENT_TYPES.containsKey(activity.getEventType())) 098 { 099 ActivityType activityType = activity.getActivityType(); 100 if (activityType instanceof AbstractWorkspacesActivityType) 101 { 102 Project project = _projectManager.getProject(activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME)); 103 getLogger().info("Listing push notification to send for activity on project '{}'", project.getId()); 104 105 Map<String, Object> activity2json = activity.toJSONForClient(); 106 107 Map<String, Object> projectJson = _feedHelper.projectToMap(project); 108 109 // we only look for tokens among project members 110 Set<ProjectMember> members = _projectMemberHelper.getProjectMembers(project, true); 111 List<UserIdentity> users = members.stream().map(member -> member.getUser()) 112 .filter(Objects::nonNull) 113 .map(User::getIdentity) 114 .toList(); 115 116 // Get all map of language => tokens for all users, and generate a new big map language => tokens 117 Map<String, Map<UserIdentity, Set<String>>> langAndTokens = new HashMap<>(); 118 for (UserIdentity user : users) 119 { 120 Map<String, Set<String>> tokensForUser = _userPreferencesHelper.getUserImpactedTokens(user, project, activity.getEventType()); 121 for (Entry<String, Set<String>> tokensByLang : tokensForUser.entrySet()) 122 { 123 Map<UserIdentity, Set<String>> tokens = langAndTokens.computeIfAbsent(tokensByLang.getKey(), __ -> new HashMap<>()); 124 tokens.put(user, tokensByLang.getValue()); 125 } 126 } 127 128 if (getLogger().isDebugEnabled()) 129 { 130 getLogger().debug("Push tokens " + langAndTokens.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue().size()).collect(Collectors.joining(", "))); 131 } 132 133 // Get the activities infos for each languages 134 Map<String, Map<String, Object>> translatedNotificationContent = langAndTokens.keySet().stream() 135 .distinct() 136 .collect(Collectors.toMap(Function.identity(), lang -> _feedHelper.getActivityInfos(activity2json, projectJson, lang))); 137 138 for (Entry<String, Map<UserIdentity, Set<String>>> entry : langAndTokens.entrySet()) 139 { 140 String lang = entry.getKey(); 141 Map<UserIdentity, Set<String>> tokens = entry.getValue(); 142 Map<String, Object> notificationData = translatedNotificationContent.get(lang); 143 144 try 145 { 146 @SuppressWarnings("unchecked") 147 Map<String, Object> category = (Map<String, Object>) ((Map<String, Object>) notificationData.get("project")).get("category"); 148 String categoryLabel = ((org.ametys.runtime.i18n.I18nizableText) category.get("title")).getLabel(); 149 category.put("title", categoryLabel); 150 } 151 catch (Exception e) 152 { 153 // Nothing, this is just a hack do pass the notification framework jsonifier 154 } 155 156 String title = project.getTitle(); 157 String message = (String) notificationData.get("short-description"); 158 _pushNotificationManager.pushNotifications(title, message, tokens, null); 159 } 160 } 161 } 162 } 163}