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 /** 185 * Get all notification tokens for current user 186 * @return the list of notification tokens 187 */ 188 public Set<String> getNotificationTokens() 189 { 190 UserIdentity user = _currentUserProvider.getUser(); 191 return getNotificationTokens(user); 192 } 193 194 /** 195 * Get all notification tokens a user 196 * @param user the user impacted 197 * @return the list of notification tokens 198 */ 199 public Set<String> getNotificationTokens(UserIdentity user) 200 { 201 try 202 { 203 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 204 return unTypedUserPrefs.keySet(); 205 } 206 catch (UserPreferencesException e) 207 { 208 getLogger().error("Impossible to read a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 209 } 210 211 return Collections.EMPTY_SET; 212 } 213 214 /** 215 * Removes a notification token for the current user 216 * @param pushToken the token to remove 217 */ 218 public void removeNotificationToken(String pushToken) 219 { 220 UserIdentity user = _currentUserProvider.getUser(); 221 removeNotificationToken(pushToken, user); 222 } 223 224 /** 225 * Removes a notification token for a user 226 * @param pushToken the token to remove 227 * @param user the user impacted 228 */ 229 public void removeNotificationToken(String pushToken, UserIdentity user) 230 { 231 try 232 { 233 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 234 unTypedUserPrefs.remove(pushToken); 235 _userPreferencesManager.setUserPreferences(user, "/mobileapp", Collections.emptyMap(), unTypedUserPrefs); 236 } 237 catch (UserPreferencesException e) 238 { 239 getLogger().error("Impossible to remove a user notification token, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 240 } 241 } 242 243 /** 244 * Remove all the notification tokens for a user 245 * @param user the user impacted 246 */ 247 public void removeAllNotificationTokens(UserIdentity user) 248 { 249 try 250 { 251 _userPreferencesManager.removeAllUserPreferences(user, "/mobileapp", Collections.emptyMap()); 252 } 253 catch (UserPreferencesException e) 254 { 255 getLogger().error("Impossible to remove all user notification tokens, for user '" + UserIdentity.userIdentityToString(user) + "'", e); 256 } 257 } 258 259 /** 260 * Save the notification settings for the current user 261 * @param pushToken the token to impact 262 * @param feeds list of feeds ID for notifications 263 * @param projects list of project ID for notifications 264 * @param types list of notifications types for notifications in projects 265 * @param lang lang of this device 266 */ 267 public void setNotificationSettings(String pushToken, List<String> feeds, List<String> projects, List<String> types, String lang) 268 { 269 UserIdentity user = _currentUserProvider.getUser(); 270 setNotificationSettings(pushToken, feeds, projects, types, lang, user); 271 } 272 273 /** 274 * Save the notification settings for a user 275 * @param pushToken token impacted by this settings 276 * @param feeds list of feeds ID for notifications 277 * @param projects list of project ID for notifications 278 * @param types list of notifications types for notifications in projects 279 * @param lang lang of this device 280 * @param user the user impacted 281 */ 282 public synchronized void setNotificationSettings(String pushToken, List<String> feeds, List<String> projects, List<String> types, String lang, UserIdentity user) 283 { 284 // synchronized because it happens that the app send multiple calls nearly simultaneously 285 Map<String, Object> values = new HashMap<>(); 286 values.put(__USERPREF_KEY_FEEDS, feeds); 287 values.put(__USERPREF_KEY_PROJECTS, projects); 288 values.put(__USERPREF_KEY_FEED_TYPES, types); 289 values.put(__USERPREF_KEY_LANG, lang); 290 291 LocalDate now = LocalDate.now(); 292 values.put("epochDay", now.toEpochDay()); 293 294 295 String valuesAsString = _jsonUtils.convertObjectToJson(values); 296 297 try 298 { 299 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 300 if (unTypedUserPrefs == null) 301 { 302 unTypedUserPrefs = new HashMap<>(); 303 } 304 305 unTypedUserPrefs.put(pushToken, valuesAsString); 306 307 _userPreferencesManager.setUserPreferences(user, "/mobileapp", Collections.emptyMap(), unTypedUserPrefs); 308 } 309 catch (UserPreferencesException e) 310 { 311 getLogger().error("Impossible to set user preferences for user '" + UserIdentity.userIdentityToString(user) + "'", e); 312 } 313 } 314 315 /** 316 * Get the notification settings for a token 317 * @param pushToken the token to read 318 * @param user the user impacted 319 * @return a map containing feeds, projects and types as Set<String>, and epochDay as the last modification date 320 */ 321 public Map<String, Object> getNotificationSettings(String pushToken, UserIdentity user) 322 { 323 try 324 { 325 Map<String, String> unTypedUserPrefs = _userPreferencesManager.getUnTypedUserPrefs(user, "/mobileapp", Collections.emptyMap()); 326 if (unTypedUserPrefs != null && unTypedUserPrefs.containsKey(pushToken)) 327 { 328 String prefs = unTypedUserPrefs.get(pushToken); 329 return _jsonUtils.convertJsonToMap(prefs); 330 } 331 } 332 catch (UserPreferencesException e) 333 { 334 getLogger().error("Impossible to retreive user preferences for user '" + UserIdentity.userIdentityToString(user) + "'", e); 335 } 336 return null; 337 } 338 339}