Index: lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/PortraitBatchUploadAction.java =================================================================== diff -u --- lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/PortraitBatchUploadAction.java (revision 0) +++ lams_admin/src/java/org/lamsfoundation/lams/admin/web/action/PortraitBatchUploadAction.java (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -0,0 +1,105 @@ +/**************************************************************** + * Copyright (C) 2005 LAMS Foundation (http://lamsfoundation.org) + * ============================================================= + * License Information: http://lamsfoundation.org/licensing/lams/2.0/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2.0 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA + * + * http://www.gnu.org/licenses/gpl.txt + * **************************************************************** + */ + +package org.lamsfoundation.lams.admin.web.action; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.struts.action.Action; +import org.apache.struts.action.ActionForm; +import org.apache.struts.action.ActionForward; +import org.apache.struts.action.ActionMapping; +import org.lamsfoundation.lams.security.ISecurityService; +import org.lamsfoundation.lams.usermanagement.dto.UserDTO; +import org.lamsfoundation.lams.usermanagement.service.IUserManagementService; +import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.web.session.SessionManager; +import org.lamsfoundation.lams.web.util.AttributeNames; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Looks for [login].png images in /tmp/portraits of user IDs within given range and starting with the given prefix + * + * @author Marcin Cieslak + */ +public class PortraitBatchUploadAction extends Action { + private static IUserManagementService userManagementService; + private static ISecurityService securityService; + + @Override + public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, + HttpServletResponse response) throws IOException { + if (!getSecurityService().isSysadmin(getUserID(), "batch upload portraits", false)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User is not a sysadmin"); + return null; + } + + Integer minUserId = WebUtil.readIntParam(request, "minUserID"); + Integer maxUserId = WebUtil.readIntParam(request, "maxUserID"); + String prefix = request.getParameter("prefix"); + + List uploadedUserNames = getUserManagementService().uploadPortraits(minUserId, maxUserId, prefix); + if (uploadedUserNames != null) { + response.setCharacterEncoding("UTF-8"); + response.setContentType("text/plain"); + Writer responseWriter = response.getWriter(); + responseWriter.write("Uploaded portraits for users:\n"); + for (String userName : uploadedUserNames) { + responseWriter.write(userName + "\n"); + } + responseWriter.close(); + } + + return null; + } + + private Integer getUserID() { + HttpSession ss = SessionManager.getSession(); + UserDTO user = (UserDTO) ss.getAttribute(AttributeNames.USER); + return user == null ? null : user.getUserID(); + } + + private IUserManagementService getUserManagementService() { + if (userManagementService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + userManagementService = (IUserManagementService) ctx.getBean("userManagementService"); + } + return userManagementService; + } + + private ISecurityService getSecurityService() { + if (securityService == null) { + WebApplicationContext ctx = WebApplicationContextUtils + .getRequiredWebApplicationContext(getServlet().getServletContext()); + securityService = (ISecurityService) ctx.getBean("securityService"); + } + return securityService; + } +} \ No newline at end of file Index: lams_admin/web/WEB-INF/struts-config.xml =================================================================== diff -u -ree2713a23c1a795eec5f782795da09918cbdf006 -ra4fd7ba340a2beac7436660039f13b9c8708f172 --- lams_admin/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision ee2713a23c1a795eec5f782795da09918cbdf006) +++ lams_admin/web/WEB-INF/struts-config.xml (.../struts-config.xml) (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -945,7 +945,10 @@ redirect="false" /> - + Index: lams_build/lib/lams/lams.jar =================================================================== diff -u -r1aff6aeac19473c0089c0f3ff578a183beb92555 -ra4fd7ba340a2beac7436660039f13b9c8708f172 Binary files differ Index: lams_central/src/java/org/lamsfoundation/lams/web/PortraitSaveAction.java =================================================================== diff -u -r39f1f7974c647ec9524ebb630759a27094027c9c -ra4fd7ba340a2beac7436660039f13b9c8708f172 --- lams_central/src/java/org/lamsfoundation/lams/web/PortraitSaveAction.java (.../PortraitSaveAction.java) (revision 39f1f7974c647ec9524ebb630759a27094027c9c) +++ lams_central/src/java/org/lamsfoundation/lams/web/PortraitSaveAction.java (.../PortraitSaveAction.java) (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -66,10 +66,6 @@ private static IAuditService auditService; private static MessageService messageService; private static CentralToolContentHandler centralToolContentHandler; - private static int LARGEST_DIMENSION_ORIGINAL = 400; - private static int LARGEST_DIMENSION_LARGE = 200; - private static int LARGEST_DIMENSION_MEDIUM = 80; - private static int LARGEST_DIMENSION_SMALL = 35; private static final String PORTRAIT_DELETE_AUDIT_KEY = "audit.delete.portrait"; /** @@ -111,7 +107,7 @@ // write to content repository NodeKey originalFileNode = null; if ((file != null) && !StringUtils.isEmpty(fileName)) { - + //Create nice file name. If file name equals to "blob" - it means it was uploaded using webcam String fileNameWithoutExt; if (fileName.equals("blob")) { @@ -131,21 +127,24 @@ + originalFileNode.getVersion()); //resize to the large size - is = ResizePictureUtil.resize(file.getInputStream(), LARGEST_DIMENSION_LARGE); + is = ResizePictureUtil.resize(file.getInputStream(), + IUserManagementService.PORTRAIT_LARGEST_DIMENSION_LARGE); NodeKey node = getCentralToolContentHandler().updateFile(originalFileNode.getUuid(), is, fileNameWithoutExt + "_large.png", "image/png"); is.close(); log.debug("saved file with uuid: " + node.getUuid() + " and version: " + node.getVersion()); //resize to the medium size - is = ResizePictureUtil.resize(file.getInputStream(), LARGEST_DIMENSION_MEDIUM); + is = ResizePictureUtil.resize(file.getInputStream(), + IUserManagementService.PORTRAIT_LARGEST_DIMENSION_MEDIUM); node = getCentralToolContentHandler().updateFile(node.getUuid(), is, fileNameWithoutExt + "_medium.png", "image/png"); is.close(); log.debug("saved file with uuid: " + node.getUuid() + " and version: " + node.getVersion()); //resize to the small size - is = ResizePictureUtil.resize(file.getInputStream(), LARGEST_DIMENSION_SMALL); + is = ResizePictureUtil.resize(file.getInputStream(), + IUserManagementService.PORTRAIT_LARGEST_DIMENSION_SMALL); node = getCentralToolContentHandler().updateFile(node.getUuid(), is, fileNameWithoutExt + "_small.png", "image/png"); is.close(); @@ -162,33 +161,35 @@ return mapping.findForward("profile"); } - + /** Called from sysadmin to delete an inappropriate portrait */ public ActionForward deletePortrait(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { Integer userId = WebUtil.readIntParam(request, "userId", true); - + // check user is sysadmin if (!(request.isUserInRole(Role.SYSADMIN))) { - log.error("Attempt to delete a portrait by user that is not sysadmin. User is "+request.getRemoteUser()+" portrait to be deleted is for user "+userId+"."); + log.error("Attempt to delete a portrait by user that is not sysadmin. User is " + request.getRemoteUser() + + " portrait to be deleted is for user " + userId + "."); return deleteResponse(response, "error"); } String responseValue = "deleted"; User userToModify = (User) getService().findById(User.class, userId); if (userToModify != null && userToModify.getPortraitUuid() != null) { - - Object[] args = new Object[] { userToModify.getFullName(), userToModify.getLogin(), userToModify.getUserId(), userToModify.getPortraitUuid() }; + + Object[] args = new Object[] { userToModify.getFullName(), userToModify.getLogin(), + userToModify.getUserId(), userToModify.getPortraitUuid() }; String auditMessage = getMessageService().getMessage(PORTRAIT_DELETE_AUDIT_KEY, args); getAuditService().log(CentralConstants.MODULE_NAME, auditMessage); try { getCentralToolContentHandler().deleteFile(userToModify.getPortraitUuid()); - userToModify.setPortraitUuid(null); - getService().saveUser(userToModify); + userToModify.setPortraitUuid(null); + getService().saveUser(userToModify); } catch (Exception e) { - log.error("Unable to delete a portrait for user "+userId+".", e); + log.error("Unable to delete a portrait for user " + userId + ".", e); return deleteResponse(response, "error"); } } @@ -200,7 +201,7 @@ response.getWriter().write(data); return null; } - + private CentralToolContentHandler getCentralToolContentHandler() { if (centralToolContentHandler == null) { WebApplicationContext wac = WebApplicationContextUtils @@ -218,7 +219,7 @@ } return service; } - + private IAuditService getAuditService() { if (PortraitSaveAction.auditService == null) { WebApplicationContext ctx = WebApplicationContextUtils Index: lams_common/src/java/org/lamsfoundation/lams/commonContext.xml =================================================================== diff -u -r23736eb1785a92ccf57cdbcb2d7b94f4a80cd1ee -ra4fd7ba340a2beac7436660039f13b9c8708f172 --- lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision 23736eb1785a92ccf57cdbcb2d7b94f4a80cd1ee) +++ lams_common/src/java/org/lamsfoundation/lams/commonContext.xml (.../commonContext.xml) (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -52,6 +52,7 @@ + @@ -78,6 +79,7 @@ PROPAGATION_REQUIRED PROPAGATION_REQUIRED PROPAGATION_REQUIRED + PROPAGATION_REQUIRED Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java =================================================================== diff -u -r60d9a173d5590295376322fc3e857ae2dca37717 -ra4fd7ba340a2beac7436660039f13b9c8708f172 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision 60d9a173d5590295376322fc3e857ae2dca37717) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/IUserManagementService.java (.../IUserManagementService.java) (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -23,6 +23,7 @@ package org.lamsfoundation.lams.usermanagement.service; +import java.io.IOException; import java.io.Serializable; import java.util.Collection; import java.util.List; @@ -51,6 +52,9 @@ * @author Fei Yang */ public interface IUserManagementService { + static int PORTRAIT_LARGEST_DIMENSION_LARGE = 200; + static int PORTRAIT_LARGEST_DIMENSION_MEDIUM = 80; + static int PORTRAIT_LARGEST_DIMENSION_SMALL = 35; /** * save(insert or update) @@ -518,10 +522,15 @@ * Stores organisation (course) groups and removes the unnecessary ones. */ void saveOrganisationGrouping(OrganisationGrouping grouping, Collection newGroups); - + /** - * Returns the SQL needed to look up portrait details for a given user. This is an efficient way to get the entries - * at the same time as retrieving the tool data, rather than making a separate lookup. + * Returns the SQL needed to look up portrait details for a given user. This is an efficient way to get the entries + * at the same time as retrieving the tool data, rather than making a separate lookup. */ String[] getPortraitSQL(String userIdString); + + /** + * Looks for [login].png images in /tmp/portraits of user IDs within given range and starting with the given prefix + */ + List uploadPortraits(Integer minUserId, Integer maxUserId, String prefix) throws IOException; } \ No newline at end of file Index: lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java =================================================================== diff -u -r60d9a173d5590295376322fc3e857ae2dca37717 -ra4fd7ba340a2beac7436660039f13b9c8708f172 --- lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision 60d9a173d5590295376322fc3e857ae2dca37717) +++ lams_common/src/java/org/lamsfoundation/lams/usermanagement/service/UserManagementService.java (.../UserManagementService.java) (revision a4fd7ba340a2beac7436660039f13b9c8708f172) @@ -23,13 +23,18 @@ package org.lamsfoundation.lams.usermanagement.service; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -39,10 +44,10 @@ import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import org.hibernate.type.IntegerType; +import org.lamsfoundation.lams.contentrepository.NodeKey; +import org.lamsfoundation.lams.contentrepository.client.IToolContentHandler; import org.lamsfoundation.lams.dao.IBaseDAO; import org.lamsfoundation.lams.learningdesign.dao.IGroupDAO; -import org.lamsfoundation.lams.notebook.service.CoreNotebookConstants; import org.lamsfoundation.lams.themes.Theme; import org.lamsfoundation.lams.usermanagement.FavoriteOrganisation; import org.lamsfoundation.lams.usermanagement.ForgotPasswordRequest; @@ -69,6 +74,7 @@ import org.lamsfoundation.lams.util.LanguageUtil; import org.lamsfoundation.lams.util.MessageService; import org.lamsfoundation.lams.util.audit.IAuditService; +import org.lamsfoundation.lams.util.imgscalr.ResizePictureUtil; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; import org.springframework.web.context.WebApplicationContext; @@ -109,6 +115,8 @@ private static IAuditService auditService; + private IToolContentHandler centralToolContentHandler; + // --------------------------------------------------------------------- // Service Methods // --------------------------------------------------------------------- @@ -1008,15 +1016,16 @@ List results = baseDAO.findByProperty(User.class, "openidURL", openidURL); return results.isEmpty() ? null : (User) results.get(0); } - + /** - * Returns the SQL needed to look up portrait details for a given user. This is an efficient way to get the entries - * at the same time as retrieving the tool data, rather than making a separate lookup. + * Returns the SQL needed to look up portrait details for a given user. This is an efficient way to get the entries + * at the same time as retrieving the tool data, rather than making a separate lookup. * - * The return values are the entry for the select clause (will always have a leading space but no trailing comma and an + * The return values are the entry for the select clause (will always have a leading space but no trailing comma and + * an * alias of luser) and the sql join clause, which should go with any other join clauses. * - * To convert the portrait id set up the sql -> java object translation using + * To convert the portrait id set up the sql -> java object translation using * addScalar("portraitId", IntegerType.INSTANCE) * * @param userIdString @@ -1032,7 +1041,111 @@ return retValue; } + /** + * Looks for [login].png images in /tmp/portraits of user IDs within given range and starting with the given prefix + */ + @Override + public List uploadPortraits(Integer minUserId, Integer maxUserId, String prefix) throws IOException { + File tmpDir = new File("/tmp/portraits"); + if (!tmpDir.canRead()) { + throw new IOException("/tmp/portraits is not readable"); + } + List uploadedPortraits = new LinkedList(); + Integer prefixLength = StringUtils.isBlank(prefix) ? null : prefix.length() + 1; + + for (int userId = minUserId; userId <= maxUserId; userId++) { + try { + User user = (User) baseDAO.find(User.class, userId); + if (user == null) { + if (log.isDebugEnabled()) { + log.debug("User " + userId + " not found when batch uploading portraits, skipping"); + } + continue; + } + String login = user.getLogin(); + if (prefixLength != null) { + if (!login.startsWith(prefix)) { + if (log.isDebugEnabled()) { + log.debug("User " + userId + " login \"" + login + + "\" does not start with the required prefix \"" + prefix + + "\" when batch uploading portraits"); + } + continue; + } + login = login.substring(prefixLength); + } + File portraitFile = new File(tmpDir, login + ".png"); + if (!portraitFile.canRead()) { + if (log.isDebugEnabled()) { + log.debug("Portrait for user " + userId + " with login \"" + login + + "\" was not found or can not be read when batch uploading portraits"); + continue; + } + } + + // upload to the content repository + FileInputStream is = new FileInputStream(portraitFile); + String fileNameWithoutExt = login; + NodeKey originalFileNode = centralToolContentHandler.uploadFile(is, + fileNameWithoutExt + "_original.png", "image/png"); + is.close(); + log.debug("Saved original portrait with uuid: " + originalFileNode.getUuid() + " and version: " + + originalFileNode.getVersion()); + + //resize to the large size + is = new FileInputStream(portraitFile); + InputStream modifiedPortraitInputStream = ResizePictureUtil.resize(is, + PORTRAIT_LARGEST_DIMENSION_LARGE); + NodeKey node = centralToolContentHandler.updateFile(originalFileNode.getUuid(), + modifiedPortraitInputStream, fileNameWithoutExt + "_large.png", "image/png"); + modifiedPortraitInputStream.close(); + is.close(); + if (log.isDebugEnabled()) { + log.debug( + "Saved large portrait with uuid: " + node.getUuid() + " and version: " + node.getVersion()); + } + + //resize to the medium size + is = new FileInputStream(portraitFile); + modifiedPortraitInputStream = ResizePictureUtil.resize(is, PORTRAIT_LARGEST_DIMENSION_MEDIUM); + node = centralToolContentHandler.updateFile(node.getUuid(), modifiedPortraitInputStream, + fileNameWithoutExt + "_medium.png", "image/png"); + modifiedPortraitInputStream.close(); + is.close(); + if (log.isDebugEnabled()) { + log.debug("Saved medium portrait with uuid: " + node.getUuid() + " and version: " + + node.getVersion()); + } + + //resize to the small size + is = new FileInputStream(portraitFile); + modifiedPortraitInputStream = ResizePictureUtil.resize(is, PORTRAIT_LARGEST_DIMENSION_SMALL); + node = centralToolContentHandler.updateFile(node.getUuid(), modifiedPortraitInputStream, + fileNameWithoutExt + "_small.png", "image/png"); + modifiedPortraitInputStream.close(); + is.close(); + if (log.isDebugEnabled()) { + log.debug( + "Saved small portrait with uuid: " + node.getUuid() + " and version: " + node.getVersion()); + } + // delete old portrait file (we only want to keep the user's current portrait) + if (user.getPortraitUuid() != null) { + centralToolContentHandler.deleteFile(user.getPortraitUuid()); + } + user.setPortraitUuid(originalFileNode.getUuid()); + saveUser(user); + + log.info("Uploaded portrait for user " + userId + " with login \"" + login + "\""); + uploadedPortraits.add(login); + } catch (Exception e) { + log.error("Error while batch uploading portraits", e); + } + } + + return uploadedPortraits; + } + // --------------------------------------------------------------------- // Inversion of Control Methods - Method injection // --------------------------------------------------------------------- @@ -1080,4 +1193,8 @@ } return UserManagementService.auditService; } + + public void setCentralToolContentHandler(IToolContentHandler centralToolContentHandler) { + this.centralToolContentHandler = centralToolContentHandler; + } } \ No newline at end of file