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