0
|
1 package nabble.model;
|
|
2
|
|
3 import fschmidt.db.DbDatabase;
|
|
4 import fschmidt.db.DbNull;
|
|
5 import fschmidt.db.DbObject;
|
|
6 import fschmidt.db.DbObjectFactory;
|
|
7 import fschmidt.db.DbRecord;
|
|
8 import fschmidt.db.DbTable;
|
|
9 import fschmidt.db.DbUtils;
|
|
10 import fschmidt.db.ListenerList;
|
|
11 import fschmidt.db.LongKey;
|
|
12 import fschmidt.util.java.Computable;
|
|
13 import fschmidt.util.java.SimpleCache;
|
|
14 import fschmidt.util.mail.Mail;
|
|
15 import fschmidt.util.mail.MailAddress;
|
|
16 import org.slf4j.Logger;
|
|
17 import org.slf4j.LoggerFactory;
|
|
18
|
|
19 import java.io.File;
|
|
20 import java.net.MalformedURLException;
|
|
21 import java.net.URL;
|
|
22 import java.sql.Connection;
|
|
23 import java.sql.Statement;
|
|
24 import java.sql.PreparedStatement;
|
|
25 import java.sql.ResultSet;
|
|
26 import java.sql.SQLException;
|
|
27 import java.util.Random;
|
|
28 import java.util.WeakHashMap;
|
|
29 import java.util.List;
|
|
30 import java.util.ArrayList;
|
|
31 import java.util.regex.Matcher;
|
|
32 import java.util.regex.Pattern;
|
|
33
|
|
34
|
|
35 public final class MailingListImpl implements MailingList, DbObject<LongKey, MailingListImpl> {
|
|
36 private static final Logger logger = LoggerFactory.getLogger(MailingListImpl.class);
|
|
37
|
|
38 final SiteKey siteKey;
|
|
39 private final DbRecord<LongKey, MailingListImpl> record;
|
|
40 private String listAddress;
|
|
41 private String listName;
|
|
42 private String url;
|
|
43 private String emailId;
|
|
44 private boolean ignoreNoArchive;
|
|
45 private boolean plainTextOnly;
|
|
46 private ListServer listServer;
|
|
47 private String exportOwner;
|
|
48
|
|
49 private NodeImpl forum;
|
|
50
|
|
51 private MailingListImpl(SiteKey siteKey,LongKey key, ResultSet rs)
|
|
52 throws SQLException
|
|
53 {
|
|
54 this.siteKey = siteKey;
|
|
55 record = table(siteKey).newRecord(this, key);
|
|
56 listAddress = rs.getString("list_address");
|
|
57 emailId = rs.getString("email_id");
|
|
58 listName = rs.getString("list_name");
|
|
59 url = rs.getString("list_home_url");
|
|
60 ignoreNoArchive = rs.getBoolean("ignore_no_archive");
|
|
61 plainTextOnly = rs.getBoolean("plain_text_only");
|
|
62 listServer = ListServer.getServer(rs.getString("list_server"));
|
|
63 exportOwner = rs.getString("export_owner");
|
|
64 }
|
|
65
|
|
66 MailingListImpl(NodeImpl forum, ListServer listServer, String listAddress, String url)
|
|
67 throws ModelException
|
|
68 {
|
|
69 this.siteKey = forum.siteKey;
|
|
70 record = table(siteKey).newRecord(this);
|
|
71 long id = forum.getId();
|
|
72 record.fields().put("node_id", id);
|
|
73 this.forum = forum;
|
|
74 setListServer(listServer);
|
|
75 setListAddress(listAddress);
|
|
76 setUrl(url);
|
|
77 record.insert();
|
|
78 }
|
|
79
|
|
80 public DbRecord<LongKey, MailingListImpl> getDbRecord() {
|
|
81 return record;
|
|
82 }
|
|
83
|
|
84 private DbTable<LongKey,MailingListImpl> table() {
|
|
85 return record.getDbTable();
|
|
86 }
|
|
87
|
|
88 private DbDatabase db() {
|
|
89 return table().getDbDatabase();
|
|
90 }
|
|
91
|
|
92 public long getId() {
|
|
93 return record.getPrimaryKey().value();
|
|
94 }
|
|
95
|
|
96 NodeImpl getForumImpl() {
|
|
97 if (DbUtils.isStale(forum)) {
|
|
98 forum = NodeImpl.getNode(siteKey,getId());
|
|
99 }
|
|
100 return forum;
|
|
101 }
|
|
102
|
|
103 public Node getForum() {
|
|
104 return getForumImpl();
|
|
105 }
|
|
106
|
|
107 public String getListAddress() {
|
|
108 return listAddress;
|
|
109 }
|
|
110
|
|
111 private static MailingListImpl getMailingList(SiteKey siteKey,long id) {
|
|
112 DbTable<LongKey,MailingListImpl> tbl = table(siteKey);
|
|
113 return tbl==null ? null : tbl.findByPrimaryKey(new LongKey(id));
|
|
114 }
|
|
115
|
|
116 public void delete() {
|
|
117 if (!db().isInTransaction()) {
|
|
118 db().beginTransaction();
|
|
119 try {
|
|
120 MailingListImpl mailingList = DbUtils.getGoodCopy(this);
|
|
121 mailingList.delete();
|
|
122 db().commitTransaction();
|
|
123 } finally {
|
|
124 db().endTransaction();
|
|
125 }
|
|
126 return;
|
|
127 }
|
|
128 record.delete();
|
|
129 }
|
|
130
|
|
131 static MailingListImpl getMailingListForForum(NodeImpl forum) {
|
|
132 MailingListImpl mailingList = table(forum.siteKey).findByPrimaryKey(new LongKey(forum.getId()));
|
|
133 if (mailingList != null) {
|
|
134 mailingList.forum = forum;
|
|
135 }
|
|
136 return mailingList;
|
|
137 }
|
|
138
|
|
139 private static class Lazy {
|
|
140 static final String emailPrefix;
|
|
141 static final String emailSuffix;
|
|
142 static final Pattern EMAIL_SUFFIXPATTERN;
|
|
143 static final Pattern pattern;
|
|
144 static {
|
|
145 String addrSpec = MailingLists.pop3Server.getUsername();
|
|
146 int ind = addrSpec.indexOf('@');
|
|
147 emailPrefix = addrSpec.substring(0, ind) + "+";
|
|
148 emailSuffix = addrSpec.substring(ind);
|
|
149 EMAIL_SUFFIXPATTERN = Pattern.compile(
|
|
150 Pattern.quote(emailPrefix) + "([^@]+)" + Pattern.quote(emailSuffix),
|
|
151 Pattern.CASE_INSENSITIVE);
|
|
152 pattern = Pattern.compile(
|
|
153 Pattern.quote(emailPrefix) + "s(\\d+)n(\\d+)h(\\d+)" + Pattern.quote(emailSuffix),
|
|
154 Pattern.CASE_INSENSITIVE);
|
|
155 }
|
|
156 }
|
|
157
|
|
158 static MailingListImpl getMailingListByEnvelopeAddress(String address) {
|
|
159 Matcher matcher = Lazy.pattern.matcher(address);
|
|
160 if( matcher.matches() ) {
|
|
161 long siteId = Long.valueOf(matcher.group(1));
|
|
162 SiteImpl site = SiteKey.getInstance(siteId).site();
|
|
163 if( site == null )
|
|
164 return null;
|
|
165 long nodeId = Long.valueOf(matcher.group(2));
|
|
166 NodeImpl node = site.getNodeImpl(nodeId);
|
|
167 if( node == null )
|
|
168 return null;
|
|
169 MailingListImpl ml = node.getMailingListImpl();
|
|
170 if( ml==null )
|
|
171 return null;
|
|
172 String hash = matcher.group(3);
|
|
173 if( !ml.generateHash().equals(hash) )
|
|
174 return null;
|
|
175 return ml;
|
|
176 }
|
|
177 matcher = Lazy.EMAIL_SUFFIXPATTERN.matcher(address);
|
|
178 if( matcher.matches() ) {
|
|
179 String emailId = matcher.group(1);
|
|
180 return getMailingListByOldEmailId(emailId);
|
|
181 }
|
|
182 return null;
|
|
183 }
|
|
184
|
|
185 private static MailingListImpl getMailingListByOldEmailId(String emailId) {
|
|
186 try {
|
|
187 Connection con = Db.dbGlobal().getConnection();
|
|
188 PreparedStatement stmt = con.prepareStatement(
|
|
189 "select * from mailing_list_lookup where email_id = ?"
|
|
190 , ResultSet.TYPE_FORWARD_ONLY,ResultSet.CONCUR_UPDATABLE
|
|
191 );
|
|
192 stmt.setString(1, emailId.trim().toLowerCase());
|
|
193 ResultSet rs = stmt.executeQuery();
|
|
194 try {
|
|
195 if( !rs.next() )
|
|
196 return null;
|
|
197 long siteId = rs.getLong("site_id");
|
|
198 long nodeId = rs.getLong("node_id");
|
|
199 SiteKey siteKey = SiteKey.getInstance(siteId);
|
|
200 MailingListImpl ml = getMailingList(siteKey,nodeId);
|
|
201 if( ml == null ) {
|
|
202 logger.error("couldn't find mailing list site="+siteId+" node="+nodeId);
|
|
203 rs.deleteRow();
|
|
204 return null;
|
|
205 }
|
|
206 return ml;
|
|
207 } finally {
|
|
208 rs.close();
|
|
209 stmt.close();
|
|
210 con.close();
|
|
211 }
|
|
212 } catch (SQLException e) {
|
|
213 throw new RuntimeException(e);
|
|
214 }
|
|
215 }
|
|
216
|
|
217 public static int cleanMailingListLookup() {
|
|
218 List<String> emailIds = new ArrayList<String>();
|
|
219 try {
|
|
220 Connection con = Db.dbGlobal().getConnection();
|
|
221 Statement stmt = con.createStatement();
|
|
222 ResultSet rs = stmt.executeQuery(
|
|
223 "select email_id from mailing_list_lookup"
|
|
224 );
|
|
225 while( rs.next() ) {
|
|
226 String emailId = rs.getString("email_id");
|
|
227 emailIds.add(emailId);
|
|
228 }
|
|
229 rs.close();
|
|
230 stmt.close();
|
|
231 con.close();
|
|
232 } catch (SQLException e) {
|
|
233 throw new RuntimeException(e);
|
|
234 }
|
|
235 int count = 0;
|
|
236 for( String emailId : emailIds ) {
|
|
237 MailingList ml = getMailingListByOldEmailId(emailId);
|
|
238 if (ml == null)
|
|
239 count++;
|
|
240 }
|
|
241 return count;
|
|
242 }
|
|
243
|
|
244 public static List<MailingList> getOldMailingLists() {
|
|
245 List<String> emailIds = new ArrayList<String>();
|
|
246 try {
|
|
247 Connection con = Db.dbGlobal().getConnection();
|
|
248 Statement stmt = con.createStatement();
|
|
249 ResultSet rs = stmt.executeQuery(
|
|
250 "select email_id from mailing_list_lookup"
|
|
251 );
|
|
252 while( rs.next() ) {
|
|
253 String emailId = rs.getString("email_id");
|
|
254 emailIds.add(emailId);
|
|
255 }
|
|
256 rs.close();
|
|
257 stmt.close();
|
|
258 con.close();
|
|
259 } catch (SQLException e) {
|
|
260 throw new RuntimeException(e);
|
|
261 }
|
|
262 List<MailingList> list = new ArrayList<MailingList>();
|
|
263 for( String emailId : emailIds ) {
|
|
264 MailingList ml = getMailingListByOldEmailId(emailId);
|
|
265 if (ml != null)
|
|
266 list.add(ml);
|
|
267 }
|
|
268 return list;
|
|
269 }
|
|
270
|
|
271 private String generateHash() {
|
|
272 int h = 31*(int)getId();
|
|
273 return Integer.toString(Math.abs(h)%100);
|
|
274 }
|
|
275
|
|
276 public void setListAddress(String listAddress) throws ModelException.EmailFormat {
|
|
277 listAddress = listAddress.trim();
|
|
278 UserImpl.validateEmail(listAddress);
|
|
279 this.listAddress = listAddress;
|
|
280 record.fields().put("list_address", listAddress);
|
|
281 }
|
|
282
|
|
283 public String getListName() {
|
|
284 return listName;
|
|
285 }
|
|
286
|
|
287 public void setListName(String listName) {
|
|
288 if( listName != null ) {
|
|
289 listName = listName.trim();
|
|
290 if( listName.equals("") )
|
|
291 listName = null;
|
|
292 }
|
|
293 this.listName = listName;
|
|
294 record.fields().put("list_name", DbNull.fix(listName));
|
|
295 }
|
|
296
|
|
297 String fixSubject(String subject) {
|
|
298 if( listName != null && subject != null ) {
|
|
299 if( subject.startsWith(listName) )
|
|
300 return subject.substring(listName.length()).trim();
|
|
301 String lowerListName = listName.toLowerCase();
|
|
302 String lowerSubject = subject.toLowerCase();
|
|
303 if( lowerSubject.startsWith( "re: " + lowerListName + " re: " ) )
|
|
304 return subject.substring(listName.length()+5);
|
|
305 if( lowerSubject.startsWith( "re: " + lowerListName ) )
|
|
306 return subject.substring(0,4) + subject.substring(4+listName.length()).trim();
|
|
307 }
|
|
308 return subject;
|
|
309 }
|
|
310
|
|
311 public String getUrl() {
|
|
312 return url;
|
|
313 }
|
|
314
|
|
315 public void setUrl(String url) throws ModelException.UrlFormat {
|
|
316 url = url.trim();
|
|
317 validateUrl(url);
|
|
318 this.url = url;
|
|
319 record.fields().put("list_home_url", url);
|
|
320 }
|
|
321
|
|
322 static void validateUrl(String url) throws ModelException.UrlFormat {
|
|
323 try {
|
|
324 new URL(url);
|
|
325 } catch(MalformedURLException e) {
|
|
326 throw new ModelException.UrlFormat(url,e);
|
|
327 }
|
|
328 }
|
|
329
|
|
330 public boolean ignoreNoArchive() {
|
|
331 return ignoreNoArchive;
|
|
332 }
|
|
333
|
|
334 public void setIgnoreNoArchive(boolean ignoreNoArchive) {
|
|
335 this.ignoreNoArchive = ignoreNoArchive;
|
|
336 record.fields().put("ignore_no_archive", ignoreNoArchive);
|
|
337 }
|
|
338
|
|
339 public boolean plainTextOnly() {
|
|
340 return plainTextOnly;
|
|
341 }
|
|
342
|
|
343 public void setPlainTextOnly(boolean plainTextOnly) {
|
|
344 this.plainTextOnly = plainTextOnly;
|
|
345 record.fields().put("plain_text_only", plainTextOnly);
|
|
346 }
|
|
347
|
|
348 public ListServer getListServer() {
|
|
349 return listServer;
|
|
350 }
|
|
351
|
|
352 public void setListServer(ListServer listServer) {
|
|
353 this.listServer = listServer;
|
|
354 record.fields().put("list_server", listServer.getType());
|
|
355 }
|
|
356
|
|
357 public String getEmailId() {
|
|
358 return emailId;
|
|
359 }
|
|
360
|
|
361 public void setEmailId(String emailId) {
|
|
362 this.emailId = emailId;
|
|
363 record.fields().put("email_id", DbNull.fix(emailId));
|
|
364 }
|
|
365
|
|
366 public void update() {
|
|
367 record.update();
|
|
368 }
|
|
369
|
|
370 public boolean equals(Object obj) {
|
|
371 return obj instanceof MailingList && ((MailingList) obj).getId() == getId();
|
|
372 }
|
|
373
|
|
374 public int hashCode() {
|
|
375 return (int) getId();
|
|
376 }
|
|
377
|
|
378 public String toString() {
|
|
379 return "mailing_list-" + getId();
|
|
380 }
|
|
381
|
|
382 public ImportResult importMbox(File file, String mailErrorsTo, int maxErrors)
|
|
383 throws ModelException
|
|
384 {
|
|
385 ModelHome.beginImport();
|
|
386 try {
|
|
387 return MailingLists.importMbox(file, this, mailErrorsTo, maxErrors);
|
|
388 } finally {
|
|
389 ModelHome.endImport();
|
|
390 }
|
|
391 }
|
|
392
|
|
393 public MailAddress getSubscriberAddress() {
|
|
394 return new MailAddress( Lazy.emailPrefix
|
|
395 + (emailId != null ? emailId
|
|
396 : "s" + siteKey.getId()
|
|
397 + 'n' + getId()
|
|
398 + 'h' + generateHash()
|
|
399 )
|
|
400 + Lazy.emailSuffix );
|
|
401 }
|
|
402
|
|
403 public String getPassword(User user) {
|
|
404 Random r = new Random(getId() + user.getId());
|
|
405 long p = Math.round(r.nextDouble() * Math.pow(Character.MAX_RADIX, 8));
|
|
406 return Long.toString(p, Character.MAX_RADIX);
|
|
407 }
|
|
408
|
|
409 public String getExportOwner() {
|
|
410 return exportOwner;
|
|
411 }
|
|
412
|
|
413 public void setExportOwner(String email) throws ModelException.EmailFormat {
|
|
414 if( email != null ) {
|
|
415 email = email.trim();
|
|
416 UserImpl.validateEmail(email);
|
|
417 }
|
|
418 this.exportOwner = email;
|
|
419 record.fields().put("export_owner", DbNull.fix(this.exportOwner));
|
|
420 }
|
|
421
|
|
422 public Node getNodeFromMessageID(String messageID) {
|
|
423 return forum.getNodeImplFromMessageID(messageID);
|
|
424 }
|
|
425
|
|
426 public void subscribe() {
|
|
427 if (listServer.canSubscribe())
|
|
428 ModelHome.send(subscribeMail());
|
|
429 }
|
|
430
|
|
431 public void unsubscribe() {
|
|
432 if (listServer.canSubscribe())
|
|
433 ModelHome.send(unsubscribeMail());
|
|
434 }
|
|
435
|
|
436 public Mail subscribeMail() {
|
|
437 return listServer.subscribeMail(getSubscriberAddress(), getListAddress(), null, true);
|
|
438 }
|
|
439
|
|
440 public Mail subscribeMail(User user) {
|
|
441 MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
|
|
442 return listServer.subscribeMail(userAddress, getListAddress(), getPassword(user), false);
|
|
443 }
|
|
444
|
|
445 public Mail unsubscribeMail() {
|
|
446 return listServer.unsubscribeMail(getSubscriberAddress(), getListAddress(), null);
|
|
447 }
|
|
448
|
|
449 public Mail unsubscribeMail(User user) {
|
|
450 MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
|
|
451 return listServer.unsubscribeMail(userAddress, getListAddress(), null);
|
|
452 }
|
|
453
|
|
454 public Mail defaultsMail(User user, String password) {
|
|
455 MailAddress userAddress = new MailAddress(user.getEmail(), user.getName());
|
|
456 return listServer.defaultsMail(userAddress, getListAddress(), password);
|
|
457 }
|
|
458
|
|
459
|
|
460 static final ListenerList<MailingListImpl> preUpdateListeners = new ListenerList<MailingListImpl>();
|
|
461 static final ListenerList<MailingListImpl> postInsertListeners = new ListenerList<MailingListImpl>();
|
|
462 static final ListenerList<MailingListImpl> postUpdateListeners = new ListenerList<MailingListImpl>();
|
|
463 static final ListenerList<MailingListImpl> postDeleteListeners = new ListenerList<MailingListImpl>();
|
|
464
|
|
465 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>>() {
|
|
466 public DbTable<LongKey,MailingListImpl> get(SiteKey siteKey) {
|
|
467 DbDatabase db;
|
|
468 try {
|
|
469 db = siteKey.getDb();
|
|
470 } catch(Db.NoSchema e) {
|
|
471 return null;
|
|
472 }
|
|
473 final long siteId = siteKey.getId();
|
|
474 DbTable<LongKey,MailingListImpl> table = db.newTable("mailing_list",db.newAssignedLongKeySetter("node_id")
|
|
475 , new DbObjectFactory<LongKey,MailingListImpl>() {
|
|
476 public MailingListImpl makeDbObject(LongKey key,ResultSet rs,String tableName)
|
|
477 throws SQLException
|
|
478 {
|
|
479 SiteKey siteKey = SiteKey.getInstance(siteId);
|
|
480 return new MailingListImpl(siteKey,key,rs);
|
|
481 }
|
|
482 }
|
|
483 );
|
|
484 table.getPreUpdateListeners().add(preUpdateListeners);
|
|
485 table.getPostInsertListeners().add(postInsertListeners);
|
|
486 table.getPostUpdateListeners().add(postUpdateListeners);
|
|
487 table.getPostDeleteListeners().add(postDeleteListeners);
|
|
488 return table;
|
|
489 }
|
|
490 });
|
|
491
|
|
492 private static DbTable<LongKey,MailingListImpl> table(SiteKey siteKey) {
|
|
493 return tables.get(siteKey);
|
|
494 }
|
|
495
|
|
496 public void rethread() {
|
|
497 try {
|
|
498 MailingLists.rethreadForum( getForumImpl(), false );
|
|
499 } catch(SQLException e) {
|
|
500 throw new RuntimeException(e);
|
|
501 }
|
|
502 }
|
|
503
|
|
504 }
|