001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.mailrepository;
019:
020: import org.apache.avalon.framework.activity.Initializable;
021: import org.apache.avalon.framework.service.ServiceException;
022: import org.apache.avalon.framework.service.ServiceManager;
023: import org.apache.avalon.framework.service.Serviceable;
024: import org.apache.avalon.framework.configuration.Configurable;
025: import org.apache.avalon.framework.configuration.Configuration;
026: import org.apache.avalon.framework.configuration.ConfigurationException;
027: import org.apache.avalon.framework.logger.AbstractLogEnabled;
028: import org.apache.james.core.MailImpl;
029: import org.apache.james.services.MailRepository;
030: import org.apache.mailet.Mail;
031: import org.apache.oro.text.regex.MalformedPatternException;
032: import org.apache.oro.text.regex.Perl5Compiler;
033: import org.apache.oro.text.regex.Pattern;
034: import org.apache.oro.text.regex.Perl5Matcher;
035:
036: import javax.mail.MessagingException;
037: import javax.mail.Session;
038: import javax.mail.internet.MimeMessage;
039:
040: import java.io.ByteArrayInputStream;
041: import java.io.ByteArrayOutputStream;
042: import java.io.File;
043: import java.io.FileNotFoundException;
044: import java.io.IOException;
045: import java.io.RandomAccessFile;
046: import java.security.NoSuchAlgorithmException;
047: import java.security.MessageDigest;
048: import java.text.SimpleDateFormat;
049: import java.util.ArrayList;
050: import java.util.Calendar;
051: import java.util.Collection;
052: import java.util.Hashtable;
053: import java.util.Iterator;
054: import java.util.Locale;
055: import java.util.Properties;
056:
057: /**
058: * Implementation of a MailRepository using UNIX mbox files.
059: *
060: * <p>Requires a configuration element in the .conf.xml file of the form:
061: * <br><repository destinationURL="mbox://<directory>"
062: * <br> type="MAIL"
063: * <br></directory> is where the individual mbox files are read from/written to
064: * <br>Type can ONLY be MAIL (SPOOL is NOT supported)
065: *
066: * <p>Requires a logger called MailRepository.
067: *
068: * <p> Implementation notes:
069: * <p>
070: * This class keeps an internal store of the mbox file
071: * When the internal mbox file is updated (added/deleted)
072: * then the file will be re-read from disk and then written back.
073: * This is a bit inefficent but means that the file on disk
074: * should be correct.
075: * <p>
076: * The mbox store is mainly meant to be used as a one-way street.
077: * Storing new emails is very fast (append to file) whereas reading them (via POP3) is
078: * slower (read from disk and parse).
079: * Therefore this implementation is best suited to people who wish to use the mbox format
080: * for taking data out of James and into something else (IMAP server or mail list displayer)
081: *
082: * @version CVS $Revision: 495537 $
083: */
084:
085: public class MBoxMailRepository extends AbstractLogEnabled implements
086: MailRepository, Serviceable, Configurable, Initializable {
087:
088: static final SimpleDateFormat dy = new SimpleDateFormat(
089: "EE MMM dd HH:mm:ss yyyy", Locale.US);
090: static final String LOCKEXT = ".lock";
091: static final String WORKEXT = ".work";
092: static final int LOCKSLEEPDELAY = 2000; // 2 second back off in the event of a problem with the lock file
093: static final int MAXSLEEPTIMES = 100; //
094: static final long MLISTPRESIZEFACTOR = 10 * 1024; // The hash table will be loaded with a initial capacity of filelength/MLISTPRESIZEFACTOR
095: static final long DEFAULTMLISTCAPACITY = 20; // Set up a hashtable to have a meaningful default
096:
097: /**
098: * Whether line buffering is turned used.
099: */
100: private static boolean BUFFERING = true;
101:
102: /**
103: * Whether 'deep debugging' is turned on.
104: */
105: private static final boolean DEEP_DEBUG = true;
106:
107: /**
108: * The internal list of the emails
109: * The key is an adapted MD5 checksum of the mail
110: */
111: private Hashtable mList = null;
112: /**
113: * The filename to read & write the mbox from/to
114: */
115: private String mboxFile;
116:
117: /**
118: * A callback used when a message is read from the mbox file
119: */
120: public interface MessageAction {
121: public boolean isComplete(); // *** Not valid until AFTER each call to messageAction(...)!
122:
123: public MimeMessage messageAction(String messageSeparator,
124: String bodyText, long messageStart);
125: }
126:
127: /**
128: * Convert a MimeMessage into raw text
129: * @param mc The mime message to convert
130: * @return A string representation of the mime message
131: * @throws IOException
132: * @throws MessagingException
133: */
134: private String getRawMessage(MimeMessage mc) throws IOException,
135: MessagingException {
136:
137: ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
138: mc.writeTo(rawMessage);
139: return rawMessage.toString();
140: }
141:
142: /**
143: * Parse a text block as an email and convert it into a mime message
144: * @param emailBody The headers and body of an email. This will be parsed into a mime message and stored
145: */
146: private MimeMessage convertTextToMimeMessage(String emailBody) {
147: //this.emailBody = emailBody;
148: MimeMessage mimeMessage = null;
149: // Parse the mime message as we have the full message now (in string format)
150: ByteArrayInputStream mb = new ByteArrayInputStream(emailBody
151: .getBytes());
152: Properties props = System.getProperties();
153: Session session = Session.getDefaultInstance(props);
154: try {
155: mimeMessage = new MimeMessage(session, mb);
156:
157: } catch (MessagingException e) {
158: getLogger().error("Unable to parse mime message!", e);
159: }
160:
161: if (mimeMessage == null && getLogger().isDebugEnabled()) {
162: StringBuffer logBuffer = new StringBuffer(128).append(
163: this .getClass().getName()).append(
164: " Mime message is null");
165: getLogger().debug(logBuffer.toString());
166: }
167:
168: /*
169: String toAddr = null;
170: try {
171: // Attempt to read the TO field and see if it errors
172: toAddr = mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO).toString();
173: } catch (Exception e) {
174: // It has errored, so time for plan B
175: // use the from field I suppose
176: try {
177: mimeMessage.setRecipients(javax.mail.Message.RecipientType.TO, mimeMessage.getFrom());
178: if (getLogger().isDebugEnabled()) {
179: StringBuffer logBuffer =
180: new StringBuffer(128)
181: .append(this.getClass().getName())
182: .append(" Patching To: field for message ")
183: .append(" with From: field");
184: getLogger().debug(logBuffer.toString());
185: }
186: } catch (MessagingException e1) {
187: getLogger().error("Unable to set to: field to from: field", e);
188: }
189: } */
190: return mimeMessage;
191: }
192:
193: /**
194: * Generate a hex representation of an MD5 checksum on the emailbody
195: * @param emailBody
196: * @return A hex representation of the text
197: * @throws NoSuchAlgorithmException
198: */
199: private String generateKeyValue(String emailBody)
200: throws NoSuchAlgorithmException {
201: // MD5 the email body for a reilable (ha ha) key
202: byte[] digArray = MessageDigest.getInstance("MD5").digest(
203: emailBody.getBytes());
204: StringBuffer digest = new StringBuffer();
205: for (int i = 0; i < digArray.length; i++) {
206: digest.append(Integer.toString(digArray[i],
207: Character.MAX_RADIX).toUpperCase(Locale.US));
208: }
209: return digest.toString();
210: }
211:
212: /**
213: * Parse the mbox file.
214: * @param ins The random access file to load. Note that the file may or may not start at offset 0 in the file
215: * @param messAct The action to take when a message is found
216: */
217: private MimeMessage parseMboxFile(RandomAccessFile ins,
218: MessageAction messAct) {
219: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
220: StringBuffer logBuffer = new StringBuffer(128).append(
221: this .getClass().getName())
222: .append(" Start parsing ").append(mboxFile);
223:
224: getLogger().debug(logBuffer.toString());
225: }
226: try {
227:
228: Perl5Compiler sepMatchCompiler = new Perl5Compiler();
229: Pattern sepMatchPattern = sepMatchCompiler
230: .compile("^From (.*) (.*):(.*):(.*)$");
231: Perl5Matcher sepMatch = new Perl5Matcher();
232:
233: int c;
234: boolean inMessage = false;
235: StringBuffer messageBuffer = new StringBuffer();
236: String previousMessageSeparator = null;
237: boolean foundSep = false;
238:
239: long prevMessageStart = ins.getFilePointer();
240: if (BUFFERING) {
241: String line = null;
242: while ((line = ins.readLine()) != null) {
243: foundSep = sepMatch.contains(line + "\n",
244: sepMatchPattern);
245:
246: if (foundSep && inMessage) {
247: // if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
248: // getLogger().debug(this.getClass().getName() + " Invoking " + messAct.getClass() + " at " + prevMessageStart);
249: // }
250: MimeMessage endResult = messAct.messageAction(
251: previousMessageSeparator, messageBuffer
252: .toString(), prevMessageStart);
253: if (messAct.isComplete()) {
254: // I've got what I want so just exit
255: return endResult;
256: }
257: previousMessageSeparator = line;
258: prevMessageStart = ins.getFilePointer()
259: - line.length();
260: messageBuffer = new StringBuffer();
261: inMessage = true;
262: }
263: // Only done at the start (first header)
264: if (foundSep && !inMessage) {
265: previousMessageSeparator = line.toString();
266: inMessage = true;
267: }
268: if (!foundSep && inMessage) {
269: messageBuffer.append(line).append("\n");
270: }
271: }
272: } else {
273: StringBuffer line = new StringBuffer();
274: while ((c = ins.read()) != -1) {
275: if (c == 10) {
276: foundSep = sepMatch.contains(line.toString(),
277: sepMatchPattern);
278: if (foundSep && inMessage) {
279: // if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
280: // getLogger().debug(this.getClass().getName() + " Invoking " + messAct.getClass() + " at " + prevMessageStart);
281: // }
282: MimeMessage endResult = messAct
283: .messageAction(
284: previousMessageSeparator,
285: messageBuffer.toString(),
286: prevMessageStart);
287: if (messAct.isComplete()) {
288: // I've got what I want so just exit
289: return endResult;
290: }
291: previousMessageSeparator = line.toString();
292: prevMessageStart = ins.getFilePointer()
293: - line.length();
294: messageBuffer = new StringBuffer();
295: inMessage = true;
296: }
297: // Only done at the start (first header)
298: if (foundSep && inMessage == false) {
299: previousMessageSeparator = line.toString();
300: inMessage = true;
301: }
302: if (!foundSep) {
303: messageBuffer.append(line).append((char) c);
304: }
305: line = new StringBuffer(); // Reset buffer
306: } else {
307: line.append((char) c);
308: }
309: }
310: }
311:
312: if (messageBuffer.length() != 0) {
313: // process last message
314: return messAct.messageAction(previousMessageSeparator,
315: messageBuffer.toString(), prevMessageStart);
316: }
317: } catch (IOException ioEx) {
318: getLogger().error(
319: "Unable to write file (General I/O problem) "
320: + mboxFile, ioEx);
321: } catch (MalformedPatternException e) {
322: getLogger().error("Bad regex passed " + mboxFile, e);
323: } finally {
324: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
325: StringBuffer logBuffer = new StringBuffer(128).append(
326: this .getClass().getName()).append(
327: " Finished parsing ").append(mboxFile);
328:
329: getLogger().debug(logBuffer.toString());
330: }
331: }
332: return null;
333: }
334:
335: /**
336: * Find a given message
337: * This method will first use selectMessage(key) to see if the key/offset combination allows us to skip
338: * parts of the file and only load the message we are interested in
339: *
340: * @param key The key of the message to find
341: */
342: private MimeMessage findMessage(String key) {
343: MimeMessage foundMessage = null;
344:
345: // See if we can get the message by using the cache position first
346: foundMessage = selectMessage(key);
347: if (foundMessage == null) {
348: // If the message is not found something has changed from
349: // the cache. The cache may have been invalidated by
350: // another method, or the file may have been replaced from
351: // underneath us. Reload the cache, and try again.
352: mList = null;
353: loadKeys();
354: foundMessage = selectMessage(key);
355: }
356: return foundMessage;
357: }
358:
359: /**
360: * Quickly find a message by using the stored message offsets
361: * @param key The key of the message to find
362: */
363: private MimeMessage selectMessage(final String key) {
364: MimeMessage foundMessage = null;
365: // Can we find the key first
366: if (mList == null || !mList.containsKey(key)) {
367: // Not initiailised so no point looking
368: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
369: StringBuffer logBuffer = new StringBuffer(128).append(
370: this .getClass().getName()).append(
371: " mList - key not found ").append(mboxFile);
372:
373: getLogger().debug(logBuffer.toString());
374: }
375: return foundMessage;
376: }
377: long messageStart = ((Long) mList.get(key)).longValue();
378: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
379: StringBuffer logBuffer = new StringBuffer(128).append(
380: this .getClass().getName()).append(
381: " Load message starting at offset ").append(
382: messageStart).append(" from file ")
383: .append(mboxFile);
384:
385: getLogger().debug(logBuffer.toString());
386: }
387: // Now try and find the position in the file
388: RandomAccessFile ins = null;
389: try {
390: ins = new RandomAccessFile(mboxFile, "r");
391: if (messageStart != 0) {
392: ins.seek(messageStart - 1);
393: }
394: MessageAction op = new MessageAction() {
395: public boolean isComplete() {
396: return true;
397: }
398:
399: public MimeMessage messageAction(
400: String messageSeparator, String bodyText,
401: long messageStart) {
402: try {
403: if (key.equals(generateKeyValue(bodyText))) {
404: getLogger()
405: .debug(
406: this .getClass().getName()
407: + " Located message. Returning MIME message");
408: return convertTextToMimeMessage(bodyText);
409: }
410: } catch (NoSuchAlgorithmException e) {
411: getLogger().error("MD5 not supported! ", e);
412: }
413: return null;
414: }
415: };
416: foundMessage = this .parseMboxFile(ins, op);
417: } catch (FileNotFoundException e) {
418: getLogger().error(
419: "Unable to save(open) file (File not found) "
420: + mboxFile, e);
421: } catch (IOException e) {
422: getLogger().error(
423: "Unable to write file (General I/O problem) "
424: + mboxFile, e);
425: } finally {
426: if (foundMessage == null) {
427: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
428: StringBuffer logBuffer = new StringBuffer(128)
429: .append(this .getClass().getName()).append(
430: " select - message not found ")
431: .append(mboxFile);
432:
433: getLogger().debug(logBuffer.toString());
434: }
435: }
436: if (ins != null)
437: try {
438: ins.close();
439: } catch (IOException e) {
440: getLogger().error(
441: "Unable to close file (General I/O problem) "
442: + mboxFile, e);
443: }
444: }
445: return foundMessage;
446: }
447:
448: /**
449: * Load the message keys and file pointer offsets from disk
450: */
451: private synchronized void loadKeys() {
452: if (mList != null) {
453: return;
454: }
455: RandomAccessFile ins = null;
456: try {
457: ins = new RandomAccessFile(mboxFile, "r");
458: long initialCapacity = (ins.length() > MLISTPRESIZEFACTOR ? ins
459: .length()
460: / MLISTPRESIZEFACTOR
461: : 0);
462: if (initialCapacity < DEFAULTMLISTCAPACITY) {
463: initialCapacity = DEFAULTMLISTCAPACITY;
464: }
465: if (initialCapacity > Integer.MAX_VALUE) {
466: initialCapacity = Integer.MAX_VALUE - 1;
467: }
468: this .mList = new Hashtable((int) initialCapacity);
469: this .parseMboxFile(ins, new MessageAction() {
470: public boolean isComplete() {
471: return false;
472: }
473:
474: public MimeMessage messageAction(
475: String messageSeparator, String bodyText,
476: long messageStart) {
477: try {
478: String key = generateKeyValue(bodyText);
479: mList.put(key, new Long(messageStart));
480: if ((DEEP_DEBUG)
481: && (getLogger().isDebugEnabled())) {
482: getLogger().debug(
483: this .getClass().getName() + " Key "
484: + key + " at "
485: + messageStart);
486: }
487:
488: } catch (NoSuchAlgorithmException e) {
489: getLogger().error("MD5 not supported! ", e);
490: }
491: return null;
492: }
493: });
494: //System.out.println("Done Load keys!");
495: } catch (FileNotFoundException e) {
496: getLogger().error(
497: "Unable to save(open) file (File not found) "
498: + mboxFile, e);
499: this .mList = new Hashtable((int) DEFAULTMLISTCAPACITY);
500: } catch (IOException e) {
501: getLogger().error(
502: "Unable to write file (General I/O problem) "
503: + mboxFile, e);
504: } finally {
505: if (ins != null)
506: try {
507: ins.close();
508: } catch (IOException e) {
509: getLogger().error(
510: "Unable to close file (General I/O problem) "
511: + mboxFile, e);
512: }
513: }
514: }
515:
516: /**
517: * Store the given email in the current mbox file
518: * @param mc The mail to store
519: */
520: public void store(Mail mc) {
521:
522: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
523: StringBuffer logBuffer = new StringBuffer(128).append(
524: this .getClass().getName()).append(
525: " Will store message to file ").append(mboxFile);
526:
527: getLogger().debug(logBuffer.toString());
528: }
529: this .mList = null;
530: // Now make up the from header
531: String fromHeader = null;
532: String message = null;
533: try {
534: message = getRawMessage(mc.getMessage());
535: // check for nullsender
536: if (mc.getMessage().getFrom() == null) {
537: fromHeader = "From "
538: + dy.format(Calendar.getInstance().getTime());
539: } else {
540: fromHeader = "From " + mc.getMessage().getFrom()[0]
541: + " "
542: + dy.format(Calendar.getInstance().getTime());
543: }
544:
545: } catch (IOException e) {
546: getLogger().error(
547: "Unable to parse mime message for " + mboxFile, e);
548: } catch (MessagingException e) {
549: getLogger().error(
550: "Unable to parse mime message for " + mboxFile, e);
551: }
552: // And save only the new stuff to disk
553: RandomAccessFile saveFile = null;
554: try {
555: saveFile = new RandomAccessFile(mboxFile, "rw");
556: saveFile.seek(saveFile.length()); // Move to the end
557: saveFile.writeBytes((fromHeader + "\n"));
558: saveFile.writeBytes((message + "\n"));
559: saveFile.close();
560:
561: } catch (FileNotFoundException e) {
562: getLogger().error(
563: "Unable to save(open) file (File not found) "
564: + mboxFile, e);
565: } catch (IOException e) {
566: getLogger().error(
567: "Unable to write file (General I/O problem) "
568: + mboxFile, e);
569: }
570: }
571:
572: /**
573: * Return the list of the current messages' keys
574: * @return A list of the keys of the emails currently loaded
575: */
576: public Iterator list() {
577: loadKeys();
578:
579: if (mList.keySet().isEmpty() == false) {
580: // find the first message. This is a trick to make sure that if
581: // the file is changed out from under us, we will detect it and
582: // correct for it BEFORE we return the iterator.
583: findMessage((String) mList.keySet().iterator().next());
584: }
585: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
586: StringBuffer logBuffer = new StringBuffer(128).append(
587: this .getClass().getName()).append(" ").append(
588: mList.size()).append(" keys to be iterated over.");
589:
590: getLogger().debug(logBuffer.toString());
591: }
592: return mList.keySet().iterator();
593: }
594:
595: /**
596: * Get a message from the backing store (disk)
597: * @param key
598: * @return The mail found from the key. Returns null if the key is not found
599: */
600: public Mail retrieve(String key) {
601:
602: loadKeys();
603: MailImpl res = null;
604: try {
605: MimeMessage foundMessage = findMessage(key);
606: if (foundMessage == null) {
607: getLogger().error("found message is null!");
608: return null;
609: }
610: res = new MailImpl(foundMessage);
611: res.setName(key);
612: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
613: StringBuffer logBuffer = new StringBuffer(128).append(
614: this .getClass().getName()).append(
615: " Retrieving entry for key ").append(key);
616:
617: getLogger().debug(logBuffer.toString());
618: }
619: } catch (MessagingException e) {
620: getLogger().error(
621: "Unable to parse mime message for " + mboxFile
622: + "\n" + e.getMessage(), e);
623: }
624: return res;
625: }
626:
627: /**
628: * Remove an existing message
629: * @param mail
630: */
631: public void remove(Mail mail) {
632: ArrayList remArray = new ArrayList();
633: remArray.add(mail);
634: remove(remArray);
635: }
636:
637: /**
638: * Attempt to get a lock on the mbox by creating
639: * the file mboxname.lock
640: * @throws Exception
641: */
642: private void lockMBox() throws Exception {
643: // Create the lock file (if possible)
644: String lockFileName = mboxFile + LOCKEXT;
645: int sleepCount = 0;
646: File mBoxLock = new File(lockFileName);
647: if (!mBoxLock.createNewFile()) {
648: // This is not good, somebody got the lock before me
649: // So wait for a file
650: while (!mBoxLock.createNewFile()
651: && sleepCount < MAXSLEEPTIMES) {
652: try {
653: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
654: StringBuffer logBuffer = new StringBuffer(128)
655: .append(this .getClass().getName())
656: .append(" Waiting for lock on file ")
657: .append(mboxFile);
658:
659: getLogger().debug(logBuffer.toString());
660: }
661:
662: Thread.sleep(LOCKSLEEPDELAY);
663: sleepCount++;
664: } catch (InterruptedException e) {
665: getLogger().error(
666: "File lock wait for " + mboxFile
667: + " interrupted!", e);
668:
669: }
670: }
671: if (sleepCount >= MAXSLEEPTIMES) {
672: throw new Exception("Unable to get lock on file "
673: + mboxFile);
674: }
675: }
676: }
677:
678: /**
679: * Unlock a previously locked mbox file
680: */
681: private void unlockMBox() {
682: // Just delete the MBOX file
683: String lockFileName = mboxFile + LOCKEXT;
684: File mBoxLock = new File(lockFileName);
685: if (!mBoxLock.delete()) {
686: StringBuffer logBuffer = new StringBuffer(128).append(
687: this .getClass().getName()).append(
688: " Failed to delete lock file ")
689: .append(lockFileName);
690: getLogger().error(logBuffer.toString());
691: }
692: }
693:
694: /**
695: * Remove a list of messages from disk
696: * The collection is simply a list of mails to delete
697: * @param mails
698: */
699: public void remove(final Collection mails) {
700: if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {
701: StringBuffer logBuffer = new StringBuffer(128).append(
702: this .getClass().getName()).append(
703: " Removing entry for key ").append(mails);
704:
705: getLogger().debug(logBuffer.toString());
706: }
707: // The plan is as follows:
708: // Attempt to locate the message in the file
709: // by reading through the
710: // once we've done that then seek to the file
711: try {
712: RandomAccessFile ins = new RandomAccessFile(mboxFile, "r"); // The source
713: final RandomAccessFile outputFile = new RandomAccessFile(
714: mboxFile + WORKEXT, "rw"); // The destination
715: parseMboxFile(ins, new MessageAction() {
716: public boolean isComplete() {
717: return false;
718: }
719:
720: public MimeMessage messageAction(
721: String messageSeparator, String bodyText,
722: long messageStart) {
723: // Write out the messages as we go, until we reach the key we want
724: try {
725: String currentKey = generateKeyValue(bodyText);
726: boolean foundKey = false;
727: Iterator mailList = mails.iterator();
728: String key;
729: while (mailList.hasNext()) {
730: // Attempt to find the current key in the array
731: key = ((Mail) mailList.next()).getName();
732: if (key.equals(currentKey)) {
733: // Don't write the message to disk
734: foundKey = true;
735: break;
736: }
737: }
738: if (foundKey == false) {
739: // We didn't find the key in the array so we will keep it
740: outputFile.writeBytes(messageSeparator
741: + "\n");
742: outputFile.writeBytes(bodyText);
743:
744: }
745: } catch (NoSuchAlgorithmException e) {
746: getLogger().error("MD5 not supported! ", e);
747: } catch (IOException e) {
748: getLogger().error(
749: "Unable to write file (General I/O problem) "
750: + mboxFile, e);
751: }
752: return null;
753: }
754: });
755: ins.close();
756: outputFile.close();
757: // Delete the old mbox file
758: File mbox = new File(mboxFile);
759: mbox.delete();
760: // And rename the lock file to be the new mbox
761: mbox = new File(mboxFile + WORKEXT);
762: if (!mbox.renameTo(new File(mboxFile))) {
763: System.out.println("Failed to rename file!");
764: }
765:
766: // Now delete the keys in mails from the main hash
767: Iterator mailList = mails.iterator();
768: String key;
769: while (mailList.hasNext()) {
770: // Attempt to find the current key in the array
771: key = ((Mail) mailList.next()).getName();
772: mList.remove(key);
773: }
774:
775: } catch (FileNotFoundException e) {
776: getLogger().error(
777: "Unable to save(open) file (File not found) "
778: + mboxFile, e);
779: } catch (IOException e) {
780: getLogger().error(
781: "Unable to write file (General I/O problem) "
782: + mboxFile, e);
783: }
784: }
785:
786: /**
787: * Remove a mail from the mbox file
788: * @param key The key of the mail to delete
789: */
790: public void remove(String key) {
791: loadKeys();
792: try {
793: lockMBox();
794: } catch (Exception e) {
795: getLogger().error("Lock failed!", e);
796: return; // No lock, so exit
797: }
798: ArrayList keys = new ArrayList();
799: keys.add(retrieve(key));
800:
801: this .remove(keys);
802: unlockMBox();
803: }
804:
805: /**
806: * Not implemented
807: * @param key
808: * @return
809: */
810: public boolean lock(String key) {
811: return false;
812: }
813:
814: /**
815: * Not implemented
816: * @param key
817: * @return
818: */
819: public boolean unlock(String key) {
820: return false;
821: }
822:
823: /**
824: * @see org.apache.avalon.framework.service.Serviceable#compose(ServiceManager )
825: */
826: public void service(final ServiceManager componentManager)
827: throws ServiceException {
828: }
829:
830: /**
831: * Configure the component
832: * @param conf
833: * @throws ConfigurationException
834: */
835: public void configure(Configuration conf)
836: throws ConfigurationException {
837: String destination;
838: this .mList = null;
839: BUFFERING = conf.getAttributeAsBoolean("BUFFERING", true);
840: destination = conf.getAttribute("destinationURL");
841: if (destination.charAt(destination.length() - 1) == '/') {
842: // Remove the trailing / as well as the protocol marker
843: mboxFile = destination.substring("mbox://".length(),
844: destination.lastIndexOf("/"));
845: } else {
846: mboxFile = destination.substring("mbox://".length());
847: }
848:
849: if (getLogger().isDebugEnabled()) {
850: getLogger()
851: .debug(
852: "MBoxMailRepository.destinationURL: "
853: + destination);
854: }
855:
856: String checkType = conf.getAttribute("type");
857: if (!(checkType.equals("MAIL") || checkType.equals("SPOOL"))) {
858: String exceptionString = "Attempt to configure MboxMailRepository as "
859: + checkType;
860: if (getLogger().isWarnEnabled()) {
861: getLogger().warn(exceptionString);
862: }
863: throw new ConfigurationException(exceptionString);
864: }
865: }
866:
867: /**
868: * Initialise the component
869: * @throws Exception
870: */
871: public void initialize() throws Exception {
872: }
873:
874: public static void main(String[] args) {
875: // Test invocation
876: MBoxMailRepository mbx = new MBoxMailRepository();
877: mbx.mboxFile = "C:\\java\\test\\1998-05.txt";
878: Iterator mList = mbx.list();
879: while (mList.hasNext()) {
880: //String key = (String) mList.next();
881: //System.out.println("key=" + key);
882: /*MailImpl mi = mbx.retrieve(key);
883: try
884: {
885: System.out.println("Subject : " + (mi.getMessage()).getSubject());
886: }
887: catch (MessagingException e)
888: {
889: e.printStackTrace(); //To change body of catch statement use Options | File Templates.
890: } */
891:
892: }
893:
894: /* MailImpl mi = mbx.retrieve("ffffffb4ffffffe2f59fffffff291dffffffde4366243ffffff971d1f24");
895: try {
896: System.out.println("Subject : " + (mi.getMessage()).getSubject());
897: } catch (MessagingException e) {
898: e.printStackTrace(); //To change body of catch statement use Options | File Templates.
899: }
900: mbx.remove("ffffffb4ffffffe2f59fffffff291dffffffde4366243ffffff971d1f24");*/
901: }
902: }
|