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