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}