001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.user; 017 018import java.io.Closeable; 019import java.io.IOException; 020import java.io.InputStream; 021import java.time.ZonedDateTime; 022import java.util.List; 023import java.util.TimeZone; 024import java.util.stream.Collectors; 025import java.util.stream.Stream; 026 027import org.apache.commons.lang3.StringUtils; 028 029import org.ametys.core.user.dataprovider.UserDataProvider; 030import org.ametys.core.user.directory.StoredUser; 031import org.ametys.core.user.directory.UserDirectory; 032import org.ametys.core.util.SizeUtils.ExcludeFromSizeCalculation; 033 034/** 035 * Implementation of the principal abstraction to represent an user with a login and a {@link StoredUser} 036 * @param <T> The type of the factory 037 */ 038public class User <T extends UserFactory> implements java.security.Principal 039{ 040 /** The id of the last name data. This data is of type {@link String} */ 041 public static final String LAST_NAME_DATA_ID = "lastname"; 042 /** The id of the first name data. This data is of type {@link String} */ 043 public static final String FIRST_NAME_DATA_ID = "firstname"; 044 /** The id of the email data. This data is of type {@link String} */ 045 public static final String EMAIL_DATA_ID = "email"; 046 /** The id of the image data. This data is of type {@link UserImage} */ 047 public static final String IMAGE_DATA_ID = "image"; 048 /** The id of the language data. This data is of type String */ 049 public static final String LANGUAGE_DATA_ID = "language"; 050 /** The id of the timezone data. This data is of type {@link TimeZone} or is a TimeZoneId of type {@link String} */ 051 public static final String TIMEZONE_DATA_ID = "timezone"; 052 /** The id of the creation date data. This data is of type {@link ZonedDateTime} */ 053 public static final String CREATION_DATE_DATA_ID = "creation-date"; 054 /** The id of the creation origin data. This data is of type {@link UserCreationOrigin} */ 055 public static final String CREATION_ORIGIN_DATA_ID = "creation-origin"; 056 057 /** 058 * The identity of this principal. 059 */ 060 protected UserIdentity _identity; 061 /** 062 * The user directory this user belongs to. 063 */ 064 @ExcludeFromSizeCalculation 065 protected UserDirectory _userDirectory; 066 067 /** The factory */ 068 @ExcludeFromSizeCalculation 069 protected T _factory; 070 071 private StoredUser _storedUser; 072 073 /** 074 * Enumeration for the user creation origin 075 * 076 */ 077 public enum UserCreationOrigin 078 { 079 /** User created by system */ 080 SYSTEM, 081 /** User created by an administrator */ 082 ADMIN, 083 /** User created by user signup */ 084 USER_SIGNUP, 085 /** When user creation is unknown or not available */ 086 NOT_AVAILABLE 087 } 088 089 /** 090 * Basic structure holding necessary data representing an user profile image 091 */ 092 public static class UserImage implements Closeable 093 { 094 private final InputStream _inputstream; 095 private final String _filename; 096 private final Long _length; 097 private final Long _lastModified; 098 private final String _type; 099 100 /** 101 * Constructor 102 * @param inputstream The image input stream 103 * @param filename The file name or null if unknown 104 * @param length The file length if known 105 * @param lastModified The last modified date 106 * @param type The type name of the source of the image to compute a cache key 107 */ 108 public UserImage(InputStream inputstream, String filename, Long length, Long lastModified, String type) 109 { 110 _inputstream = inputstream; 111 _filename = filename; 112 _length = length; 113 _lastModified = lastModified; 114 _type = type; 115 } 116 117 /** 118 * Retrieve the type name of the source of the image to compute a cache key 119 * @return the type 120 */ 121 public String getType() 122 { 123 return _type; 124 } 125 126 /** 127 * Retrieves the input stream 128 * @return the input stream 129 */ 130 public InputStream getInputstream() 131 { 132 return _inputstream; 133 } 134 135 /** 136 * Retrieves the filename 137 * @return the filename or null if not defined 138 */ 139 public String getFilename() 140 { 141 return _filename; 142 } 143 144 /** 145 * Retrieves the length 146 * @return the length or null or -1 if unknown 147 */ 148 public Long getLength() 149 { 150 return _length; 151 } 152 153 /** 154 * Retrieve the last modified date. 155 * @return The last modified date. Can be null if unknown. 156 */ 157 public Long getLastModified() 158 { 159 return _lastModified; 160 } 161 162 /** 163 * Close input stream 164 * @implNote Close the input stream of the image. 165 */ 166 public void close() throws IOException 167 { 168 _inputstream.close(); 169 } 170 } 171 172 /** 173 * Construct a new UserPrincipal, associated with a last name, a first name, 174 * an email and identified by a login. 175 * @param storedUser The storedUser. Cannot be null 176 * @param userDirectory The user directory the user belongs to. Cannot be null. 177 * @param factory The factory 178 */ 179 User(StoredUser storedUser, UserDirectory userDirectory, T factory) 180 { 181 _identity = new UserIdentity(storedUser.getIdentifier(), userDirectory.getPopulationId()); 182 _storedUser = storedUser; 183 _userDirectory = userDirectory; 184 _factory = factory; 185 } 186 187 /** 188 * The identity of the user. 189 * 190 * @return The identity. 191 */ 192 public UserIdentity getIdentity() 193 { 194 return _identity; 195 } 196 197 /** 198 * Get the StoredUser of the User 199 * @return The {@link StoredUser} 200 */ 201 public StoredUser getStoredUser() 202 { 203 return _storedUser; 204 } 205 206 @Override 207 public String getName() 208 { 209 return UserIdentity.userIdentityToString(_identity); 210 } 211 212 /** 213 * The user directory this user belongs to. 214 * @return The user directory 215 */ 216 public UserDirectory getUserDirectory() 217 { 218 return _userDirectory; 219 } 220 221 /** 222 * The last name of the user 223 * @return The last name. 224 */ 225 public String getLastName() 226 { 227 return (String) getValue(LAST_NAME_DATA_ID); 228 } 229 230 /** 231 * The first name of the user 232 * @return The first name. 233 */ 234 public String getFirstName() 235 { 236 return (String) getValue(FIRST_NAME_DATA_ID); 237 } 238 239 /** 240 * The email of the user represented by this Principal. 241 * 242 * @return The email. 243 */ 244 public String getEmail() 245 { 246 return (String) getValue(EMAIL_DATA_ID); 247 } 248 249 /** 250 * The fullname of this user. 251 * @return The full name 252 */ 253 public String getFullName() 254 { 255 return _getFullName(true); 256 } 257 258 /** 259 * The fullname to use to display if sort is needed. 260 * Ensure the sort will be on 261 * @return The sortable name 262 */ 263 public String getSortableName() 264 { 265 return _getFullName(false); 266 } 267 268 /** 269 * Get user's avatar from the {@link UserDataProvider} with the biggest priority 270 * @param size The size 271 * @param maxSize The maxSize 272 * @return A {@link UserImage} the user's avatar 273 */ 274 public UserImage getImage(int size, int maxSize) 275 { 276 UserImageAccessor imageAccessor = (UserImageAccessor) getValue(IMAGE_DATA_ID); 277 278 return imageAccessor != null ? imageAccessor.getImage(size, maxSize) : null; 279 } 280 281 /** 282 * Get user's language from the {@link UserDataProvider} with the biggest priority 283 * @return The user's language 284 */ 285 public String getLanguage() 286 { 287 return (String) getValue(LANGUAGE_DATA_ID); 288 } 289 290 /** 291 * Get user's TimeZone from the {@link UserDataProvider} with the biggest priority 292 * @return The user's timezone 293 */ 294 public TimeZone getTimeZone() 295 { 296 Object timezoneFound = getValue(TIMEZONE_DATA_ID); 297 if (timezoneFound instanceof TimeZone timeZone) 298 { 299 return timeZone; 300 } 301 else if (timezoneFound instanceof String timeZoneId && List.of(TimeZone.getAvailableIDs()).contains(timeZoneId)) 302 { 303 return TimeZone.getTimeZone(timeZoneId); 304 } 305 306 return null; 307 } 308 309 /** 310 * Get the last connection date of the user 311 * @return the last connection date or null if the user never logged in 312 */ 313 public ZonedDateTime getLastConnectionDate() 314 { 315 return _factory.getUserStatusManager().getLastConnectionDate(this.getIdentity()).orElse(null); 316 } 317 318 /** 319 * Get the user's creation date 320 * @return the creation date 321 */ 322 public ZonedDateTime getCreationDate() 323 { 324 return (ZonedDateTime) getValue(CREATION_DATE_DATA_ID); 325 } 326 327 /** 328 * Get the user's creation origin 329 * @return the creation origin 330 */ 331 public UserCreationOrigin getCreationOrigin() 332 { 333 return (UserCreationOrigin) getValue(CREATION_ORIGIN_DATA_ID); 334 } 335 336 /** 337 * Get a user's data from the {@link UserDataProvider} with the biggest priority 338 * @param dataId The data id 339 * @return A user's data 340 */ 341 public Object getValue(String dataId) 342 { 343 return _factory.getUserDataProviderEP().getValue(this, dataId); 344 } 345 346 /** 347 * Get the {@link UserDataProvider} with the biggest priority and a value not null for the data requested 348 * @param dataId The data id 349 * @return A {@link UserDataProvider} the user data provider which is going to provide the data requested 350 */ 351 public UserDataProvider getProviderFor(String dataId) 352 { 353 return _factory.getUserDataProviderEP().getProviderFor(this, dataId); 354 } 355 356 /** 357 * The full name of the user represented by this Principal. 358 * @param firstnameThenLastname Define the name order in the full name. Set to <code>true</code> to retrieve the fullname with firstname first and lastname second. Set to <code>false</code> to retrieve the fullname with lastname first and firstname second. 359 * @return The full name. 360 */ 361 protected String _getFullName(boolean firstnameThenLastname) 362 { 363 Stream<String> stream = firstnameThenLastname 364 ? Stream.of(getFirstName(), getLastName()) 365 : Stream.of(getLastName(), getFirstName()); 366 367 String sortableName = stream.filter(StringUtils::isNotEmpty) 368 .collect(Collectors.joining(" ")); 369 370 return StringUtils.defaultIfEmpty(sortableName, _identity.getLogin()); 371 } 372 373 /** 374 * Return a String representation of this object, which exposes only 375 * information that should be public. 376 * 377 * @return A string representing the user. 378 */ 379 @Override 380 public String toString() 381 { 382 StringBuilder sb = new StringBuilder("Principal["); 383 sb.append(_userDirectory.getPopulationId()); 384 sb.append(", "); 385 sb.append(_storedUser.toString()); 386 sb.append("]"); 387 return sb.toString(); 388 } 389 390 /** 391 * Test if two principal are equals. 392 * @return true if the given Object represents the same Principal. 393 */ 394 @Override 395 public boolean equals(Object another) 396 { 397 if (another == null || !(another instanceof User)) 398 { 399 return false; 400 } 401 402 User otherUser = (User) another; 403 404 return _identity != null && _identity.equals(otherUser.getIdentity()); 405 } 406 407 @Override 408 public int hashCode() 409 { 410 return _identity.hashCode(); 411 } 412}