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