001/*
002 *  Copyright 2021 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.workspaces.project.notification.preferences;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Map.Entry;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang3.StringUtils;
031
032import org.ametys.core.ui.Callable;
033import org.ametys.core.user.CurrentUserProvider;
034import org.ametys.core.user.User;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.core.userpref.UserPreferencesException;
037import org.ametys.core.userpref.UserPreferencesExtensionPoint;
038import org.ametys.core.userpref.UserPreferencesManager;
039import org.ametys.core.util.JSONUtils;
040import org.ametys.plugins.workspaces.project.ProjectManager;
041import org.ametys.plugins.workspaces.project.objects.Project;
042import org.ametys.runtime.plugin.component.AbstractLogEnabled;
043
044/**
045 * Helper for notifications preferences
046 */
047public class NotificationPreferencesHelper extends AbstractLogEnabled implements Component, Serviceable
048{
049    /** the id of the user prefs for notification preferences */
050    public static final String USERPREFS_ID = "workspaces.notifications";
051    
052    /** The avalon role */
053    public static final String ROLE = NotificationPreferencesHelper.class.getName();
054    
055    /** The JSON utils */
056    protected JSONUtils _jsonUtils;
057    /** The user preferences manager */
058    protected UserPreferencesManager _userPrefManager;
059    /** The user preferences extension point */
060    protected UserPreferencesExtensionPoint _userPrefEP;
061    /** The project manager */
062    protected ProjectManager _projectManager;
063    /** To get the current user */
064    protected CurrentUserProvider _currentUserProvider;
065    
066    /**
067     * The frequency of notifications
068     *
069     */
070    public static enum Frequency 
071    {
072        /** Each frequency */
073        EACH,
074        /** Daily frequency */
075        DAILY,
076        /** Weekly frequency */
077        WEEKLY,
078        /** Monthly frequency */
079        MONTHLY
080    }
081    
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        _userPrefManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE + ".FO");
085        _userPrefEP = (UserPreferencesExtensionPoint) manager.lookup(UserPreferencesExtensionPoint.ROLE + ".FO");
086        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
087        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
088        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
089    }
090    
091    /**
092     * Determines if a user asked to be notify for a given project and frequency
093     * @param user the user
094     * @param projectName the project name
095     * @param frequency the required frequency (each, daily, weekly or monthly)
096     * @return true if the user need to be notified
097     */
098    public boolean askedToBeNotified(User user, String projectName, Frequency frequency)
099    {
100        return askedToBeNotified(user.getIdentity(), projectName, frequency);
101    }
102    
103    /**
104     * Determines if a user asked to be notify for a given project and frequency
105     * @param userIdentity the user identity
106     * @param projectName the project name
107     * @param frequency the required frequency (each, daily, weekly or monthly). Cannot be null.
108     * @return true if the user need to be notified
109     */
110    public boolean askedToBeNotified(UserIdentity userIdentity, String projectName, Frequency frequency)
111    {
112        try
113        {
114            Map<String, Object> notificationPreferences = getNotificationPreferences(userIdentity);
115            
116            if ((Boolean) notificationPreferences.get("disable"))
117            {
118                return false;
119            }
120            
121            @SuppressWarnings("unchecked")
122            Map<String, Object> projectsPreferences = (Map<String, Object>) notificationPreferences.get("projects");
123            if (!projectsPreferences.isEmpty() && projectsPreferences.get(projectName) != null)
124            {
125                @SuppressWarnings("unchecked")
126                Map<String, Object> projectPreference = (Map<String, Object>) projectsPreferences.get(projectName);
127                return !(Boolean) projectPreference.get("disable") && StringUtils.equalsIgnoreCase(frequency.name(), (String) (projectPreference.get("frequency")));
128            }
129            else
130            {
131                return StringUtils.equalsIgnoreCase(frequency.name(), (String) notificationPreferences.get("frequency"));
132            }
133        }
134        catch (UserPreferencesException e)
135        {
136            getLogger().warn("Failed to retrieve the user preferences for user {}", UserIdentity.userIdentityToString(userIdentity), e);
137            return false;
138        }
139    }
140    
141    /**
142     * Get the collection of paused project for a given user
143     * @param user The user to consider
144     * @return The set of projects names that are paused or null for a general pause
145     */
146    @SuppressWarnings("unchecked")
147    public Set<String> getPausedProjects(UserIdentity user)
148    {
149        try
150        {
151            Map<String, Object> notificationPreferences = getNotificationPreferences(user);
152            
153            if (Boolean.TRUE.equals(notificationPreferences.get("disable")))
154            {
155                return null;
156            }
157            
158            Set<String> paused = new HashSet<>();
159            
160            Map<String, Object> projectsPreferences = (Map<String, Object>) notificationPreferences.get("projects");
161            for (Entry<String, Object> projectPreference : projectsPreferences.entrySet())
162            {
163                if (Boolean.TRUE.equals(((Map<String, Object>) projectPreference.getValue()).get("disable")))
164                {
165                    paused.add(projectPreference.getKey());
166                }
167            }
168            
169            return paused;
170        }
171        catch (UserPreferencesException e)
172        {
173            getLogger().warn("Failed to retrieve the user preferences for user {}", UserIdentity.userIdentityToString(user), e);
174            return Set.of();
175        }
176    }
177    
178    /**
179     * Change the pause status for the notifications of one project
180     * @param projectName The project name to change
181     * @param pause The pause status
182     * @return true is the change was done successfully
183     */
184    @SuppressWarnings("unchecked")
185    @Callable
186    public boolean setPauseProject(String projectName, boolean pause)
187    {
188        UserIdentity user = _currentUserProvider.getUser();
189        try
190        {
191            Map<String, Object> notificationPreferences = getNotificationPreferences(user);
192            
193            Map<String, Object> projectsPreferences = (Map<String, Object>) notificationPreferences.computeIfAbsent("projects", s -> new HashMap());
194            Map<String, Object> projectPreference = (Map<String, Object>) projectsPreferences.computeIfAbsent(projectName, s -> new HashMap());
195            
196            if (!pause && notificationPreferences.get("frequency").equals(projectPreference.get("frequency")))
197            {
198                projectsPreferences.remove(projectName); 
199            }
200            else
201            {
202                projectPreference.put("disable", pause);
203                projectPreference.computeIfAbsent("frequency", s -> notificationPreferences.get("frequency"));
204            }
205
206            String preferencesAsString = _jsonUtils.convertObjectToJson(notificationPreferences);
207                
208            Map<String, String> values = _userPrefManager.getUnTypedUserPrefs(user, _getStorageContext(), Map.of());
209            
210            values.put(USERPREFS_ID, preferencesAsString);
211            
212            _userPrefManager.setUserPreferences(user, _getStorageContext(), Map.of(), values);
213            
214            return true;
215        }
216        catch (UserPreferencesException e)
217        {
218            getLogger().error("Could not change favorite status of user " + UserIdentity.userIdentityToString(user) + " on project " + projectName);
219            return false; 
220        }
221    }
222    
223    /**
224     * Determines if a user has at least one notification preference with the given frequency
225     * @param user the user
226     * @param frequency the frequency
227     * @return true if the user has at least one notification preference with the given frequency 
228     */
229    public boolean hasFrequencyInPreferences(User user, Frequency frequency)
230    {
231        // TODO We should use a cache here to avoid computing the userPref multiple time for the same user
232        try
233        {
234            String notificationPreferencesJSON = getNotificationPreferencesAsString(user);
235            return notificationPreferencesJSON.contains(frequency.name().toLowerCase());
236        }
237        catch (UserPreferencesException e)
238        {
239            getLogger().warn("Failed to retrieve the user preferences for user {}", user.getFullName(), e);
240        }
241        return false;
242    }
243    
244    private boolean _isDisabled(Map<String, Map<String, Object>> projectPrefs, String projectName)
245    {
246        return projectPrefs.containsKey(projectName) && (Boolean) projectPrefs.get(projectName).get("disable");
247    }
248    
249    private boolean _matchFrequency(Map<String, Map<String, Object>> projectPrefs, String projectName, Frequency frequency)
250    {
251        return projectPrefs.containsKey(projectName) && StringUtils.equalsIgnoreCase(frequency.name(), (String) projectPrefs.get(projectName).get("frequency"));
252    }
253    
254    /**
255     * Get the user's project set to receive notification for given frequency
256     * @param user the user
257     * @param frequency the matching frequency
258     * @return The names of user's project's with same frequency
259     */
260    public Set<String> getUserProjectsWithFrequency(User user, Frequency frequency)
261    {
262        try
263        {
264            Map<String, Object> notificationPreferences = getNotificationPreferences(user);
265            if ((Boolean) notificationPreferences.get("disable"))
266            {
267                // Notifications are disabled for user
268                return Collections.EMPTY_SET;
269            }
270            
271            String globalFrequency = (String) notificationPreferences.get("frequency");
272            @SuppressWarnings("unchecked")
273            Map<String, Map<String, Object>> customProjectsPrefs = (Map<String, Map<String, Object>>) notificationPreferences.get("projects");
274            
275            if (globalFrequency.equals(frequency.name().toLowerCase()))
276            {
277                // Get all user projects and filter custom projects
278                Set<Project> userProjects = _projectManager.getUserProjects(user.getIdentity()).keySet();
279                
280                return userProjects.stream()
281                    .map(Project::getName)
282                    .filter(p -> !_isDisabled(customProjectsPrefs, p))
283                    .filter(p -> !customProjectsPrefs.containsKey(p) || _matchFrequency(customProjectsPrefs, p, frequency))
284                    .collect(Collectors.toSet());
285            }
286            else
287            {
288                // Get custom projects with same frequency
289                return customProjectsPrefs.entrySet()
290                    .stream()
291                    .filter(e -> !(Boolean) e.getValue().get("disable"))
292                    .filter(e -> frequency.name().toLowerCase().equals(e.getValue().get("frequency")))
293                    .map(e -> e.getKey())
294                    .collect(Collectors.toSet());
295                            
296            }
297        }
298        catch (UserPreferencesException e)
299        {
300            getLogger().warn("Failed to retrieve the user preferences for user {}", user.getFullName(), e);
301        }
302        
303        return Collections.EMPTY_SET;
304    }
305    
306    /**
307     * Get the user's notification preferences
308     * @param user the user 
309     * @return the user's notification preferences
310     * @throws UserPreferencesException if failed to retrieve user's preferences
311     */
312    public Map<String, Object> getNotificationPreferences(User user) throws UserPreferencesException
313    {
314        return getNotificationPreferences(user.getIdentity());
315    }
316    
317    /**
318     * Get the user's notification preferences
319     * @param userIdentity the user identity
320     * @return the user's notification preferences
321     * @throws UserPreferencesException if failed to retrieve user's preference
322     */
323    public Map<String, Object> getNotificationPreferences(UserIdentity userIdentity) throws UserPreferencesException
324    {
325        String prefAsString = getNotificationPreferencesAsString(userIdentity);
326        return _jsonUtils.convertJsonToMap(prefAsString);
327    }
328    
329    /**
330     * Get the user's notification preferences as json String
331     * @param user the user
332     * @return the user's notification preferences as json String
333     * @throws UserPreferencesException if failed to retrieve user's preference
334     */
335    public String getNotificationPreferencesAsString(User user) throws UserPreferencesException
336    {
337        return getNotificationPreferencesAsString(user.getIdentity());
338    }
339    
340    
341    /**
342     * Get the user's notification preferences as json String
343     * @param userIdentity the user identity
344     * @return the user's notification preferences as json String
345     * @throws UserPreferencesException if failed to retrieve user's preference
346     */
347    public String getNotificationPreferencesAsString(UserIdentity userIdentity) throws UserPreferencesException
348    {
349        String notificationPreferencesJSON = _userPrefManager.getUserPreferenceAsString(userIdentity, _getStorageContext(), Map.of(), USERPREFS_ID);
350        if (notificationPreferencesJSON == null)
351        {
352            // get default value
353            notificationPreferencesJSON = (String) _userPrefEP.getUserPreference(Map.of(), USERPREFS_ID).getDefaultValue();
354        }
355        
356        return notificationPreferencesJSON;
357    }
358
359    /**
360     * Delete the user preferences for notification on the given project
361     * @param userIdentity the user identity
362     * @param projectName the name of the project to delete
363     */
364    public void deleteProjectNotificationPreferences(UserIdentity userIdentity, String projectName)
365    {
366        try
367        {
368            Map<String, String> values = _userPrefManager.getUnTypedUserPrefs(userIdentity, _getStorageContext(), Map.of());
369            
370            String notificationPreferencesJSON = values.get(USERPREFS_ID);
371            Map<String, Object> notificationPreferences = _jsonUtils.convertJsonToMap(notificationPreferencesJSON);
372            if (notificationPreferences.containsKey("projects"))
373            {
374                @SuppressWarnings("unchecked")
375                Map<String, Object> projectsPreferences = (Map<String, Object>) notificationPreferences.get("projects");
376                if (projectsPreferences.containsKey(projectName))
377                {
378                    projectsPreferences.remove(projectName);
379                    notificationPreferencesJSON = _jsonUtils.convertObjectToJson(notificationPreferences);
380                    
381                    values.put(USERPREFS_ID, notificationPreferencesJSON);
382                    
383                    _userPrefManager.setUserPreferences(userIdentity, _getStorageContext(), Map.of(), values);
384                }
385            }
386        }
387        catch (UserPreferencesException e)
388        {
389            getLogger().warn("Failed to delete the notification user preference for project {} of user {}", projectName, UserIdentity.userIdentityToString(userIdentity), e);
390        }
391    }
392
393    private String _getStorageContext()
394    {
395        return "/sites/" + _projectManager.getCatalogSiteName();
396    }
397    
398
399}