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