package org.subethamail.entity; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.MapKey; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.QueryHint; import lombok.extern.java.Log; import org.hibernate.annotations.Cache; import org.hibernate.annotations.CacheConcurrencyStrategy; import org.hibernate.validator.constraints.Length; import org.subethamail.entity.i.Permission; import org.subethamail.entity.i.Validator; /** * Entity of a human user of the system. * * @author Jeff Schnitzer */ @NamedQueries({ @NamedQuery( name="SiteAdmin", query="from Person p where p.siteAdmin = true order by p.name", hints={ @QueryHint(name="org.hibernate.readOnly", value="true"), @QueryHint(name="org.hibernate.cacheable", value="true") } ), @NamedQuery( name="CountPerson", query="select count(*) from Person", hints={ @QueryHint(name="org.hibernate.readOnly", value="true"), @QueryHint(name="org.hibernate.cacheable", value="true") } ) }) @Entity @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL) @Log public class Person implements Serializable, Comparable<Person> { private static final long serialVersionUID = 1L; /** */ public static final String ROLE_USER = "user"; public static final String ROLE_ADMIN = "siteAdmin"; /** */ static final Set<String> USER_ROLES = Collections.singleton(ROLE_USER); static final Set<String> SITE_ADMIN_ROLES; static { Set<String> roles = new HashSet<String>(); roles.add(ROLE_USER); roles.add(ROLE_ADMIN); SITE_ADMIN_ROLES = Collections.unmodifiableSet(roles); } /** */ @Id @GeneratedValue Long id; @Column(name="passwd", nullable=false, length=Validator.MAX_PERSON_PASSWORD) @Length(min=3) String password; @Column(nullable=false, length=Validator.MAX_PERSON_NAME) String name; @Column(nullable=false) boolean siteAdmin; @OneToMany(cascade=CascadeType.ALL, mappedBy="person") @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL) @MapKey(name="id") Map<String, EmailAddress> emailAddresses; @OneToMany(cascade=CascadeType.ALL, mappedBy="person") @Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL) @MapKey(name="listId") Map<Long, Subscription> subscriptions; /** not cached */ @OneToMany(cascade=CascadeType.ALL, mappedBy="person") @MapKey(name="listId") Map<Long, SubscriptionHold> heldSubscriptions; /** */ public Person() {} /** */ public Person(String password, String name) { log.log(Level.FINE,"Creating new person"); // These are validated normally. this.setPassword(password); this.setName(name); this.emailAddresses = new HashMap<String, EmailAddress>(); this.subscriptions = new HashMap<Long, Subscription>(); this.heldSubscriptions = new HashMap<Long, SubscriptionHold>(); } /** */ public Long getId() { return this.id; } /** * TODO: consider minimal two-way encryption so that pws are not easily readable in db */ public String getPassword() { return this.password; } /** * Note that the password is stored in cleartext so that * we can email it back to its owner. * * TODO: consider minimal two-way encryption (even rot13) so that pws are not easily readable in db dumps * * @param value is a plaintext password */ public void setPassword(String value) { log.log(Level.FINE,"Setting password of {0}", this); this.password = value; } /** * Checks to see if the password matches. * * @return true if the password does match. */ public boolean checkPassword(String plainText) { return this.password.equals(plainText); } /** */ public String getName() { return this.name; } /** */ public void setName(String value) { log.log(Level.FINE,"Setting name of {0} to {1}", new Object[]{this, value}); this.name = value; } /** */ public boolean isSiteAdmin() { return this.siteAdmin; } /** */ public void setSiteAdmin(boolean value) { log.log(Level.FINE,"Setting admin flag of {0} to {1}", new Object[]{this, value}); this.siteAdmin = value; } /** */ public Map<String, EmailAddress> getEmailAddresses() { return this.emailAddresses; } /** */ public void addEmailAddress(EmailAddress value) { this.emailAddresses.put(value.getId(), value); } /** * Normalizes the email address first. Ensures that the * last email address cannot be removed. */ public EmailAddress removeEmailAddress(String email) { if (this.emailAddresses.size() <= 1) throw new IllegalStateException("Cannot remove last email address"); email = Validator.normalizeEmail(email); // This odd construct is to work around hibernate bug HHH-2142 EmailAddress addy = this.emailAddresses.get(email); this.emailAddresses.remove(email); return addy; } /** * Gets the email address associated with that email, properly * normalizing before checking. * * @return null if that is not one of my email addresses. */ public EmailAddress getEmailAddress(String email) { email = Validator.normalizeEmail(email); return this.emailAddresses.get(email); } /** * Convenience method */ public List<String> getEmailList() { List<String> addresses = new ArrayList<String>(this.emailAddresses.size()); int i = 0; for (EmailAddress addy: this.emailAddresses.values()) { addresses.add(addy.getId()); i++; } // This wouldn't be necessary if @Sort worked on Maps Collections.sort(addresses); return addresses; } /** * @return all the subscriptions associated with this person */ public Map<Long, Subscription> getSubscriptions() { return this.subscriptions; } /** * Adds a subscription to our internal map. */ public void addSubscription(Subscription value) { this.subscriptions.put(value.getList().getId(), value); } /** * @return true if this person is subscribed to the list */ public boolean isSubscribed(MailingList list) { return this.subscriptions.containsKey(list.getId()); } /** * @return the subscription, or null if not subscribed to the list */ public Subscription getSubscription(Long listId) { return this.subscriptions.get(listId); } /** * @return (not cached) the subscription holds for this user */ public Map<Long, SubscriptionHold> getHeldSubscriptions() { return this.heldSubscriptions; } /** * Convenience method */ public void addHeldSubscription(SubscriptionHold hold) { this.heldSubscriptions.put(hold.getList().getId(), hold); } /** */ public Role getRoleIn(MailingList list) { Subscription sub = this.subscriptions.get(list.getId()); return (sub == null) ? list.getAnonymousRole() : sub.getRole(); } /** */ public Set<Permission> getPermissionsIn(MailingList list) { if (this.siteAdmin) return Permission.ALL; else return this.getRoleIn(list).getPermissions(); } /** @return the j2ee security roles associated with this person */ public Set<String> getRoles() { if (this.siteAdmin) return SITE_ADMIN_ROLES; else return USER_ROLES; } /** */ @Override public String toString() { return this.getClass().getName() + " {id=" + this.id + ", emailAddresses=" + this.emailAddresses + "}"; } /** * Natural sort order is based on name */ public int compareTo(Person other) { return this.name.compareTo(other.getName()); } }