package org.subethamail.entity;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
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.JoinColumn;
import javax.persistence.MapKey;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.QueryHint;
import lombok.extern.java.Log;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.hibernate.validator.constraints.Email;
import org.subethamail.core.util.ContextAware;
import org.subethamail.entity.i.Permission;
import org.subethamail.entity.i.PermissionException;
import org.subethamail.entity.i.Validator;
import org.subethamail.smtp.util.EmailUtils;
/**
* Entity for a single mailing list
*
* @author Jeff Schnitzer
*/
@NamedQueries({
@NamedQuery(
name="MailingListByEmail",
query="from MailingList l where l.email = :email",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
),
@NamedQuery(
name="MailingListByUrl",
query="from MailingList l where l.url = :url",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
),
@NamedQuery(
name="AllMailingLists",
query="from MailingList l order by l.name",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
),
@NamedQuery(
name="SearchMailingLists",
query="from MailingList l where (l.name like :name) or " +
"(l.email like :email) or" +
"(l.url like :url) or" +
"(l.description like :description) order by l.name",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
),
@NamedQuery(
name="CountMailingLists",
query="select count(*) from MailingList l",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
),
@NamedQuery(
name="CountMailingListsQuery",
query="select count(*) from MailingList l where (l.name like :name) or " +
"(l.email like :email) or" +
"(l.url like :url) or" +
"(l.description like :description)",
hints={
@QueryHint(name="org.hibernate.readOnly", value="true"),
@QueryHint(name="org.hibernate.cacheable", value="true")
}
)
})
@Entity
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@Log
public class MailingList implements Serializable, Comparable<MailingList>
{
private static final long serialVersionUID = 1L;
/**
* TreeSet requires a weird comparator because it uses the comparator
* instead of equals(). When we return 0, it means the objects must
* really be equal.
*/
public static class SubscriptionComparator implements Comparator<Subscription>
{
public int compare(Subscription s1, Subscription s2)
{
if (s1 == null || s2 == null)
return 0;
int result = s1.getPerson().compareTo(s2.getPerson());
if (result == 0)
return s1.getPerson().getId().compareTo(s2.getPerson().getId());
else
return result;
}
};
/** */
@Id
@GeneratedValue
Long id;
/** */
@Column(nullable=false, length=Validator.MAX_LIST_EMAIL)
@Email
String email;
@Column(nullable=false, length=Validator.MAX_LIST_NAME)
String name;
/** */
@Column(nullable=false, length=Validator.MAX_LIST_URL)
String url;
@Column(nullable=false, length=Validator.MAX_LIST_DESCRIPTION)
String description;
/** Hold subs for moderator approval */
@Column(nullable=false)
boolean subscriptionHeld;
@Column(nullable=false, length=Validator.MAX_LIST_WELCOME_MESSAGE)
String welcomeMessage;
//
// TODO: set these two columns back to nullable=false when
// this hibernate bug is fixed:
// http://opensource.atlassian.com/projects/hibernate/browse/HHH-1654
// Also, this affects the creation sequence for mailing lists
//
/** The default role for new subscribers */
@OneToOne
@JoinColumn(name="defaultRoleId", nullable=true)
Role defaultRole;
/** The role to consider anonymous (not subscribed) people */
@OneToOne
@JoinColumn(name="anonymousRoleId", nullable=true)
Role anonymousRole;
/** */
@OneToMany(cascade=CascadeType.ALL, mappedBy="list")
@Sort(type=SortType.COMPARATOR, comparator=SubscriptionComparator.class)
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
SortedSet<Subscription> subscriptions;
/** not cached */
@OneToMany(cascade=CascadeType.ALL, mappedBy="list")
@OrderBy(value="dateCreated")
Set<SubscriptionHold> subscriptionHolds;
/** */
@OneToMany(cascade=CascadeType.ALL, mappedBy="list")
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@MapKey(name="className")
Map<String, EnabledFilter> enabledFilters;
/** */
@OneToMany(cascade=CascadeType.ALL, mappedBy="list")
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@OrderBy(value="name")
Set<Role> roles;
/** The only reason this is here is to provide cascading delete */
@OneToMany(cascade=CascadeType.ALL, mappedBy="list")
Set<Mail> mails;
/**
*/
public MailingList() {}
/**
*/
public MailingList(String email, String name, String url, String description)
{
log.log(Level.FINE,"Creating new mailing list");
// These are validated normally.
this.setEmail(email);
this.setName(name);
this.setUrl(url);
this.setDescription(description);
// Make sure collections start empty
this.subscriptions = new TreeSet<Subscription>(new SubscriptionComparator());
this.enabledFilters = new HashMap<String, EnabledFilter>();
this.roles = new HashSet<Role>();
this.subscriptionHolds = new HashSet<SubscriptionHold>();
// We have to start with one role, the owner role
Role owner = new Role(this);
this.roles.add(owner);
this.welcomeMessage = "";
// TODO: restore this code when hibernate bug fixed. In the mean time,
// the creator MUST persist the MailingList *then* set these values.
// http://opensource.atlassian.com/projects/hibernate/browse/HHH-1654
// this.defaultRole = owner;
// this.anonymousRole = owner;
}
/** */
public Long getId() { return this.id; }
/**
*/
public String getEmail() { return this.email; }
/**
*/
public void setEmail(String value)
{
log.log(Level.FINE,"Setting email of {0} to {1}", new Object[]{this, value});
value = EmailUtils.normalizeEmail(value);
this.email = value;
}
/**
*/
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 String getUrl() { return this.url; }
/**
*/
public void setUrl(String value)
{
if (value == null || value.length() > Validator.MAX_LIST_URL)
throw new IllegalArgumentException("Invalid url");
try { new URL(value); }
catch (MalformedURLException e) { throw new IllegalArgumentException("Invalid url"); }
log.log(Level.FINE,"Setting url of {0} to {1}", new Object[]{this, value});
this.url = value;
}
/**
*/
public String getDescription() { return this.description; }
/**
*/
public void setDescription(String value)
{
log.log(Level.FINE,"Setting description of {0} to {1}", new Object[]{this, value});
this.description = value;
}
public String getWelcomeMessage()
{
return this.welcomeMessage;
}
public void setWelcomeMessage(String welcomeMessage)
{
log.log(Level.FINE,"Setting welcomeMessage of {0} to {1}", new Object[]{this,welcomeMessage});
this.welcomeMessage = welcomeMessage;
}
/**
*/
public boolean isSubscriptionHeld() { return this.subscriptionHeld; }
public void setSubscriptionHeld(boolean value)
{
this.subscriptionHeld = value;
}
/**
* @return all the subscriptions associated with this list
*/
public Set<Subscription> getSubscriptions() { return this.subscriptions; }
/**
* @return all the held subscriptions
*/
public Set<SubscriptionHold> getSubscriptionHolds() { return this.subscriptionHolds; }
/**
* @return all plugins enabled on this list
*/
public Map<String, EnabledFilter> getEnabledFilters() { return this.enabledFilters; }
/**
* Convenience method.
*/
public void addEnabledFilter(EnabledFilter filt)
{
this.enabledFilters.put(filt.getClassName(), filt);
}
/**
* @return all roles available for this list
*/
public Set<Role> getRoles() { return this.roles; }
/**
* @return true if role is valid for this list
*/
public boolean isValidRole(Role role)
{
return this.roles.contains(role);
}
/** */
public Role getDefaultRole() { return this.defaultRole; }
public void setDefaultRole(Role value)
{
if (!this.isValidRole(value))
throw new IllegalArgumentException("Role belongs to some other list");
this.defaultRole = value;
}
/** */
public Role getAnonymousRole() { return this.anonymousRole; }
public void setAnonymousRole(Role value)
{
if (!this.isValidRole(value))
throw new IllegalArgumentException("Role belongs to some other list");
this.anonymousRole = value;
}
/**
* Figures out which role is the owner and returns it
*/
public Role getOwnerRole()
{
for (Role check: this.roles)
if (check.isOwner())
return check;
throw new IllegalStateException("Missing owner role");
}
/**
* Figures out the role for a person. If pers is null,
* returns the anonymous role.
*/
public Role getRoleFor(Person pers)
{
if (pers == null)
return this.anonymousRole;
else
return pers.getRoleIn(this);
}
/**
* Figures out the permissions for a person. Very
* similar to getRoleFor() but takes into account
* site adminstrators which always have permission.
*/
public Set<Permission> getPermissionsFor(Person pers)
{
if (pers == null)
return this.anonymousRole.getPermissions();
else
return pers.getPermissionsIn(this);
}
/**
* @param pers can be null to indicate anonymous person. Site admins have all permissions.
* @throws PermissionException if person doesn't have the permission
*/
public void checkPermission(Person pers, Permission check) throws PermissionException
{
if (! this.getPermissionsFor(pers).contains(check))
throw new PermissionException(check);
}
/**
* Uses the URL for the list and the context path to figure out the
* appropriate path to the SubEtha instance. Eg:
*
* http://my.list/ctx/somelist -> http://my.list/ctx/
*
* @return the context root of the SubEtha web application, determined
* from the main URL. Includes trailing /.
*/
public String getUrlBase()
{
String contextPath = ContextAware.getContextPath();
try
{
URL u = new URL(this.url);
return u.getProtocol()
+ "://"
+ u.getHost()
+ ((u.getPort() < 0) ? "" : (":"+u.getPort()))
+ contextPath
+ "/";
}
catch (MalformedURLException e)
{
// Should be impossible
throw new IllegalStateException("Stored an illegal url " + this.url);
}
}
/**
* @return the owner email address for this list
*/
public String getOwnerEmail()
{
int atIndex = this.email.indexOf('@');
String box = this.email.substring(0, atIndex);
String remainder = this.email.substring(atIndex);
return box + "-owner" + remainder;
}
/** */
@Override
public String toString()
{
return this.getClass() + " {id=" + this.id + ", address=" + this.email + "}";
}
/**
* Natural sort order is based on email address
*/
public int compareTo(MailingList other)
{
return this.email.compareTo(other.getEmail());
}
}