📄 mboxmailrepository.java
字号:
/*********************************************************************** * Copyright (c) 2000-2004 The Apache Software Foundation. * * All rights reserved. * * ------------------------------------------------------------------- * * Licensed under the Apache License, Version 2.0 (the "License"); you * * may not use this file except in compliance with the License. You * * may obtain a copy of the License at: * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * * implied. See the License for the specific language governing * * permissions and limitations under the License. * ***********************************************************************//* TODO: * * 1. Currently, iterating through the message collection does not * preserve the order in the file. Change this with some form of * OrderedMap. There is a suitable class in Jakarta Commons * Collections. * * 2. Optimize the remove operation. * * 3. Don't load entire message into memory. This would mean computing * the hash during I/O streaming, rather than loading entire message * into memory, and using a MimeMessageWrapper with a suitable data * source. As a strawman, the interface to MessageAction would * carry the hash, along with a size-limited stream providing the * message body. * * 4. Decide what to do when there are IDENTICAL messages in the file. * Right now only the last one will ever be processed, due to key * collissions. * * 5. isComplete() - DONE. * * 6. Buffered I/O. - Partially done, and optional. * */package org.apache.james.mailrepository;import org.apache.avalon.framework.activity.Initializable;import org.apache.avalon.framework.component.Component;import org.apache.avalon.framework.component.ComponentException;import org.apache.avalon.framework.component.ComponentManager;import org.apache.avalon.framework.component.Composable;import org.apache.avalon.framework.configuration.Configurable;import org.apache.avalon.framework.configuration.Configuration;import org.apache.avalon.framework.configuration.ConfigurationException;import org.apache.avalon.framework.context.Context;import org.apache.avalon.framework.context.ContextException;import org.apache.avalon.framework.context.Contextualizable;import org.apache.avalon.framework.logger.AbstractLogEnabled;import org.apache.james.core.MailImpl;import org.apache.james.services.MailRepository;import org.apache.oro.text.regex.MalformedPatternException;import org.apache.oro.text.regex.Perl5Compiler;import org.apache.oro.text.regex.Pattern;import org.apache.oro.text.regex.Perl5Matcher;import javax.mail.MessagingException;import javax.mail.Session;import javax.mail.internet.MimeMessage;import javax.mail.internet.InternetAddress;import java.util.*;import java.io.*;import java.security.NoSuchAlgorithmException;import java.security.MessageDigest;import java.text.SimpleDateFormat;import java.lang.reflect.Array;/** * Implementation of a MailRepository using UNIX mbox files. * * <p>Requires a configuration element in the .conf.xml file of the form: * <br><repository destinationURL="mbox://<directory>" * <br> type="MAIL" * <br></directory> is where the individual mbox files are read from/written to * <br>Type can ONLY be MAIL (SPOOL is NOT supported) * * <p>Requires a logger called MailRepository. * * <p> Implementation notes: * <p> * This class keeps an internal store of the mbox file * When the internal mbox file is updated (added/deleted) * then the file will be re-read from disk and then written back. * This is a bit inefficent but means that the file on disk * should be correct. * <p> * The mbox store is mainly meant to be used as a one-way street. * Storing new emails is very fast (append to file) whereas reading them (via POP3) is * slower (read from disk and parse). * Therefore this implementation is best suited to people who wish to use the mbox format * for taking data out of James and into something else (IMAP server or mail list displayer) * * @version CVS $Revision: 1.1.2.5 $ */public class MBoxMailRepository extends AbstractLogEnabled implements MailRepository, Component, Contextualizable, Composable, Configurable, Initializable { static final SimpleDateFormat dy = new SimpleDateFormat("EE MMM dd HH:mm:ss yyyy", Locale.US); static final String LOCKEXT = ".lock"; static final String WORKEXT = ".work"; static final int LOCKSLEEPDELAY = 2000; // 2 second back off in the event of a problem with the lock file static final int MAXSLEEPTIMES = 100; // static final long MLISTPRESIZEFACTOR = 10 * 1024; // The hash table will be loaded with a initial capacity of filelength/MLISTPRESIZEFACTOR static final long DEFAULTMLISTCAPACITY = 20; // Set up a hashtable to have a meaningful default /** * Whether line buffering is turned used. */ private static boolean BUFFERING = true; /** * Whether 'deep debugging' is turned on. */ private static final boolean DEEP_DEBUG = true; /** * The Avalon context used by the instance */ private Context context; /** * The internal list of the emails * The key is an adapted MD5 checksum of the mail */ private Hashtable mList = null; /** * The filename to read & write the mbox from/to */ private String mboxFile; /** * A callback used when a message is read from the mbox file */ public interface MessageAction { public boolean isComplete(); // *** Not valid until AFTER each call to messageAction(...)! public MimeMessage messageAction(String messageSeparator, String bodyText, long messageStart); } /** * Convert a MimeMessage into raw text * @param mc The mime message to convert * @return A string representation of the mime message * @throws IOException * @throws MessagingException */ private String getRawMessage(MimeMessage mc) throws IOException, MessagingException { ByteArrayOutputStream rawMessage = new ByteArrayOutputStream(); mc.writeTo(rawMessage); return rawMessage.toString(); } /** * Parse a text block as an email and convert it into a mime message * @param emailBody The headers and body of an email. This will be parsed into a mime message and stored */ private MimeMessage convertTextToMimeMessage(String emailBody) { //this.emailBody = emailBody; MimeMessage mimeMessage = null; // Parse the mime message as we have the full message now (in string format) ByteArrayInputStream mb = new ByteArrayInputStream(emailBody.getBytes()); Properties props = System.getProperties(); Session session = Session.getDefaultInstance(props); String toAddr = null; try { mimeMessage = new MimeMessage(session, mb); } catch (MessagingException e) { getLogger().error("Unable to parse mime message!", e); } if (mimeMessage == null && getLogger().isDebugEnabled()) { StringBuffer logBuffer = new StringBuffer(128) .append(this.getClass().getName()) .append(" Mime message is null"); getLogger().debug(logBuffer.toString()); } /* try { // Attempt to read the TO field and see if it errors toAddr = mimeMessage.getRecipients(javax.mail.Message.RecipientType.TO).toString(); } catch (Exception e) { // It has errored, so time for plan B // use the from field I suppose try { mimeMessage.setRecipients(javax.mail.Message.RecipientType.TO, mimeMessage.getFrom()); if (getLogger().isDebugEnabled()) { StringBuffer logBuffer = new StringBuffer(128) .append(this.getClass().getName()) .append(" Patching To: field for message ") .append(" with From: field"); getLogger().debug(logBuffer.toString()); } } catch (MessagingException e1) { getLogger().error("Unable to set to: field to from: field", e); } } */ return mimeMessage; } /** * Generate a hex representation of an MD5 checksum on the emailbody * @param emailBody * @return A hex representation of the text * @throws NoSuchAlgorithmException */ private String generateKeyValue(String emailBody) throws NoSuchAlgorithmException { // MD5 the email body for a reilable (ha ha) key byte[] digArray = MessageDigest.getInstance("MD5").digest(emailBody.getBytes()); StringBuffer digest = new StringBuffer(); for (int i = 0; i < digArray.length; i++) { digest.append(Integer.toString(digArray[i], Character.MAX_RADIX).toUpperCase()); } return digest.toString(); } /** * Parse the mbox file. * @param ins The random access file to load. Note that the file may or may not start at offset 0 in the file * @param messAct The action to take when a message is found */ private MimeMessage parseMboxFile(RandomAccessFile ins, MessageAction messAct) { if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) { StringBuffer logBuffer = new StringBuffer(128) .append(this.getClass().getName()) .append(" Start parsing ") .append(mboxFile); getLogger().debug(logBuffer.toString()); } try { Perl5Compiler sepMatchCompiler = new Perl5Compiler(); Pattern sepMatchPattern = sepMatchCompiler.compile("^From (.*) (.*):(.*):(.*)$"); Perl5Matcher sepMatch = new Perl5Matcher(); int c; boolean inMessage = false; StringBuffer messageBuffer = new StringBuffer(); String previousMessageSeparator = null; boolean foundSep = false; long prevMessageStart = ins.getFilePointer(); if (BUFFERING) { String line = null; while ((line = ins.readLine()) != null) { foundSep = sepMatch.contains(line + "\n", sepMatchPattern); if (foundSep && inMessage) {// if ((DEEP_DEBUG) && (getLogger().isDebugEnabled())) {// getLogger().debug(this.getClass().getName() + " Invoking " + messAct.getClass() + " at " + prevMessageStart);// } MimeMessage endResult = messAct.messageAction(previousMessageSeparator, messageBuffer.toString(), prevMessageStart); if (messAct.isComplete()) { // I've got what I want so just exit return endResult; } previousMessageSeparator = line; prevMessageStart = ins.getFilePointer() - line.length(); messageBuffer = new StringBuffer(); inMessage = true; } // Only done at the start (first header) if (foundSep && !inMessage) { previousMessageSeparator = line.toString(); inMessage = true; } if (!foundSep && inMessage) { messageBuffer.append(line).append("\n");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -