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; 017 018import java.time.LocalDate; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.collections4.CollectionUtils; 032 033import org.ametys.core.user.CurrentUserProvider; 034import org.ametys.core.user.UserIdentity; 035import org.ametys.core.userpref.UserPreferencesException; 036import org.ametys.core.userpref.UserPreferencesManager; 037import org.ametys.core.util.JSONUtils; 038import org.ametys.plugins.workspaces.project.objects.Project; 039import org.ametys.runtime.plugin.component.AbstractLogEnabled; 040import org.ametys.web.repository.site.Site; 041 042/** 043 * Helper to store/retreive conf per user 044 */ 045public class UserPreferencesHelper extends AbstractLogEnabled implements Serviceable, Component 046{ 047 /** The avalon role. */ 048 public static final String ROLE = UserPreferencesHelper.class.getName(); 049 050 private static final String __USERPREF_KEY_LANG = "lang"; 051 private static final String __USERPREF_KEY_SITE = "site"; 052 private static final String __USERPREF_KEY_ENABLED = "enabled"; 053 private static final String __USERPREF_KEY_ALL_FEEDS = "allFeeds"; 054 private static final String __USERPREF_KEY_FEEDS = "feeds"; 055 private static final String __USERPREF_KEY_ALL_PROJECTS = "allProjects"; 056 private static final String __USERPREF_KEY_PROJECTS = "projects"; 057 private static final String __USERPREF_KEY_ALL_TYPES = "allTypes"; 058 private static final String __USERPREF_KEY_TYPES = "types"; 059 060 /** User Preferences Manager */ 061 protected UserPreferencesManager _userPreferencesManager; 062 063 /** The current user provider */ 064 protected CurrentUserProvider _currentUserProvider; 065 066 /** JSON Utils */ 067 protected JSONUtils _jsonUtils; 068 069 070 public void service(ServiceManager manager) throws ServiceException 071 { 072 _userPreferencesManager = (UserPreferencesManager) manager.lookup(UserPreferencesManager.ROLE); 073 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 074 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 075 } 076 077 /** 078 * Get the list of impacted tokens for each feeds, for a user 079 * @param user user to check 080 * @param feedIds list of feeds to check 081 * @param site the site for the validated content 082 * @return a map with a Set of tokens for each feedId 083 */ 084 @SuppressWarnings("unchecked") 085 public Map<String, Set<String>> getUserImpactedTokens(UserIdentity user, Set<String> feedIds, Site site) 086 { 087 Map<String, Set<String>> feedsAndTokens = new HashMap<>(); 088 try 089 { 090 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 091 for (Map.Entry<String, String> entry : unTypedUserPrefs.entrySet()) 092 { 093 String token = entry.getKey(); 094 String value = entry.getValue(); 095 Map<String, Object> prefs = _jsonUtils.convertJsonToMap(value); 096 097 List<String> feeds = (List<String>) prefs.get(__USERPREF_KEY_FEEDS); 098 String siteName = (String) prefs.get(__USERPREF_KEY_SITE); 099 100 boolean enabled = _getValueOrDefault((Boolean) prefs.get(__USERPREF_KEY_ENABLED), true); 101 boolean allFeeds = _getValueOrDefault((Boolean) prefs.get(__USERPREF_KEY_ALL_FEEDS), feeds == null); 102 103 if (enabled) 104 { 105 boolean hasSiteQueries = site.hasValue(QueriesHelper.QUERY_CONTAINER_SITE_CONF_ID); 106 107 if (siteName == null || hasSiteQueries && siteName.equals(site.getName())) 108 { 109 Collection<String> matchingFeeds = allFeeds || feeds == null ? feedIds : CollectionUtils.intersection(feeds, feedIds); 110 111 for (String feedId : matchingFeeds) 112 { 113 Set tokens = feedsAndTokens.computeIfAbsent(feedId, __ -> new HashSet<>()); 114 tokens.add(token); 115 } 116 } 117 } 118 } 119 } 120 catch (UserPreferencesException e) 121 { 122 getLogger().error("Impossible to read a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 123 } 124 125 return feedsAndTokens; 126 } 127 128 /** 129 * Get the list of impacted tokens for each feeds, for a user 130 * @param user user to check 131 * @param project test if a notification should be sent for this project 132 * @param eventType test if a notification should be sent for this event type 133 * @return a map with a the set of tokens impacted for each languages 134 */ 135 @SuppressWarnings("unchecked") 136 public Map<String, Set<String>> getUserImpactedTokens(UserIdentity user, Project project, String eventType) 137 { 138 Map<String, Set<String>> tokensForLanguage = new HashMap<>(); 139 try 140 { 141 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 142 for (Map.Entry<String, String> entry : unTypedUserPrefs.entrySet()) 143 { 144 String token = entry.getKey(); 145 String value = entry.getValue(); 146 Map<String, Object> prefs = _jsonUtils.convertJsonToMap(value); 147 148 List<String> projects = (List<String>) prefs.get(__USERPREF_KEY_PROJECTS); 149 List<String> types = (List<String>) prefs.get(__USERPREF_KEY_TYPES); 150 151 boolean enabled = _getValueOrDefault((Boolean) prefs.get(__USERPREF_KEY_ENABLED), true); 152 boolean allProjects = _getValueOrDefault((Boolean) prefs.get(__USERPREF_KEY_ALL_PROJECTS), projects == null); 153 boolean allTypes = _getValueOrDefault((Boolean) prefs.get(__USERPREF_KEY_ALL_TYPES), types == null); 154 155 if (enabled) 156 { 157 boolean projectMatch = allProjects || projects == null || projects.contains(project.getId()); 158 boolean typeMatch = allTypes || types == null || types.contains(eventType); 159 160 if (projectMatch && typeMatch) 161 { 162 String lang = (String) prefs.get(__USERPREF_KEY_LANG); 163 164 Set<String> tokens = tokensForLanguage.computeIfAbsent(lang, __ -> new HashSet<>()); 165 tokens.add(token); 166 } 167 } 168 } 169 } 170 catch (UserPreferencesException e) 171 { 172 getLogger().error("Impossible to read a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 173 } 174 175 return tokensForLanguage; 176 } 177 178 private boolean _getValueOrDefault(Boolean value, boolean defaultValue) 179 { 180 return value != null ? value : defaultValue; 181 } 182 183 /** 184 * Get all notification tokens for current user 185 * @return the list of notification tokens 186 */ 187 public Set<String> getNotificationTokens() 188 { 189 UserIdentity user = _currentUserProvider.getUser(); 190 return getNotificationTokens(user); 191 } 192 193 /** 194 * Get all notification tokens a user 195 * @param user the user impacted 196 * @return the list of notification tokens 197 */ 198 public Set<String> getNotificationTokens(UserIdentity user) 199 { 200 try 201 { 202 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 203 return unTypedUserPrefs.keySet(); 204 } 205 catch (UserPreferencesException e) 206 { 207 getLogger().error("Impossible to read a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 208 } 209 210 return Collections.EMPTY_SET; 211 } 212 213 /** 214 * Removes a notification token for the current user 215 * @param pushToken the token to remove 216 */ 217 public void removeNotificationToken(String pushToken) 218 { 219 UserIdentity user = _currentUserProvider.getUser(); 220 removeNotificationToken(pushToken, user); 221 } 222 223 /** 224 * Removes a notification token for a user 225 * @param pushToken the token to remove 226 * @param user the user impacted 227 */ 228 public void removeNotificationToken(String pushToken, UserIdentity user) 229 { 230 try 231 { 232 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 233 unTypedUserPrefs.remove(pushToken); 234 _userPreferencesManager.setUserPreferences(user, "/mobileapp", Collections.emptyMap(), unTypedUserPrefs); 235 } 236 catch (UserPreferencesException e) 237 { 238 getLogger().error("Impossible to remove a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 239 } 240 } 241 242 /** 243 * Remove all the notification tokens for a user 244 * @param user the user impacted 245 */ 246 public void removeAllNotificationTokens(UserIdentity user) 247 { 248 try 249 { 250 _userPreferencesManager.removeAllUserPreferences(user, "/mobileapp", Collections.emptyMap()); 251 } 252 catch (UserPreferencesException e) 253 { 254 getLogger().error("Impossible to remove all user notification tokens, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 255 } 256 } 257 258 /** 259 * Save the notification settings for the current user 260 * @param pushToken the token to impact 261 * @param enabled if notifications are enabled 262 * @param allFeeds if all feeds are allowed to be notified 263 * @param feeds list of feeds ID for notifications 264 * @param allProjects if all projects are allowed to be notified 265 * @param projects list of project ID for notifications 266 * @param allTypes if all types are allowed to be notified 267 * @param types list of notifications types for notifications in projects 268 * @param lang lang of this device 269 */ 270 public void setNotificationSettings(String pushToken, boolean enabled, boolean allFeeds, List<String> feeds, boolean allProjects, List<String> projects, boolean allTypes, List<String> types, String lang) 271 { 272 UserIdentity user = _currentUserProvider.getUser(); 273 setNotificationSettings(pushToken, enabled, allFeeds, feeds, allProjects, projects, allTypes, types, lang, user); 274 } 275 276 /** 277 * Save the notification settings for a user 278 * @param pushToken token impacted by this settings 279 * @param enabled if notifications are enabled 280 * @param allFeeds if all feeds are allowed to be notified 281 * @param feeds list of feeds ID for notifications 282 * @param allProjects if all projects are allowed to be notified 283 * @param projects list of project ID for notifications 284 * @param allTypes if all types are allowed to be notified 285 * @param types list of notifications types for notifications in projects 286 * @param lang lang of this device 287 * @param user the user impacted 288 */ 289 public synchronized void setNotificationSettings(String pushToken, boolean enabled, boolean allFeeds, List<String> feeds, boolean allProjects, List<String> projects, boolean allTypes, List<String> types, String lang, UserIdentity user) 290 { 291 // synchronized because it happens that the app send multiple calls nearly simultaneously 292 Map<String, Object> values = new HashMap<>(); 293 values.put(__USERPREF_KEY_LANG, lang); 294 295 values.put(__USERPREF_KEY_ENABLED, enabled); 296 297 values.put(__USERPREF_KEY_ALL_FEEDS, allFeeds); 298 if (feeds != null) 299 { 300 values.put(__USERPREF_KEY_FEEDS, feeds); 301 } 302 303 values.put(__USERPREF_KEY_ALL_PROJECTS, allProjects); 304 if (projects != null) 305 { 306 values.put(__USERPREF_KEY_PROJECTS, projects); 307 } 308 309 values.put(__USERPREF_KEY_ALL_TYPES, allTypes); 310 if (types != null) 311 { 312 values.put(__USERPREF_KEY_TYPES, types); 313 } 314 315 values.put("epochDay", LocalDate.now().toEpochDay()); 316 317 String valuesAsString = _jsonUtils.convertObjectToJson(values); 318 319 try 320 { 321 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 322 if (unTypedUserPrefs == null) 323 { 324 unTypedUserPrefs = new HashMap<>(); 325 } 326 327 unTypedUserPrefs.put(pushToken, valuesAsString); 328 329 _userPreferencesManager.setUserPreferences(user, "/mobileapp", Collections.emptyMap(), unTypedUserPrefs); 330 } 331 catch (UserPreferencesException e) 332 { 333 getLogger().error("Impossible to set user preferences for user '" + UserIdentity.userIdentityToString(user) + "'", e); 334 } 335 } 336 337 /** 338 * Get the notification settings for a token 339 * @param pushToken the token to read 340 * @param user the user impacted 341 * @return a map containing feeds, projects and types as Set<String>, and epochDay as the last modification date 342 */ 343 public Map<String, Object> getNotificationSettings(String pushToken, UserIdentity user) 344 { 345 try 346 { 347 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 348 if (unTypedUserPrefs != null && unTypedUserPrefs.containsKey(pushToken)) 349 { 350 String prefs = unTypedUserPrefs.get(pushToken); 351 return _jsonUtils.convertJsonToMap(prefs); 352 } 353 } 354 catch (UserPreferencesException e) 355 { 356 getLogger().error("Impossible to retreive user preferences for user '" + UserIdentity.userIdentityToString(user) + "'", e); 357 } 358 return null; 359 } 360 361}