001/*
002 *  Copyright 2012 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.core.userpref;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Map.Entry;
025import java.util.Set;
026
027import org.apache.avalon.framework.activity.Initializable;
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.configuration.Configurable;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.logger.AbstractLogEnabled;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.avalon.framework.thread.ThreadSafe;
037import org.apache.commons.lang3.StringUtils;
038
039import org.ametys.core.user.UserIdentity;
040
041/**
042 * Component handling user preference values retrieval and storage.
043 */
044public class UserPreferencesManager extends AbstractLogEnabled implements ThreadSafe, Component, Serviceable, Configurable, Initializable
045{
046    
047    /** The avalon role. */
048    public static final String ROLE = UserPreferencesManager.class.getName();
049    
050    /** The user preferences extensions point. */
051    protected UserPreferencesExtensionPoint _userPrefEP;
052    
053    /** A list of storage managers. */
054    protected Map<String, UserPreferencesStorage> _storageManagers;
055    
056    /** The default storage component role. */
057    protected String _defaultStorageRole;
058    
059    /** The avalon service manager. */
060    protected ServiceManager _serviceManager;
061    
062    @Override
063    public void service(ServiceManager manager) throws ServiceException
064    {
065        _serviceManager = manager;
066        _userPrefEP = (UserPreferencesExtensionPoint) manager.lookup(UserPreferencesExtensionPoint.ROLE);
067    }
068    
069    @Override
070    public void configure(Configuration configuration) throws ConfigurationException
071    {
072        _defaultStorageRole = configuration.getChild("default-storage-role").getValue();
073    }
074    
075    @Override
076    public void initialize() throws Exception
077    {
078        _storageManagers = new HashMap<>();
079    }
080    
081    /**
082     * Get all user's preference values (as String) for a given context.
083     * @param storageContext the preferences context.
084     * @param contextVars the context variables.
085     * @return the user preference values as a Map of String indexed by preference ID.
086     * @throws UserPreferencesException if an error occurs getting the preferences.
087     */
088    public Map<UserIdentity, Map<String, String>> getAllUnTypedUserPrefs(String storageContext, Map<String, String> contextVars) throws UserPreferencesException
089    {
090        Map<UserIdentity, Map<String, String>> preferences = new HashMap<>();
091        
092        Set<String> storageRoles = getStorageRoles(contextVars);
093        
094        for (String storageRole : storageRoles)
095        {
096            UserPreferencesStorage storageManager = getStorageManager(storageRole);
097            preferences.putAll(storageManager.getAllUnTypedUserPrefs(storageContext, contextVars));
098        }
099        
100        return preferences;
101    }
102    
103    /**
104     * Get a user's preference values (as String) for a given context.
105     * @param user the user.
106     * @param storageContext the preferences context.
107     * @param contextVars the context variables.
108     * @return the user preference values as a Map of String indexed by preference ID.
109     * @throws UserPreferencesException if an error occurs getting the preferences.
110     */
111    public Map<String, String> getUnTypedUserPrefs(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException
112    {
113        Map<String, String> preferences = new HashMap<>();
114        
115//        Map<String, Collection<UserPreference>> userPrefsByStorage = getUserPrefsByStorage(contextVars);
116        Set<String> storageRoles = getStorageRoles(contextVars);
117        
118        for (String storageRole : storageRoles)
119        {
120            UserPreferencesStorage storageManager = getStorageManager(storageRole);
121            
122            preferences.putAll(storageManager.getUnTypedUserPrefs(user, storageContext, contextVars));
123        }
124        
125        return preferences;
126    }
127    
128    
129    /**
130     * Get a user's preference values cast as their own type for a given context.
131     * @param user the user.
132     * @param storageContext the preferences context.
133     * @param contextVars the context variables.
134     * @return the user preference values as a Map of Object indexed by preference ID.
135     * @throws UserPreferencesException if an error occurs getting the preferences.
136     */
137    public Map<String, Object> getTypedUserPrefs(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException
138    {
139        Map<String, String> unTypedUserPrefs = getUnTypedUserPrefs(user, storageContext, contextVars);
140        
141        return _castValues(unTypedUserPrefs, contextVars);
142    }
143    
144    /**
145     * Add a user preference 
146     * @param user the user.
147     * @param storageContext the preferences context.
148     * @param contextVars the context variables.
149     * @param name the user pref name
150     * @param value the user pref value
151     * @throws UserPreferencesException if an error occurred
152     */
153    public void addUserPreference(UserIdentity user, String storageContext, Map<String, String> contextVars, String name, String value) throws UserPreferencesException
154    {
155        Map<String, String> userPrefs = getUnTypedUserPrefs(user, storageContext, contextVars);
156        userPrefs.put(name, value);
157        
158        setUserPreferences(user, storageContext, contextVars, userPrefs);
159    }
160    
161    /**
162     * Add a user preference 
163     * @param user the user.
164     * @param storageContext the preferences context.
165     * @param contextVars the context variables.
166     * @param values the user prefs to add
167     * @throws UserPreferencesException if an error occurred
168     */
169    public void addUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars, Map<String, String> values) throws UserPreferencesException
170    {
171        Map<String, String> userPrefs = getUnTypedUserPrefs(user, storageContext, contextVars);
172        userPrefs.putAll(values);
173        
174        setUserPreferences(user, storageContext, contextVars, userPrefs);
175    }
176    
177    /**
178     * Remove a user preference 
179     * @param user the user.
180     * @param storageContext the preferences context.
181     * @param contextVars the context variables.
182     * @param name the user pref name
183     * @throws UserPreferencesException if an error occurred
184     */
185    public void removeUserPreference(UserIdentity user, String storageContext, Map<String, String> contextVars, String name) throws UserPreferencesException
186    {
187        Map<String, String> userPrefs = getUnTypedUserPrefs(user, storageContext, contextVars);
188        if (userPrefs.containsKey(name))
189        {
190            userPrefs.remove(name);
191        }
192        setUserPreferences(user, storageContext, contextVars, userPrefs);
193    }
194    
195    /**
196     * Remove all user preferences. 
197     * @param user the user.
198     * @param storageContext the preferences context.
199     * @param contextVars the context variables.
200     * @throws UserPreferencesException if an error occurred
201     */
202    public void removeAllUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars) throws UserPreferencesException
203    {
204        Set<String> storageRoles = getStorageRoles(contextVars);
205        
206        for (String storageRole : storageRoles)
207        {
208            UserPreferencesStorage storageManager = getStorageManager(storageRole);
209            
210            storageManager.removeUserPreferences(user, storageContext, contextVars);
211        }
212    }
213    
214    /**
215     * Set a user's preferences for a given context.
216     * @param user the user.
217     * @param storageContext the preferences context.
218     * @param contextVars the context variables.
219     * @param preferenceValues a Map of the preference values indexed by ID.
220     * @throws UserPreferencesException if an error occurred
221     */
222    public void setUserPreferences(UserIdentity user, String storageContext, Map<String, String> contextVars, Map<String, String> preferenceValues) throws UserPreferencesException
223    {
224        Map<String, Map<String, String>> preferenceValuesByStorage = getUserPrefsValuesByStorage(contextVars, preferenceValues);
225        
226        for (String managerRole : preferenceValuesByStorage.keySet())
227        {
228            Map<String, String> storagePrefValues = preferenceValuesByStorage.get(managerRole);
229            
230            // Retrieve the right storage manager.
231            UserPreferencesStorage storageManager = getStorageManager(managerRole);
232            
233            // Call set on the storage manager.
234            storageManager.setUserPreferences(user, storageContext, contextVars, storagePrefValues);
235        }
236    }
237    
238    /**
239     * Get a single string user preference value for a given context.
240     * @param user the user.
241     * @param storageContext the preferences context.
242     * @param contextVars the context variables.
243     * @param id the preference ID.
244     * @return the user preference value as a String.
245     * @throws UserPreferencesException if an error occurred
246     */
247    public String getUserPreferenceAsString(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
248    {
249        UserPreferencesStorage storageManager = getStorageManager(contextVars, id);
250        
251        return storageManager.getUserPreferenceAsString(user, storageContext, contextVars, id);
252    }
253    
254    /**
255     * Get a single long user preference value for a given context.
256     * @param user the user.
257     * @param storageContext the preferences context.
258     * @param contextVars the context variables.
259     * @param id the preference ID.
260     * @return the user preference value as a Long.
261     * @throws UserPreferencesException if an error occurred
262     */
263    public Long getUserPreferenceAsLong(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
264    {
265        UserPreferencesStorage storageManager = getStorageManager(contextVars, id);
266        
267        return storageManager.getUserPreferenceAsLong(user, storageContext, contextVars, id);
268    }
269    
270    /**
271     * Get a single date user preference value for a given context.
272     * @param user the user.
273     * @param storageContext the preferences context.
274     * @param contextVars the context variables.
275     * @param id the preference ID.
276     * @return the user preference value as a Date.
277     * @throws UserPreferencesException if an error occurred
278     */
279    public ZonedDateTime getUserPreferenceAsDate(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
280    {
281        UserPreferencesStorage storageManager = getStorageManager(contextVars, id);
282        
283        return storageManager.getUserPreferenceAsDate(user, storageContext, contextVars, id);
284    }
285    
286    /**
287     * Get a single boolean user preference value for a given context.
288     * @param user the user.
289     * @param storageContext the preferences context.
290     * @param contextVars the context variables.
291     * @param id the preference ID.
292     * @return the user preference value as a Boolean.
293     * @throws UserPreferencesException if an error occurred
294     */
295    public Boolean getUserPreferenceAsBoolean(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
296    {
297        UserPreferencesStorage storageManager = getStorageManager(contextVars, id);
298        
299        return storageManager.getUserPreferenceAsBoolean(user, storageContext, contextVars, id);
300    }
301    
302    /**
303     * Get a single double user preference value for a given context.
304     * @param user the user.
305     * @param storageContext the preferences context.
306     * @param contextVars the context variables.
307     * @param id the preference ID.
308     * @return the user preference value as a Double.
309     * @throws UserPreferencesException if an error occurred
310     */
311    public Double getUserPreferenceAsDouble(UserIdentity user, String storageContext, Map<String, String> contextVars, String id) throws UserPreferencesException
312    {
313        UserPreferencesStorage storageManager = getStorageManager(contextVars, id);
314        
315        return storageManager.getUserPreferenceAsDouble(user, storageContext, contextVars, id);
316    }
317    
318    /**
319     * Get all user preference storages.
320     * @param contextVars the context variables.
321     * @return a Set of storage roles.
322     */
323    protected Set<String> getStorageRoles(Map<String, String> contextVars)
324    {
325        Set<String> storageRoles = new HashSet<>();
326        
327        // Add the default storage role.
328        storageRoles.add(_defaultStorageRole);
329        
330        for (UserPreference preference : _userPrefEP.getUserPreferences(contextVars))
331        {
332            String role = StringUtils.defaultIfEmpty(preference.getManagerRole(), _defaultStorageRole);
333            
334            storageRoles.add(role);
335        }
336        
337        return storageRoles;
338    }
339    
340    /**
341     * Get all user preferences, grouped by storage point.
342     * @param contextVars the context variables.
343     * @return a Map of storage role -&gt; collection of user preferences.
344     */
345    protected Map<String, Collection<UserPreference>> getUserPrefsByStorage(Map<String, String> contextVars)
346    {
347        Map<String, Collection<UserPreference>> userPrefs = new HashMap<>();
348        
349        for (UserPreference preference : _userPrefEP.getUserPreferences(contextVars))
350        {
351            String role = StringUtils.defaultIfEmpty(preference.getManagerRole(), _defaultStorageRole);
352            
353            Collection<UserPreference> rolePrefs = userPrefs.get(role);
354            if (rolePrefs == null)
355            {
356                rolePrefs = new ArrayList<>();
357                userPrefs.put(role, rolePrefs);
358            }
359            
360            rolePrefs.add(preference);
361        }
362        
363        return userPrefs;
364    }
365    
366    /**
367     * Get user preference values, divided up by storage role.
368     * @param contextVars the context variables.
369     * @param preferenceValues the unsorted preference values.
370     * @return the preference values, divided up by storage role, as a Map of storage role -gt; preference values.
371     */
372    protected Map<String, Map<String, String>> getUserPrefsValuesByStorage(Map<String, String> contextVars, Map<String, String> preferenceValues)
373    {
374        Map<String, Map<String, String>> preferenceValuesByStorage = new HashMap<>();
375        
376        // Initialize with empty maps.
377        for (String storageRole : getStorageRoles(contextVars))
378        {
379            preferenceValuesByStorage.put(storageRole, new HashMap<>());
380        }
381        
382        Map<String, Collection<UserPreference>> userPrefsByStorage = getUserPrefsByStorage(contextVars);
383        Map<String, String> unknownPreferenceValues = new HashMap<>(preferenceValues);
384        
385        for (String storageRole : userPrefsByStorage.keySet())
386        {
387            Map<String, String> storagePrefValues = preferenceValuesByStorage.get(storageRole);
388            
389            // Iterate over declared user preferences for this storage.
390            Collection<UserPreference> storageUserPrefs = userPrefsByStorage.get(storageRole);
391            for (UserPreference pref : storageUserPrefs)
392            {
393                String prefId = pref.getName();
394                if (preferenceValues.containsKey(prefId))
395                {
396                    // Add the value to the corresponding storage map.
397                    storagePrefValues.put(prefId, preferenceValues.get(prefId));
398                    // Remove the value from the unknown preferences.
399                    unknownPreferenceValues.remove(prefId);
400                }
401            }
402        }
403        
404        Map<String, String> defaultStorageRoleValues = preferenceValuesByStorage.get(_defaultStorageRole);
405        if (!unknownPreferenceValues.isEmpty())
406        {
407            // At this point, the unknownPreferenceValues map contains only undeclared preferences:
408            // add them to the default storage.
409            defaultStorageRoleValues.putAll(unknownPreferenceValues);
410        }
411        // If no value is to be set in the default storage role, remove the role from the preference values by storage
412        else if (defaultStorageRoleValues.isEmpty())
413        {
414            preferenceValuesByStorage.remove(_defaultStorageRole);
415        }
416        
417        return preferenceValuesByStorage;
418    }
419    
420    /**
421     * Get the storage component for a given role.
422     * @param role the storage component role.
423     * @return the storage component.
424     * @throws UserPreferencesException if an error occurs looking up the storage manager.
425     */
426    protected UserPreferencesStorage getStorageManager(String role) throws UserPreferencesException
427    {
428        UserPreferencesStorage storageManager = null;
429        
430        String componentRole = role;
431        
432        if (_storageManagers.containsKey(componentRole))
433        {
434            storageManager = _storageManagers.get(componentRole);
435        }
436        else
437        {
438            try
439            {
440                storageManager = (UserPreferencesStorage) _serviceManager.lookup(componentRole);
441                _storageManagers.put(componentRole, storageManager);
442            }
443            catch (ServiceException e)
444            {
445                throw new UserPreferencesException("Error looking up the user preference storage component of role " + componentRole, e);
446            }
447        }
448        
449        return storageManager;
450    }
451    
452    /**
453     * Get the storage component for a given role.
454     * @param contextVars The context vars
455     * @param id The preference id
456     * @return the storage component.
457     * @throws UserPreferencesException if an error occurs looking up the storage manager.
458     */
459    protected UserPreferencesStorage getStorageManager(Map<String, String> contextVars, String id) throws UserPreferencesException
460    {
461        String storageManagerRole = _defaultStorageRole;
462        
463        UserPreference preference = _userPrefEP.getUserPreference(contextVars, id);
464        
465        if (preference != null && StringUtils.isNotEmpty(preference.getManagerRole()))
466        {
467            storageManagerRole = preference.getManagerRole();
468        }
469        
470        return getStorageManager(storageManagerRole);
471    }
472    
473    /**
474     * Cast the preference values as their real type.
475     * @param untypedValues the untyped user preferences
476     * @param contextVars the context variables.
477     * @return typed user preferences
478     */
479    protected Map<String, Object> _castValues(Map<String, String> untypedValues, Map<String, String> contextVars)
480    {
481        Map<String, Object> typedValues = new HashMap<>(untypedValues.size());
482        
483        for (Entry<String, String> entry : untypedValues.entrySet())
484        {
485            UserPreference userPref = _userPrefEP.getUserPreference(contextVars, entry.getKey());
486            if (userPref != null)
487            {
488                Object value = userPref.getType().castValue(entry.getValue());
489                typedValues.put(userPref.getName(), value);
490            }
491            else
492            {
493                typedValues.put(entry.getKey(), entry.getValue());
494            }
495        }
496        
497        return typedValues;
498    }
499    
500}