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