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