http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java
new file mode 100644
index 0000000..106d46f
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MailImpl.java
@@ -0,0 +1,691 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OptionalDataException;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.ParseException;
+
+import org.apache.james.lifecycle.api.Disposable;
+import org.apache.james.lifecycle.api.LifecycleUtil;
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.Mail;
+import org.apache.mailet.PerRecipientHeaders;
+import org.apache.mailet.PerRecipientHeaders.Header;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>
+ * Wraps a MimeMessage adding routing information (from SMTP) and some simple
+ * API enhancements.
+ * </p>
+ * <p>
+ * From James version > 2.2.0a8 "mail attributes" have been added. Backward and
+ * forward compatibility is supported:
+ * <ul>
+ * <li>messages stored in file repositories <i>without</i> attributes by James
+ * version <= 2.2.0a8 will be processed by later versions as having an empty
+ * attributes hashmap;</li>
+ * <li>messages stored in file repositories <i>with</i> attributes by James
+ * version > 2.2.0a8 will be processed by previous versions, ignoring the
+ * attributes.</li>
+ * </ul>
+ * </p>
+ */
+public class MailImpl implements Disposable, Mail {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(MailImpl.class);
+
+    /**
+     * We hardcode the serialVersionUID so that from James 1.2 on, MailImpl 
will
+     * be deserializable (so your mail doesn't get lost)
+     */
+    public static final long serialVersionUID = -4289663364703986260L;
+    /**
+     * The error message, if any, associated with this mail.
+     */
+    private String errorMessage;
+    /**
+     * The state of this mail, which determines how it is processed.
+     */
+    private String state;
+    /**
+     * The MimeMessage that holds the mail data.
+     */
+    private MimeMessage message;
+    /**
+     * The sender of this mail.
+     */
+    private MailAddress sender;
+    /**
+     * The collection of recipients to whom this mail was sent.
+     */
+    private Collection<MailAddress> recipients;
+    /**
+     * The identifier for this mail message
+     */
+    private String name;
+    /**
+     * The remote host from which this mail was sent.
+     */
+    private String remoteHost = "localhost";
+    /**
+     * The remote address from which this mail was sent.
+     */
+    private String remoteAddr = "127.0.0.1";
+    /**
+     * The last time this message was updated.
+     */
+    private Date lastUpdated = new Date();
+    /**
+     * Attributes added to this MailImpl instance
+     */
+    private Map<String, Object> attributes;
+    /**
+     * Specific headers for some recipients
+     * These headers will be added at delivery time
+     */
+    private PerRecipientHeaders perRecipientSpecificHeaders;
+
+    /**
+     * A constructor that creates a new, uninitialized MailImpl
+     */
+    public MailImpl() {
+        setState(Mail.DEFAULT);
+        attributes = new HashMap<>();
+        perRecipientSpecificHeaders = new PerRecipientHeaders();
+    }
+
+    /**
+     * A constructor that creates a MailImpl with the specified name, sender,
+     * and recipients.
+     *
+     * @param name       the name of the MailImpl
+     * @param sender     the sender for this MailImpl
+     * @param recipients the collection of recipients of this MailImpl
+     */
+    public MailImpl(String name, MailAddress sender, Collection<MailAddress> 
recipients) {
+        this();
+        this.name = name;
+        this.sender = sender;
+        this.recipients = null;
+
+        // Copy the recipient list
+        if (recipients != null) {
+            this.recipients = new ArrayList<>();
+            this.recipients.addAll(recipients);
+        }
+    }
+
+    /**
+     * Create a copy of the input mail and assign it a new name
+     *
+     * @param mail original mail
+     * @throws MessagingException when the message is not clonable
+     */
+    public MailImpl(Mail mail) throws MessagingException {
+        this(mail, newName(mail));
+    }
+
+    /**
+     * @param mail
+     * @param newName
+     * @throws MessagingException
+     */
+    @SuppressWarnings("unchecked")
+    public MailImpl(Mail mail, String newName) throws MessagingException {
+        this(newName, mail.getSender(), mail.getRecipients(), 
mail.getMessage());
+        setRemoteHost(mail.getRemoteHost());
+        setRemoteAddr(mail.getRemoteAddr());
+        setLastUpdated(mail.getLastUpdated());
+        try {
+            if (mail instanceof MailImpl) {
+                setAttributesRaw((HashMap<String, Object>) 
cloneSerializableObject(((MailImpl) mail).getAttributesRaw()));
+            } else {
+                HashMap<String, Object> attribs = new HashMap<>();
+                for (Iterator<String> i = mail.getAttributeNames(); 
i.hasNext(); ) {
+                    String hashKey = i.next();
+                    attribs.put(hashKey, 
cloneSerializableObject(mail.getAttribute(hashKey)));
+                }
+                setAttributesRaw(attribs);
+            }
+        } catch (IOException | ClassNotFoundException e) {
+            LOGGER.error("Error while deserializing attributes", e);
+            setAttributesRaw(new HashMap<>());
+        }
+    }
+
+    /**
+     * A constructor that creates a MailImpl with the specified name, sender,
+     * recipients, and message data.
+     *
+     * @param name       the name of the MailImpl
+     * @param sender     the sender for this MailImpl
+     * @param recipients the collection of recipients of this MailImpl
+     * @param messageIn  a stream containing the message source
+     */
+    public MailImpl(String name, MailAddress sender, Collection<MailAddress> 
recipients, InputStream messageIn) throws MessagingException {
+        this(name, sender, recipients);
+        MimeMessageSource source = new MimeMessageInputStreamSource(name, 
messageIn);
+        // if MimeMessageCopyOnWriteProxy throws an error in the constructor we
+        // have to manually care disposing our source.
+        try {
+            this.setMessage(new MimeMessageCopyOnWriteProxy(source));
+        } catch (MessagingException e) {
+            LifecycleUtil.dispose(source);
+            throw e;
+        }
+    }
+
+    /**
+     * A constructor that creates a MailImpl with the specified name, sender,
+     * recipients, and MimeMessage.
+     *
+     * @param name       the name of the MailImpl
+     * @param sender     the sender for this MailImpl
+     * @param recipients the collection of recipients of this MailImpl
+     * @param message    the MimeMessage associated with this MailImpl
+     */
+    public MailImpl(String name, MailAddress sender, Collection<MailAddress> 
recipients, MimeMessage message) {
+        this(name, sender, recipients);
+        this.setMessage(new MimeMessageCopyOnWriteProxy(message));
+    }
+
+    /**
+     * Duplicate the MailImpl.
+     *
+     * @return a MailImpl that is a duplicate of this one
+     */
+    public Mail duplicate() {
+        return duplicate(name);
+    }
+
+    /**
+     * Duplicate the MailImpl, replacing the mail name with the one passed in 
as
+     * an argument.
+     *
+     * @param newName the name for the duplicated mail
+     * @return a MailImpl that is a duplicate of this one with a different name
+     */
+    public Mail duplicate(String newName) {
+        try {
+            return new MailImpl(this, newName);
+        } catch (MessagingException me) {
+            // Ignored. Return null in the case of an error.
+        }
+        return null;
+    }
+
+    /**
+     * Get the error message associated with this MailImpl.
+     *
+     * @return the error message associated with this MailImpl
+     */
+    @Override
+    public String getErrorMessage() {
+        return errorMessage;
+    }
+
+    /**
+     * Get the MimeMessage associated with this MailImpl.
+     *
+     * @return the MimeMessage associated with this MailImpl
+     */
+    @Override
+    public MimeMessage getMessage() throws MessagingException {
+        return message;
+    }
+
+    /**
+     * Set the name of this MailImpl.
+     *
+     * @param name the name of this MailImpl
+     */
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Get the name of this MailImpl.
+     *
+     * @return the name of this MailImpl
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Get the recipients of this MailImpl.
+     *
+     * @return the recipients of this MailImpl
+     */
+    @Override
+    public Collection<MailAddress> getRecipients() {
+        return recipients;
+    }
+
+    /**
+     * Get the sender of this MailImpl.
+     *
+     * @return the sender of this MailImpl
+     */
+    @Override
+    public MailAddress getSender() {
+        return sender;
+    }
+
+    /**
+     * Get the state of this MailImpl.
+     *
+     * @return the state of this MailImpl
+     */
+    @Override
+    public String getState() {
+        return state;
+    }
+
+    /**
+     * Get the remote host associated with this MailImpl.
+     *
+     * @return the remote host associated with this MailImpl
+     */
+    @Override
+    public String getRemoteHost() {
+        return remoteHost;
+    }
+
+    /**
+     * Get the remote address associated with this MailImpl.
+     *
+     * @return the remote address associated with this MailImpl
+     */
+    @Override
+    public String getRemoteAddr() {
+        return remoteAddr;
+    }
+
+    /**
+     * Get the last updated time for this MailImpl.
+     *
+     * @return the last updated time for this MailImpl
+     */
+    @Override
+    public Date getLastUpdated() {
+        return lastUpdated;
+    }
+
+    /**
+     * <p>
+     * Return the size of the message including its headers.
+     * MimeMessage.getSize() method only returns the size of the message body.
+     * </p>
+     * <p/>
+     * <p>
+     * Note: this size is not guaranteed to be accurate - see Sun's
+     * documentation of MimeMessage.getSize().
+     * </p>
+     *
+     * @return approximate size of full message including headers.
+     * @throws MessagingException if a problem occurs while computing the 
message size
+     */
+    @Override
+    public long getMessageSize() throws MessagingException {
+        return MimeMessageUtil.getMessageSize(message);
+    }
+
+    /**
+     * Set the error message associated with this MailImpl.
+     *
+     * @param msg the new error message associated with this MailImpl
+     */
+    @Override
+    public void setErrorMessage(String msg) {
+        this.errorMessage = msg;
+    }
+
+    /**
+     * Set the MimeMessage associated with this MailImpl.
+     *
+     * @param message the new MimeMessage associated with this MailImpl
+     */
+    @Override
+    public void setMessage(MimeMessage message) {
+
+        // TODO: We should use the MimeMessageCopyOnWriteProxy
+        // everytime we set the MimeMessage. We should
+        // investigate if we should wrap it here
+
+        if (this.message != message) {
+            // If a setMessage is called on a Mail that already have a message
+            // (discouraged) we have to make sure that the message we remove is
+            // correctly unreferenced and disposed, otherwise it will keep 
locks
+            if (this.message != null) {
+                LifecycleUtil.dispose(this.message);
+            }
+            this.message = message;
+        }
+    }
+
+    /**
+     * Set the recipients for this MailImpl.
+     *
+     * @param recipients the recipients for this MailImpl
+     */
+    @Override
+    public void setRecipients(Collection<MailAddress> recipients) {
+        this.recipients = recipients;
+    }
+
+    /**
+     * Set the sender of this MailImpl.
+     *
+     * @param sender the sender of this MailImpl
+     */
+    public void setSender(MailAddress sender) {
+        this.sender = sender;
+    }
+
+    /**
+     * Set the state of this MailImpl.
+     *
+     * @param state the state of this MailImpl
+     */
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    /**
+     * Set the remote address associated with this MailImpl.
+     *
+     * @param remoteHost the new remote host associated with this MailImpl
+     */
+    public void setRemoteHost(String remoteHost) {
+        this.remoteHost = remoteHost;
+    }
+
+    /**
+     * Set the remote address associated with this MailImpl.
+     *
+     * @param remoteAddr the new remote address associated with this MailImpl
+     */
+    public void setRemoteAddr(String remoteAddr) {
+        this.remoteAddr = remoteAddr;
+    }
+
+    /**
+     * Set the date this mail was last updated.
+     *
+     * @param lastUpdated the date the mail was last updated
+     */
+    public void setLastUpdated(Date lastUpdated) {
+        // Make a defensive copy to ensure that the date
+        // doesn't get changed external to the class
+        if (lastUpdated != null) {
+            lastUpdated = new Date(lastUpdated.getTime());
+        }
+        this.lastUpdated = lastUpdated;
+    }
+
+    /**
+     * Writes the message out to an OutputStream.
+     *
+     * @param out the OutputStream to which to write the content
+     * @throws MessagingException if the MimeMessage is not set for this 
MailImpl
+     * @throws IOException        if an error occurs while reading or writing 
from the stream
+     */
+    public void writeMessageTo(OutputStream out) throws IOException, 
MessagingException {
+        if (message != null) {
+            message.writeTo(out);
+        } else {
+            throw new MessagingException("No message set for this MailImpl.");
+        }
+    }
+
+    // Serializable Methods
+    // TODO: These need some work. Currently very tightly coupled to
+    // the internal representation.
+
+    /**
+     * Read the MailImpl from an <code>ObjectInputStream</code>.
+     *
+     * @param in the ObjectInputStream from which the object is read
+     * @throws IOException            if an error occurs while reading from 
the stream
+     * @throws ClassNotFoundException ?
+     * @throws ClassCastException     if the serialized objects are not of the 
appropriate type
+     */
+    @SuppressWarnings("unchecked")
+    private void readObject(java.io.ObjectInputStream in) throws IOException, 
ClassNotFoundException {
+        try {
+            Object obj = in.readObject();
+            if (obj == null) {
+                sender = null;
+            } else if (obj instanceof String) {
+                sender = new MailAddress((String) obj);
+            } else if (obj instanceof MailAddress) {
+                sender = (MailAddress) obj;
+            }
+        } catch (ParseException pe) {
+            throw new IOException("Error parsing sender address: " + 
pe.getMessage());
+        }
+        recipients = (Collection<MailAddress>) in.readObject();
+        state = (String) in.readObject();
+        errorMessage = (String) in.readObject();
+        name = (String) in.readObject();
+        remoteHost = (String) in.readObject();
+        remoteAddr = (String) in.readObject();
+        setLastUpdated((Date) in.readObject());
+        // the following is under try/catch to be backwards compatible
+        // with messages created with James version <= 2.2.0a8
+        try {
+            attributes = (HashMap<String, Object>) in.readObject();
+        } catch (OptionalDataException ode) {
+            if (ode.eof) {
+                attributes = new HashMap<>();
+            } else {
+                throw ode;
+            }
+        }
+    }
+
+    /**
+     * Write the MailImpl to an <code>ObjectOutputStream</code>.
+     *
+     * @param out the ObjectOutputStream to which the object is written
+     * @throws IOException if an error occurs while writing to the stream
+     */
+    private void writeObject(java.io.ObjectOutputStream out) throws 
IOException {
+        out.writeObject(sender);
+        out.writeObject(recipients);
+        out.writeObject(state);
+        out.writeObject(errorMessage);
+        out.writeObject(name);
+        out.writeObject(remoteHost);
+        out.writeObject(remoteAddr);
+        out.writeObject(lastUpdated);
+        out.writeObject(attributes);
+    }
+
+    @Override
+    public void dispose() {
+        LifecycleUtil.dispose(message);
+        message = null;
+    }
+
+    /**
+     * <p>
+     * This method is necessary, when Mail repositories needs to deal 
explicitly
+     * with storing Mail attributes as a Serializable
+     * </p>
+     * <p>
+     * <strong>Note</strong>: This method is not exposed in the Mail interface,
+     * it is for internal use by James only.
+     * </p>
+     *
+     * @return Serializable of the entire attributes collection
+     * @since 2.2.0
+     */
+    public Map<String, Object> getAttributesRaw() {
+        return attributes;
+    }
+
+    /**
+     * <p>
+     * This method is necessary, when Mail repositories needs to deal 
explicitly
+     * with retriving Mail attributes as a Serializable
+     * </p>
+     * <p>
+     * <strong>Note</strong>: This method is not exposed in the Mail interface,
+     * it is for internal use by James only.
+     * </p>
+     *
+     * @param attr Serializable of the entire attributes collection
+     * @since 2.2.0
+     */
+    public void setAttributesRaw(HashMap<String, Object> attr) {
+        this.attributes = (attr == null) ? new HashMap<>() : attr;
+    }
+
+    @Override
+    public Serializable getAttribute(String key) {
+        return (Serializable) attributes.get(key);
+    }
+
+    @Override
+    public Serializable setAttribute(String key, Serializable object) {
+        return (Serializable) attributes.put(key, object);
+    }
+
+    @Override
+    public Serializable removeAttribute(String key) {
+        return (Serializable) attributes.remove(key);
+    }
+
+    @Override
+    public void removeAllAttributes() {
+        attributes.clear();
+    }
+
+    @Override
+    public Iterator<String> getAttributeNames() {
+        return attributes.keySet().iterator();
+    }
+
+    @Override
+    public boolean hasAttributes() {
+        return !attributes.isEmpty();
+    }
+
+    /**
+     * This methods provide cloning for serializable objects. Mail Attributes
+     * are Serializable but not Clonable so we need a deep copy
+     *
+     * @param o Object to be cloned
+     * @return the cloned Object
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    private static Object cloneSerializableObject(Object o) throws 
IOException, ClassNotFoundException {
+        ByteArrayOutputStream b = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(b);
+        out.writeObject(o);
+        out.flush();
+        out.close();
+        ByteArrayInputStream bi = new ByteArrayInputStream(b.toByteArray());
+        ObjectInputStream in = new ObjectInputStream(bi);
+        return in.readObject();
+    }
+
+    private static final java.util.Random random = new java.util.Random(); // 
Used
+    // to
+    // generate
+    // new
+    // mail
+    // names
+
+    /**
+     * Create a unique new primary key name for the given MailObject.
+     *
+     * @param mail the mail to use as the basis for the new mail name
+     * @return a new name
+     */
+    public static String newName(Mail mail) throws MessagingException {
+        String oldName = mail.getName();
+
+        // Checking if the original mail name is too long, perhaps because of a
+        // loop caused by a configuration error.
+        // it could cause a "null pointer exception" in AvalonMailRepository
+        // much
+        // harder to understand.
+        if (oldName.length() > 76) {
+            int count = 0;
+            int index = 0;
+            while ((index = oldName.indexOf('!', index + 1)) >= 0) {
+                count++;
+            }
+            // It looks like a configuration loop. It's better to stop.
+            if (count > 7) {
+                throw new MessagingException("Unable to create a new message 
name: too long." + " Possible loop in config.xml.");
+            } else {
+                oldName = oldName.substring(0, 76);
+            }
+        }
+
+        return oldName + "-!" + random.nextInt(1048576);
+    }
+
+    /**
+     * Generate a new identifier/name for a mail being processed by this 
server.
+     *
+     * @return the new identifier
+     */
+    public static String getId() {
+        return "Mail" + System.currentTimeMillis() + "-" + UUID.randomUUID();
+    }
+
+    @Override
+    public PerRecipientHeaders getPerRecipientSpecificHeaders() {
+        return perRecipientSpecificHeaders;
+    }
+
+    @Override
+    public void addSpecificHeaderForRecipient(Header header, MailAddress 
recipient) {
+        perRecipientSpecificHeaders.addHeaderForRecipient(header, recipient);
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageCopyOnWriteProxy.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageCopyOnWriteProxy.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageCopyOnWriteProxy.java
new file mode 100644
index 0000000..53483eb
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageCopyOnWriteProxy.java
@@ -0,0 +1,561 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import javax.activation.DataHandler;
+import javax.mail.Address;
+import javax.mail.Flags;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Flags.Flag;
+import javax.mail.internet.MimeMessage;
+import javax.mail.search.SearchTerm;
+
+import org.apache.james.lifecycle.api.Disposable;
+import org.apache.james.lifecycle.api.LifecycleUtil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Date;
+import java.util.Enumeration;
+
+/**
+ * This object wraps a "possibly shared" MimeMessage tracking copies and
+ * automatically cloning it (if shared) when a write operation is invoked.
+ */
+public class MimeMessageCopyOnWriteProxy extends MimeMessage implements 
Disposable {
+
+    /**
+     * Used internally to track the reference count It is important that this 
is
+     * static otherwise it will keep a reference to the parent object.
+     */
+    protected static class MessageReferenceTracker {
+
+        /**
+         * reference counter
+         */
+        private int referenceCount = 1;
+
+        /**
+         * The mime message in memory
+         */
+        private MimeMessage wrapped = null;
+
+        public MessageReferenceTracker(MimeMessage ref) {
+            wrapped = ref;
+        }
+
+        protected synchronized void incrementReferenceCount() {
+            referenceCount++;
+        }
+
+        protected synchronized void decrementReferenceCount() {
+            referenceCount--;
+            if (referenceCount <= 0) {
+                LifecycleUtil.dispose(wrapped);
+                wrapped = null;
+            }
+        }
+
+        protected synchronized int getReferenceCount() {
+            return referenceCount;
+        }
+
+        public synchronized MimeMessage getWrapped() {
+            return wrapped;
+        }
+
+    }
+
+    protected MessageReferenceTracker refCount;
+
+    public MimeMessageCopyOnWriteProxy(MimeMessage original) {
+        this(original, false);
+    }
+
+    public MimeMessageCopyOnWriteProxy(MimeMessageSource original) throws 
MessagingException {
+        this(new MimeMessageWrapper(original), true);
+    }
+
+    /**
+     * Private constructor providing an external reference counter.
+     */
+    private MimeMessageCopyOnWriteProxy(MimeMessage original, boolean 
writeable) {
+        super(Session.getDefaultInstance(System.getProperties(), null));
+
+        if (original instanceof MimeMessageCopyOnWriteProxy) {
+            refCount = ((MimeMessageCopyOnWriteProxy) original).refCount;
+        } else {
+            refCount = new MessageReferenceTracker(original);
+        }
+
+        if (!writeable) {
+            refCount.incrementReferenceCount();
+        }
+    }
+
+    /**
+     * Check the number of references over the MimeMessage and clone it if
+     * needed before returning the reference
+     * 
+     * @throws MessagingException
+     *             exception
+     */
+    protected synchronized MimeMessage getWrappedMessageForWriting() throws 
MessagingException {
+        if (refCount.getReferenceCount() > 1) {
+            refCount.decrementReferenceCount();
+            refCount = new MessageReferenceTracker(new 
MimeMessageWrapper(refCount.getWrapped()));
+        }
+        return refCount.getWrapped();
+    }
+
+    /**
+     * Return wrapped mimeMessage
+     * 
+     * @return wrapped return the wrapped mimeMessage
+     */
+    public synchronized MimeMessage getWrappedMessage() {
+        return refCount.getWrapped();
+    }
+
+    @Override
+    public synchronized void dispose() {
+        if (refCount != null) {
+            refCount.decrementReferenceCount();
+            refCount = null;
+        }
+    }
+
+    @Override
+    public void writeTo(OutputStream os) throws IOException, 
MessagingException {
+        getWrappedMessage().writeTo(os);
+    }
+
+    /**
+     * Rewritten for optimization purposes
+     */
+    @Override
+    public void writeTo(OutputStream os, String[] ignoreList) throws 
IOException, MessagingException {
+        getWrappedMessage().writeTo(os, ignoreList);
+    }
+
+    /*
+     * Various reader methods
+     */
+
+    @Override
+    public Address[] getFrom() throws MessagingException {
+        return getWrappedMessage().getFrom();
+    }
+
+    @Override
+    public Address[] getRecipients(Message.RecipientType type) throws 
MessagingException {
+        return getWrappedMessage().getRecipients(type);
+    }
+
+    @Override
+    public Address[] getAllRecipients() throws MessagingException {
+        return getWrappedMessage().getAllRecipients();
+    }
+
+    @Override
+    public Address[] getReplyTo() throws MessagingException {
+        return getWrappedMessage().getReplyTo();
+    }
+
+    @Override
+    public String getSubject() throws MessagingException {
+        return getWrappedMessage().getSubject();
+    }
+
+    @Override
+    public Date getSentDate() throws MessagingException {
+        return getWrappedMessage().getSentDate();
+    }
+
+    @Override
+    public Date getReceivedDate() throws MessagingException {
+        return getWrappedMessage().getReceivedDate();
+    }
+
+    @Override
+    public int getSize() throws MessagingException {
+        return getWrappedMessage().getSize();
+    }
+
+    @Override
+    public int getLineCount() throws MessagingException {
+        return getWrappedMessage().getLineCount();
+    }
+
+    @Override
+    public String getContentType() throws MessagingException {
+        return getWrappedMessage().getContentType();
+    }
+
+    @Override
+    public boolean isMimeType(String mimeType) throws MessagingException {
+        return getWrappedMessage().isMimeType(mimeType);
+    }
+
+    @Override
+    public String getDisposition() throws MessagingException {
+        return getWrappedMessage().getDisposition();
+    }
+
+    @Override
+    public String getEncoding() throws MessagingException {
+        return getWrappedMessage().getEncoding();
+    }
+
+    @Override
+    public String getContentID() throws MessagingException {
+        return getWrappedMessage().getContentID();
+    }
+
+    @Override
+    public String getContentMD5() throws MessagingException {
+        return getWrappedMessage().getContentMD5();
+    }
+
+    @Override
+    public String getDescription() throws MessagingException {
+        return getWrappedMessage().getDescription();
+    }
+
+    @Override
+    public String[] getContentLanguage() throws MessagingException {
+        return getWrappedMessage().getContentLanguage();
+    }
+
+    @Override
+    public String getMessageID() throws MessagingException {
+        return getWrappedMessage().getMessageID();
+    }
+
+    @Override
+    public String getFileName() throws MessagingException {
+        return getWrappedMessage().getFileName();
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException, MessagingException 
{
+        return getWrappedMessage().getInputStream();
+    }
+
+    @Override
+    public DataHandler getDataHandler() throws MessagingException {
+        return getWrappedMessage().getDataHandler();
+    }
+
+    @Override
+    public Object getContent() throws IOException, MessagingException {
+        return getWrappedMessage().getContent();
+    }
+
+    @Override
+    public String[] getHeader(String name) throws MessagingException {
+        return getWrappedMessage().getHeader(name);
+    }
+
+    @Override
+    public String getHeader(String name, String delimiter) throws 
MessagingException {
+        return getWrappedMessage().getHeader(name, delimiter);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getAllHeaders() throws MessagingException {
+        return getWrappedMessage().getAllHeaders();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getMatchingHeaders(String[] names) throws 
MessagingException {
+        return getWrappedMessage().getMatchingHeaders(names);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getNonMatchingHeaders(String[] names) throws 
MessagingException {
+        return getWrappedMessage().getNonMatchingHeaders(names);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+        return getWrappedMessage().getAllHeaderLines();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getMatchingHeaderLines(String[] names) throws 
MessagingException {
+        return getWrappedMessage().getMatchingHeaderLines(names);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names) 
throws MessagingException {
+        return getWrappedMessage().getNonMatchingHeaderLines(names);
+    }
+
+    @Override
+    public Flags getFlags() throws MessagingException {
+        return getWrappedMessage().getFlags();
+    }
+
+    @Override
+    public boolean isSet(Flags.Flag flag) throws MessagingException {
+        return getWrappedMessage().isSet(flag);
+    }
+
+    @Override
+    public Address getSender() throws MessagingException {
+        return getWrappedMessage().getSender();
+    }
+
+    @Override
+    public boolean match(SearchTerm arg0) throws MessagingException {
+        return getWrappedMessage().match(arg0);
+    }
+
+    @Override
+    public InputStream getRawInputStream() throws MessagingException {
+        return getWrappedMessage().getRawInputStream();
+    }
+
+    @Override
+    public Folder getFolder() {
+        return getWrappedMessage().getFolder();
+    }
+
+    @Override
+    public int getMessageNumber() {
+        return getWrappedMessage().getMessageNumber();
+    }
+
+    @Override
+    public boolean isExpunged() {
+        return getWrappedMessage().isExpunged();
+    }
+
+    @Override
+    public boolean equals(Object arg0) {
+        return getWrappedMessage().equals(arg0);
+    }
+
+    @Override
+    public int hashCode() {
+        return getWrappedMessage().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return getWrappedMessage().toString();
+    }
+
+    @Override
+    public void setFrom(Address address) throws MessagingException {
+        getWrappedMessageForWriting().setFrom(address);
+    }
+
+    @Override
+    public void setFrom() throws MessagingException {
+        getWrappedMessageForWriting().setFrom();
+    }
+
+    @Override
+    public void addFrom(Address[] addresses) throws MessagingException {
+        getWrappedMessageForWriting().addFrom(addresses);
+    }
+
+    @Override
+    public void setRecipients(Message.RecipientType type, Address[] addresses) 
throws MessagingException {
+        getWrappedMessageForWriting().setRecipients(type, addresses);
+    }
+
+    @Override
+    public void addRecipients(Message.RecipientType type, Address[] addresses) 
throws MessagingException {
+        getWrappedMessageForWriting().addRecipients(type, addresses);
+    }
+
+    @Override
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+        getWrappedMessageForWriting().setReplyTo(addresses);
+    }
+
+    @Override
+    public void setSubject(String subject) throws MessagingException {
+        getWrappedMessageForWriting().setSubject(subject);
+    }
+
+    @Override
+    public void setSubject(String subject, String charset) throws 
MessagingException {
+        getWrappedMessageForWriting().setSubject(subject, charset);
+    }
+
+    @Override
+    public void setSentDate(Date d) throws MessagingException {
+        getWrappedMessageForWriting().setSentDate(d);
+    }
+
+    @Override
+    public void setDisposition(String disposition) throws MessagingException {
+        getWrappedMessageForWriting().setDisposition(disposition);
+    }
+
+    @Override
+    public void setContentID(String cid) throws MessagingException {
+        getWrappedMessageForWriting().setContentID(cid);
+    }
+
+    @Override
+    public void setContentMD5(String md5) throws MessagingException {
+        getWrappedMessageForWriting().setContentMD5(md5);
+    }
+
+    @Override
+    public void setDescription(String description) throws MessagingException {
+        getWrappedMessageForWriting().setDescription(description);
+    }
+
+    @Override
+    public void setDescription(String description, String charset) throws 
MessagingException {
+        getWrappedMessageForWriting().setDescription(description, charset);
+    }
+
+    @Override
+    public void setContentLanguage(String[] languages) throws 
MessagingException {
+        getWrappedMessageForWriting().setContentLanguage(languages);
+    }
+
+    @Override
+    public void setFileName(String filename) throws MessagingException {
+        getWrappedMessageForWriting().setFileName(filename);
+    }
+
+    @Override
+    public void setDataHandler(DataHandler dh) throws MessagingException {
+        getWrappedMessageForWriting().setDataHandler(dh);
+    }
+
+    @Override
+    public void setContent(Object o, String type) throws MessagingException {
+        getWrappedMessageForWriting().setContent(o, type);
+    }
+
+    @Override
+    public void setText(String text) throws MessagingException {
+        getWrappedMessageForWriting().setText(text);
+    }
+
+    @Override
+    public void setText(String text, String charset) throws MessagingException 
{
+        getWrappedMessageForWriting().setText(text, charset);
+    }
+
+    @Override
+    public void setContent(Multipart mp) throws MessagingException {
+        getWrappedMessageForWriting().setContent(mp);
+    }
+
+    /**
+     * This does not need a writable message
+     */
+    @Override
+    public Message reply(boolean replyToAll) throws MessagingException {
+        return getWrappedMessage().reply(replyToAll);
+    }
+
+    @Override
+    public void setHeader(String name, String value) throws MessagingException 
{
+        getWrappedMessageForWriting().setHeader(name, value);
+    }
+
+    @Override
+    public void addHeader(String name, String value) throws MessagingException 
{
+        getWrappedMessageForWriting().addHeader(name, value);
+    }
+
+    @Override
+    public void removeHeader(String name) throws MessagingException {
+        getWrappedMessageForWriting().removeHeader(name);
+    }
+
+    @Override
+    public void addHeaderLine(String line) throws MessagingException {
+        getWrappedMessageForWriting().addHeaderLine(line);
+    }
+
+    @Override
+    public void setFlags(Flags flag, boolean set) throws MessagingException {
+        getWrappedMessageForWriting().setFlags(flag, set);
+    }
+
+    @Override
+    public void saveChanges() throws MessagingException {
+        getWrappedMessageForWriting().saveChanges();
+    }
+
+    /*
+     * Since JavaMail 1.2
+     */
+
+    @Override
+    public void addRecipients(Message.RecipientType type, String addresses) 
throws MessagingException {
+        getWrappedMessageForWriting().addRecipients(type, addresses);
+    }
+
+    @Override
+    public void setRecipients(Message.RecipientType type, String addresses) 
throws MessagingException {
+        getWrappedMessageForWriting().setRecipients(type, addresses);
+    }
+
+    @Override
+    public void setSender(Address arg0) throws MessagingException {
+        getWrappedMessageForWriting().setSender(arg0);
+    }
+
+    public void addRecipient(RecipientType arg0, Address arg1) throws 
MessagingException {
+        getWrappedMessageForWriting().addRecipient(arg0, arg1);
+    }
+
+    @Override
+    public void setFlag(Flag arg0, boolean arg1) throws MessagingException {
+        getWrappedMessageForWriting().setFlag(arg0, arg1);
+    }
+
+    public long getMessageSize() throws MessagingException {
+        return MimeMessageUtil.getMessageSize(getWrappedMessage());
+    }
+
+    /**
+     * Since javamail 1.4
+     */
+    @Override
+    public void setText(String text, String charset, String subtype) throws 
MessagingException {
+        getWrappedMessage().setText(text, charset, subtype);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStream.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStream.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStream.java
new file mode 100644
index 0000000..c3e27d4
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStream.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * Provide an {@link InputStream} over an {@link MimeMessage}
+ */
+public class MimeMessageInputStream extends InputStream {
+    private final InputStream in;
+
+    /**
+     * Provide an {@link InputStream} over a {@link MimeMessage}.
+     * 
+     * @param message
+     *            the message to wrap
+     * @param tryCast
+     *            try to cast the {@link MimeMessage} to
+     *            {@link MimeMessageCopyOnWriteProxy} /
+     *            {@link MimeMessageWrapper} to do some optimized processing if
+     *            possible
+     * @throws MessagingException
+     */
+    public MimeMessageInputStream(MimeMessage message, boolean tryCast) throws 
MessagingException {
+        MimeMessage m = message;
+
+        // check if we need to use the wrapped message
+        if (tryCast && m instanceof MimeMessageCopyOnWriteProxy) {
+            m = ((MimeMessageCopyOnWriteProxy) m).getWrappedMessage();
+        }
+
+        // check if we can use optimized operations
+        if (tryCast && m instanceof MimeMessageWrapper) {
+            in = ((MimeMessageWrapper) m).getMessageInputStream();
+        } else {
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            try {
+                message.writeTo(out);
+                in = new ByteArrayInputStream(out.toByteArray());
+            } catch (IOException e1) {
+                throw new MessagingException("Unable to read message " + 
message, e1);
+            }
+
+        }
+
+    }
+
+    /**
+     * Use true as tryCast parameter
+     * 
+     * {@link #MimeMessageInputStream(MimeMessage, boolean)}
+     */
+    public MimeMessageInputStream(MimeMessage message) throws 
MessagingException {
+        this(message, true);
+    }
+
+    @Override
+    public int read() throws IOException {
+        return in.read();
+    }
+
+    @Override
+    public void close() throws IOException {
+        in.close();
+    }
+
+    @Override
+    public int available() throws IOException {
+        return in.available();
+    }
+
+    @Override
+    public void mark(int readlimit) {
+        in.mark(readlimit);
+    }
+
+    @Override
+    public boolean markSupported() {
+        return in.markSupported();
+    }
+
+    @Override
+    public int read(byte[] b, int off, int len) throws IOException {
+        return in.read(b, off, len);
+    }
+
+    @Override
+    public int read(byte[] b) throws IOException {
+        return in.read(b);
+    }
+
+    @Override
+    public void reset() throws IOException {
+        in.reset();
+    }
+
+    @Override
+    public long skip(long n) throws IOException {
+        return in.skip(n);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStreamSource.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStreamSource.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStreamSource.java
new file mode 100644
index 0000000..0eea1de
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageInputStreamSource.java
@@ -0,0 +1,174 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.util.SharedByteArrayInputStream;
+import javax.mail.util.SharedFileInputStream;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.DeferredFileOutputStream;
+import org.apache.james.lifecycle.api.Disposable;
+
+/**
+ * Takes an input stream and creates a repeatable input stream source for a
+ * MimeMessageWrapper. It does this by completely reading the input stream and
+ * saving that to data to an {@link DeferredFileOutputStream} with its 
threshold set to 100kb
+ */
+public class MimeMessageInputStreamSource extends MimeMessageSource implements 
Disposable {
+
+    private final List<InputStream> streams = new ArrayList<>();
+
+    /**
+     * A temporary file used to hold the message stream
+     */
+    private DeferredFileOutputStream out;
+
+    /**
+     * The full path of the temporary file
+     */
+    private final String sourceId;
+
+    /**
+     * 100kb threshold for the stream.
+     */
+    private final static int THRESHOLD = 1024 * 100;
+
+    /**
+     * Temporary directory to use
+     */
+    private final static File TMPDIR = new 
File(System.getProperty("java.io.tmpdir"));
+
+    /**
+     * Construct a new MimeMessageInputStreamSource from an
+     * <code>InputStream</code> that contains the bytes of a MimeMessage.
+     *
+     * @param key the prefix for the name of the temp file
+     * @param in  the stream containing the MimeMessage
+     * @throws MessagingException if an error occurs while trying to store the 
stream
+     */
+    public MimeMessageInputStreamSource(String key, InputStream in) throws 
MessagingException {
+        super();
+        // We want to immediately read this into a temporary file
+        // Create a temp file and channel the input stream into it
+        try {
+            out = new DeferredFileOutputStream(THRESHOLD, "mimemessage-" + 
key, ".m64", TMPDIR);
+            IOUtils.copy(in, out);
+            sourceId = key;
+        } catch (IOException ioe) {
+            File file = out.getFile();
+            if (file != null) {
+                FileUtils.deleteQuietly(file);
+            }
+            throw new MessagingException("Unable to retrieve the data: " + 
ioe.getMessage(), ioe);
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+            } catch (IOException ioe) {
+                // Ignored - logging unavailable to log this non-fatal error.
+            }
+
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ioe) {
+                // Ignored - logging unavailable to log this non-fatal error.
+            }
+
+        }
+    }
+
+    public MimeMessageInputStreamSource(String key) {
+        super();
+        out = new DeferredFileOutputStream(THRESHOLD, key, ".m64", TMPDIR);
+        sourceId = key;
+    }
+
+    /**
+     * Returns the unique identifier of this input stream source
+     *
+     * @return the unique identifier for this MimeMessageInputStreamSource
+     */
+    public String getSourceId() {
+        return sourceId;
+    }
+
+    /**
+     * Get an input stream to retrieve the data stored in the temporary file
+     *
+     * @return a <code>BufferedInputStream</code> containing the data
+     */
+    public synchronized InputStream getInputStream() throws IOException {
+        InputStream in;
+        if (out.isInMemory()) {
+            in = new SharedByteArrayInputStream(out.getData());
+        } else {
+            in = new SharedFileInputStream(out.getFile());
+        }
+        streams.add(in);
+        return in;
+    }
+
+    /**
+     * Get the size of the temp file
+     *
+     * @return the size of the temp file
+     * @throws IOException if an error is encoutered while computing the size 
of the
+     *                     message
+     */
+    @Override
+    public long getMessageSize() throws IOException {
+        return out.getByteCount();
+    }
+
+    public OutputStream getWritableOutputStream() {
+        return out;
+    }
+
+    @Override
+    public void dispose() {
+        // explicit close all streams
+        for (InputStream stream : streams) {
+            IOUtils.closeQuietly(stream);
+        }
+
+        if (out != null) {
+            IOUtils.closeQuietly(out);
+            File file = out.getFile();
+            if (file != null) {
+                FileUtils.deleteQuietly(file);
+                file = null;
+            }
+            out = null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageSource.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageSource.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageSource.java
new file mode 100644
index 0000000..d5375a1
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageSource.java
@@ -0,0 +1,84 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This defines a reusable datasource that can supply an input stream with
+ * MimeMessage data. This allows a MimeMessageWrapper or other classes to grab
+ * the underlying data.
+ * 
+ * @see MimeMessageWrapper
+ */
+public abstract class MimeMessageSource {
+
+    /**
+     * Returns a unique String ID that represents the location from where this
+     * file is loaded. This will be used to identify where the data is,
+     * primarily to avoid situations where this data would get overwritten.
+     * 
+     * @return the String ID
+     */
+    public abstract String getSourceId();
+
+    /**
+     * Get an input stream to retrieve the data stored in the datasource
+     * 
+     * @return a <code>InputStream</code> containing the data
+     * 
+     * @throws IOException
+     *             if an error occurs while generating the InputStream
+     */
+    public abstract InputStream getInputStream() throws IOException;
+
+    /**
+     * Return the size of all the data. Default implementation... others can
+     * override to do this much faster
+     * 
+     * @return the size of the data represented by this source
+     * @throws IOException
+     *             if an error is encountered while computing the message size
+     */
+    public long getMessageSize() throws IOException {
+        int size = 0;
+        InputStream in = null;
+        try {
+            in = getInputStream();
+            int read;
+            byte[] data = new byte[1024];
+            while ((read = in.read(data)) > 0) {
+                size += read;
+            }
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException ioe) {
+                // Exception ignored because logging is
+                // unavailable
+            }
+        }
+        return size;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/936746b9/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageUtil.java
----------------------------------------------------------------------
diff --git 
a/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageUtil.java
 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageUtil.java
new file mode 100644
index 0000000..698ad5d
--- /dev/null
+++ 
b/server/container/core/src/main/java/org/apache/james/server/core/MimeMessageUtil.java
@@ -0,0 +1,311 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you 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.                                           *
+ ****************************************************************/
+
+package org.apache.james.server.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Enumeration;
+
+import javax.activation.UnsupportedDataTypeException;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Utility class to provide optimized write methods for the various MimeMessage
+ * implementations.
+ */
+public class MimeMessageUtil {
+
+    /**
+     * Convenience method to take any MimeMessage and write the headers and 
body
+     * to two different output streams
+     * 
+     * @param message
+     *            the MimeMessage reading from
+     * @param headerOs
+     *            the OutputStream writting the headers to
+     * @param bodyOs
+     *            the OutputStream writting the body to
+     * @throws IOException
+     *             get thrown if an IO Error detected while writing to the
+     *             streams
+     * @throws MessagingException
+     *             get thrown if an error detected while reading the message
+     */
+    public static void writeTo(MimeMessage message, OutputStream headerOs, 
OutputStream bodyOs) throws IOException, MessagingException {
+        writeTo(message, headerOs, bodyOs, null);
+    }
+
+    /**
+     * Convenience method to take any MimeMessage and write the headers and 
body
+     * to two different output streams, with an ignore list
+     * 
+     * @param message
+     *            the MimeMessage reading from
+     * @param headerOs
+     *            the OutputStream writting the headers to
+     * @param bodyOs
+     *            the OutputStream writting the body to
+     * @param ignoreList
+     *            the String[] which contains headers which should be ignored
+     * @throws IOException
+     *             get thrown if an IO Error detected while writing to the
+     *             streams
+     * @throws MessagingException
+     *             get thrown if an error detected while reading the message
+     */
+    public static void writeTo(MimeMessage message, OutputStream headerOs, 
OutputStream bodyOs, String[] ignoreList) throws IOException, 
MessagingException {
+        MimeMessage testMessage = message;
+        if (message instanceof MimeMessageCopyOnWriteProxy) {
+            MimeMessageCopyOnWriteProxy wr = (MimeMessageCopyOnWriteProxy) 
message;
+            testMessage = wr.getWrappedMessage();
+        }
+        if (testMessage instanceof MimeMessageWrapper) {
+            MimeMessageWrapper wrapper = (MimeMessageWrapper) testMessage;
+            if (!wrapper.isModified()) {
+                wrapper.writeTo(headerOs, bodyOs, ignoreList);
+                return;
+            }
+        }
+        writeToInternal(message, headerOs, bodyOs, ignoreList);
+    }
+
+    /**
+     * 
+     * @param message
+     * @param headerOs
+     * @param bodyOs
+     * @param ignoreList
+     * @throws MessagingException
+     * @throws IOException
+     * @throws UnsupportedDataTypeException
+     */
+    public static void writeToInternal(MimeMessage message, OutputStream 
headerOs, OutputStream bodyOs, String[] ignoreList) throws MessagingException, 
IOException {
+        if (message.getMessageID() == null) {
+            message.saveChanges();
+        }
+
+        writeHeadersTo(message, headerOs, ignoreList);
+
+        // Write the body to the output stream
+        writeMessageBodyTo(message, bodyOs);
+    }
+
+    /**
+     * Write message body of given mimeessage to the given outputStream
+     * 
+     * @param message
+     *            the MimeMessage used as input
+     * @param bodyOs
+     *            the OutputStream to write the message body to
+     * @throws IOException
+     * @throws UnsupportedDataTypeException
+     * @throws MessagingException
+     */
+    public static void writeMessageBodyTo(MimeMessage message, OutputStream 
bodyOs) throws IOException, MessagingException {
+        OutputStream bos;
+        InputStream bis;
+
+        try {
+            // Get the message as a stream. This will encode
+            // objects as necessary, and we have some input from
+            // decoding an re-encoding the stream. I'd prefer the
+            // raw stream, but see
+            bos = MimeUtility.encode(bodyOs, message.getEncoding());
+            bis = message.getInputStream();
+        } catch (UnsupportedDataTypeException | MessagingException udte) {
+            /*
+             * If we get an UnsupportedDataTypeException try using the raw 
input
+             * stream as a "best attempt" at rendering a message.
+             * 
+             * WARNING: JavaMail v1.3 getRawInputStream() returns INVALID
+             * (unchanged) content for a changed message. getInputStream() 
works
+             * properly, but in this case has failed due to a missing
+             * DataHandler.
+             * 
+             * MimeMessage.getRawInputStream() may throw a "no content"
+             * MessagingException. In JavaMail v1.3, when you initially create 
a
+             * message using MimeMessage APIs, there is no raw content
+             * available. getInputStream() works, but getRawInputStream() 
throws
+             * an exception. If we catch that exception, throw the UDTE. It
+             * should mean that someone has locally constructed a message part
+             * for which JavaMail doesn't have a DataHandler.
+             */
+
+            try {
+                bis = message.getRawInputStream();
+                bos = bodyOs;
+            } catch (javax.mail.MessagingException ignored) {
+                throw udte;
+            }
+        }
+
+        try {
+            IOUtils.copy(bis, bos);
+        } finally {
+            IOUtils.closeQuietly(bis);
+        }
+    }
+
+    /**
+     * Write the message headers to the given outputstream
+     * 
+     * @param message
+     *            the MimeMessage to read from
+     * @param headerOs
+     *            the OutputStream to which the headers get written
+     * @param ignoreList
+     *            the String[] which holds headers which should be ignored
+     * @throws MessagingException
+     */
+    private static void writeHeadersTo(MimeMessage message, OutputStream 
headerOs, String[] ignoreList) throws MessagingException {
+        // Write the headers (minus ignored ones)
+        @SuppressWarnings("unchecked")
+        Enumeration<String> headers = 
message.getNonMatchingHeaderLines(ignoreList);
+        writeHeadersTo(headers, headerOs);
+    }
+
+    /**
+     * Write the message headers to the given outputstream
+     * 
+     * @param headers
+     *            the Enumeration which holds the headers
+     * @param headerOs
+     *            the OutputStream to which the headers get written
+     * @throws MessagingException
+     */
+    public static void writeHeadersTo(Enumeration<String> headers, 
OutputStream headerOs) throws MessagingException {
+        try {
+            IOUtils.copy(new InternetHeadersInputStream(headers), headerOs);
+        } catch (IOException e) {
+            throw new MessagingException("Unable to write headers to stream", 
e);
+        }
+    }
+
+    /**
+     * Get an InputStream which holds all headers of the given MimeMessage
+     * 
+     * @param message
+     *            the MimeMessage used as source
+     * @param ignoreList
+     *            the String[] which holds headers which should be ignored
+     * @return stream the InputStream which holds the headers
+     * @throws MessagingException
+     */
+    @SuppressWarnings("unchecked")
+    public static InputStream getHeadersInputStream(MimeMessage message, 
String[] ignoreList) throws MessagingException {
+        return new 
InternetHeadersInputStream(message.getNonMatchingHeaderLines(ignoreList));
+    }
+
+    /**
+     * Slow method to calculate the exact size of a message!
+     */
+    private static final class SizeCalculatorOutputStream extends OutputStream 
{
+        long size = 0;
+
+        public void write(int arg0) throws IOException {
+            size++;
+        }
+
+        public long getSize() {
+            return size;
+        }
+
+        public void write(byte[] arg0, int arg1, int arg2) throws IOException {
+            size += arg2;
+        }
+
+        public void write(byte[] arg0) throws IOException {
+            size += arg0.length;
+        }
+    }
+
+    /**
+     * Return the full site of an mimeMessage
+     * 
+     * @return size of full message including headers
+     * @throws MessagingException
+     *             if a problem occours while computing the message size
+     */
+    public static long getMessageSize(MimeMessage message) throws 
MessagingException {
+        // If we have a MimeMessageWrapper, then we can ask it for just the
+        // message size and skip calculating it
+        long size = -1;
+
+        if (message instanceof MimeMessageWrapper) {
+            MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
+            size = wrapper.getMessageSize();
+        } else if (message instanceof MimeMessageCopyOnWriteProxy) {
+            MimeMessageCopyOnWriteProxy wrapper = 
(MimeMessageCopyOnWriteProxy) message;
+            size = wrapper.getMessageSize();
+        }
+
+        if (size == -1) {
+            size = calculateMessageSize(message);
+        }
+
+        return size;
+    }
+
+    /**
+     * Calculate the size of the give mimeMessage
+     * 
+     * @param message
+     *            the MimeMessage
+     * @return size the calculated size
+     * @throws MessagingException
+     *             if a problem occours while calculate the message size
+     */
+    public static long calculateMessageSize(MimeMessage message) throws 
MessagingException {
+        long size;
+        // SK: Should probably eventually store this as a locally
+        // maintained value (so we don't have to load and reparse
+        // messages each time).
+        size = message.getSize();
+        if (size != -1) {
+            Enumeration<?> e = message.getAllHeaderLines();
+            if (e.hasMoreElements()) {
+                size += 2;
+            }
+            while (e.hasMoreElements()) {
+                // add 2 bytes for the CRLF
+                size += ((String) e.nextElement()).length() + 2;
+            }
+        }
+
+        if (size == -1) {
+            SizeCalculatorOutputStream out = new SizeCalculatorOutputStream();
+            try {
+                message.writeTo(out);
+            } catch (IOException e) {
+                // should never happen as SizeCalculator does not actually 
throw
+                // IOExceptions.
+                throw new MessagingException("IOException wrapped by 
getMessageSize", e);
+            }
+            size = out.getSize();
+        }
+        return size;
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to