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 -&gt; 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&lt;String&gt;, 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}