view src/nabble/model/MailingListImpl.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line source

package nabble.model;

import fschmidt.db.DbDatabase;
import fschmidt.db.DbNull;
import fschmidt.db.DbObject;
import fschmidt.db.DbObjectFactory;
import fschmidt.db.DbRecord;
import fschmidt.db.DbTable;
import fschmidt.db.DbUtils;
import fschmidt.db.ListenerList;
import fschmidt.db.LongKey;
import fschmidt.util.java.Computable;
import fschmidt.util.java.SimpleCache;
import fschmidt.util.mail.Mail;
import fschmidt.util.mail.MailAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Random;
import java.util.WeakHashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public final class MailingListImpl implements MailingList, DbObject<LongKey, MailingListImpl> {
	private static final Logger logger = LoggerFactory.getLogger(MailingListImpl.class);

	final SiteKey siteKey;
	private final DbRecord<LongKey, MailingListImpl> record;
	private String listAddress;
	private String listName;
	private String url;
	private String emailId;
	private boolean ignoreNoArchive;
	private boolean plainTextOnly;
	private ListServer listServer;
	private String exportOwner;

	private NodeImpl forum;

	private MailingListImpl(SiteKey siteKey,LongKey key, ResultSet rs)
			throws SQLException
	{
		this.siteKey = siteKey;
		record = table(siteKey).newRecord(this, key);
		listAddress = rs.getString("list_address");
		emailId = rs.getString("email_id");
		listName = rs.getString("list_name");
		url = rs.getString("list_home_url");
		ignoreNoArchive = rs.getBoolean("ignore_no_archive");
		plainTextOnly = rs.getBoolean("plain_text_only");
		listServer = ListServer.getServer(rs.getString("list_server"));
		exportOwner = rs.getString("export_owner");
	}

	MailingListImpl(NodeImpl forum, ListServer listServer, String listAddress, String url)
		throws ModelException
	{
		this.siteKey = forum.siteKey;
		record = table(siteKey).newRecord(this);
		long id = forum.getId();
		record.fields().put("node_id", id);
		this.forum = forum;
		setListServer(listServer);
		setListAddress(listAddress);
		setUrl(url);
		record.insert();
	}

	public DbRecord<LongKey, MailingListImpl> getDbRecord() {
		return record;
	}

	private DbTable<LongKey,MailingListImpl> table() {
		return record.getDbTable();
	}

	private DbDatabase db() {
		return table().getDbDatabase();
	}

	public long getId() {
		return record.getPrimaryKey().value();
	}

	NodeImpl getForumImpl() {
		if (DbUtils.isStale(forum)) {
			forum = NodeImpl.getNode(siteKey,getId());
		}
		return forum;
	}

	public Node getForum() {
		return getForumImpl();
	}

	public String getListAddress() {
		return listAddress;
	}

	private static MailingListImpl getMailingList(SiteKey siteKey,long id) {
		DbTable<LongKey,MailingListImpl> tbl = table(siteKey);
		return tbl==null ? null : tbl.findByPrimaryKey(new LongKey(id));
	}

	public void delete() {
		if (!db().isInTransaction()) {
			db().beginTransaction();
			try {
				MailingListImpl mailingList = DbUtils.getGoodCopy(this);
				mailingList.delete();
				db().commitTransaction();
			} finally {
				db().endTransaction();
			}
			return;
		}
		record.delete();
	}

	static MailingListImpl getMailingListForForum(NodeImpl forum) {
		MailingListImpl mailingList = table(forum.siteKey).findByPrimaryKey(new LongKey(forum.getId()));
		if (mailingList != null) {
			mailingList.forum = forum;
		}
		return mailingList;
	}

	private static class Lazy {
		static final String emailPrefix;
		static final String emailSuffix;
		static final Pattern EMAIL_SUFFIXPATTERN;
		static final Pattern pattern;
		static {
			String addrSpec = MailingLists.pop3Server.getUsername();
			int ind = addrSpec.indexOf('@');
			emailPrefix = addrSpec.substring(0, ind) + "+";
			emailSuffix = addrSpec.substring(ind);
			EMAIL_SUFFIXPATTERN = Pattern.compile(
					Pattern.quote(emailPrefix) + "([^@]+)" + Pattern.quote(emailSuffix),
					Pattern.CASE_INSENSITIVE);
			pattern = Pattern.compile(
					Pattern.quote(emailPrefix) + "s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix),
					Pattern.CASE_INSENSITIVE);
		}
	}

	static MailingListImpl getMailingListByEnvelopeAddress(String address) {
		Matcher matcher = Lazy.pattern.matcher(address);
		if( matcher.matches() ) {
			long siteId = Long.valueOf(matcher.group(1));
			SiteImpl site = SiteKey.getInstance(siteId).site();
			if( site == null )
				return null;
			long nodeId = Long.valueOf(matcher.group(2));
			NodeImpl node = site.getNodeImpl(nodeId);
			if( node == null )
				return null;
			MailingListImpl ml = node.getMailingListImpl();
			if( ml==null )
				return null;
			String hash = matcher.group(3);
			if( !ml.generateHash().equals(hash) )
				return null;
			return ml;
		}
		matcher = Lazy.EMAIL_SUFFIXPATTERN.matcher(address);
		if( matcher.matches() ) {
			String emailId = matcher.group(1);
			return getMailingListByOldEmailId(emailId);
		}
		return null;
	}

	private static MailingListImpl getMailingListByOldEmailId(String emailId) {
		try {
			Connection con = Db.dbGlobal().getConnection();
			PreparedStatement stmt = con.prepareStatement(
				"select * from mailing_list_lookup where email_id = ?"
				, ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE
			);
			stmt.setString(1, emailId.trim().toLowerCase());
			ResultSet rs = stmt.executeQuery();
			try {
				if( !rs.next() )
					return null;
				long siteId = rs.getLong("site_id");
				long nodeId = rs.getLong("node_id");
				SiteKey siteKey = SiteKey.getInstance(siteId);
				MailingListImpl ml = getMailingList(siteKey,nodeId);
				if( ml == null ) {
					logger.error("couldn't find mailing list site="+siteId+" node="+nodeId);
					rs.deleteRow();
					return null;
				}
				return ml;
			} finally {
				rs.close();
				stmt.close();
				con.close();
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	public static int cleanMailingListLookup() {
		List<String> emailIds = new ArrayList<String>();
		try {
			Connection con = Db.dbGlobal().getConnection();
			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(
				"select email_id from mailing_list_lookup"
			);
			while( rs.next() ) {
				String emailId = rs.getString("email_id");
				emailIds.add(emailId);
			}
			rs.close();
			stmt.close();
			con.close();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
		int count = 0;
		for( String emailId : emailIds ) {
			MailingList ml = getMailingListByOldEmailId(emailId);
			if (ml == null)
				count++;
		}
		return count;
	}

	public static List<MailingList> getOldMailingLists() {
		List<String> emailIds = new ArrayList<String>();
		try {
			Connection con = Db.dbGlobal().getConnection();
			Statement stmt = con.createStatement();
			ResultSet rs = stmt.executeQuery(
				"select email_id from mailing_list_lookup"
			);
			while( rs.next() ) {
				String emailId = rs.getString("email_id");
				emailIds.add(emailId);
			}
			rs.close();
			stmt.close();
			con.close();
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
		List<MailingList> list = new ArrayList<MailingList>();
		for( String emailId : emailIds ) {
			MailingList ml = getMailingListByOldEmailId(emailId);
			if (ml != null)
				list.add(ml);
		}
		return list;
	}

	private String generateHash() {
		int h = 31*(int)getId();
		return Integer.toString(Math.abs(h)%100);
	}

	public void setListAddress(String listAddress) throws ModelException.EmailFormat {
		listAddress = listAddress.trim();
		UserImpl.validateEmail(listAddress);
		this.listAddress = listAddress;
		record.fields().put("list_address", listAddress);
	}

	public String getListName() {
		return listName;
	}

	public void setListName(String listName) {
		if( listName != null ) {
			listName = listName.trim();
			if( listName.equals("") )
				listName = null;
		}
		this.listName = listName;
		record.fields().put("list_name", DbNull.fix(listName));
	}

	String fixSubject(String subject) {
		if( listName != null && subject != null ) {
			if( subject.startsWith(listName) )
				return subject.substring(listName.length()).trim();
			String lowerListName = listName.toLowerCase();
			String lowerSubject = subject.toLowerCase();
			if( lowerSubject.startsWith( "re: " + lowerListName + " re: " ) )
				return subject.substring(listName.length()+5);
			if( lowerSubject.startsWith( "re: " + lowerListName ) )
				return subject.substring(0,4) + subject.substring(4+listName.length()).trim();
		}
		return subject;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) throws ModelException.UrlFormat {
		url = url.trim();
		validateUrl(url);
		this.url = url;
		record.fields().put("list_home_url", url);
	}

	static void validateUrl(String url) throws ModelException.UrlFormat {
		try {
			new URL(url);
		} catch(MalformedURLException e) {
			throw new ModelException.UrlFormat(url,e);
		}
	}

	public boolean ignoreNoArchive() {
		return ignoreNoArchive;
	}

	public void setIgnoreNoArchive(boolean ignoreNoArchive) {
		this.ignoreNoArchive = ignoreNoArchive;
		record.fields().put("ignore_no_archive", ignoreNoArchive);
	}

	public boolean plainTextOnly() {
		return plainTextOnly;
	}

	public void setPlainTextOnly(boolean plainTextOnly) {
		this.plainTextOnly = plainTextOnly;
		record.fields().put("plain_text_only", plainTextOnly);
	}

	public ListServer getListServer() {
		return listServer;
	}

	public void setListServer(ListServer listServer) {
		this.listServer = listServer;
		record.fields().put("list_server", listServer.getType());
	}

	public String getEmailId() {
		return emailId;
	}

	public void setEmailId(String emailId) {
		this.emailId = emailId;
		record.fields().put("email_id", DbNull.fix(emailId));
	}

	public void update() {
		record.update();
	}

	public boolean equals(Object obj) {
		return obj instanceof MailingList && ((MailingList) obj).getId() == getId();
	}

	public int hashCode() {
		return (int) getId();
	}

	public String toString() {
		return "mailing_list-" + getId();
	}

	public ImportResult importMbox(File file, String mailErrorsTo, int maxErrors)
		throws ModelException
	{
		ModelHome.beginImport();
		try {
			return MailingLists.importMbox(file, this, mailErrorsTo, maxErrors);
		} finally {
			ModelHome.endImport();
		}
	}

	public MailAddress getSubscriberAddress() {
		return new MailAddress( Lazy.emailPrefix
			+ (emailId != null ? emailId
				: "s" + siteKey.getId()
				+ 'n' + getId()
				+ 'h' + generateHash()
			)
		+ Lazy.emailSuffix );
	}

	public String getPassword(User user) {
		Random r = new Random(getId() + user.getId());
		long p = Math.round(r.nextDouble() * Math.pow(Character.MAX_RADIX, 8));
		return Long.toString(p, Character.MAX_RADIX);
	}

	public String getExportOwner() {
		return exportOwner;
	}

	public void setExportOwner(String email) throws ModelException.EmailFormat {
		if( email != null ) {
			email = email.trim();
			UserImpl.validateEmail(email);
		}
		this.exportOwner = email;
		record.fields().put("export_owner", DbNull.fix(this.exportOwner));
	}

	public Node getNodeFromMessageID(String messageID) {
		return forum.getNodeImplFromMessageID(messageID);
	}

	public void subscribe() {
		if (listServer.canSubscribe())
			ModelHome.send(subscribeMail());
	}

	public void unsubscribe() {
		if (listServer.canSubscribe())
			ModelHome.send(unsubscribeMail());
	}

	public Mail subscribeMail() {
		return listServer.subscribeMail(getSubscriberAddress(), getListAddress(), null, true);
	}

	public Mail subscribeMail(User user) {
		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
		return listServer.subscribeMail(userAddress, getListAddress(), getPassword(user), false);
	}

	public Mail unsubscribeMail() {
		return listServer.unsubscribeMail(getSubscriberAddress(), getListAddress(), null);
	}

	public Mail unsubscribeMail(User user) {
		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
		return listServer.unsubscribeMail(userAddress, getListAddress(), null);
	}

	public Mail defaultsMail(User user, String password) {
		MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
		return listServer.defaultsMail(userAddress, getListAddress(), password);
	}


	static final ListenerList<MailingListImpl> preUpdateListeners = new ListenerList<MailingListImpl>();
	static final ListenerList<MailingListImpl> postInsertListeners = new ListenerList<MailingListImpl>();
	static final ListenerList<MailingListImpl> postUpdateListeners = new ListenerList<MailingListImpl>();
	static final ListenerList<MailingListImpl> postDeleteListeners = new ListenerList<MailingListImpl>();

	private static Computable<SiteKey,DbTable<LongKey,MailingListImpl>> tables = new SimpleCache<SiteKey,DbTable<LongKey,MailingListImpl>>(new WeakHashMap<SiteKey,DbTable<LongKey,MailingListImpl>>(), new Computable<SiteKey,DbTable<LongKey,MailingListImpl>>() {
		public DbTable<LongKey,MailingListImpl> get(SiteKey siteKey) {
			DbDatabase db;
			try {
				db = siteKey.getDb();
			} catch(Db.NoSchema e) {
				return null;
			}
			final long siteId = siteKey.getId();
			DbTable<LongKey,MailingListImpl> table = db.newTable("mailing_list",db.newAssignedLongKeySetter("node_id")
				, new DbObjectFactory<LongKey,MailingListImpl>() {
					public MailingListImpl makeDbObject(LongKey key,ResultSet rs,String tableName)
						throws SQLException
					{
						SiteKey siteKey = SiteKey.getInstance(siteId);
						return new MailingListImpl(siteKey,key,rs);
					}
				}
			);
			table.getPreUpdateListeners().add(preUpdateListeners);
			table.getPostInsertListeners().add(postInsertListeners);
			table.getPostUpdateListeners().add(postUpdateListeners);
			table.getPostDeleteListeners().add(postDeleteListeners);
			return table;
		}
	});

	private static DbTable<LongKey,MailingListImpl> table(SiteKey siteKey) {
		return tables.get(siteKey);
	}

	public void rethread() {
		try {
			MailingLists.rethreadForum( getForumImpl(), false );
		} catch(SQLException e) {
			throw new RuntimeException(e);
		}
	}

}