org.sakaiproject.profile2.conversion.ProfileConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.profile2.conversion.ProfileConverter.java

Source

/**
 * Copyright (c) 2008-2012 The Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *             http://www.osedu.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sakaiproject.profile2.conversion;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import org.apache.commons.lang.StringUtils;
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.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.MimeTypeByteArray;
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;

    ConvertedImage ci = null;

    private final static String DEFAULT_FILE_NAME = "Profile Image";
    private final static String DEFAULT_MIME_TYPE = "image/jpeg";

    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);

            ci = new ConvertedImage();
            ci.setUserUuid(userUuid);

            //if no record, we need to run all conversions
            if (uploadedProfileImage == null) {
                //main
                convertSakaiPersonImage();

                if (StringUtils.isNotBlank(ci.getMainResourceId())) {
                    //thumbnail
                    generateAndPersistThumbnail();
                    //avatar
                    generateAndPersistAvatar();
                }
            } else {

                //get any existing values and set into object so we know if we need to generate or save anything
                ci.setMainResourceId(uploadedProfileImage.getMainResource());
                ci.setThumbnailResourceId(uploadedProfileImage.getThumbnailResource());
                ci.setAvatarResourceId(uploadedProfileImage.getAvatarResource());

                //get the existing profile image
                MimeTypeByteArray mtba = sakaiProxy.getResource(ci.getMainResourceId());

                ci.setImage(mtba.getBytes());
                ci.setMimeType(mtba.getMimeType());

                //if we need thumb or avatar, create as necessary
                if (ci.needsThumb()) {
                    generateAndPersistThumbnail();
                }
                if (ci.needsAvatar()) {
                    generateAndPersistAvatar();
                }
            }

            //save image resource IDs
            if (ci.isNeedsSaving()) {
                ProfileImageUploaded convertedProfileImage = new ProfileImageUploaded(userUuid,
                        ci.getMainResourceId(), ci.getThumbnailResourceId(), ci.getAvatarResourceId(), true);

                if (dao.addNewProfileImage(convertedProfileImage)) {
                    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 external image urls
            //

            //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, 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;
    }

    /**
     * This imports URL profile images into upload profile images.
     */
    public void importProfileImages() {

        //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 = i.next();

            //get image record from dao directly, we don't need privacy/prefs here
            ProfileImageUploaded uploadedProfileImage = dao.getCurrentProfileImageRecord(userUuid);

            ci = new ConvertedImage();
            ci.setUserUuid(userUuid);

            //if no record, we need to run all conversions
            if (uploadedProfileImage == null) {

                //main
                ProfileImageExternal externalProfileImage = dao.getExternalImageRecordForUser(userUuid);
                if (externalProfileImage == null) {
                    log.info("No existing external profile images for " + userUuid);
                } else {
                    String mainUrl = externalProfileImage.getMainUrl();
                    if (StringUtils.isNotBlank(mainUrl)) {
                        retrieveMainImage(userUuid, mainUrl);
                    } else {
                        log.info("No URL set for " + userUuid);
                    }
                }

                if (StringUtils.isNotBlank(ci.getMainResourceId())) {
                    //thumbnail
                    generateAndPersistThumbnail();
                    //avatar
                    generateAndPersistAvatar();
                }
            }

            //save image resource IDs
            if (ci.isNeedsSaving()) {
                ProfileImageUploaded convertedProfileImage = new ProfileImageUploaded(userUuid,
                        ci.getMainResourceId(), ci.getThumbnailResourceId(), ci.getAvatarResourceId(), true);

                if (dao.addNewProfileImage(convertedProfileImage)) {
                    log.info("Profile2 image converter: Binary image converted and saved for " + userUuid);
                } else {
                    log.warn("Profile2 image converter: Binary image conversion failed for " + userUuid);
                }
            }
        }
    }

    private void retrieveMainImage(String userUuid, String mainUrl) {
        InputStream inputStream = null;
        try {
            URL url = new URL(mainUrl);
            HttpURLConnection openConnection = (HttpURLConnection) url.openConnection();
            openConnection.setReadTimeout(5000);
            openConnection.setConnectTimeout(5000);
            openConnection.setInstanceFollowRedirects(true);
            openConnection.connect();
            int responseCode = openConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {

                String mimeType = openConnection.getContentType();
                inputStream = openConnection.getInputStream();
                // Convert the image.
                byte[] imageMain = ProfileUtils.scaleImage(inputStream, ProfileConstants.MAX_IMAGE_XY, mimeType);

                //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, DEFAULT_FILE_NAME, mimeType, imageMain)) {
                    log.error("Profile2 image importer: Saving main profile image failed.");
                } else {
                    ci.setImage(imageMain);
                    ci.setMimeType(mimeType);
                    ci.setFileName(DEFAULT_FILE_NAME);
                    ci.setMainResourceId(mainResourceId);
                    ci.setNeedsSaving(true);
                }
            } else {
                log.warn("Failed to get good response for user " + userUuid + " for " + mainUrl + " got "
                        + responseCode);
            }
        } catch (MalformedURLException e) {
            log.info("Invalid URL for user: " + userUuid + " of: " + mainUrl);
        } catch (IOException e) {
            log.warn("Failed to download image for: " + userUuid + " from: " + mainUrl + " error of: "
                    + e.getMessage());
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ioe) {
                    log.info("Failed to close input stream for request to: " + mainUrl);
                }
            }
        }
    }

    /**
     * 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);
    }

    /**
     * Helper to convert an image stored in SakaiPerson into a main image
     * @return 
     */
    private void convertSakaiPersonImage() {

        String userUuid = ci.getUserUuid();

        //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 user...");
        } else {

            //scale the main image
            byte[] imageMain = ProfileUtils.scaleImage(image, ProfileConstants.MAX_IMAGE_XY, DEFAULT_MIME_TYPE);

            //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, DEFAULT_FILE_NAME, DEFAULT_MIME_TYPE, imageMain)) {
                log.error("Profile2 image converter: Saving main profile image failed.");
            } else {
                ci.setImage(imageMain);
                ci.setMimeType(DEFAULT_MIME_TYPE);
                ci.setFileName(DEFAULT_FILE_NAME);
                ci.setMainResourceId(mainResourceId);
                ci.setNeedsSaving(true);
            }
        }
    }

    /**
     * Helper to convert an image into a thumbnail
     * @return 
     */
    private void generateAndPersistThumbnail() {

        String userUuid = ci.getUserUuid();

        byte[] imageThumbnail = ProfileUtils.scaleImage(ci.getImage(), ProfileConstants.MAX_THUMBNAIL_IMAGE_XY,
                ci.getMimeType());

        //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, DEFAULT_FILE_NAME, ci.getMimeType(),
                imageThumbnail)) {
            log.warn(
                    "Profile2 image converter: Saving thumbnail profile image failed. Main image will be used instead.");
        } else {
            ci.setThumbnailResourceId(thumbnailResourceId);
            ci.setNeedsSaving(true);
        }
    }

    /**
     * Helper to convert an image into a thumbnail
     * @return 
     */
    private void generateAndPersistAvatar() {

        String userUuid = ci.getUserUuid();

        byte[] imageAvatar = ProfileUtils.createAvatar(ci.getImage(), ci.getMimeType());

        //create resource ID
        String avatarResourceId = sakaiProxy.getProfileImageResourcePath(userUuid,
                ProfileConstants.PROFILE_IMAGE_THUMBNAIL);
        log.info("Profile2 image converter: avatarResourceId:" + avatarResourceId);

        //save, if error, log and return.
        if (!sakaiProxy.saveFile(avatarResourceId, userUuid, DEFAULT_FILE_NAME, ci.getMimeType(), imageAvatar)) {
            log.warn(
                    "Profile2 image converter: Saving avatar profile image failed. Main image will be used instead.");
        } else {
            ci.setAvatarResourceId(avatarResourceId);
            ci.setNeedsSaving(true);
        }
    }

    /**
     * Private class to store some info while we perform the conversions
     * 
     * @author Steve Swinsburg (steve.swinsburg@gmail.com)
     *
     */

    class ConvertedImage {
        @Getter
        @Setter
        private String mainResourceId;
        @Getter
        @Setter
        private String thumbnailResourceId;
        @Getter
        @Setter
        private String avatarResourceId;
        @Getter
        @Setter
        private byte[] image;
        @Getter
        @Setter
        private String mimeType;
        @Getter
        @Setter
        private String userUuid;
        @Getter
        @Setter
        private String fileName;
        //only want to save if we have to, otherwise we will be duplicating records
        @Getter
        @Setter
        private boolean needsSaving = false;

        public boolean needsThumb() {
            return (validBytes() && StringUtils.isBlank(thumbnailResourceId));
        }

        public boolean needsAvatar() {
            return (validBytes() && StringUtils.isBlank(avatarResourceId));
        }

        public boolean validBytes() {
            return (image != null && image.length > 0);
        }

    }

}