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}