001/*
002 *  Copyright 2015 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.plugins.core.ui.user;
017
018import java.awt.image.BufferedImage;
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.NoSuchElementException;
026
027import javax.imageio.ImageIO;
028
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.cocoon.environment.Request;
032import org.apache.commons.codec.binary.Base64;
033import org.apache.commons.io.FilenameUtils;
034import org.apache.commons.lang.StringUtils;
035
036import org.ametys.core.upload.Upload;
037import org.ametys.core.upload.UploadManager;
038import org.ametys.core.user.UserIdentity;
039import org.ametys.core.userpref.UserPreferencesErrors;
040import org.ametys.core.util.JSONUtils;
041import org.ametys.plugins.core.ui.user.DefaultProfileImageProvider.ProfileImageSource;
042import org.ametys.plugins.core.userpref.SetUserPreferencesAction;
043
044/**
045 * Action which saves the user profile in user preferences
046 */
047public class SetUserProfileAction extends SetUserPreferencesAction
048{
049    /** JSON Utils */
050    protected JSONUtils _jsonUtils;
051    
052    /** User profile image provider */
053    protected DefaultProfileImageProvider _profileImageProvider;
054    
055    /** Upload manager */
056    protected UploadManager _uploadManager;
057    
058    @Override
059    public void service(ServiceManager smanager) throws ServiceException
060    {
061        super.service(smanager);
062        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
063    }
064    
065    @Override
066    protected Map<String, String> _getValues(Request request, Map<String, String> contextVars, UserIdentity user, Collection<String> preferenceIds, UserPreferencesErrors errors)
067    {
068        // Delayed initialized to ensure safe mode do not fail to load
069        if (_profileImageProvider == null)
070        {
071            try
072            {
073                _uploadManager = (UploadManager) manager.lookup(UploadManager.ROLE);
074                _profileImageProvider = (DefaultProfileImageProvider) manager.lookup(ProfileImageProvider.ROLE);
075            }
076            catch (ServiceException e)
077            {
078                throw new RuntimeException("Lazy initialization failed.", e);
079            }
080        }
081        
082        Map<String, String> preferences = super._getValues(request, contextVars, user, preferenceIds, errors);
083        
084        String userPrefImageJson = preferences.get(DefaultProfileImageProvider.USERPREF_PROFILE_IMAGE);
085        if (StringUtils.isNotEmpty(userPrefImageJson))
086        {
087            Map<String, Object> userPrefImage = null;
088            
089            // Handle upload case.
090            try
091            {
092                userPrefImage = _jsonUtils.convertJsonToMap(userPrefImageJson);
093            }
094            catch (Exception e)
095            {
096                getLogger().error(String.format("Unable to extract image user pref for user '%s'.", user), e);
097            }
098            
099            if (userPrefImage != null)
100            {
101                // If upload, need to be cropped and stored as base64.
102                ProfileImageSource profileImageSource = _profileImageProvider.getProfileImageSource((String) userPrefImage.get("source"));
103                if (ProfileImageSource.USERPREF.equals(profileImageSource))
104                {
105                    // Nothing to do, userpref has not changed
106                    preferences.remove(DefaultProfileImageProvider.USERPREF_PROFILE_IMAGE);
107                }
108                else if (ProfileImageSource.UPLOAD.equals(profileImageSource))
109                {
110                    @SuppressWarnings("unchecked")
111                    Map<String, Object> sourceParams = (Map<String, Object>) userPrefImage.get("parameters");
112                    String uploadId = sourceParams != null ? (String) sourceParams.get("id") : null;
113                    
114                    Map<String, Object> base64SourceParams = null;
115                    
116                    if (StringUtils.isNotEmpty(uploadId))
117                    {
118                        Upload upload = null;
119                        try
120                        {
121                            upload = _uploadManager.getUpload(user, uploadId);
122                            
123                            base64SourceParams = new HashMap<>();
124                            try (InputStream is = upload.getInputStream())
125                            {
126                                String filename = upload.getFilename();
127                                base64SourceParams.put("data", _convertFile(filename, is));
128                                base64SourceParams.put("filename", filename);
129                            }
130                            catch (IOException e)
131                            {
132                                base64SourceParams = null;
133                                
134                                getLogger().error(
135                                        String.format("Unable to store the profile image user pref for user '%s'. Error while trying to convert the uploaded file '%s' to base64.",
136                                                user, uploadId), e); 
137                            }
138                        }
139                        catch (NoSuchElementException e)
140                        {
141                            // Invalid upload id
142                            getLogger().error(String.format("Cannot find the temporary uploaded file for id '%s' and login '%s'.", uploadId, user), e);
143                        }
144                    }
145                    
146                    if (base64SourceParams != null)
147                    {
148                        Map<String, Object> base64UserPrefImage = new HashMap<>();
149                        base64UserPrefImage.put("source", ProfileImageSource.BASE64.name().toLowerCase());
150                        base64UserPrefImage.put("parameters", base64SourceParams);
151                        
152                        preferences.put(DefaultProfileImageProvider.USERPREF_PROFILE_IMAGE, _jsonUtils.convertObjectToJson(base64UserPrefImage));
153                    }
154                    else
155                    {
156                        // Upload seems corrupted, remove this data from the values.
157                        preferences.remove(DefaultProfileImageProvider.USERPREF_PROFILE_IMAGE);
158                    }
159                }
160            }
161        }
162        
163        return preferences;
164    }
165    
166    /**
167     * Convert the uploaded file to base64.
168     * Also automatically crop the image to 64x64 pixels.
169     * @param filename The file name
170     * @param is The input stream of the uploaded file
171     * @return The base64 string
172     * @throws IOException If an exception occurs while manipulating streams
173     */
174    protected String _convertFile(String filename, InputStream is) throws IOException
175    {
176        // Crop the image the get a square image, vertically centered to the input image.
177        BufferedImage image = _profileImageProvider.cropUploadedImage(is);
178        
179        // Base64 encoding
180        try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
181        {
182            String format = FilenameUtils.getExtension(filename);
183            format = ProfileImageReader.ALLOWED_IMG_FORMATS.contains(format) ? format : "png";
184            
185            ImageIO.write(image, format, baos);
186            return Base64.encodeBase64URLSafeString(baos.toByteArray());
187        }
188    }
189}