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}