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