1 package org.unitedfront2.domain.communication;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.Date;
7 import java.util.List;
8 import java.util.Locale;
9
10 import org.apache.commons.lang.RandomStringUtils;
11 import org.apache.commons.lang.builder.EqualsBuilder;
12 import org.apache.commons.lang.builder.HashCodeBuilder;
13 import org.apache.commons.lang.builder.ToStringBuilder;
14 import org.apache.commons.logging.Log;
15 import org.apache.commons.logging.LogFactory;
16 import org.unitedfront2.dao.MailDao;
17 import org.unitedfront2.domain.AccountTable;
18 import org.unitedfront2.domain.Domain;
19 import org.unitedfront2.domain.Identifiable;
20 import org.unitedfront2.domain.SimpleUser;
21 import org.unitedfront2.domain.SimpleUserTable;
22 import org.unitedfront2.domain.Storable;
23 import org.unitedfront2.domain.User;
24
25 /**
26 * メールクラスです。メールは1対1のコミュニケーション用のツールです。親メールに対して返信していくことで、リスト
27 * 構造を持つ一連のメール群を形成できます。これをメールのスレッドと呼びます。
28 *
29 * @author kurokkie
30 *
31 */
32 public class Mail implements Identifiable<Mail>, Storable, Serializable,
33 Domain {
34
35 /** ランダムで生成されるときのコードの長さ (32) */
36 public static final int GENERATED_CODE_LENGTH = 32;
37
38 /** シリアル番号 */
39 private static final long serialVersionUID = -8522418960016784969L;
40
41 /** ランダム生成コードが重複した際に、再生成して再チャレンジする回数 (10) */
42 private static final int MAX_CHALLENGE_COUNT = 10;
43
44 /**
45 * 宛先と差出人のユーザオブジェクトを復元します。
46 *
47 * @param mails メールリスト
48 * @ensure 全てのメールの宛先にユーザオブジェクトが設定される。もし宛先 ID が
49 * <code>null</code> なら設定されない
50 * @ensure 全てのメールの差出人にユーザオブジェクトが設定される。もし差出人 ID が
51 * <code>null</code> なら設定されない
52 */
53 public static void retrieveUsers(List<Mail> mails) {
54 for (Mail m : mails) {
55 m.retrieveTo();
56 m.retrieveFrom();
57 }
58 }
59
60 /** ログ */
61 protected final transient Log logger = LogFactory.getLog(getClass());
62
63 /** ID */
64 private Integer id;
65
66 /** コード */
67 private String code;
68
69 /** 宛先のユーザ ID */
70 private Integer toId;
71
72 /** 差出人のユーザ ID */
73 private Integer fromId;
74
75 /** 送信日時 */
76 private Date sentDate;
77
78 /** 件名 */
79 private String subject;
80
81 /** 本文 */
82 private String body;
83
84 /** 宛先ユーザが既読の場合は true 、そうでなければ false */
85 private boolean read;
86
87 /** 宛先ユーザ */
88 private transient SimpleUser to;
89
90 /** 差出人ユーザ */
91 private transient SimpleUser from;
92
93 /** 宛先のメールアドレス。実際にメールを送信するための変数。 */
94 private transient String toMailAddr;
95
96 /** このメールに対して返信したメール */
97 private transient Mail next;
98
99 /** メールデータアクセスオブジェクト **/
100 private transient MailDao mailDao;
101
102 /** ユーザテーブル */
103 private transient SimpleUserTable simpleUserTable;
104
105 /** アカウントテーブル */
106 private transient AccountTable accountTable;
107
108 public Mail() {
109 super();
110 }
111
112 public Mail(Integer toId, Integer fromId, String subject, String body) {
113 super();
114 this.toId = toId;
115 this.fromId = fromId;
116 this.subject = subject;
117 this.body = body;
118 }
119
120 public Mail(Integer id, String code, Integer toId, Integer fromId,
121 String subject, String body, Date sentDate, boolean read) {
122 this(toId, fromId, subject, body);
123 this.id = id;
124 this.code = code;
125 this.sentDate = (Date) sentDate.clone();
126 this.read = read;
127 }
128
129 @Override
130 public String toString() {
131 ToStringBuilder tsb = new ToStringBuilder(this)
132 .append("id", id)
133 .append("code", code)
134 .append("toId", toId)
135 .append("fromId", fromId)
136 .append("sentDate", sentDate)
137 .append("subject", subject)
138 .append("body", body)
139 .append("read", read)
140 .append("next", next);
141 if (to != null) {
142 tsb.append("to", to);
143 }
144 if (from != null) {
145 tsb.append("from", from);
146 }
147 return tsb.toString();
148 }
149
150 @Override
151 public boolean equals(final Object other) {
152 if (!(other instanceof Mail)) {
153 return false;
154 }
155 Mail castOther = (Mail) other;
156 return new EqualsBuilder()
157 .append(id, castOther.id)
158 .append(code, castOther.code)
159 .append(toId, castOther.toId)
160 .append(fromId, castOther.fromId)
161 .append(sentDate, castOther.sentDate)
162 .append(subject, castOther.subject)
163 .append(body, castOther.body)
164 .append(read, castOther.read).isEquals();
165 }
166
167 @Override
168 public int hashCode() {
169 return new HashCodeBuilder()
170 .append(id)
171 .append(code)
172 .append(toId)
173 .append(fromId)
174 .append(sentDate)
175 .append(subject)
176 .append(body)
177 .append(read).toHashCode();
178 }
179
180 @Override
181 public boolean identify(Mail other) {
182 if (id == null || other.getId() == null) {
183 return false;
184 }
185 return id.equals(other.getId());
186 }
187
188 /**
189 * {@link #send()} と同様の処理です。
190 *
191 * @see Storable#store()
192 */
193 @Override
194 public void store() {
195 send();
196 }
197
198 /**
199 * このメールを送信します。コードは自動で発行されます。送信日時は現在日時が設定されます。
200 *
201 * @require ${this.toId} is not null.
202 * @ensure ${this.code} is auto generated.
203 * @ensure ${this.lastUpdateDate} is current date.
204 */
205 public void send() {
206 if (toId == null) {
207 String message = "The toId must not be null. Mail[" + this + "]";
208 logger.error(message);
209 throw new IllegalStateException(message);
210 }
211 this.code = generateCode();
212 this.sentDate = mailDao.getCurrentDate();
213 mailDao.register(this);
214 }
215
216 private String generateCode() {
217 for (int i = 0; i < MAX_CHALLENGE_COUNT; i++) {
218 String code = RandomStringUtils.randomAlphanumeric(
219 GENERATED_CODE_LENGTH).toLowerCase(Locale.ENGLISH);
220 if (mailDao.findByCode(code) == null) {
221 return code;
222 }
223 }
224 String message = "Failed to generate code.";
225 logger.error(message);
226 throw new IllegalStateException(message);
227 }
228
229 /**
230 * メールを返信します。コードは自動で発行されます。送信日時は現在日時が設定されます。
231 *
232 * @param parentId 親メール ID
233 * @require ${this.toId} is not null.
234 * @require ${this.fromId} is not null.
235 * @ensure ${this.code} is auto generated.
236 * @ensure ${this.lastUpdateDate} is current date.
237 * @see #store()
238 */
239 public void send(int parentId) {
240 if (toId == null) {
241 String message = "The To must not be null. Mail[" + this + "]";
242 logger.error(message);
243 throw new IllegalStateException(message);
244 }
245 if (fromId == null) {
246 String message = "The from must not be null. Mail[" + this + "]";
247 logger.error(message);
248 throw new IllegalStateException(message);
249 }
250 this.code = generateCode();
251 this.sentDate = mailDao.getCurrentDate();
252 mailDao.register(this, parentId);
253 }
254
255 /**
256 * このメールが ${userId} 宛の場合、このメールを既読にします。
257 *
258 * @param userId 閲覧ユーザの ID
259 * @ensure このメールが ${userId} 宛の場合、このメールの既読/未読状態がデータベースと同期する
260 */
261 public void read(int userId) {
262 if (userId == toId.intValue()) {
263 read = true;
264 mailDao.updateRead(id, true);
265 }
266 }
267
268 /**
269 * このメールスレッド中の全ての ${userId} 宛のメールを既読にします。
270 *
271 * @param userId 閲覧ユーザの ID
272 * @ensure このメールスレッド中の全ての ${userId} 宛のメールの既読/未読状態がデータベースと同
273 * 期する
274 */
275 public void readAll(int userId) {
276 read(userId);
277 if (next != null) {
278 next.readAll(userId);
279 }
280 }
281
282 /**
283 * このメールが ${userId} 宛の場合、このメールを未読にします。
284 *
285 * @param userId 閲覧ユーザの ID
286 * @ensure このメールが ${userId} 宛の場合、このメールの既読/未読状態がデータベースと同期する
287 */
288 public void unread(int userId) {
289 if (userId == toId.intValue()) {
290 read = false;
291 mailDao.updateRead(id, false);
292 }
293 }
294
295 /**
296 * このメールスレッド中の全ての ${userId} 宛のメールを未読にします。
297 *
298 * @param userId 閲覧ユーザの ID
299 * @ensure このメールスレッド中の全ての ${userId} 宛のメールの既読/未読状態がデータベースと同
300 * 期する
301 */
302 public void unreadAll(int userId) {
303 unread(userId);
304 if (next != null) {
305 next.unreadAll(userId);
306 }
307 }
308
309 /**
310 * メールスレッド中に未読メールがあるかどうか判定します。
311 *
312 * @param toId 宛先ユーザ ID
313 * @return 未読のメールがあれば <code>true</code> 、なければ <code>false</code>
314 */
315 public boolean hasUnread(int toId) {
316 if (toId == this.toId && !isRead()) {
317 return true;
318 } else {
319 return next != null && next.hasUnread(toId);
320 }
321 }
322
323 /**
324 * スレッド内のメール数を取得します。
325 *
326 * @return スレッド内のメール数
327 */
328 public int getCount() {
329 int count = 1;
330 if (next != null) {
331 count += next.getCount();
332 }
333 return count;
334 }
335
336 /**
337 * メールスレッドを親から順のリストとして返します。
338 *
339 * @return メールリスト
340 */
341 public List<Mail> asList() {
342 List<Mail> list = new ArrayList<Mail>(getCount());
343 addToList(list, this);
344 return list;
345 }
346
347 private void addToList(List<Mail> list, Mail mail) {
348 list.add(mail);
349 if (mail.next != null) {
350 addToList(list, mail.next);
351 }
352 }
353
354 /**
355 * メールスレッドを子から順のリストとして返します。
356 *
357 * @return メールリスト
358 */
359 public List<Mail> asListDesc() {
360 List<Mail> list = asList();
361 Collections.reverse(list);
362 return list;
363 }
364
365 /**
366 * 末端に位置するサブメールを返します。サブメールが設定されていない場合は自身が返ります。
367 *
368 * @return 末端のメール
369 */
370 public Mail getTail() {
371 Mail mail = this;
372 while (mail.getNext() != null) {
373 mail = mail.getNext();
374 }
375 return mail;
376 }
377
378 /**
379 * <code>user</code> にとっての通信相手を返します。<code>user</code> はこのメールの宛先
380 * または差出人に含まれている必要があります。
381 *
382 * @param user ユーザ
383 * @require ${this.to} not null.
384 * @require ${this.from} not null.
385 * @return 通信相手
386 */
387 public SimpleUser other(User user) {
388 if (user.identify(to)) {
389 return from;
390 } else if (user.identify(from)) {
391 return to;
392 } else {
393 throw new IllegalArgumentException("The user '" + user
394 + "' is neither TO or FROM.");
395 }
396 }
397
398 /**
399 * ${this.toId} が設定されていれば、${this.to} に値を設定します。
400 */
401 public void retrieveTo() {
402 if (this.toId != null) {
403 this.to = simpleUserTable.find(this.toId);
404 }
405 }
406
407 /**
408 * 宛先ユーザのメールアドレスを復元します。${this.to} が復元されていなければ復元します。
409 */
410 public void retrieveToMailAddr() {
411 if (to == null) {
412 retrieveTo();
413 }
414 this.toMailAddr = accountTable.find(to.getId()).getMailAddr();
415 }
416
417 /**
418 * ${this.fromId} が設定されていれば、${this.from} に値を設定します。
419 */
420 public void retrieveFrom() {
421 if (this.fromId != null) {
422 this.from = simpleUserTable.find(this.fromId);
423 }
424 }
425
426 public Integer getId() {
427 return id;
428 }
429
430 public void setId(Integer id) {
431 this.id = id;
432 }
433
434 public String getCode() {
435 return code;
436 }
437
438 public void setCode(String code) {
439 this.code = code;
440 }
441
442 public Integer getToId() {
443 return toId;
444 }
445
446 public void setToId(Integer toId) {
447 this.toId = toId;
448 }
449
450 public Integer getFromId() {
451 return fromId;
452 }
453
454 public void setFromId(Integer fromId) {
455 this.fromId = fromId;
456 }
457
458 public Date getSentDate() {
459 if (sentDate == null) {
460 return null;
461 } else {
462 return (Date) sentDate.clone();
463 }
464 }
465
466 public void setSentDate(Date sentDate) {
467 if (sentDate == null) {
468 this.sentDate = null;
469 } else {
470 this.sentDate = (Date) sentDate.clone();
471 }
472 }
473
474 public String getSubject() {
475 return subject;
476 }
477
478 public void setSubject(String subject) {
479 this.subject = subject;
480 }
481
482 public String getBody() {
483 return body;
484 }
485
486 public void setBody(String body) {
487 this.body = body;
488 }
489
490 public boolean isRead() {
491 return read;
492 }
493
494 public void setRead(boolean read) {
495 this.read = read;
496 }
497
498 public SimpleUser getTo() {
499 return to;
500 }
501
502 public void setTo(SimpleUser to) {
503 this.to = to;
504 }
505
506 public SimpleUser getFrom() {
507 return from;
508 }
509
510 public void setFrom(SimpleUser from) {
511 this.from = from;
512 }
513
514 public String getToMailAddr() {
515 return toMailAddr;
516 }
517
518 public void setToMailAddr(String toMailAddr) {
519 this.toMailAddr = toMailAddr;
520 }
521
522 public Mail getNext() {
523 return next;
524 }
525
526 public void setNext(Mail next) {
527 this.next = next;
528 }
529
530 public void setMailDao(MailDao mailDao) {
531 this.mailDao = mailDao;
532 }
533
534 public void setSimpleUserTable(SimpleUserTable simpleUserTable) {
535 this.simpleUserTable = simpleUserTable;
536 }
537
538 public void setAccountTable(AccountTable accountTable) {
539 this.accountTable = accountTable;
540 }
541 }