/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/common/trunk/common-composite-component/src/java/org/sakaiproject/component/common/edu/person/SakaiPersonManagerImpl.java $ * $Id: SakaiPersonManagerImpl.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006 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.opensource.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.component.common.edu.person; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.hibernate.Criteria; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.criterion.Example; import org.hibernate.criterion.Expression; import org.hibernate.criterion.Order; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.api.common.edu.person.PhotoService; import org.sakaiproject.api.common.edu.person.SakaiPerson; import org.sakaiproject.api.common.edu.person.SakaiPersonManager; import org.sakaiproject.api.common.type.Type; import org.sakaiproject.api.common.type.TypeManager; import org.sakaiproject.authz.cover.SecurityService; import org.sakaiproject.component.api.ServerConfigurationService; import org.sakaiproject.component.common.manager.PersistableHelper; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.id.cover.IdManager; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserEdit; import org.sakaiproject.user.api.UserNotDefinedException; import org.sakaiproject.user.api.UserDirectoryService; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * @author <a href="mailto:lance@indiana.edu">Lance Speelmon</a> */ public class SakaiPersonManagerImpl extends HibernateDaoSupport implements SakaiPersonManager { private static final Log LOG = LogFactory.getLog(SakaiPersonManagerImpl.class); private static final String PERCENT_SIGN = "%"; private static final String SURNAME = "surname"; private static final String GIVENNAME = "givenName"; private static final String UID = "uid"; private static final String TYPE_UUID = "typeUuid"; private static final String AGENT_UUID = "agentUuid"; private static final String AGENT_UUID_COLLECTION = "agentUuidCollection"; private static final String FERPA_ENABLED = "ferpaEnabled"; private static final String HQL_FIND_SAKAI_PERSON_BY_AGENT_AND_TYPE = "findEduPersonByAgentAndType"; private static final String HQL_FIND_SAKAI_PERSONS_BY_AGENTS_AND_TYPE = "findEduPersonsByAgentsAndType"; private static final String HQL_FIND_SAKAI_PERSON_BY_UID = "findSakaiPersonByUid"; private static final int MAX_QUERY_COLLECTION_SIZE = 1000; private TypeManager typeManager; // dep inj private PersistableHelper persistableHelper; // dep inj // SakaiPerson record types private Type systemMutableType; // oba constant private Type userMutableType; // oba constant // hibernate cannot cache BLOB data types - rshastri // private boolean cacheFindSakaiPersonString = true; // private boolean cacheFindSakaiPersonStringType = true; // private boolean cacheFindSakaiPersonSakaiPerson = true; // private boolean cacheFindSakaiPersonByUid = true; private static final String[] SYSTEM_MUTABLE_PRIMITIVES = { "org.sakaiproject", "api.common.edu.person", "SakaiPerson.recordType.systemMutable", "System Mutable SakaiPerson", "System Mutable SakaiPerson", }; private static final String[] USER_MUTABLE_PRIMITIVES = { "org.sakaiproject", "api.common.edu.person", "SakaiPerson.recordType.userMutable", "User Mutable SakaiPerson", "User Mutable SakaiPerson", }; private ServerConfigurationService serverConfigurationService; public void setServerConfigurationService(ServerConfigurationService scs) { serverConfigurationService = scs; } private UserDirectoryService userDirectoryService; /** * @param userDirectoryService * The userDirectoryService to set. */ public void setUserDirectoryService(UserDirectoryService userDirectoryService) { if (LOG.isDebugEnabled()) { LOG.debug("setUserDirectoryService(userDirectoryService " + userDirectoryService + ")"); } this.userDirectoryService = userDirectoryService; } private EventTrackingService eventTrackingService; public void setEventTrackingService(EventTrackingService eventTrackingService) { this.eventTrackingService = eventTrackingService; } private PhotoService photoService; public void setPhotoService(PhotoService ps) { this.photoService = ps; } public void init() { LOG.debug("init()"); LOG.debug("// init systemMutableType"); systemMutableType = typeManager.getType(SYSTEM_MUTABLE_PRIMITIVES[0], SYSTEM_MUTABLE_PRIMITIVES[1], SYSTEM_MUTABLE_PRIMITIVES[2]); if (systemMutableType == null) { systemMutableType = typeManager.createType(SYSTEM_MUTABLE_PRIMITIVES[0], SYSTEM_MUTABLE_PRIMITIVES[1], SYSTEM_MUTABLE_PRIMITIVES[2], SYSTEM_MUTABLE_PRIMITIVES[3], SYSTEM_MUTABLE_PRIMITIVES[4]); } if (systemMutableType == null) throw new IllegalStateException("systemMutableType == null"); LOG.debug("// init userMutableType"); userMutableType = typeManager.getType(USER_MUTABLE_PRIMITIVES[0], USER_MUTABLE_PRIMITIVES[1], USER_MUTABLE_PRIMITIVES[2]); if (userMutableType == null) { userMutableType = typeManager.createType(USER_MUTABLE_PRIMITIVES[0], USER_MUTABLE_PRIMITIVES[1], USER_MUTABLE_PRIMITIVES[2], USER_MUTABLE_PRIMITIVES[3], USER_MUTABLE_PRIMITIVES[4]); } if (userMutableType == null) throw new IllegalStateException("userMutableType == null"); LOG.debug("init() has completed successfully"); } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#create(java.lang.String, java.lang.String, org.sakaiproject.api.common.type.Type) */ public SakaiPerson create(String userId, Type recordType) { if (LOG.isDebugEnabled()) { LOG.debug("create(String " + userId + ", Type " + recordType + ")"); } if (userId == null || userId.length() < 1) throw new IllegalArgumentException("Illegal agentUuid argument passed!");; // a null uid is valid if (!isSupportedType(recordType)) throw new IllegalArgumentException("Illegal recordType argument passed!"); SakaiPersonImpl spi = new SakaiPersonImpl(); persistableHelper.createPersistableFields(spi); spi.setUuid(IdManager.createUuid()); spi.setAgentUuid(userId); spi.setUid(userId); spi.setTypeUuid(recordType.getUuid()); spi.setLocked(Boolean.valueOf(false)); this.getHibernateTemplate().save(spi); //log the event String ref = getReference(spi); eventTrackingService.post(eventTrackingService.newEvent("profile.new", ref, true)); //do not do this for system profiles if (serverConfigurationService.getBoolean("profile.updateUser",false)) { try { User u = userDirectoryService.getUser(userId); spi.setGivenName(u.getFirstName()); spi.setSurname(u.getLastName()); spi.setMail(u.getEmail()); } catch (UserNotDefinedException uue) { LOG.error("User " + userId + "doesn't exist"); } } LOG.debug("return spi;"); return spi; } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#getSakaiPerson(org.sakaiproject.api.common.type.Type) */ public SakaiPerson getSakaiPerson(Type recordType) { if (LOG.isDebugEnabled()) { LOG.debug("getSakaiPerson(Type " + recordType + ")"); }; // no validation required; method is delegated. LOG.debug("return findSakaiPerson(agent.getUuid(), recordType);"); return getSakaiPerson(SessionManager.getCurrentSessionUserId(), recordType); } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#getPrototype() */ public SakaiPerson getPrototype() { LOG.debug("getPrototype()"); return new SakaiPersonImpl(); } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#findSakaiPersonByUid(java.lang.String) */ public List findSakaiPersonByUid(final String uid) { if (LOG.isDebugEnabled()) { LOG.debug("findSakaiPersonByUid(String " + uid + ")"); } if (uid == null || uid.length() < 1) throw new IllegalArgumentException("Illegal uid argument passed!"); final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { final Query q = session.getNamedQuery(HQL_FIND_SAKAI_PERSON_BY_UID); q.setParameter(UID, uid, Hibernate.STRING); // q.setCacheable(cacheFindSakaiPersonByUid); return q.list(); } }; LOG.debug("return getHibernateTemplate().executeFind(hcb);"); List hb = getHibernateTemplate().executeFind(hcb); if (photoService.overRidesDefault()) { return this.getDiskPhotosForList(hb); } else { return hb; } } /** * @see SakaiPersonManager#save(SakaiPerson) */ public void save(SakaiPerson sakaiPerson) { if (LOG.isDebugEnabled()) { LOG.debug("save(SakaiPerson " + sakaiPerson + ")"); } if (sakaiPerson == null) throw new IllegalArgumentException("Illegal sakaiPerson argument passed!"); if (!isSupportedType(sakaiPerson.getTypeUuid())) throw new IllegalArgumentException("The sakaiPerson argument contains an invalid Type!"); // AuthZ // Only superusers can update system records if (getSystemMutableType().getUuid().equals(sakaiPerson.getTypeUuid()) && !SecurityService.isSuperUser()) { throw new IllegalAccessError("System mutable records cannot be updated."); } // if it is a user mutable record, ensure the user is updating their own record // this can be overriden with a security advisor so the admin user to allow access if(!SecurityService.unlock(UserDirectoryService.ADMIN_ID, SakaiPerson.PROFILE_SAVE_PERMISSION, sakaiPerson.getAgentUuid())) { if (!StringUtils.equals(SessionManager.getCurrentSessionUserId(), sakaiPerson.getAgentUuid()) && !SecurityService.isSuperUser()) { // AuthZ - Ensure the current user is updating their own record if (!StringUtils.equals(SessionManager.getCurrentSessionUserId(), sakaiPerson.getAgentUuid())) { throw new IllegalAccessError("You do not have permissions to update this record!"); } } } // store record if (!(sakaiPerson instanceof SakaiPersonImpl)) { // TODO support alternate implementations of SakaiPerson // copy bean properties into new SakaiPersonImpl with beanutils? throw new UnsupportedOperationException("Unknown SakaiPerson implementation found!"); } else { // update lastModifiedDate SakaiPersonImpl spi = (SakaiPersonImpl) sakaiPerson; persistableHelper.modifyPersistableFields(spi); //if the repository path is set save if there if (photoService.overRidesDefault()){ photoService.savePhoto(spi.getJpegPhoto(), spi.getAgentUuid()); spi.setJpegPhoto(null); } // use update(..) method to ensure someone does not try to insert a // prototype. getHibernateTemplate().update(spi); //set the event String ref = getReference(spi); LOG.debug("got ref of: " + ref + " about to set events"); eventTrackingService.post(eventTrackingService.newEvent("profile.update", ref, true)); LOG.debug("User record updated for Id :-" + spi.getAgentUuid()); //update the account too -only if not system profile if (serverConfigurationService.getBoolean("profile.updateUser",false) && spi.getTypeUuid().equals(this.userMutableType.getUuid()) ) { try { UserEdit userEdit = null; userEdit = userDirectoryService.editUser(spi.getAgentUuid()); userEdit.setFirstName(spi.getGivenName()); userEdit.setLastName(spi.getSurname()); userEdit.setEmail(spi.getMail()); userDirectoryService.commitEdit(userEdit); LOG.debug("Saved user object"); } catch (Exception e) { e.printStackTrace(); } } } } private String getReference(SakaiPerson spi) { String ref = "/profile/type/" + spi.getTypeUuid() + "/id/" + spi.getAgentUuid(); return ref; } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#findSakaiPerson(java.lang.String, org.sakaiproject.api.common.type.Type) */ public SakaiPerson getSakaiPerson(final String agentUuid, final Type recordType) { if (LOG.isDebugEnabled()) { LOG.debug("getSakaiPerson(String " + agentUuid + ", Type " + recordType + ")"); } if (agentUuid == null || agentUuid.length() < 1) throw new IllegalArgumentException("Illegal agentUuid argument passed!"); if (recordType == null || !isSupportedType(recordType)) throw new IllegalArgumentException("Illegal recordType argument passed!"); final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery(HQL_FIND_SAKAI_PERSON_BY_AGENT_AND_TYPE); q.setParameter(AGENT_UUID, agentUuid, Hibernate.STRING); q.setParameter(TYPE_UUID, recordType.getUuid(), Hibernate.STRING); // q.setCacheable(false); return q.uniqueResult(); } }; LOG.debug("return (SakaiPerson) getHibernateTemplate().execute(hcb);"); SakaiPerson sp = (SakaiPerson) getHibernateTemplate().execute(hcb); if (photoService.overRidesDefault() && sp != null && sp.getTypeUuid().equals(this.getSystemMutableType().getUuid())) { sp.setJpegPhoto(photoService.getPhotoAsByteArray(sp.getAgentUuid())); } return sp; } public Map<String, SakaiPerson> getSakaiPersons(final Set<String> userIds, final Type recordType) { if (LOG.isDebugEnabled()) { LOG.debug("getSakaiPersons(Collection size " + userIds.size() + ", Type " + recordType + ")"); } if (userIds == null || userIds.size() == 0) throw new IllegalArgumentException("Illegal agentUuid argument passed!"); if (recordType == null || !isSupportedType(recordType)) throw new IllegalArgumentException("Illegal recordType argument passed!"); int collectionSize = userIds.size(); // Keep an ordered list of userIds List<String> userIdList = new ArrayList<String>(userIds); // The map we're return Map<String, SakaiPerson> map = new HashMap<String, SakaiPerson>(collectionSize); // Oracle (maybe others, too) can only take up to 1000 parameters total, so chunk the list if necessary if(collectionSize > MAX_QUERY_COLLECTION_SIZE) { int offset = 0; List<String> userListChunk = new ArrayList<String>(); while(offset < collectionSize) { if(offset > 0 && offset % MAX_QUERY_COLLECTION_SIZE == 0) { // Our chunk is full, so process the list, clear it, and continue List<SakaiPerson> personListChunk = listSakaiPersons(userListChunk, recordType); addSakaiPersonsToMap(personListChunk, map); userListChunk.clear(); } userListChunk.add(userIdList.get(offset)); offset++; } // We may (and probably do) have remaining users that haven't been queried if( ! userListChunk.isEmpty()) { List<SakaiPerson> lastChunk = listSakaiPersons(userListChunk, recordType); addSakaiPersonsToMap(lastChunk, map); } } else { addSakaiPersonsToMap(listSakaiPersons(userIds, recordType), map); } return map; } private void addSakaiPersonsToMap(List<SakaiPerson> sakaiPersons, Map<String, SakaiPerson> map) { for(Iterator<SakaiPerson> iter = sakaiPersons.iterator(); iter.hasNext();) { SakaiPerson person = iter.next(); map.put(person.getAgentUuid(), person); } } private List<SakaiPerson> listSakaiPersons(final Collection<String> userIds, final Type recordType) { final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery(HQL_FIND_SAKAI_PERSONS_BY_AGENTS_AND_TYPE); q.setParameterList(AGENT_UUID_COLLECTION, userIds); q.setParameter(TYPE_UUID, recordType.getUuid(), Hibernate.STRING); // q.setCacheable(false); return q.list(); } }; List hb = getHibernateTemplate().executeFind(hcb); if (photoService.overRidesDefault()) { return getDiskPhotosForList(hb); } else { return hb; } } /** * @see SakaiPersonManager#findSakaiPerson(String) */ public List findSakaiPerson(final String simpleSearchCriteria) { if (LOG.isDebugEnabled()) { LOG.debug("findSakaiPerson(String " + simpleSearchCriteria + ")"); } if (simpleSearchCriteria == null || simpleSearchCriteria.length() < 1) throw new IllegalArgumentException("Illegal simpleSearchCriteria argument passed!"); final String match = PERCENT_SIGN + simpleSearchCriteria + PERCENT_SIGN; final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { final Criteria c = session.createCriteria(SakaiPersonImpl.class); c.add(Expression.disjunction().add(Expression.ilike(UID, match)).add(Expression.ilike(GIVENNAME, match)).add( Expression.ilike(SURNAME, match))); c.addOrder(Order.asc(SURNAME)); // c.setCacheable(cacheFindSakaiPersonString); return c.list(); } }; LOG.debug("return getHibernateTemplate().executeFind(hcb);"); List hb = getHibernateTemplate().executeFind(hcb); if (photoService.overRidesDefault()) { return getDiskPhotosForList(hb); } else { return hb; } } /** * @param typeManager * The typeManager to set. */ public void setTypeManager(TypeManager typeManager) { if (LOG.isDebugEnabled()) { LOG.debug("setTypeManager(TypeManager " + typeManager + ")"); } this.typeManager = typeManager; } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#getUserMutableType() */ public Type getUserMutableType() { LOG.debug("getUserMutableType()"); return userMutableType; } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#getSystemMutableType() */ public Type getSystemMutableType() { LOG.debug("getSystemMutableType()"); return systemMutableType; } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#findSakaiPerson(org.sakaiproject.api.common.edu.person.SakaiPerson) */ public List findSakaiPerson(final SakaiPerson queryByExample) { if (LOG.isDebugEnabled()) { LOG.debug("findSakaiPerson(SakaiPerson " + queryByExample + ")"); } if (queryByExample == null) throw new IllegalArgumentException("Illegal queryByExample argument passed!"); final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Criteria criteria = session.createCriteria(queryByExample.getClass()); criteria.add(Example.create(queryByExample)); // criteria.setCacheable(cacheFindSakaiPersonSakaiPerson); return criteria.list(); } }; LOG.debug("return getHibernateTemplate().executeFind(hcb);"); List hb = getHibernateTemplate().executeFind(hcb); if (photoService.overRidesDefault()) { return getDiskPhotosForList(hb); } else { return hb; } } /** * @see org.sakaiproject.api.common.edu.person.SakaiPersonManager#delete(org.sakaiproject.api.common.edu.person.SakaiPerson) */ public void delete(final SakaiPerson sakaiPerson) { if (LOG.isDebugEnabled()) { LOG.debug("delete(SakaiPerson " + sakaiPerson + ")"); } if (sakaiPerson == null) throw new IllegalArgumentException("Illegal sakaiPerson argument passed!"); String ref = getReference(sakaiPerson); //only someone with the appropriate permissions can delete if(!SecurityService.unlock("user.del", ref)){ throw new SecurityException("You do not have permission to delete this sakaiPerson."); } LOG.debug("getHibernateTemplate().delete(sakaiPerson);"); getHibernateTemplate().delete(sakaiPerson); eventTrackingService.post(eventTrackingService.newEvent("profile.delete", ref, true)); } private boolean isSupportedType(Type recordType) { if (LOG.isDebugEnabled()) { LOG.debug("isSupportedType(Type " + recordType + ")"); } if (recordType == null) return false; if (this.getUserMutableType().equals(recordType)) return true; if (this.getSystemMutableType().equals(recordType)) return true; return false; } private boolean isSupportedType(String typeUuid) { if (LOG.isDebugEnabled()) { LOG.debug("isSupportedType(String " + typeUuid + ")"); } if (typeUuid == null) return false; if (this.getUserMutableType().getUuid().equals(typeUuid)) return true; if (this.getSystemMutableType().getUuid().equals(typeUuid)) return true; return false; } /** * @param cacheFindSakaiPersonStringType * The cacheFindSakaiPersonStringType to set. */ // public void setCacheFindSakaiPersonStringType( // boolean cacheFindSakaiPersonStringType) // { // this.cacheFindSakaiPersonStringType = cacheFindSakaiPersonStringType; // } /** * @param cacheFindSakaiPersonString * The cacheFindSakaiPersonString to set. */ // public void setCacheFindSakaiPersonString(boolean cacheFindSakaiPersonString) // { // this.cacheFindSakaiPersonString = cacheFindSakaiPersonString; // } /** * @param cacheFindSakaiPersonSakaiPerson * The cacheFindSakaiPersonSakaiPerson to set. */ // public void setCacheFindSakaiPersonSakaiPerson( // boolean cacheFindSakaiPersonSakaiPerson) // { // this.cacheFindSakaiPersonSakaiPerson = cacheFindSakaiPersonSakaiPerson; // } /** * @param cacheFindSakaiPersonByUid * The cacheFindSakaiPersonByUid to set. */ // public void setCacheFindSakaiPersonByUid(boolean cacheFindSakaiPersonByUid) // { // this.cacheFindSakaiPersonByUid = cacheFindSakaiPersonByUid; // } /** * @param persistableHelper * The persistableHelper to set. */ public void setPersistableHelper(PersistableHelper persistableHelper) { this.persistableHelper = persistableHelper; } public List isFerpaEnabled(final Collection agentUuids) { if (LOG.isDebugEnabled()) { LOG.debug("isFerpaEnabled(Set " + agentUuids + ")"); } if (agentUuids == null || agentUuids.isEmpty()) { throw new IllegalArgumentException("Illegal Set agentUuids argument!"); } final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { final Criteria c = session.createCriteria(SakaiPersonImpl.class); c.add(Expression.in(AGENT_UUID, agentUuids)); c.add(Expression.eq(FERPA_ENABLED, Boolean.TRUE)); return c.list(); } }; return getHibernateTemplate().executeFind(hcb); } public List findAllFerpaEnabled() { LOG.debug("findAllFerpaEnabled()"); final HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { final Criteria c = session.createCriteria(SakaiPersonImpl.class); c.add(Expression.eq(FERPA_ENABLED, Boolean.TRUE)); return c.list(); } }; return getHibernateTemplate().executeFind(hcb); } private List getDiskPhotosForList(List listIn) { List listOut = new ArrayList(); for (int i = 0; i < listIn.size(); i++) { SakaiPerson sp = (SakaiPerson)listIn.get(i); if (sp.getAgentUuid() != null && sp.getTypeUuid().equals(this.getSystemMutableType().getUuid())) { sp.setJpegPhoto(photoService.getPhotoAsByteArray(sp.getAgentUuid())); } listOut.add(sp); } return listOut; } }