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}