KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > james > mailrepository > MBoxMailRepository


1 /***********************************************************************
2  * Copyright (c) 2000-2004 The Apache Software Foundation. *
3  * All rights reserved. *
4  * ------------------------------------------------------------------- *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you *
6  * may not use this file except in compliance with the License. You *
7  * may obtain a copy of the License at: *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
14  * implied. See the License for the specific language governing *
15  * permissions and limitations under the License. *
16  ***********************************************************************/

17
18 /* TODO:
19  *
20  * 1. Currently, iterating through the message collection does not
21  * preserve the order in the file. Change this with some form of
22  * OrderedMap. There is a suitable class in Jakarta Commons
23  * Collections.
24  *
25  * 2. Optimize the remove operation.
26  *
27  * 3. Don't load entire message into memory. This would mean computing
28  * the hash during I/O streaming, rather than loading entire message
29  * into memory, and using a MimeMessageWrapper with a suitable data
30  * source. As a strawman, the interface to MessageAction would
31  * carry the hash, along with a size-limited stream providing the
32  * message body.
33  *
34  * 4. Decide what to do when there are IDENTICAL messages in the file.
35  * Right now only the last one will ever be processed, due to key
36  * collissions.
37  *
38  * 5. isComplete() - DONE.
39  *
40  * 6. Buffered I/O. - Partially done, and optional.
41  *
42  */

43
44
45 package org.apache.james.mailrepository;
46
47 import org.apache.avalon.framework.activity.Initializable;
48 import org.apache.avalon.framework.component.Component;
49 import org.apache.avalon.framework.component.ComponentException;
50 import org.apache.avalon.framework.component.ComponentManager;
51 import org.apache.avalon.framework.component.Composable;
52 import org.apache.avalon.framework.configuration.Configurable;
53 import org.apache.avalon.framework.configuration.Configuration;
54 import org.apache.avalon.framework.configuration.ConfigurationException;
55 import org.apache.avalon.framework.context.Context;
56 import org.apache.avalon.framework.context.ContextException;
57 import org.apache.avalon.framework.context.Contextualizable;
58 import org.apache.avalon.framework.logger.AbstractLogEnabled;
59 import org.apache.james.core.MailImpl;
60 import org.apache.james.services.MailRepository;
61 import org.apache.oro.text.regex.MalformedPatternException;
62 import org.apache.oro.text.regex.Perl5Compiler;
63 import org.apache.oro.text.regex.Pattern;
64 import org.apache.oro.text.regex.Perl5Matcher;
65
66 import javax.mail.MessagingException JavaDoc;
67 import javax.mail.Session JavaDoc;
68 import javax.mail.internet.MimeMessage JavaDoc;
69 import javax.mail.internet.InternetAddress JavaDoc;
70 import java.util.*;
71 import java.io.*;
72 import java.security.NoSuchAlgorithmException JavaDoc;
73 import java.security.MessageDigest JavaDoc;
74 import java.text.SimpleDateFormat JavaDoc;
75 import java.lang.reflect.Array JavaDoc;
76
77 /**
78  * Implementation of a MailRepository using UNIX mbox files.
79  *
80  * <p>Requires a configuration element in the .conf.xml file of the form:
81  * <br>&lt;repository destinationURL="mbox://&lt;directory&gt;"
82  * <br> type="MAIL"
83  * <br>&lt;/directory&gt; is where the individual mbox files are read from/written to
84  * <br>Type can ONLY be MAIL (SPOOL is NOT supported)
85  *
86  * <p>Requires a logger called MailRepository.
87  *
88  * <p> Implementation notes:
89  * <p>
90  * This class keeps an internal store of the mbox file
91  * When the internal mbox file is updated (added/deleted)
92  * then the file will be re-read from disk and then written back.
93  * This is a bit inefficent but means that the file on disk
94  * should be correct.
95  * <p>
96  * The mbox store is mainly meant to be used as a one-way street.
97  * Storing new emails is very fast (append to file) whereas reading them (via POP3) is
98  * slower (read from disk and parse).
99  * Therefore this implementation is best suited to people who wish to use the mbox format
100  * for taking data out of James and into something else (IMAP server or mail list displayer)
101  *
102  * @version CVS $Revision: 1.1.2.5 $
103  */

104
105
106 public class MBoxMailRepository
107         extends AbstractLogEnabled
108             implements MailRepository, Component, Contextualizable, Composable, Configurable, Initializable {
109
110
111     static final SimpleDateFormat JavaDoc dy = new SimpleDateFormat JavaDoc("EE MMM dd HH:mm:ss yyyy", Locale.US);
112     static final String JavaDoc LOCKEXT = ".lock";
113     static final String JavaDoc WORKEXT = ".work";
114     static final int LOCKSLEEPDELAY = 2000; // 2 second back off in the event of a problem with the lock file
115
static final int MAXSLEEPTIMES = 100; //
116
static final long MLISTPRESIZEFACTOR = 10 * 1024; // The hash table will be loaded with a initial capacity of filelength/MLISTPRESIZEFACTOR
117
static final long DEFAULTMLISTCAPACITY = 20; // Set up a hashtable to have a meaningful default
118

119     /**
120      * Whether line buffering is turned used.
121      */

122     private static boolean BUFFERING = true;
123
124     /**
125      * Whether 'deep debugging' is turned on.
126      */

127     private static final boolean DEEP_DEBUG = true;
128
129     /**
130      * The Avalon context used by the instance
131      */

132     private Context context;
133
134     /**
135      * The internal list of the emails
136      * The key is an adapted MD5 checksum of the mail
137      */

138     private Hashtable mList = null;
139     /**
140      * The filename to read & write the mbox from/to
141      */

142     private String JavaDoc mboxFile;
143
144     /**
145      * A callback used when a message is read from the mbox file
146      */

147     public interface MessageAction {
148         public boolean isComplete(); // *** Not valid until AFTER each call to messageAction(...)!
149
public MimeMessage JavaDoc messageAction(String JavaDoc messageSeparator, String JavaDoc bodyText, long messageStart);
150     }
151
152
153     /**
154      * Convert a MimeMessage into raw text
155      * @param mc The mime message to convert
156      * @return A string representation of the mime message
157      * @throws IOException
158      * @throws MessagingException
159      */

160     private String JavaDoc getRawMessage(MimeMessage JavaDoc mc) throws IOException, MessagingException JavaDoc {
161
162         ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
163         mc.writeTo(rawMessage);
164         return rawMessage.toString();
165     }
166
167     /**
168      * Parse a text block as an email and convert it into a mime message
169      * @param emailBody The headers and body of an email. This will be parsed into a mime message and stored
170      */

171     private MimeMessage JavaDoc convertTextToMimeMessage(String JavaDoc emailBody) {
172         //this.emailBody = emailBody;
173
MimeMessage JavaDoc mimeMessage = null;
174         // Parse the mime message as we have the full message now (in string format)
175
ByteArrayInputStream mb = new ByteArrayInputStream(emailBody.getBytes());
176         Properties props = System.getProperties();
177         Session JavaDoc session = Session.getDefaultInstance(props);
178         String JavaDoc toAddr = null;
179         try {
180             mimeMessage = new MimeMessage JavaDoc(session, mb);
181
182
183         } catch (MessagingException JavaDoc e) {
184             getLogger().error("Unable to parse mime message!", e);
185         }
186
187         if (mimeMessage == null && getLogger().isDebugEnabled()) {
188             StringBuffer JavaDoc logBuffer =
189                     new StringBuffer JavaDoc(128)
190                     .append(this.getClass().getName())
191                     .append(" Mime message is null");
192             getLogger().debug(logBuffer.toString());
193         }
194
195         /*
196         try {
197             // Attempt to read the TO field and see if it errors
198             toAddr = mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO).toString();
199         } catch (Exception e) {
200             // It has errored, so time for plan B
201             // use the from field I suppose
202             try {
203                 mimeMessage.setRecipients(javax.mail.Message.RecipientType.TO, mimeMessage.getFrom());
204                 if (getLogger().isDebugEnabled()) {
205                     StringBuffer logBuffer =
206                             new StringBuffer(128)
207                             .append(this.getClass().getName())
208                             .append(" Patching To: field for message ")
209                             .append(" with From: field");
210                     getLogger().debug(logBuffer.toString());
211                 }
212             } catch (MessagingException e1) {
213                 getLogger().error("Unable to set to: field to from: field", e);
214             }
215         } */

216         return mimeMessage;
217     }
218
219     /**
220      * Generate a hex representation of an MD5 checksum on the emailbody
221      * @param emailBody
222      * @return A hex representation of the text
223      * @throws NoSuchAlgorithmException
224      */

225     private String JavaDoc generateKeyValue(String JavaDoc emailBody) throws NoSuchAlgorithmException JavaDoc {
226         // MD5 the email body for a reilable (ha ha) key
227
byte[] digArray = MessageDigest.getInstance("MD5").digest(emailBody.getBytes());
228         StringBuffer JavaDoc digest = new StringBuffer JavaDoc();
229         for (int i = 0; i < digArray.length; i++) {
230             digest.append(Integer.toString(digArray[i], Character.MAX_RADIX).toUpperCase());
231         }
232         return digest.toString();
233     }
234
235     /**
236      * Parse the mbox file.
237      * @param ins The random access file to load. Note that the file may or may not start at offset 0 in the file
238      * @param messAct The action to take when a message is found
239      */

240     private MimeMessage JavaDoc parseMboxFile(RandomAccessFile ins, MessageAction messAct) {
241         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
242             StringBuffer JavaDoc logBuffer =
243                     new StringBuffer JavaDoc(128)
244                     .append(this.getClass().getName())
245                     .append(" Start parsing ")
246                     .append(mboxFile);
247
248             getLogger().debug(logBuffer.toString());
249         }
250         try {
251
252             Perl5Compiler sepMatchCompiler = new Perl5Compiler();
253             Pattern sepMatchPattern = sepMatchCompiler.compile("^From (.*) (.*):(.*):(.*)$");
254             Perl5Matcher sepMatch = new Perl5Matcher();
255
256             int c;
257             boolean inMessage = false;
258             StringBuffer JavaDoc messageBuffer = new StringBuffer JavaDoc();
259             String JavaDoc previousMessageSeparator = null;
260             boolean foundSep = false;
261
262             long prevMessageStart = ins.getFilePointer();
263             if (BUFFERING) {
264             String JavaDoc line = null;
265             while ((line = ins.readLine()) != null) {
266                 foundSep = sepMatch.contains(line + "\n", sepMatchPattern);
267
268                 if (foundSep && inMessage) {
269 // if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
270
// getLogger().debug(this.getClass().getName() + " Invoking " + messAct.getClass() + " at " + prevMessageStart);
271
// }
272
MimeMessage JavaDoc endResult = messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart);
273                     if (messAct.isComplete()) {
274                         // I've got what I want so just exit
275
return endResult;
276                     }
277                     previousMessageSeparator = line;
278                     prevMessageStart = ins.getFilePointer() - line.length();
279                     messageBuffer = new StringBuffer JavaDoc();
280                     inMessage = true;
281                 }
282                 // Only done at the start (first header)
283
if (foundSep && !inMessage) {
284                     previousMessageSeparator = line.toString();
285                     inMessage = true;
286                 }
287                 if (!foundSep && inMessage) {
288                     messageBuffer.append(line).append("\n");
289                 }
290             }
291             } else {
292             StringBuffer JavaDoc line = new StringBuffer JavaDoc();
293             while ((c = ins.read()) != -1) {
294                 if (c == 10) {
295                     foundSep = sepMatch.contains(line.toString(), sepMatchPattern);
296                     if (foundSep && inMessage) {
297 // if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
298
// getLogger().debug(this.getClass().getName() + " Invoking " + messAct.getClass() + " at " + prevMessageStart);
299
// }
300
MimeMessage JavaDoc endResult = messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart);
301                         if (messAct.isComplete()) {
302                             // I've got what I want so just exit
303
return endResult;
304                         }
305                         previousMessageSeparator = line.toString();
306                         prevMessageStart = ins.getFilePointer() - line.length();
307                         messageBuffer = new StringBuffer JavaDoc();
308                         inMessage = true;
309                     }
310                     // Only done at the start (first header)
311
if (foundSep && inMessage == false) {
312                         previousMessageSeparator = line.toString();
313                         inMessage = true;
314                     }
315                     if (!foundSep) {
316                         messageBuffer.append(line).append((char) c);
317                     }
318                     line = new StringBuffer JavaDoc(); // Reset buffer
319
} else {
320                     line.append((char) c);
321                 }
322             }
323             }
324
325             if (messageBuffer.length() != 0) {
326                 // process last message
327
return messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart);
328             }
329         } catch (IOException ioEx) {
330             getLogger().error("Unable to write file (General I/O problem) " + mboxFile, ioEx);
331         } catch (MalformedPatternException e) {
332             getLogger().error("Bad regex passed " + mboxFile, e);
333         } finally {
334             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
335                 StringBuffer JavaDoc logBuffer =
336                         new StringBuffer JavaDoc(128)
337                         .append(this.getClass().getName())
338                         .append(" Finished parsing ")
339                         .append(mboxFile);
340
341                 getLogger().debug(logBuffer.toString());
342             }
343         }
344         return null;
345     }
346
347     /**
348      * Find a given message
349      * This method will first use selectMessage(key) to see if the key/offset combination allows us to skip
350      * parts of the file and only load the message we are interested in
351      *
352      * @param key The key of the message to find
353      */

354     private MimeMessage JavaDoc findMessage(String JavaDoc key) {
355         MimeMessage JavaDoc foundMessage = null;
356         final String JavaDoc keyValue = key;
357
358         // See if we can get the message by using the cache position first
359
foundMessage = selectMessage(key);
360         if (foundMessage == null) {
361             // If the message is not found something has changed from
362
// the cache. The cache may have been invalidated by
363
// another method, or the file may have been replaced from
364
// underneath us. Reload the cache, and try again.
365
mList = null;
366             loadKeys();
367             foundMessage = selectMessage(key);
368         }
369         return foundMessage;
370     }
371
372     /**
373      * Quickly find a message by using the stored message offsets
374      * @param key The key of the message to find
375      */

376     private MimeMessage JavaDoc selectMessage(final String JavaDoc key) {
377         MimeMessage JavaDoc foundMessage = null;
378         // Can we find the key first
379
if (mList == null || !mList.containsKey(key)) {
380             // Not initiailised so no point looking
381
if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
382                 StringBuffer JavaDoc logBuffer =
383                         new StringBuffer JavaDoc(128)
384                         .append(this.getClass().getName())
385                         .append(" mList - key not found ")
386                         .append(mboxFile);
387
388                 getLogger().debug(logBuffer.toString());
389             }
390             return foundMessage;
391         }
392         long messageStart = ((Long JavaDoc) mList.get(key)).longValue();
393         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
394             StringBuffer JavaDoc logBuffer =
395                     new StringBuffer JavaDoc(128)
396                     .append(this.getClass().getName())
397                     .append(" Load message starting at offset ")
398                     .append(messageStart)
399                     .append(" from file ")
400                     .append(mboxFile);
401
402             getLogger().debug(logBuffer.toString());
403         }
404         // Now try and find the position in the file
405
RandomAccessFile ins = null;
406         try {
407             ins = new RandomAccessFile(mboxFile, "r");
408             if (messageStart != 0) {
409                 ins.seek(messageStart - 1);
410             }
411             MessageAction op = new MessageAction() {
412                 public boolean isComplete() { return true; }
413                 public MimeMessage JavaDoc messageAction(String JavaDoc messageSeparator, String JavaDoc bodyText, long messageStart) {
414                     try {
415                         if (key.equals(generateKeyValue(bodyText))) {
416                             getLogger().debug(this.getClass().getName() + " Located message. Returning MIME message");
417                             return convertTextToMimeMessage(bodyText);
418                         }
419                     } catch (NoSuchAlgorithmException JavaDoc e) {
420                         getLogger().error("MD5 not supported! ",e);
421                     }
422                     return null;
423                 }
424             };
425             foundMessage = this.parseMboxFile(ins, op);
426         } catch (FileNotFoundException e) {
427             getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e);
428         } catch (IOException e) {
429             getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e);
430         } finally {
431             if (foundMessage == null) {
432                 if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
433                     StringBuffer JavaDoc logBuffer =
434                             new StringBuffer JavaDoc(128)
435                             .append(this.getClass().getName())
436                             .append(" select - message not found ")
437                             .append(mboxFile);
438
439                     getLogger().debug(logBuffer.toString());
440                 }
441             }
442             if (ins != null) try { ins.close(); } catch (IOException e) { getLogger().error("Unable to close file (General I/O problem) " + mboxFile, e); }
443             return foundMessage;
444         }
445     }
446
447     /**
448      * Load the message keys and file pointer offsets from disk
449      */

450     private synchronized void loadKeys() {
451         if (mList!=null) {
452             return;
453         }
454         RandomAccessFile ins = null;
455         try {
456             ins = new RandomAccessFile(mboxFile, "r");
457             long initialCapacity = (ins.length() > MLISTPRESIZEFACTOR ? ins.length() /MLISTPRESIZEFACTOR : 0);
458             if (initialCapacity < DEFAULTMLISTCAPACITY ) {
459                 initialCapacity = DEFAULTMLISTCAPACITY;
460             }
461             if (initialCapacity > Integer.MAX_VALUE) {
462                 initialCapacity = Integer.MAX_VALUE - 1;
463             }
464             this.mList = new Hashtable((int)initialCapacity);
465             this.parseMboxFile(ins, new MessageAction() {
466                 public boolean isComplete() { return false; }
467                 public MimeMessage JavaDoc messageAction(String JavaDoc messageSeparator, String JavaDoc bodyText, long messageStart) {
468                     try {
469                         String JavaDoc key = generateKeyValue(bodyText);
470                         mList.put(key, new Long JavaDoc(messageStart));
471                         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
472                             getLogger().debug(this.getClass().getName() + " Key " + key + " at " + messageStart);
473                         }
474                         
475                     } catch (NoSuchAlgorithmException JavaDoc e) {
476                         getLogger().error("MD5 not supported! ",e);
477                     }
478                     return null;
479                 }
480             });
481             //System.out.println("Done Load keys!");
482
} catch (FileNotFoundException e) {
483             getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e);
484         } catch (IOException e) {
485             getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e);
486         } finally {
487             if (ins != null) try { ins.close(); } catch (IOException e) { getLogger().error("Unable to close file (General I/O problem) " + mboxFile, e); }
488         }
489     }
490
491
492     /**
493      * @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
494      */

495     public void contextualize(final Context context)
496             throws ContextException {
497         this.context = context;
498     }
499
500     /**
501      * Store the given email in the current mbox file
502      * @param mc The mail to store
503      */

504     public void store(MailImpl mc) {
505
506         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
507             StringBuffer JavaDoc logBuffer =
508                     new StringBuffer JavaDoc(128)
509                     .append(this.getClass().getName())
510                     .append(" Will store message to file ")
511                     .append(mboxFile);
512
513             getLogger().debug(logBuffer.toString());
514         }
515         this.mList = null;
516         // Now make up the from header
517
String JavaDoc fromHeader = null;
518         String JavaDoc message = null;
519         try {
520             message = getRawMessage(mc.getMessage());
521             fromHeader = "From " + ((InternetAddress JavaDoc)mc.getMessage().getFrom()[0]).getAddress() + " " + dy.format(Calendar.getInstance().getTime());
522         } catch (IOException e) {
523             getLogger().error("Unable to parse mime message for " + mboxFile, e);
524         } catch (MessagingException JavaDoc e) {
525             getLogger().error("Unable to parse mime message for " + mboxFile, e);
526         }
527         // And save only the new stuff to disk
528
RandomAccessFile saveFile = null;
529         try {
530             saveFile = new RandomAccessFile(mboxFile, "rw");
531             saveFile.seek(saveFile.length()); // Move to the end
532
saveFile.writeBytes((fromHeader + "\n"));
533             saveFile.writeBytes((message + "\n"));
534             saveFile.close();
535
536         } catch (FileNotFoundException e) {
537             getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e);
538         } catch (IOException e) {
539             getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e);
540         }
541     }
542
543     /**
544      * Return the list of the current messages' keys
545      * @return A list of the keys of the emails currently loaded
546      */

547     public Iterator list() {
548         loadKeys();
549         // find the first message. This is a trick to make sure that if
550
// the file is changed out from under us, we will detect it and
551
// correct for it BEFORE we return the iterator.
552
findMessage((String JavaDoc) mList.keySet().iterator().next());
553         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
554             StringBuffer JavaDoc logBuffer =
555                     new StringBuffer JavaDoc(128)
556                     .append(this.getClass().getName())
557                     .append(" ")
558                     .append(mList.size())
559                     .append(" keys to be iterated over.");
560
561             getLogger().debug(logBuffer.toString());
562         }
563         return mList.keySet().iterator();
564     }
565
566     /**
567      * Get a message from the backing store (disk)
568      * @param key
569      * @return The mail found from the key. Returns null if the key is not found
570      */

571     public MailImpl retrieve(String JavaDoc key) {
572
573         loadKeys();
574         MailImpl res = null;
575         try {
576             MimeMessage JavaDoc foundMessage = findMessage(key);
577             if (foundMessage == null) {
578                 getLogger().error("found message is null!");
579                 return null;
580             }
581             res = new MailImpl(foundMessage);
582             res.setName(key);
583             if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
584                 StringBuffer JavaDoc logBuffer =
585                         new StringBuffer JavaDoc(128)
586                         .append(this.getClass().getName())
587                         .append(" Retrieving entry for key ")
588                         .append(key);
589
590                 getLogger().debug(logBuffer.toString());
591             }
592         } catch (MessagingException JavaDoc e) {
593             getLogger().error("Unable to parse mime message for " + mboxFile + "\n" + e.getMessage(), e);
594         }
595         return res;
596     }
597
598     /**
599      * Remove an existing message
600      * @param mail
601      */

602     public void remove(MailImpl mail) {
603         // Convert the message into a key
604
Vector delVec = new Vector();
605         delVec.addElement(mail);
606         remove(delVec);
607     }
608
609     /**
610      * Attempt to get a lock on the mbox by creating
611      * the file mboxname.lock
612      * @throws Exception
613      */

614     private void lockMBox() throws Exception JavaDoc {
615         // Create the lock file (if possible)
616
String JavaDoc lockFileName = mboxFile + LOCKEXT;
617         int sleepCount = 0;
618         File mBoxLock = new File(lockFileName);
619         if (!mBoxLock.createNewFile()) {
620             // This is not good, somebody got the lock before me
621
// So wait for a file
622
while (!mBoxLock.createNewFile() && sleepCount < MAXSLEEPTIMES) {
623                 try {
624                     if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
625                         StringBuffer JavaDoc logBuffer =
626                                 new StringBuffer JavaDoc(128)
627                                 .append(this.getClass().getName())
628                                 .append(" Waiting for lock on file ")
629                                 .append(mboxFile);
630
631                         getLogger().debug(logBuffer.toString());
632                     }
633
634                     Thread.sleep(LOCKSLEEPDELAY);
635                     sleepCount++;
636                 } catch (InterruptedException JavaDoc e) {
637                     getLogger().error("File lock wait for " + mboxFile + " interrupted!",e);
638
639                 }
640             }
641             if (sleepCount >= MAXSLEEPTIMES) {
642                 throw new Exception JavaDoc("Unable to get lock on file " + mboxFile);
643             }
644         }
645     }
646
647     /**
648      * Unlock a previously locked mbox file
649      */

650     private void unlockMBox() {
651         // Just delete the MBOX file
652
String JavaDoc lockFileName = mboxFile + LOCKEXT;
653         File mBoxLock = new File(lockFileName);
654         if (!mBoxLock.delete()) {
655             StringBuffer JavaDoc logBuffer =
656                     new StringBuffer JavaDoc(128)
657                     .append(this.getClass().getName())
658                     .append(" Failed to delete lock file ")
659                     .append(lockFileName);
660             getLogger().error(logBuffer.toString());
661         }
662     }
663
664
665
666     /**
667      * Remove a list of messages from disk
668      * The collection is simply a list of mails to delete
669      * @param mails
670      */

671     public void remove(final Collection mails)
672     {
673         if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
674             StringBuffer JavaDoc logBuffer =
675                     new StringBuffer JavaDoc(128)
676                     .append(this.getClass().getName())
677                     .append(" Removing entry for key ")
678                     .append(mails);
679
680             getLogger().debug(logBuffer.toString());
681         }
682         // The plan is as follows:
683
// Attempt to locate the message in the file
684
// by reading through the
685
// once we've done that then seek to the file
686
try {
687             RandomAccessFile ins = new RandomAccessFile(mboxFile, "r"); // The source
688
final RandomAccessFile outputFile = new RandomAccessFile(mboxFile + WORKEXT, "rw"); // The destination
689
parseMboxFile(ins, new MessageAction() {
690                 public boolean isComplete() { return false; }
691                 public MimeMessage JavaDoc messageAction(String JavaDoc messageSeparator, String JavaDoc bodyText, long messageStart) {
692                     // Write out the messages as we go, until we reach the key we want
693
try {
694                         String JavaDoc currentKey=generateKeyValue(bodyText);
695                         boolean foundKey=false;
696                         Iterator mailList = mails.iterator();
697                         String JavaDoc key;
698                         while (mailList.hasNext()) {
699                             // Attempt to find the current key in the array
700
key = ((MailImpl)mailList.next()).getName();
701                             if (key.equals(currentKey)) {
702                                 // Don't write the message to disk
703
foundKey = true;
704                                 break;
705                             }
706                         }
707                         if (foundKey == false)
708                         {
709                             // We didn't find the key in the array so we will keep it
710
outputFile.writeBytes(messageSeparator + "\n");
711                             outputFile.writeBytes(bodyText);
712
713                         }
714                     } catch (NoSuchAlgorithmException JavaDoc e) {
715                         getLogger().error("MD5 not supported! ",e);
716                     } catch (IOException e) {
717                         getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e);
718                     }
719                     return null;
720                 }
721             });
722             ins.close();
723             outputFile.close();
724             // Delete the old mbox file
725
File mbox = new File(mboxFile);
726             mbox.delete();
727             // And rename the lock file to be the new mbox
728
mbox = new File(mboxFile + WORKEXT);
729             if (!mbox.renameTo(new File(mboxFile)))
730             {
731                  System.out.println("Failed to rename file!");
732             }
733
734             // Now delete the keys in mails from the main hash
735
Iterator mailList = mails.iterator();
736             String JavaDoc key;
737             while (mailList.hasNext()) {
738                 // Attempt to find the current key in the array
739
key = ((MailImpl)mailList.next()).getName();
740                 mList.remove(key);
741             }
742
743
744         } catch (FileNotFoundException e) {
745             getLogger().error("Unable to save(open) file (File not found) " + mboxFile, e);
746         } catch (IOException e) {
747             getLogger().error("Unable to write file (General I/O problem) " + mboxFile, e);
748         }
749     }
750
751     /**
752      * Remove a mail from the mbox file
753      * @param key The key of the mail to delete
754      */

755     public void remove(String JavaDoc key) {
756         loadKeys();
757         try {
758             lockMBox();
759         } catch (Exception JavaDoc e) {
760             getLogger().error("Lock failed!",e);
761             return; // No lock, so exit
762
}
763         ArrayList keys = new ArrayList();
764         keys.add(key);
765
766         this.remove(keys);
767         unlockMBox();
768     }
769
770     /**
771      * Not implemented
772      * @param key
773      * @return
774      */

775     public boolean lock(String JavaDoc key) {
776         return false;
777     }
778
779     /**
780      * Not implemented
781      * @param key
782      * @return
783      */

784     public boolean unlock(String JavaDoc key) {
785         return false;
786     }
787
788
789     public void compose(ComponentManager componentManager) throws ComponentException {
790     }
791
792     /**
793      * Configure the component
794      * @param conf
795      * @throws ConfigurationException
796      */

797     public void configure(Configuration conf) throws ConfigurationException {
798         String JavaDoc destination;
799         this.mList = null;
800         BUFFERING = conf.getAttributeAsBoolean("BUFFERING", true);
801         destination = conf.getAttribute("destinationURL");
802         if (destination.charAt(destination.length() - 1) == '/') {
803             // Remove the trailing / as well as the protocol marker
804
mboxFile = destination.substring("mbox://".length(), destination.lastIndexOf("/"));
805         } else {
806             mboxFile = destination.substring("mbox://".length());
807         }
808
809         if (getLogger().isDebugEnabled()) {
810             getLogger().debug("MBoxMailRepository.destinationURL: " + destination);
811         }
812
813         String JavaDoc checkType = conf.getAttribute("type");
814         if (!(checkType.equals("MAIL") || checkType.equals("SPOOL"))) {
815             String JavaDoc exceptionString = "Attempt to configure MboxMailRepository as " + checkType;
816             if (getLogger().isWarnEnabled()) {
817                 getLogger().warn(exceptionString);
818             }
819             throw new ConfigurationException(exceptionString);
820         }
821     }
822
823
824     /**
825      * Initialise the component
826      * @throws Exception
827      */

828     public void initialize() throws Exception JavaDoc {
829     }
830
831
832     public static void main(String JavaDoc[] args) {
833         // Test invocation
834
MBoxMailRepository mbx = new MBoxMailRepository();
835         mbx.mboxFile = "C:\\java\\test\\1998-05.txt";
836         Iterator mList = mbx.list();
837         while (mList.hasNext()) {
838             String JavaDoc key = (String JavaDoc) mList.next();
839             //System.out.println("key=" + key);
840
/*MailImpl mi = mbx.retrieve(key);
841             try
842             {
843                 System.out.println("Subject : " + (mi.getMessage()).getSubject());
844             }
845             catch (MessagingException e)
846             {
847                 e.printStackTrace(); //To change body of catch statement use Options | File Templates.
848             } */

849
850         }
851
852
853 /* MailImpl mi = mbx.retrieve("ffffffb4ffffffe2f59fffffff291dffffffde4366243ffffff971d1f24");
854         try {
855             System.out.println("Subject : " + (mi.getMessage()).getSubject());
856         } catch (MessagingException e) {
857             e.printStackTrace(); //To change body of catch statement use Options | File Templates.
858         }
859         mbx.remove("ffffffb4ffffffe2f59fffffff291dffffffde4366243ffffff971d1f24");*/

860     }
861 }
862
Popular Tags