1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.james.mailrepository.javamail;
21
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.SortedMap;
32 import java.util.TreeMap;
33
34 import javax.mail.Flags;
35 import javax.mail.Folder;
36 import javax.mail.Message;
37 import javax.mail.MessagingException;
38 import javax.mail.Flags.Flag;
39 import javax.mail.internet.MimeMessage;
40
41 import org.apache.james.core.MailImpl;
42 import org.apache.james.util.stream.CRLFOutputStream;
43 import org.apache.mailet.Mail;
44
45
46
47
48
49
50
51
52
53 public class HashJavamailStoreMailRepository extends
54 AbstractJavamailStoreMailRepository {
55
56
57
58
59
60 protected KeyToMsgMap keyToMsgMap = null;
61
62
63 private boolean getMessageCountOnClosed =true;
64
65
66 protected int getMessageCount() throws MessagingException {
67 try {
68 getFolderGateKeeper().use();
69 int n=-1;
70 if (getMessageCountOnClosed) {
71 n=getFolderGateKeeper().getFolder().getMessageCount();
72 if (n==-1) {
73 getMessageCountOnClosed=false;
74 }
75 }
76 if (!getMessageCountOnClosed) {
77 n=getFolderGateKeeper().getOpenFolder().getMessageCount();
78 }
79 return n;
80 } finally {
81 getFolderGateKeeper().free();
82 }
83 }
84
85
86
87
88
89
90
91
92 public synchronized void store(Mail mc) throws MessagingException {
93
94 final String key = mc.getName();
95 boolean wasLocked = true;
96 log.debug("Store: (hash) " + key);
97 if (!mc.getMessage().isSet(Flag.RECENT)) {
98 log.debug("Message didn't have RECENT flag");
99 mc.getMessage().setFlag(Flag.RECENT,true);
100 }
101
102 boolean use=false;
103 try {
104
105
106
107 synchronized (this) {
108 wasLocked = getLock().isLocked(key);
109 if (!wasLocked) {
110
111 lock(key);
112 }
113 }
114
115
116
117
118
119
120 int countBefore = getMessageCount();
121
122 getFolderGateKeeper().use();
123 use=true;
124
125 if (getKeyToMsgMap().contains(key)) {
126 log.debug("store is a update");
127 Message mm = getMessageFromInbox(key);
128 if (mm != null) {
129 countBefore--;
130 mm.setFlag(Flags.Flag.DELETED, true);
131 mc.getMessage().setFlag(Flags.Flag.RECENT, false);
132 }
133 getKeyToMsgMap().removeByKey(key, true);
134 }
135 getFolderGateKeeper().getFolder().appendMessages(new Message[] { mc.getMessage() });
136 use=false;
137 getFolderGateKeeper().free();
138
139
140 int no = -1;
141 int count=getMessageCount();
142 if (count - countBefore == 1) {
143 no = count;
144 log.debug("Assigned message number "+ count);
145 } else {
146 log.debug("count - countBefore = "+ (count - countBefore ));
147 }
148
149 getKeyToMsgMap().put(mc.getMessage(), mc.getName(), no);
150 } catch (MessagingException e) {
151 log.error("Exception in HashJavamailStore: ", e);
152 throw e;
153 } finally {
154 if (!wasLocked) {
155
156 unlock(key);
157 synchronized (this) {
158 notify();
159 }
160 }
161 if (use) {
162 getFolderGateKeeper().free();
163 }
164 log.debug("closed.");
165 }
166 log.debug("store finished");
167 }
168
169
170
171
172
173
174 public Iterator list() throws MessagingException {
175 try {
176 getFolderGateKeeper().use();
177 log.debug("list()");
178 rehash(null);
179 final String[] keys = getKeyToMsgMap().getKeys();
180 final Iterator it = Arrays.asList(keys).iterator();
181 return it;
182 } catch (MessagingException e) {
183 throw e;
184 } finally {
185 getFolderGateKeeper().free();
186 }
187
188 }
189
190
191
192
193
194
195 public Mail retrieve(String key) throws MessagingException {
196 log.debug("retrieve: " + key);
197 Mail m = null;
198 try {
199 getFolderGateKeeper().use();
200 MimeMessage mm = getMessageFromInbox(key);
201 if (mm != null) {
202 m = new MailImpl();
203 m.setMessage(mm);
204 m.setName(key);
205 } else {
206 log.debug("could not retrieve a MimeMessage from folder");
207 }
208
209 } catch (MessagingException e) {
210 throw e;
211 } finally {
212 getFolderGateKeeper().free();
213 }
214 return m;
215 }
216
217
218
219
220
221
222
223
224 public synchronized void remove(String key) throws MessagingException {
225 log.debug("HashJavamailStore remove key:" + key);
226 if (lock(key)) {
227 try {
228 getFolderGateKeeper().use();
229 Message mm = getMessageFromInbox(key);
230 if (mm != null) {
231
232 mm.setFlag(Flags.Flag.DELETED, true);
233 }
234 getKeyToMsgMap().removeByKey(key, true);
235 } catch (MessagingException e) {
236 throw e;
237 } finally {
238 unlock(key);
239 getFolderGateKeeper().free();
240 }
241 } else {
242 log.debug("could not optain lock");
243 throw new MessagingException("could not optain lock for remove");
244 }
245 }
246
247
248
249
250
251
252
253
254
255
256
257 protected MimeMessage rehash(String filterkey) throws MessagingException {
258 if (DEEP_DEBUG)
259 log.debug("doing rehash");
260 String[] keysBefore = getKeyToMsgMap().getKeys();
261 MimeMessage mm = null;
262 Message[] msgs = getFolderGateKeeper().getOpenFolder().getMessages();
263 String[] keys = new String[msgs.length];
264 for (int i = 0; i < msgs.length; i++) {
265 Message message = msgs[i];
266 MsgObj mo = getKeyToMsgMap()
267 .put((MimeMessage) message, null, i + 1);
268 keys[i] = mo.key;
269 if (DEEP_DEBUG)
270 log.debug("No " + mo.no + " key:" + mo.key);
271 if (mo.key.equals(filterkey)) {
272 if (DEEP_DEBUG)
273 log.debug("Found message!");
274 mm = (MimeMessage) message;
275 }
276 }
277 getKeyToMsgMap().retainAllListedAndAddedByKeys(keysBefore, keys);
278 return mm;
279 }
280
281
282
283
284
285
286
287
288
289
290
291 protected MimeMessage getMessageFromInbox(String key)
292 throws MessagingException {
293 MsgObj mo = getKeyToMsgMap().getByKey(key);
294 log.debug("getMessageFromInbox: Looking for hash "+mo.hash);
295 if (mo == null) {
296 log.debug("Key not found");
297 return null;
298 }
299 MimeMessage mm = null;
300 if (cacheMessages && mo.message != null) {
301
302 mm = mo.message;
303 } else {
304 try {
305 getFolderGateKeeper().use();
306 Object hash = null;
307 if (mo.no >= 0) {
308 try {
309 mm = (MimeMessage) getFolderGateKeeper().getOpenFolder()
310 .getMessage(mo.no);
311 hash = calcMessageHash(mm);
312 if (!hash.equals(mo.hash)) {
313 log
314 .debug("Message at guessed position does not match "
315 + mo.no);
316 mm = null;
317 }
318 } catch (IndexOutOfBoundsException e) {
319 log.debug("no Message found at guessed position "
320 + mo.no);
321 }
322 } else {
323 log.debug("cannot guess message number");
324 }
325 if (mm == null) {
326 mm = rehash(mo.key);
327 if (mm == null)
328 log.debug("rehashing was fruitless");
329 }
330 } finally {
331 getFolderGateKeeper().free();
332 }
333 }
334 return mm;
335 }
336
337
338
339
340
341
342 protected KeyToMsgMap getKeyToMsgMap() {
343 if (keyToMsgMap == null) {
344 keyToMsgMap = new KeyToMsgMap();
345 }
346 return keyToMsgMap;
347 }
348
349 public static final class HasherOutputStream extends OutputStream {
350 int hashCode = 1;
351
352 public void write(int b) throws IOException {
353 hashCode = 41 * hashCode + b;
354 }
355
356 public int getHash() {
357 return hashCode;
358 }
359 }
360
361
362
363 protected class KeyToMsgMap {
364 protected SortedMap noToMsgObj;
365
366 protected Map keyToMsgObj;
367
368 protected Map hashToMsgObj;
369
370 protected KeyToMsgMap() {
371 noToMsgObj = new TreeMap();
372 keyToMsgObj = new HashMap();
373 hashToMsgObj = new HashMap();
374 }
375
376
377
378
379
380
381
382 public synchronized boolean contains(String key) {
383 return keyToMsgObj.containsKey(key);
384 }
385
386
387
388
389
390
391
392
393
394
395 public synchronized void retainAllListedAndAddedByKeys(
396 String[] keysBefore, String[] listed) {
397 if (DEEP_DEBUG)
398 log.debug("stat before retain: " + getStat());
399 Set added = new HashSet(keyToMsgObj.keySet());
400 added.removeAll(Arrays.asList(keysBefore));
401
402 Set retain = new HashSet(Arrays.asList(listed));
403 retain.addAll(added);
404
405 Collection remove = new HashSet(keyToMsgObj.keySet());
406 remove.removeAll(retain);
407
408
409 for (Iterator iter = remove.iterator(); iter.hasNext();) {
410 removeByKey((String) iter.next(), false);
411 }
412 if (DEEP_DEBUG)
413 log.debug("stat after retain: " + getStat());
414 }
415
416
417
418
419
420
421 public String getStat() {
422 String s = "keyToMsgObj:" + keyToMsgObj.size() + " hashToMsgObj:"
423 + hashToMsgObj.size() + " noToMsgObj:" + noToMsgObj.size();
424 return s;
425 }
426
427
428
429
430
431
432
433
434
435
436 public synchronized void removeByKey(String key, boolean decrement) {
437 MsgObj mo = getByKey(key);
438 if (mo != null) {
439 keyToMsgObj.remove(mo.key);
440 noToMsgObj.remove(new Integer(mo.no));
441 hashToMsgObj.remove(mo.hash);
442 if (decrement) {
443
444
445
446 MsgObj[] dmos = (MsgObj[]) noToMsgObj.tailMap(
447 new Integer(mo.no)).values().toArray(new MsgObj[0]);
448 for (int i = 0; i < dmos.length; i++) {
449 MsgObj dmo = dmos[i];
450 noToMsgObj.remove(new Integer(dmo.no));
451 dmo.no--;
452 noToMsgObj.put(new Integer(dmo.no), dmo);
453 }
454 }
455 }
456 }
457
458
459
460
461
462
463 public synchronized String[] getKeys() {
464 return (String[]) keyToMsgObj.keySet().toArray(new String[0]);
465 }
466
467
468
469
470
471
472
473 public synchronized MsgObj getByKey(String key) {
474 return (MsgObj) keyToMsgObj.get(key);
475 }
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491 public synchronized MsgObj put(final MimeMessage mm, String key,
492 final int no) throws MessagingException {
493 final Object hash = calcMessageHash(mm);
494 MsgObj mo;
495 if (key != null) {
496 mo = getMsgObj(key);
497 } else {
498 mo = (MsgObj) hashToMsgObj.get(hash);
499 if (mo == null) {
500 key = generateKey(hash.toString());
501 }
502 }
503 if (mo == null) {
504 mo = new MsgObj();
505 keyToMsgObj.put(key, mo);
506 mo.key = key;
507 }
508 if (!hash.equals(mo.hash)) {
509 if (mo.hash != null) {
510 hashToMsgObj.remove(mo.hash);
511 }
512 mo.hash = hash;
513 hashToMsgObj.put(hash, mo);
514 }
515 if (no != mo.no) {
516 if (mo.no > -1) {
517 noToMsgObj.remove(new Integer(mo.no));
518 }
519 mo.no = no;
520 noToMsgObj.put(new Integer(no), mo);
521 }
522 if (cacheMessages) {
523 mo.message = mm;
524 }
525 return mo;
526
527 }
528
529
530
531
532
533
534 public synchronized MsgObj getMsgObj(String key) {
535 return (MsgObj) keyToMsgObj.get(key);
536 }
537
538 }
539
540
541
542
543
544 protected static final class MsgObj {
545 MimeMessage message;
546
547 int no = -1;
548
549 Object hash;
550
551 String key;
552 }
553
554
555
556
557
558
559
560
561
562
563
564 protected static Object calcMessageHash(MimeMessage mm)
565 throws MessagingException {
566 HasherOutputStream hos = new HasherOutputStream();
567 try {
568 mm.writeTo(new CRLFOutputStream(hos));
569 } catch (IOException e) {
570 throw new MessagingException("error while calculating hash", e);
571 }
572
573 Integer i = new Integer(hos.getHash());
574 return i;
575 }
576
577
578
579
580
581
582
583 protected static String generateKey(String hash) {
584 String key = "james-hashed:" + hash + ";" + System.currentTimeMillis()
585 + ";" + getRandom().nextLong();
586 return key;
587 }
588
589
590
591
592
593 public FolderInterface createAdapter(Folder folder) {
594 return new FolderAdapter(folder);
595 }
596
597 }