001/*
002 *  Copyright 2016 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.user;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang3.tuple.Pair;
032
033import org.ametys.core.cache.AbstractCacheManager;
034import org.ametys.core.cache.Cache;
035import org.ametys.core.user.directory.NotUniqueUserException;
036import org.ametys.core.user.directory.UserDirectory;
037import org.ametys.core.user.population.PopulationContextHelper;
038import org.ametys.core.user.population.UserPopulation;
039import org.ametys.core.user.population.UserPopulationDAO;
040import org.ametys.plugins.core.user.UserHelper;
041import org.ametys.runtime.i18n.I18nizableText;
042import org.ametys.runtime.plugin.component.AbstractLogEnabled;
043
044/**
045 * Component for getting user list and verify the presence of a particular user on a context or for user directory(ies).
046 */
047public class UserManager extends AbstractLogEnabled implements Component, Serviceable, Initializable
048{
049    /** Avalon Role */
050    public static final String ROLE = UserManager.class.getName(); 
051
052    private static final String __USER_CACHE_ID = UserHelper.class.getName() + "$userCache";
053    
054    private static final String __USER_CACHE_BY_EMAIL_ID = UserHelper.class.getName() + "$userByEmailCache";
055    
056    /** The DAO for User Population */
057    protected UserPopulationDAO _userPopulationDAO;
058    /** The helper for the associations population/context */
059    protected PopulationContextHelper _populationContextHelper;
060
061    private AbstractCacheManager _abstractCacheManager;
062    
063    @Override
064    public void service(ServiceManager manager) throws ServiceException
065    {
066        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
067        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
068        _abstractCacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE);
069    }
070
071    @Override
072    public void initialize() throws Exception
073    {
074        _abstractCacheManager.createRequestCache(__USER_CACHE_ID,
075                new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_USER_BY_USER_IDENTITY_LABEL"),
076                new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_USER_BY_USER_IDENTITY_DESCRIPTION"),
077                true);
078        _abstractCacheManager.createRequestCache(__USER_CACHE_BY_EMAIL_ID,
079                new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_USER_BY_EMAIL_LABEL"),
080                new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_USER_BY_EMAIL_DESCRIPTION"),
081                true);
082    }
083    
084    /**
085     * Get the list of users on some given contexts
086     * @param contexts The contexts
087     * @param checkRight True to check that current user belongs to one of populations on theses contexts or he's an administrator user
088     * @return the collection of users
089     */
090    public Collection<User> getUsersByContext(Set<String> contexts, boolean checkRight)
091    {
092        List<UserPopulation> userPopulations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, checkRight).stream()
093            .map(upId -> _userPopulationDAO.getUserPopulation(upId))
094            .collect(Collectors.toList());
095        
096        return getUsersByPopulations(userPopulations);
097    }
098    
099    /**
100     * Get the users for given users' populations
101     * @param userPopulationIds the id of population of users
102     * @return the collection of users
103     */
104    public Collection<User> getUsersByPopulationIds(List<String> userPopulationIds)
105    {
106        List<User> users = new ArrayList<>();
107        for (String id : userPopulationIds)
108        {
109            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(id);
110            if (userPopulation != null)
111            {
112                for (User user : getUsers(userPopulation))
113                {
114                    if (!users.contains(user))
115                    {
116                        users.add(user);
117                    }
118                }
119            }
120        }
121        return users;
122    }
123    
124    /**
125     * Get the users for given users' populations
126     * @param userPopulations the population of users
127     * @return the collection of users
128     */
129    public Collection<User> getUsersByPopulations(List<UserPopulation> userPopulations)
130    {
131        List<User> users = new ArrayList<>();
132        for (UserPopulation userPopulation : userPopulations)
133        {
134            for (User user : getUsers(userPopulation))
135            {
136                if (!users.contains(user))
137                {
138                    users.add(user);
139                }
140            }
141        }
142        return users;
143    }
144    
145    /**
146     * Gets all the users of a {@link UserPopulation}
147     * @param userPopulationId The ID of user population
148     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
149     */
150    public Collection<User> getUsers(String userPopulationId)
151    {
152        UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
153        if (userPopulation != null)
154        {
155            return getUsers(userPopulation);
156        }
157        else
158        {
159            return Collections.EMPTY_LIST;
160        }
161    }
162    
163    /**
164     * Gets all the users of a {@link UserPopulation}
165     * @param userPopulation The user population
166     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
167     */
168    public Collection<User> getUsers(UserPopulation userPopulation)
169    {
170        List<User> users = new ArrayList<>();
171        
172        for (UserDirectory ud : userPopulation.getUserDirectories())
173        {
174            for (User user : ud.getUsers())
175            {
176                if (!users.contains(user))
177                {
178                    users.add(user);
179                }
180            }
181        }
182        
183        return users;
184    }
185    
186    /**
187     * Get a list of users given the parameters
188     * @param contexts The contexts
189     * @param count The limit of users to retrieve
190     * @param offset The number of result to ignore before starting to collect users. 
191     * @param parameters A map of additional parameters, see implementation.
192     * @param checkRight True to check that current user belongs to one of populations on theses contexts or he's an administrator user
193     * @return The list of retrieved {@link User}
194     */
195    public List<User> getUsersByContext(Set<String> contexts, int count, int offset, Map<String, Object> parameters, boolean checkRight)
196    {
197        List<UserPopulation> userPopulations = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, checkRight).stream()
198                .map(upId -> _userPopulationDAO.getUserPopulation(upId))
199                .collect(Collectors.toList());
200        
201        return getUsers(userPopulations, count, offset, parameters);
202    }
203    
204    /**
205     * Get a list of users given the parameters
206     * @param userPopulations the population of users
207     * @param count The limit of users to retrieve
208     * @param offset The number of result to ignore before starting to collect users. 
209     * @param parameters A map of additional parameters, see implementation.
210     * @return The list of retrieved {@link User}
211     */
212    public List<User> getUsers(List<UserPopulation> userPopulations, int count, int offset, Map<String, Object> parameters)
213    {
214        List<User> users = new ArrayList<>();
215        for (UserPopulation userPopulation : userPopulations)
216        {
217            for (User user : getUsers(userPopulation, count + offset, 0, parameters))
218            {
219                if (!users.contains(user))
220                {
221                    users.add(user);
222                }
223            }
224        }
225        
226        int boundedCount = count >= 0 ? count : Integer.MAX_VALUE;
227        int boundedOffset = offset >= 0 ? offset : 0;
228        int toIndex;
229        if (boundedOffset + boundedCount >= 0)
230        {
231            toIndex = Math.min(boundedOffset + boundedCount, users.size());
232        }
233        else
234        {
235            // particular case where count was initially negative (to say "no limit") and we set it to Integer.MAX_VALUE
236            // so if the offset is strictly positive, the sum overflows
237            toIndex = users.size();
238        }
239        return users.subList(boundedOffset, toIndex);
240    }
241    
242    /**
243     * Gets all the users of a {@link UserPopulation}
244     * @param userPopulationId The ID of user population
245     * @param count The limit of users to retrieve
246     * @param offset The number of result to ignore before starting to collect users. 
247     * @param parameters A map of additional parameters, see implementation.
248     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
249     */
250    public Collection<User> getUsers(String userPopulationId, int count, int offset, Map<String, Object> parameters)
251    {
252        UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
253        if (userPopulation != null)
254        {
255            return getUsers(userPopulation, count, offset, parameters);
256        }
257        else
258        {
259            return Collections.EMPTY_LIST;
260        }
261    }
262    
263    /**
264     * Gets all the users of a given {@link UserPopulation} and {@link UserDirectory}
265     * @param userPopulationId The ID of user population
266     * @param userDirectoryId The id of the user directory
267     * @param count The limit of users to retrieve
268     * @param offset The number of result to ignore before starting to collect users. 
269     * @param parameters A map of additional parameters, see implementation.
270     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
271     */
272    public Collection<User> getUsersByDirectory(String userPopulationId, String userDirectoryId, int count, int offset, Map<String, Object> parameters)
273    {
274        UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
275        
276        if (userPopulation == null)
277        {
278            return Collections.EMPTY_LIST;
279        }
280        
281        UserDirectory ud = userPopulation.getUserDirectory(userDirectoryId);
282        if (ud == null)
283        {
284            throw new IllegalArgumentException("In the population '" + userPopulationId + "' the directory '" + userDirectoryId + "' was referenced but does not exists");
285        }
286        return ud.getUsers(count, offset, parameters);
287    }
288    
289    /**
290     * Gets all the users of a {@link UserPopulation}
291     * @param userPopulation The users population
292     * @param count The limit of users to retrieve
293     * @param offset The number of result to ignore before starting to collect users. 
294     * @param parameters A map of additional parameters, see implementation.
295     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
296     */
297    public Collection<User> getUsers(UserPopulation userPopulation, int count, int offset, Map<String, Object> parameters)
298    {
299        List<User> users = new ArrayList<>();
300        
301        for (UserDirectory ud : userPopulation.getUserDirectories())
302        {
303            for (User user : ud.getUsers(-1, 0, parameters))
304            {
305                if (!users.contains(user))
306                {
307                    users.add(user);
308                }
309            }
310        }
311        
312        int boundedCount = count >= 0 ? count : Integer.MAX_VALUE;
313        int boundedOffset = offset >= 0 ? offset : 0;
314        int toIndex;
315        if (boundedOffset + boundedCount >= 0)
316        {
317            toIndex = Math.min(boundedOffset + boundedCount, users.size());
318        }
319        else
320        {
321            // particular case where count was initially negative (to say "no limit") and we set it to Integer.MAX_VALUE
322            // so if the offset is strictly positive, the sum overflows
323            toIndex = users.size();
324        }
325        return users.subList(boundedOffset, toIndex);
326    }
327    
328    /**
329     * Gets all the users of a {@link UserPopulation}
330     * @param userPopulation The users population
331     * @param userDirectoryId The id of the user directory
332     * @param count The limit of users to retrieve
333     * @param offset The number of result to ignore before starting to collect users. 
334     * @param parameters A map of additional parameters, see implementation.
335     * @return list of users as Collection of {@link User}s, empty if a problem occurs.
336     */
337    public Collection<User> getUsersByDirectory(UserPopulation userPopulation, String userDirectoryId, int count, int offset, Map<String, Object> parameters)
338    {
339        UserDirectory ud = userPopulation.getUserDirectory(userDirectoryId);
340        return ud.getUsers(count, offset, parameters);
341    }
342    
343    /**
344     * Get a user by his login on some given contexts
345     * @param contexts The contexts
346     * @param login Login of the user to get. Cannot be null.
347     * @param checkRight True to check that current user is authorized to retrieve this user (true if he belongs to one of populations on theses contexts or he's an administrator user)
348     * @return User's information as a {@link User} instance or null if the user login does not exist.
349     */
350    public User getUserByContext(Set<String> contexts, String login, boolean checkRight)
351    {
352        Set<String> upIds = _populationContextHelper.getUserPopulationsOnContexts(contexts, false, checkRight);
353        for (String upId : upIds)
354        {
355            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(upId);
356            User user = getUser(userPopulation, login);
357            if (user != null)
358            {
359                return user;
360            }
361        }
362        return null;
363    }
364    
365    /**
366     * Get the user from its user identity
367     * @param userIdentity The user identity
368     * @return The User or null if the user login does not exist.
369     */
370    public User getUser (UserIdentity userIdentity)
371    {
372        if (userIdentity == null)
373        {
374            return null;
375        }
376        
377        Cache<UserIdentity, User> cache = _getUserCache();
378        return cache.get(userIdentity, key -> 
379        {
380            UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(key.getPopulationId());
381            if (userPopulation != null)
382            {
383                return getUser(userPopulation, key.getLogin());
384            }
385            else
386            {
387                return null;
388            }
389        });
390    }
391    
392    /**
393     * Get a particular user of the given users population by his login.
394     * @param userPopulationId The ID of user population
395     * @param login Login of the user to get. Cannot be null.
396     * @return User's information as a {@link User} instance or null if the user login does not exist.
397     */
398    public User getUser(String userPopulationId, String login)
399    {
400        UserIdentity userIdentity = new UserIdentity(login, userPopulationId);
401        return getUser(userIdentity);
402    }
403
404    private Cache<UserIdentity, User> _getUserCache()
405    {
406        return _abstractCacheManager.get(__USER_CACHE_ID);
407    }
408    
409    /**
410     * Get a particular user of the given users population by his email.
411     * @param userPopulationId The ID of user population
412     * @param email Email of the user to get. Cannot be null.
413     * @return User's information as a {@link User} instance or null if the user login does not exist.
414     * @throws NotUniqueUserException if many users match the given email
415     */
416    public User getUserByEmail(String userPopulationId, String email) throws NotUniqueUserException
417    {
418        if (email == null)
419        {
420            return null;
421        }
422
423        Cache<Pair<String, String>, User> cache = _getUserByEmailCache();
424        return cache.get(Pair.of(userPopulationId, email), key -> 
425        {
426            try
427            {
428                UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
429                if (userPopulation != null)
430                {
431                    return getUserByEmail(userPopulation, email);
432                }
433                else
434                {
435                    return null;
436                }
437            }
438            catch (NotUniqueUserException e)
439            {
440                return null;
441            }
442        });
443    }
444
445    private Cache<Pair<String, String>, User> _getUserByEmailCache()
446    {
447        return _abstractCacheManager.get(__USER_CACHE_BY_EMAIL_ID);
448    }
449    
450    
451    
452    
453    /**
454     * Get a particular user of the given user population and given user directory by his login.
455     * @param userPopulationId The ID of user population
456     * @param userDirectoryId The id of the user directory
457     * @param login Login of the user to get. Cannot be null.
458     * @return User's information as a {@link User} instance or null if the user login does not exist.
459     */
460    public User getUserByDirectory(String userPopulationId, String userDirectoryId, String login)
461    {
462        UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
463        if (userPopulation != null)
464        {
465            return getUserByDirectory(userPopulation, userDirectoryId, login);
466        }
467        else
468        {
469            return null;
470        }
471    }
472    
473    /**
474     * Get a particular user of the given user population by his login.
475     * @param userPopulation The user population
476     * @param login Login of the user to get. Cannot be null.
477     * @return User's information as a {@link User} instance or null if the user login does not exist.
478     */
479    public User getUser(UserPopulation userPopulation, String login)
480    {
481        for (UserDirectory ud : userPopulation.getUserDirectories())
482        {
483            User user = ud.getUser(login);
484            if (user != null)
485            {
486                return user;
487            }
488        }
489        return null;
490    }
491    
492    /**
493     * Get a particular user of the given user population by his email.
494     * @param userPopulation The user population
495     * @param email Email of the user to get. Cannot be null.
496     * @return User's information as a {@link User} instance or null if the user login does not exist.
497     * @throws NotUniqueUserException if many users match the given email
498     */
499    public User getUserByEmail(UserPopulation userPopulation, String email) throws NotUniqueUserException
500    {
501        for (UserDirectory ud : userPopulation.getUserDirectories())
502        {
503            User user = ud.getUserByEmail(email);
504            if (user != null)
505            {
506                return user;
507            }
508        }
509        return null;
510    }
511    
512    /**
513     * Get a particular user of the given user population and given user directory by his login.
514     * @param userPopulation The user population
515     * @param userDirectoryId The id of the user directory
516     * @param login Login of the user to get. Cannot be null.
517     * @return User's information as a {@link User} instance or null if the user login does not exist.
518     */
519    public User getUserByDirectory(UserPopulation userPopulation, String userDirectoryId, String login)
520    {
521        UserDirectory ud = userPopulation.getUserDirectory(userDirectoryId);
522        
523        User user = ud.getUser(login);
524        if (user != null)
525        {
526            return user;
527        }
528            
529        return null;
530    }
531    
532    /**
533     * Get the user directory the given user belongs to
534     * @param userPopulationId The id of the user population
535     * @param login Login of the user to get. Cannot be null.
536     * @return The user directory the user belongs to.
537     */
538    public UserDirectory getUserDirectory(String userPopulationId, String login)
539    {
540        UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(userPopulationId);
541        if (userPopulation == null)
542        {
543            return null;
544        }
545        
546        for (UserDirectory ud : userPopulation.getUserDirectories())
547        {
548            User user = ud.getUser(login);
549            if (user != null)
550            {
551                return ud;
552            }
553        }
554        return null;
555    }
556}