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