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}