package org.sakaiproject.profile2.conversion; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import lombok.Setter; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.apache.log4j.Logger; import org.sakaiproject.api.common.edu.person.SakaiPerson; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice; import org.sakaiproject.profile2.dao.ProfileDao; import org.sakaiproject.profile2.exception.ProfileNotDefinedException; import org.sakaiproject.profile2.hbm.model.ProfileImageExternal; import org.sakaiproject.profile2.hbm.model.ProfileImageUploaded; import org.sakaiproject.profile2.logic.ProfileImageLogic; import org.sakaiproject.profile2.logic.SakaiProxy; import org.sakaiproject.profile2.model.ImportableUserProfile; import org.sakaiproject.profile2.model.UserProfile; import org.sakaiproject.profile2.util.ProfileConstants; import org.sakaiproject.profile2.util.ProfileUtils; import au.com.bytecode.opencsv.CSVReader; import au.com.bytecode.opencsv.bean.CsvToBean; import au.com.bytecode.opencsv.bean.HeaderColumnNameTranslateMappingStrategy; /** * Handles the conversion and import of profiles and images. This is not part of the public API. * * @author Steve Swinsburg (steve.swinsburg@gmail.com) * */ public class ProfileConverter { private static final Logger log = Logger.getLogger(ProfileConverter.class); @Setter private SakaiProxy sakaiProxy; @Setter private ProfileDao dao; @Setter private SecurityService securityService; @Setter private ProfileImageLogic imageLogic; public void init() { log.info("Profile2: ==============================="); log.info("Profile2: Conversion utility starting up."); log.info("Profile2: ==============================="); } /** * Convert profile images */ public void convertProfileImages() { //get list of users List<String> allUsers = new ArrayList<String>(dao.getAllSakaiPersonIds()); if(allUsers.isEmpty()){ log.warn("Profile2 image converter: No SakaiPersons to process. Nothing to do!"); return; } //for each, do they have a profile image record. if so, skip (perhaps null the SakaiPerson JPEG_PHOTO bytes?) for(Iterator<String> i = allUsers.iterator(); i.hasNext();) { String userUuid = (String)i.next(); //get image record from dao directly, we don't need privacy/prefs here ProfileImageUploaded uploadedProfileImage = dao.getCurrentProfileImageRecord(userUuid); if(uploadedProfileImage != null) { log.info("Profile2 image converter: ProfileImage record exists for " + userUuid + ". Nothing to do here, skipping to next section..."); } else { log.info("Profile2 image converter: No existing ProfileImage record for " + userUuid + ". Processing..."); //get photo from SakaiPerson byte[] image = sakaiProxy.getSakaiPersonJpegPhoto(userUuid); //if none, nothing to do if(image == null || image.length == 0) { log.info("Profile2 image converter: No image binary to convert for " + userUuid + ". Skipping to next section..."); } else { //set some defaults for the image we are adding to ContentHosting String fileName = "Profile Image"; String mimeType = "image/jpeg"; //scale the main image byte[] imageMain = ProfileUtils.scaleImage(image, ProfileConstants.MAX_IMAGE_XY); //create resource ID String mainResourceId = sakaiProxy.getProfileImageResourcePath(userUuid, ProfileConstants.PROFILE_IMAGE_MAIN); log.info("Profile2 image converter: mainResourceId: " + mainResourceId); //save, if error, log and return. if(!sakaiProxy.saveFile(mainResourceId, userUuid, fileName, mimeType, imageMain)) { log.error("Profile2 image converter: Saving main profile image failed."); continue; } /* * THUMBNAIL PROFILE IMAGE */ //scale image byte[] imageThumbnail = ProfileUtils.scaleImage(image, ProfileConstants.MAX_THUMBNAIL_IMAGE_XY); //create resource ID String thumbnailResourceId = sakaiProxy.getProfileImageResourcePath(userUuid, ProfileConstants.PROFILE_IMAGE_THUMBNAIL); log.info("Profile2 image converter: thumbnailResourceId:" + thumbnailResourceId); //save, if error, log and return. if(!sakaiProxy.saveFile(thumbnailResourceId, userUuid, fileName, mimeType, imageThumbnail)) { log.warn("Profile2 image converter: Saving thumbnail profile image failed. Main image will be used instead."); thumbnailResourceId = null; } /* * SAVE IMAGE RESOURCE IDS */ uploadedProfileImage = new ProfileImageUploaded(userUuid, mainResourceId, thumbnailResourceId, true); if(dao.addNewProfileImage(uploadedProfileImage)){ log.info("Profile2 image converter: Binary image converted and saved for " + userUuid); } else { log.warn("Profile2 image converter: Binary image conversion failed for " + userUuid); } } } //process any image URLs, if they don't already have a valid record. ProfileImageExternal externalProfileImage = dao.getExternalImageRecordForUser(userUuid); if(externalProfileImage != null) { log.info("Profile2 image converter: ProfileImageExternal record exists for " + userUuid + ". Nothing to do here, skipping..."); } else { log.info("Profile2 image converter: No existing ProfileImageExternal record for " + userUuid + ". Processing..."); String url = sakaiProxy.getSakaiPersonImageUrl(userUuid); //if none, nothing to do if(StringUtils.isBlank(url)) { log.info("Profile2 image converter: No url image to convert for " + userUuid + ". Skipping..."); } else { externalProfileImage = new ProfileImageExternal(userUuid, url, null); if(dao.saveExternalImage(externalProfileImage)) { log.info("Profile2 image converter: Url image converted and saved for " + userUuid); } else { log.warn("Profile2 image converter: Url image conversion failed for " + userUuid); } } } log.info("Profile2 image converter: Finished converting user profile for: " + userUuid); //go to next user } return; } /** * Import profiles from the given CSV file * * <p>The CSV file may contain any of the following headings, in any order: * * <ul> * <li>eid</li> * <li>nickname</li> * <li>position</li> * <li>department</li> * <li>school</li> * <li>room</li> * <li>web site</li> * <li>work phone</li> * <li>home phone</li> * <li>mobile phone</li> * <li>fax</li> * <li>books</li> * <li>tv</li> * <li>movies</li> * <li>quotes</li> * <li>summary</li> * <li>course</li> * <li>subjects</li> * <li>staff profile</li> * <li>uni profile url</li> * <li>academic profile url</li> * <li>publications</li> * <li>official image url</li> * </ul> * * <p>Column headings must match EXACTLY the list above. They do not need to be in the same order, or even all present. * * <p>Fields must be comma separated and each field surrounded with double quotes. There must be no spaces between fields. * * <p>Only users that do not currently have a profile will be imported. * * @param path path to CSV file on the server */ public void importProfiles(String path) { if(StringUtils.isBlank(path)) { log.warn("Profile2 importer: invalid path to CSV file. Aborting."); return; } HeaderColumnNameTranslateMappingStrategy<ImportableUserProfile> strat = new HeaderColumnNameTranslateMappingStrategy<ImportableUserProfile>(); strat.setType(ImportableUserProfile.class); //map the column headers to the field names in the UserProfile class //this mapping is not exhaustive and can be added to at any time since we are mapping //on column name not position Map<String, String> map = new HashMap<String, String>(); map.put("eid", "eid"); map.put("nickname", "nickname"); map.put("position", "position"); map.put("department", "department"); map.put("school", "school"); map.put("room", "room"); map.put("web site", "homepage"); map.put("work phone", "workphone"); map.put("home phone", "homephone"); map.put("mobile phone", "mobilephone"); map.put("fax", "facsimile"); map.put("books", "favouriteBooks"); map.put("tv", "favouriteTvShows"); map.put("movies", "favouriteMovies"); map.put("quotes", "favouriteQuotes"); map.put("summary", "personalSummary"); map.put("course", "course"); map.put("subjects", "subjects"); map.put("staff profile", "staffProfile"); map.put("uni profile url", "universityProfileUrl"); map.put("academic profile url", "academicProfileUrl"); map.put("publications", "publications"); map.put("official image url", "officialImageUrl"); strat.setColumnMapping(map); CsvToBean<ImportableUserProfile> csv = new CsvToBean<ImportableUserProfile>(); List<ImportableUserProfile> list = new ArrayList<ImportableUserProfile>(); try { list = csv.parse(strat, new CSVReader(new FileReader(path))); } catch (FileNotFoundException fnfe) { log.error("Profile2 importer: Couldn't find file: " + fnfe.getClass() + " : " + fnfe.getMessage()); } //setup a security advisor so we can save profiles SecurityAdvisor securityAdvisor = new SecurityAdvisor(){ public SecurityAdvice isAllowed(String userId, String function, String reference){ return SecurityAdvice.ALLOWED; } }; enableSecurityAdvisor(securityAdvisor); //process each for(ImportableUserProfile profile: list) { log.info("Processing user: " + profile.getEid()); //get uuid String uuid = sakaiProxy.getUserIdForEid(profile.getEid()); if(StringUtils.isBlank(uuid)) { log.error("Invalid user: " + profile.getEid() + ". Skipping..."); continue; } profile.setUserUuid(uuid); //check if user already has a profile. Skip if so. if(hasPersistentProfile(uuid)) { log.warn("User: " + profile.getEid() + " already has a profile. Skipping..."); continue; } //persist user profile try { SakaiPerson sp = transformUserProfileToSakaiPerson(profile); if(sp == null){ //already logged continue; } if(sakaiProxy.updateSakaiPerson(sp)) { log.info("Profile saved for user: " + profile.getEid()); } else { log.error("Couldn't save profile for user: " + profile.getEid()); continue; } } catch (ProfileNotDefinedException pnde) { //already logged continue; } //add/update official image, if supplied in the CSV if(StringUtils.isNotBlank(profile.getOfficialImageUrl())) { if(imageLogic.saveOfficialImageUrl(uuid, profile.getOfficialImageUrl())) { log.info("Official image saved for user: " + profile.getEid()); } else { log.error("Couldn't save official image for user: " + profile.getEid()); } } } disableSecurityAdvisor(securityAdvisor); } /** * Does the given user already have a <b>persistent</b> user profile? * * @param userUuid uuid of the user * @return */ private boolean hasPersistentProfile(String userUuid) { SakaiPerson sp = sakaiProxy.getSakaiPerson(userUuid); if(sp != null){ return true; } return false; } /** * Convenience method to map a UserProfile object onto a SakaiPerson object for persisting * * @param up input UserProfile * @return returns a SakaiPerson representation of the UserProfile object which can be persisted */ private SakaiPerson transformUserProfileToSakaiPerson(UserProfile up) { log.info("Transforming: " + up.toString()); String userUuid = up.getUserUuid(); if(StringUtils.isBlank(userUuid)) { log.error("Profile was invalid (missing uuid), cannot transform."); return null; } //get SakaiPerson SakaiPerson sakaiPerson = sakaiProxy.getSakaiPerson(userUuid); //if null, create one if(sakaiPerson == null) { sakaiPerson = sakaiProxy.createSakaiPerson(userUuid); //if its still null, throw exception if(sakaiPerson == null) { throw new ProfileNotDefinedException("Couldn't create a SakaiPerson for " + userUuid); } } //map fields from UserProfile to SakaiPerson //basic info sakaiPerson.setNickname(up.getNickname()); sakaiPerson.setDateOfBirth(up.getDateOfBirth()); //contact info sakaiPerson.setLabeledURI(up.getHomepage()); sakaiPerson.setTelephoneNumber(up.getWorkphone()); sakaiPerson.setHomePhone(up.getHomephone()); sakaiPerson.setMobile(up.getMobilephone()); sakaiPerson.setFacsimileTelephoneNumber(up.getFacsimile()); //staff info sakaiPerson.setOrganizationalUnit(up.getDepartment()); sakaiPerson.setTitle(up.getPosition()); sakaiPerson.setCampus(up.getSchool()); sakaiPerson.setRoomNumber(up.getRoom()); sakaiPerson.setStaffProfile(up.getStaffProfile()); sakaiPerson.setUniversityProfileUrl(up.getUniversityProfileUrl()); sakaiPerson.setAcademicProfileUrl(up.getAcademicProfileUrl()); sakaiPerson.setPublications(up.getPublications()); // student info sakaiPerson.setEducationCourse(up.getCourse()); sakaiPerson.setEducationSubjects(up.getSubjects()); //personal info sakaiPerson.setFavouriteBooks(up.getFavouriteBooks()); sakaiPerson.setFavouriteTvShows(up.getFavouriteTvShows()); sakaiPerson.setFavouriteMovies(up.getFavouriteMovies()); sakaiPerson.setFavouriteQuotes(up.getFavouriteQuotes()); sakaiPerson.setNotes(up.getPersonalSummary()); return sakaiPerson; } /** * Add the supplied security advisor to the stack for this transaction */ private void enableSecurityAdvisor(SecurityAdvisor securityAdvisor) { securityService.pushAdvisor(securityAdvisor); } /** * Remove security advisor from the stack */ private void disableSecurityAdvisor(SecurityAdvisor advisor){ securityService.popAdvisor(advisor); } }