package edu.ualberta.med.biobank.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import org.hibernate.annotations.Type;
/**
* A {@link User} should only be able to create
* {@link getManageablePermissionsMembership}-s on other {@link User}-s that are
* of a lesser {@link Rank} and {@link #getLevel()} than his or her own.
* <p>
* Having a {@link Role} grants manager access on all getManageablePermissionsof
* the individual {@link PermissionEnum}-s in that {@link Role}; however, having
* all of the {@link PermissionEnum}-s in a {@link Role} does <em>not</em> mean
* a {@link Membership} has that role.
* <p>
* Manageable means that <em>some</em> portion of the {@link Membership} can be
* manipulated (e.g. some {@link PermissionEnum}-s or {@link Role}-s can be
* added or removed).
* <p>
* Also note that it is easier to <em>Directly</em> modify the elements of a
* collection (e.g. a {@link Membership})
*
* @author Jonathan Ferland
*/
@Entity
@Table(name = "MEMBERSHIP")
public class Membership extends AbstractBiobankModel {
private static final long serialVersionUID = 1L;
private Principal principal;
private Domain domain = new Domain();
private Set<PermissionEnum> permissions = new HashSet<PermissionEnum>(0);
private Set<Role> roles = new HashSet<Role>(0);
private boolean userManager = false;
private boolean everyPermission = false;
public Membership() {
}
public Membership(Membership m, Principal p) {
setPrincipal(p);
p.getMemberships().add(this);
setDomain(new Domain(m.getDomain()));
setUserManager(m.isUserManager());
setEveryPermission(m.isEveryPermission());
getPermissions().addAll(m.getPermissions());
getRoles().addAll(m.getRoles());
}
@NotNull(message = "{edu.ualberta.med.biobank.model.Membership.domain.NotNull}")
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "DOMAIN_ID", unique = true)
public Domain getDomain() {
return domain;
}
public void setDomain(Domain domain) {
this.domain = domain;
}
@ElementCollection(targetClass = PermissionEnum.class, fetch = FetchType.EAGER)
@CollectionTable(name = "MEMBERSHIP_PERMISSION",
joinColumns = @JoinColumn(name = "ID"))
@Column(name = "PERMISSION_ID", nullable = false)
@Type(type = "permissionEnum")
public Set<PermissionEnum> getPermissions() {
return this.permissions;
}
public void setPermissions(Set<PermissionEnum> permissions) {
this.permissions = permissions;
}
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "MEMBERSHIP_ROLE",
joinColumns = { @JoinColumn(name = "MEMBERSHIP_ID", nullable = false, updatable = false) },
inverseJoinColumns = { @JoinColumn(name = "ROLE_ID", nullable = false, updatable = false) })
public Set<Role> getRoles() {
return this.roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
@NotNull(message = "{edu.ualberta.med.biobank.model.Membership.principal.NotNull}")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PRINCIPAL_ID", nullable = false)
public Principal getPrincipal() {
return this.principal;
}
public void setPrincipal(Principal principal) {
this.principal = principal;
}
@Column(name = "USER_MANAGER")
public boolean isUserManager() {
return userManager;
}
public void setUserManager(boolean userManager) {
this.userManager = userManager;
if (userManager) setEveryPermission(true);
}
@Column(name = "EVERY_PERMISSION")
public boolean isEveryPermission() {
return everyPermission || isUserManager();
}
public void setEveryPermission(boolean everyPermission) {
this.everyPermission = everyPermission;
if (!everyPermission) setUserManager(false);
}
/**
* Returns a {@link Set} of all the {@link PermissionEnum}-s on this
* {@link Membership} that the given {@link User} is allowed to manage (add
* or remove from this {@link Membership}).
*
* @param user the manager, who is allowed to modify the returned
* {@link PermissionEnum}-s
* @return the {@link PermissionEnum}-s that the manager can manipulate
*/
@Transient
public Set<PermissionEnum> getManageablePermissions(User user) {
Set<PermissionEnum> permissions = new HashSet<PermissionEnum>();
int maxSize = PermissionEnum.valuesList().size();
for (Membership membership : user.getAllMemberships()) {
if (isManageable(membership)) {
permissions.addAll(membership.getAllPermissions());
}
// early out if we already have all permissions
if (permissions.size() == maxSize) break;
}
return permissions;
}
/**
* Get a {@link Set} of <em>all</em> {@link PermissionEnum}-s that this
* {@link Membership} has directly, through its {@link Role}-s, and
* considering its {@link Rank} (if {@link Rank#ADMINISTRATOR}, then all
* {@link PermissionEnum}-s are included).
*
* @return
*/
@Transient
public Set<PermissionEnum> getAllPermissions() {
Set<PermissionEnum> permissions = new HashSet<PermissionEnum>();
if (isEveryPermission()) {
permissions.addAll(PermissionEnum.valuesList());
} else {
permissions.addAll(getPermissions());
for (Role role : getRoles()) {
permissions.addAll(role.getPermissions());
}
}
return permissions;
}
/**
* Removes redundant {@link PermissionEnum}-s that are already reachable
* through {@link #getRoles()} and {@link #isEveryPermission()}.
*/
@Transient
public void reducePermissions() {
Set<PermissionEnum> nonRedundantPerms = new HashSet<PermissionEnum>();
nonRedundantPerms.addAll(getPermissions());
for (Role role : getRoles()) {
nonRedundantPerms.removeAll(role.getPermissions());
}
if (isEveryPermission()) {
nonRedundantPerms.clear();
}
getPermissions().retainAll(nonRedundantPerms);
}
/**
* Returns a {@link Set} of all the <strong>existing</strong> {@link Role}-s
* on this {@link Membership} that the given {@link User} is allowed to
* manage (add or remove from this {@link Membership}).
* <p>
* Note that if person X can manage all the {@link PermissionEnum}-s in a
* {@link Role} that does </em>not</em> mean that person X can manage the
* {@link Role}, as the {@link Role} may be changed later to include other
* {@link PermissionEnum}-s.
*
* @param user the manager, who is allowed to modify the returned
* {@link Role}-s
* @param defaultAdminRoles which {@link Role}-s to add to the set if the
* {@link User} is an {@link Rank#ADMINISTRATOR}.
* @return the {@link Role}-s that the manager can manipulate
*/
@Transient
public Set<Role> getManageableRoles(User user, Set<Role> defaultAdminRoles) {
Set<Role> roles = new HashSet<Role>();
for (Membership membership : user.getAllMemberships()) {
if (isManageable(membership)) {
if (membership.isEveryPermission()) {
roles.addAll(defaultAdminRoles);
}
roles.addAll(membership.getRoles());
}
}
return roles;
}
/**
* Return true if the given {@link User} is able to manage <em>all</em> of
* the {@link PermissionEnum}-s and {@link Role}-s that this
* {@link Membership} has, otherwise false.
* <p>
* Require at least one {@link PermissionEnum} or {@link Role} to be
* manageable, otherwise an empty {@link Membership} is fully manageable by
* default.
*
* @param u other
* @return
*/
@Transient
public boolean isFullyManageable(User u) {
Set<PermissionEnum> manageablePerms = getManageablePermissions(u);
if (!manageablePerms.containsAll(getPermissions())) return false;
Set<Role> manageableRoles = getManageableRoles(u, getRoles());
if (!manageableRoles.containsAll(getRoles())) return false;
// otherwise an empty membership is fully manageable by default
if (manageablePerms.isEmpty() && manageableRoles.isEmpty())
return false;
return true;
}
@Transient
public boolean isPartiallyManageable(User u) {
for (Membership membership : u.getAllMemberships()) {
if (isManageable(membership)) return true;
}
return false;
}
/**
* Return true if at least one {@link PermissionEnum} or {@link Role} in
* this {@link Membership} can be managed by the given {@link Membership},
* otherwise false. Several criteria must be met:
* <ol>
* <li>the given {@link Membership} must contain at least one
* {@link PermissionEnum} or {@link Role}, otherwise it can't manage
* anything.
* <li>the given {@link Membership} must be able to manage users (i.e.
* {@link Membership#isUserManager()} must be true)</li>
* <li>if this {@link Membership} has every {@link PermissionEnum} (i.e.
* {@link Membership#isEveryPermission()} return true), then that
* {@link Membership} must also have it</li>
* <li>that {@link Domain} must be a superset of this {@link Domain}</li>
* </ol>
*
* @param membership
* @return
*/
@Transient
public boolean isManageable(Membership that) {
if (that.getAllPermissions().isEmpty()
&& that.getRoles().isEmpty()) return false;
if (!that.isUserManager()) return false;
if (isEveryPermission() && !that.isEveryPermission()) return false;
if (!that.getDomain().isSuperset(getDomain())) return false;
return true;
}
}