// $HeadURL$ // $Id$ // // Copyright © 2006, 2010, 2011, 2012 by the President and Fellows of Harvard College. // // Screensaver is an open-source project developed by the ICCB-L and NSRB labs // at Harvard Medical School. This software is distributed under the terms of // the GNU General Public License. package edu.harvard.med.screensaver.model.users; import java.security.Principal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.OneToMany; import javax.persistence.Transient; import javax.persistence.Version; import org.apache.log4j.Logger; import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Sort; import org.hibernate.annotations.SortType; import org.hibernate.annotations.Type; import org.joda.time.LocalDate; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import edu.harvard.med.screensaver.model.AuditedAbstractEntity; import edu.harvard.med.screensaver.model.DataModelViolationException; import edu.harvard.med.screensaver.model.activities.Activity; import edu.harvard.med.screensaver.model.activities.AdministrativeActivity; import edu.harvard.med.screensaver.model.annotations.ToMany; import edu.harvard.med.screensaver.model.meta.PropertyPath; import edu.harvard.med.screensaver.model.meta.RelationshipPath; import edu.harvard.med.screensaver.util.CryptoUtils; import edu.harvard.med.screensaver.util.StringUtils; /** * A person that is a user of the screening facility and/or Screensaver itself. A Screensaver user * may be an {@link AdministratorUser} and/or a {@link ScreeningRoomUser}. * * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a> * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a> */ @Entity @Inheritance(strategy=InheritanceType.JOINED) @org.hibernate.annotations.Proxy abstract public class ScreensaverUser extends AuditedAbstractEntity<Integer> implements Principal, Comparable<ScreensaverUser> { // static fields private static final Logger log = Logger.getLogger(ScreensaverUser.class); private static final long serialVersionUID = 0L; public static final RelationshipPath<ScreensaverUser> labAffiliation = RelationshipPath.from(ScreensaverUser.class).to("labAffiliation"); public static final RelationshipPath<ScreensaverUser> activitiesPerformed = RelationshipPath.from(ScreensaverUser.class).to("activitiesPerformed"); public static final RelationshipPath<ScreensaverUser> updateActivities = RelationshipPath.from(ScreensaverUser.class).to("updateActivities"); public static final PropertyPath<ScreensaverUser> roles = RelationshipPath.from(ScreensaverUser.class).toCollectionOfValues("screensaverUserRoles"); public static final Function<ScreensaverUser,String> ToDisplayStringFunction = new Function<ScreensaverUser,String>() { public String apply(ScreensaverUser u) { return u.getFullNameFirstLast() + " (" + u.getEntityId() + ")"; } }; public static final Function<ScreensaverUser,String> ToFullNameLastFirstAndId = new Function<ScreensaverUser,String>() { public String apply(ScreensaverUser u) { return u.getFullNameLastFirst() + " (" + u.getEntityId() + ")"; } }; // instance fields private Integer _version; private transient HashMap<String,Boolean> _rolesMap; private SortedSet<Activity> _activitiesPerformed = Sets.newTreeSet(); private String _firstName; private String _lastName; private String _email; private String _phone; private String _mailingAddress; private String _comments; private Set<ScreensaverUserRole> _roles = Sets.newHashSet(); private Set<ScreensaverUserRole> _primaryRoles; private String _loginId; private String _digestedPassword; private String _eCommonsId; private String _harvardId; private LocalDate _harvardIdExpirationDate; private LocalDate _harvardIdRequestedExpirationDate; private Gender _gender; // public constructors /** * @motivation for new ScreensaverUser creation via user interface, where even required * fields are allowed to be uninitialized, initially */ protected ScreensaverUser(AdministratorUser createdBy) { super(createdBy); } /** @motivation for test code only */ protected ScreensaverUser(String firstName, String lastName) { super(null); /* TODO */ setFirstName(firstName); setLastName(lastName); } // public instance methods /** * Get the id for the Screensaver user. * @return the id for the Screensaver user */ @Id @org.hibernate.annotations.GenericGenerator( name="screensaver_user_id_seq", strategy="sequence", parameters = { @Parameter(name="sequence", value="screensaver_user_id_seq") } ) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="screensaver_user_id_seq") public Integer getScreensaverUserId() { return getEntityId(); } @ManyToMany(fetch = FetchType.LAZY, cascade={ CascadeType.PERSIST, CascadeType.MERGE }) @JoinTable(name="screensaverUserUpdateActivity", joinColumns=@JoinColumn(name="screensaverUserId", nullable=false, updatable=false), inverseJoinColumns=@JoinColumn(name="updateActivityId", nullable=false, updatable=false)) @org.hibernate.annotations.Cascade(value={org.hibernate.annotations.CascadeType.SAVE_UPDATE}) @Sort(type=SortType.NATURAL) @ToMany(singularPropertyName="updateActivity", hasNonconventionalMutation=true /* model testing framework doesn't understand this is a containment relationship, and so requires addUpdateActivity() method*/) @Override public SortedSet<AdministrativeActivity> getUpdateActivities() { return _updateActivities; } /** * Get the set of user roles that this user belongs to. Do not modify the * contents of the returned collection: use * {@link #addScreensaverUserRole(ScreensaverUserRole)} or * {@link #removeScreensaverUserRole(ScreensaverUserRole)} instead. * * @return the set of user roles that this user belongs to */ @ElementCollection @edu.harvard.med.screensaver.model.annotations.ElementCollection(hasNonconventionalMutation = true /* * valid roles * depend upon * concrete entity * type */) @Column(name = "screensaverUserRole", nullable = false) @JoinTable(name = "screensaverUserRole", joinColumns = @JoinColumn(name = "screensaverUserId")) @org.hibernate.annotations.Type(type = "edu.harvard.med.screensaver.model.users.ScreensaverUserRole$UserType") @org.hibernate.annotations.ForeignKey(name = "fk_screensaver_user_role_type_to_screensaver_user") public Set<ScreensaverUserRole> getScreensaverUserRoles() { return _roles; } /** * @return the subset of this user's roles that are not implied by one of the extant roles */ @Transient public Set<ScreensaverUserRole> getPrimaryScreensaverUserRoles() { _primaryRoles = Sets.difference(getScreensaverUserRoles(), getImpliedRoles()); return ImmutableSet.copyOf(_primaryRoles); } @Transient public Set<ScreensaverUserRole> getImpliedRoles() { Set<ScreensaverUserRole> impliedRoles = Sets.newHashSet(); for (ScreensaverUserRole role : getScreensaverUserRoles()) { impliedRoles.addAll(role.getImpliedRoles()); } return impliedRoles; } /** * Add a role to this user (i.e., place the user into a new role). * @param role the role to add * @return true iff the user was not already added to this role */ public boolean addScreensaverUserRole(ScreensaverUserRole role) { boolean result = _roles.add(role); for (ScreensaverUserRole impliedRole : role.getImpliedRoles()) { _roles.add(impliedRole); } validateRoles(); return result; } /** * Remove the specified role from the user, including all roles implied by this role. * @param role the role to remove this user from * @return true iff the user previously belonged to the role * @throws DataModelViolationException if another of the user's roles implies this role */ public boolean removeScreensaverUserRole(ScreensaverUserRole role) { if (!!!_roles.contains(role)) { return false; } Set<ScreensaverUserRole> primaryRoles = Sets.newHashSet(getPrimaryScreensaverUserRoles()); if (!!!primaryRoles.contains(role)) { throw new DataModelViolationException("cannot remove an implied role: " + role + " (remove the primary role instead)"); } primaryRoles.remove(role); _roles.clear(); for (ScreensaverUserRole primaryRole : primaryRoles) { addScreensaverUserRole(primaryRole); } return true; } /** * Remove this user from all roles. */ public void removeScreensaverUserRoles() { _roles.clear(); } /** * Returns true whenever the user is in the specified role * @param role the role to check whether the user is in * @return true whenever the user is in the specified role */ @Transient public boolean isUserInRole(ScreensaverUserRole role) { if (role == null || ! validateRole(role)) { return false; } return _roles.contains(role); } /** * Get a mapping from the names of the roles to boolean values indicating whether or not the * user is in the role. For use in with JSF EL expressions. * @return a mapping from the names of the roles to boolean values indicating whether or not the * user is in the role. * @motivation for JSF EL expressions */ @Transient public Map<String,Boolean> getIsUserInRoleOfNameMap() { if (_rolesMap == null) { _rolesMap = new HashMap<String,Boolean>(); for (ScreensaverUserRole role : ScreensaverUserRole.values()) { _rolesMap.put(role.getRoleName(), isUserInRole(role)); } } return _rolesMap; } /** * Get the set of activities performed by this user. * @return the set of activities performed by this user */ @OneToMany(mappedBy = "performedBy") @edu.harvard.med.screensaver.model.annotations.ToMany(singularPropertyName="activityPerformed") @edu.harvard.med.screensaver.model.annotations.Column(hasNonconventionalSetterMethod=true) @Sort(type = SortType.NATURAL) public SortedSet<Activity> getActivitiesPerformed() { return _activitiesPerformed; } /** * Get the first name. * @return the first name */ @Column(nullable=false) @org.hibernate.annotations.Type(type="text") public String getFirstName() { return _firstName; } /** * Set the first name. * @param firstName the new first name */ public void setFirstName(String firstName) { _firstName = firstName; } /** * Get the last name. * @return the last name */ @Column(nullable=false) @org.hibernate.annotations.Type(type="text") public String getLastName() { return _lastName; } /** * Set the last name. * @param lastName the new last name */ public void setLastName(String lastName) { _lastName = lastName; } /** * Get the full name "Last, First". * @return the full name */ @Transient public String getFullNameLastFirst() { return getFullName(true); } /** * Get the full name as "First Last". * @return the full name */ @Transient public String getFullNameFirstLast() { return getFullName(false); } /** * Get the full name. * @param lastFirst true if desired format is "Last, First", false if desired format is "First Last" * @return the full name */ @Transient public String getFullName(boolean lastFirst) { List<String> nameParts = new ArrayList<String>(); if (!StringUtils.isEmpty(_firstName)) { nameParts.add(_firstName); } if (!StringUtils.isEmpty(_lastName)) { nameParts.add(_lastName); } if (lastFirst) { Collections.reverse(nameParts); } return Joiner.on(lastFirst ? ", " : " ").join(nameParts); } /** * Get the email. * @return the email */ @org.hibernate.annotations.Type(type="text") public String getEmail() { return _email; } /** * Set the email. * @param email the new email */ public void setEmail(String email) { _email = email; } /** * Get the phone. * @return the phone */ @org.hibernate.annotations.Type(type="text") public String getPhone() { return _phone; } /** * Set the phone. * @param phone the new phone */ public void setPhone(String phone) { _phone = phone; } /** * Get the mailing address. * @return the mailing address */ @org.hibernate.annotations.Type(type="text") public String getMailingAddress() { return _mailingAddress; } /** * Set the mailing address. * @param mailingAddress the new mailing address */ public void setMailingAddress(String mailingAddress) { _mailingAddress = mailingAddress; } /** * Get the comments. * @return the comments */ @org.hibernate.annotations.Type(type="text") public String getComments() { return _comments; } /** * Set the comments. * @param comments the new comments */ public void setComments(String comments) { _comments = comments; } /** * Get the user's Screensaver-managed login ID. * @return the Screensaver login ID */ @Column(unique=true) @org.hibernate.annotations.Type(type="text") public String getLoginId() { return _loginId; } /** * Set the user's Screensaver-managed login ID. * @param loginId the new Screensaver login ID */ public void setLoginId(String loginId) { _loginId = loginId; } /** * Get the digested (hashed) password. * @return the digested (hashed) password */ @org.hibernate.annotations.Type(type="text") public String getDigestedPassword() { return _digestedPassword; } /** * Set the digested (hashed) version of the password associated with the user's login ID. * @param digestedPassword */ public void setDigestedPassword(String digestedPassword) { _digestedPassword = digestedPassword; } /** * Set the password associated with the user's login ID, specified as a * plaintext password, but which will be digested (hashed) before being * stored, for security purposes. * @param screensaverPassword the plaintext password */ public void updateScreensaverPassword(String screensaverPassword) { String digestedPassword = CryptoUtils.digest(screensaverPassword); if (digestedPassword != null) { setDigestedPassword(digestedPassword); } else { log.error("could not set new password for ScreensaverUser " + this); } } /** * Harvard-specific user identifier for authentication purposes. */ // TODO: make this unique when duplicates are taken care of in the database //@Column(unique=true) @org.hibernate.annotations.Type(type="text") public String getECommonsId() { return _eCommonsId; } public void setECommonsId(String eCommonsId) { if (eCommonsId != null && !eCommonsId.toLowerCase().equals(eCommonsId)) { throw new IllegalArgumentException("eCommons ID must contain only lowercase letters"); } _eCommonsId = eCommonsId; } /** * Harvard-specific user identifier. * * @return the harvard id */ @org.hibernate.annotations.Type(type="text") public String getHarvardId() { return _harvardId; } public void setHarvardId(String harvardId) { _harvardId = harvardId; } /** * Get the harvard id expiration date. * @return the harvard id expiration date */ @Type(type="edu.harvard.med.screensaver.db.usertypes.LocalDateType") public LocalDate getHarvardIdExpirationDate() { return _harvardIdExpirationDate; } /** * Set the harvard id expiration date. * @param harvardIdExpirationDate the new harvard id expiration date */ public void setHarvardIdExpirationDate(LocalDate harvardIdExpirationDate) { _harvardIdExpirationDate = harvardIdExpirationDate; } /** * Get the harvard id requested expiration date. */ @Type(type="edu.harvard.med.screensaver.db.usertypes.LocalDateType") public LocalDate getHarvardIdRequestedExpirationDate() { return _harvardIdRequestedExpirationDate; } /** * Set the harvard id requested expiration date. */ public void setHarvardIdRequestedExpirationDate(LocalDate value) { _harvardIdRequestedExpirationDate = value; } // Comparable interface public int compareTo(ScreensaverUser other) { return ScreensaverUserComparator.getInstance().compare(this, other); } // Principal interface @Transient public String getName() { return getFullNameFirstLast(); } // protected constructor and instance method /** * Construct an uninitialized <code>ScreeningRoomUser</code>. * @motivation for hibernate and proxy/concrete subclass constructors */ protected ScreensaverUser() {} /** * Validate the specified role for this user. Throw a {@link DataModelViolationException} * when it is illegal for this type of user to have this role. * @throws DataModelViolationException when it is illegal for this type of user to have this role */ abstract protected boolean validateRole(ScreensaverUserRole role); // private instance methods /** * Set the id for the Screensaver user. * @param screeningRoomUserId the new id for the Screensaver user * @motivation for hibernate */ private void setScreensaverUserId(Integer screensaverUserId) { setEntityId(screensaverUserId); } /** * Get the version for the Screensaver user. * @return the version for the Screensaver user * @motivation for hibernate */ @Version @Column(nullable=false) private Integer getVersion() { return _version; } /** * Set the version for the Screensaver user. * @param version the new version for the Screensaver user * @motivation for hibernate */ private void setVersion(Integer version) { _version = version; } /** * Set the screensaver user roles. * @param roles the new screensaver user roles * @motivation for hibernate */ private void setScreensaverUserRoles(Set<ScreensaverUserRole> roles) { _roles = roles; } /** * Set the activities performed by this user. * @param activitiesPerformed the screening room activities performed by this user * @motivation for hibernate */ private void setActivitiesPerformed(SortedSet<Activity> activitiesPerformed) { _activitiesPerformed = activitiesPerformed; } /** * Validate the set of roles that this user has. Throw a {@link DataModelViolationException} * when one or more of the roles is not valid. * @throws DataModelViolationException when one or more of the roles is not valid */ private void validateRoles() { for (ScreensaverUserRole role : _roles) { if (! validateRole(role)) { throw new DataModelViolationException( "user " + this + " has been granted illegal role: " + role); } } } @Column @org.hibernate.annotations.Type(type="edu.harvard.med.screensaver.model.users.Gender$UserType") public Gender getGender() { return _gender; } public void setGender(Gender value) { _gender = value; } }