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