You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ba...@apache.org on 2006/01/03 01:58:28 UTC

svn commit: r365502 - in /james/server/trunk/src: java/org/apache/james/core/ java/org/apache/james/mailrepository/ test/org/apache/james/core/ test/org/apache/james/transport/

Author: bago
Date: Mon Jan  2 16:58:17 2006
New Revision: 365502

URL: http://svn.apache.org/viewcvs?rev=365502&view=rev
Log:
Implementation of a CopyOnWrite proxy over the MimeMessages. Now James always try to wrap a MimeMessage in this proxy counting references and cloning the message on write operations if shared. This should fix JAMES-421 without performance loss.
Also added a few tests of the new classes. The previous LinearProcessorTest now passes!

Added:
    james/server/trunk/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java
    james/server/trunk/src/java/org/apache/james/core/MimeMessageUtil.java
    james/server/trunk/src/test/org/apache/james/core/MimeMessageCopyOnWriteProxyTest.java
Modified:
    james/server/trunk/src/java/org/apache/james/core/MailImpl.java
    james/server/trunk/src/java/org/apache/james/core/MimeMessageWrapper.java
    james/server/trunk/src/java/org/apache/james/mailrepository/AvalonMailRepository.java
    james/server/trunk/src/java/org/apache/james/mailrepository/JDBCMailRepository.java
    james/server/trunk/src/test/org/apache/james/transport/LinearProcessorTest.java

Modified: james/server/trunk/src/java/org/apache/james/core/MailImpl.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/core/MailImpl.java?rev=365502&r1=365501&r2=365502&view=diff
==============================================================================
--- james/server/trunk/src/java/org/apache/james/core/MailImpl.java (original)
+++ james/server/trunk/src/java/org/apache/james/core/MailImpl.java Mon Jan  2 16:58:17 2006
@@ -133,7 +133,7 @@
             }
         }
     }
-    
+
     /**
      * @param mail
      * @param newName
@@ -177,8 +177,7 @@
         throws MessagingException {
         this(name, sender, recipients);
         MimeMessageSource source = new MimeMessageInputStreamSource(name, messageIn);
-        MimeMessageWrapper wrapper = new MimeMessageWrapper(source);
-        this.setMessage(wrapper);
+        this.setMessage(new MimeMessageCopyOnWriteProxy(source));
     }
 
     /**
@@ -190,9 +189,9 @@
      * @param recipients the collection of recipients of this MailImpl
      * @param message the MimeMessage associated with this MailImpl
      */
-    public MailImpl(String name, MailAddress sender, Collection recipients, MimeMessage message) {
+    public MailImpl(String name, MailAddress sender, Collection recipients, MimeMessage message) throws MessagingException {
         this(name, sender, recipients);
-        this.setMessage(message);
+        this.setMessage(new MimeMessageCopyOnWriteProxy(message));
     }
 
     /**
@@ -289,6 +288,7 @@
     public MimeMessage getMessage() throws MessagingException {
         return message;
     }
+    
     /**
      * Set the name of this MailImpl.
      *
@@ -372,6 +372,10 @@
             MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
             return wrapper.getMessageSize();
         }
+        if (message instanceof MimeMessageCopyOnWriteProxy) {
+            MimeMessageCopyOnWriteProxy wrapper = (MimeMessageCopyOnWriteProxy) message;
+            return wrapper.getMessageSize();
+        }
         //SK: Should probably eventually store this as a locally
         //  maintained value (so we don't have to load and reparse
         //  messages each time).
@@ -535,12 +539,8 @@
      * @see org.apache.avalon.framework.activity.Disposable#dispose()
      */
     public void dispose() {
-        try {
-            MimeMessage wrapper = getMessage();
-            ContainerUtil.dispose(wrapper);
-        } catch (MessagingException me) {
-            // Ignored
-        }
+        ContainerUtil.dispose(message);
+        message = null;
     }
 
     /**

Added: james/server/trunk/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java?rev=365502&view=auto
==============================================================================
--- james/server/trunk/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java (added)
+++ james/server/trunk/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java Mon Jan  2 16:58:17 2006
@@ -0,0 +1,818 @@
+/***********************************************************************
+ * Copyright (c) 2000-2005 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.                      *
+ ***********************************************************************/
+
+package org.apache.james.core;
+
+import org.apache.avalon.framework.activity.Disposable;
+
+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 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
+     */
+    class ReferenceCounter {
+
+        /**
+         * reference counter
+         */
+        private int referenceCount = 1;
+
+        /**
+         * @param original
+         *            MimeMessageWrapper
+         * @throws MessagingException
+         */
+        public ReferenceCounter() throws MessagingException {
+            this(0);
+        }
+
+        /**
+         * @param original
+         *            MimeMessage to wrap
+         * @param writable
+         *            if true we can alter the message itself, otherwise copy on
+         *            write it.
+         * @throws MessagingException
+         */
+        public ReferenceCounter(int startCounter) throws MessagingException {
+            referenceCount = startCounter;
+        }
+
+        protected synchronized void incrementReferenceCount() {
+            referenceCount++;
+        }
+
+        protected synchronized void decrementReferenceCount() {
+            referenceCount--;
+        }
+
+        protected synchronized int getReferenceCount() {
+            return referenceCount;
+        }
+
+    }
+
+    ReferenceCounter refCount;
+
+    /**
+     * @param original
+     *            MimeMessageWrapper
+     * @throws MessagingException
+     */
+    public MimeMessageCopyOnWriteProxy(MimeMessage original)
+            throws MessagingException {
+        this(original, false);
+    }
+
+    /**
+     * @param original
+     *            MimeMessageSource
+     * @throws MessagingException
+     */
+    public MimeMessageCopyOnWriteProxy(MimeMessageSource original)
+            throws MessagingException {
+        this(new MimeMessageWrapper(original), true);
+    }
+
+    /**
+     * Private constructor providing an external reference counter.
+     * 
+     * @param original
+     * @param refCount
+     * @throws MessagingException
+     */
+    private MimeMessageCopyOnWriteProxy(MimeMessage original,
+            boolean writeable)
+            throws MessagingException {
+        super(Session.getDefaultInstance(System.getProperties(), null));
+
+        this.wrapped = original;
+        if (wrapped instanceof MimeMessageCopyOnWriteProxy) {
+            refCount = ((MimeMessageCopyOnWriteProxy) wrapped).refCount;
+            wrapped = ((MimeMessageCopyOnWriteProxy) wrapped).getWrappedMessage();
+        } else {
+            refCount = new ReferenceCounter();
+        }
+        
+        if (!writeable) {
+            refCount.incrementReferenceCount();
+        }
+    }
+
+    /**
+     * Check the number of references over the MimeMessage and clone it if
+     * needed.
+     * 
+     * @throws MessagingException
+     *             exception
+     */
+    protected void checkCopyOnWrite() throws MessagingException {
+        synchronized (refCount) {
+            if (refCount.getReferenceCount() > 1) {
+                refCount.decrementReferenceCount();
+
+                refCount = new ReferenceCounter(1);
+                if (wrapped instanceof MimeMessageWrapper) {
+                    MimeMessageWrapper mmw = (MimeMessageWrapper) wrapped;
+                    if (!mmw.isModified()) {
+                        wrapped = new MimeMessageWrapper(mmw.source);
+                    } else {
+                        wrapped = new MimeMessage(wrapped);
+                    }
+                } else {
+                    // Not sure this will really clone the MimeMessage!
+                    wrapped = new MimeMessage(wrapped);
+                }
+            }
+        }
+    }
+
+    /**
+     * The mime message in memory
+     */
+    protected MimeMessage wrapped = null;
+
+    /**
+     * Rewritten for optimization purposes
+     */
+    public void writeTo(OutputStream os) throws IOException, MessagingException {
+        wrapped.writeTo(os);
+    }
+
+    /**
+     * Rewritten for optimization purposes
+     */
+    public void writeTo(OutputStream os, String[] ignoreList)
+            throws IOException, MessagingException {
+        wrapped.writeTo(os, ignoreList);
+    }
+
+    /**
+     * Various reader methods
+     */
+    
+    /**
+     * @see javax.mail.Message#getFrom()
+     */
+    public Address[] getFrom() throws MessagingException {
+        return wrapped.getFrom();
+    }
+
+    /**
+     * @see javax.mail.Message#getRecipients(javax.mail.Message.RecipientType)
+     */
+    public Address[] getRecipients(Message.RecipientType type)
+            throws MessagingException {
+        return wrapped.getRecipients(type);
+    }
+
+    /**
+     * @see javax.mail.Message#getAllRecipients()
+     */
+    public Address[] getAllRecipients() throws MessagingException {
+        return wrapped.getAllRecipients();
+    }
+
+    /**
+     * @see javax.mail.Message#getReplyTo()
+     */
+    public Address[] getReplyTo() throws MessagingException {
+        return wrapped.getReplyTo();
+    }
+
+    /**
+     * @see javax.mail.Message#getSubject()
+     */
+    public String getSubject() throws MessagingException {
+        return wrapped.getSubject();
+    }
+
+    /**
+     * @see javax.mail.Message#getSentDate()
+     */
+    public Date getSentDate() throws MessagingException {
+        return wrapped.getSentDate();
+    }
+
+    /**
+     * @see javax.mail.Message#getReceivedDate()
+     */
+    public Date getReceivedDate() throws MessagingException {
+        return wrapped.getReceivedDate();
+    }
+
+    /**
+     * @see javax.mail.Part#getSize()
+     */
+    public int getSize() throws MessagingException {
+        return wrapped.getSize();
+    }
+
+    /**
+     * @see javax.mail.Part#getLineCount()
+     */
+    public int getLineCount() throws MessagingException {
+        return wrapped.getLineCount();
+    }
+
+    /**
+     * @see javax.mail.Part#getContentType()
+     */
+    public String getContentType() throws MessagingException {
+        return wrapped.getContentType();
+    }
+
+    /**
+     * @see javax.mail.Part#isMimeType(java.lang.String)
+     */
+    public boolean isMimeType(String mimeType) throws MessagingException {
+        return wrapped.isMimeType(mimeType);
+    }
+
+    /**
+     * @see javax.mail.Part#getDisposition()
+     */
+    public String getDisposition() throws MessagingException {
+        return wrapped.getDisposition();
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getEncoding()
+     */
+    public String getEncoding() throws MessagingException {
+        return wrapped.getEncoding();
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getContentID()
+     */
+    public String getContentID() throws MessagingException {
+        return wrapped.getContentID();
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getContentMD5()
+     */
+    public String getContentMD5() throws MessagingException {
+        return wrapped.getContentMD5();
+    }
+
+    /**
+     * @see javax.mail.Part#getDescription()
+     */
+    public String getDescription() throws MessagingException {
+        return wrapped.getDescription();
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getContentLanguage()
+     */
+    public String[] getContentLanguage() throws MessagingException {
+        return wrapped.getContentLanguage();
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#getMessageID()
+     */
+    public String getMessageID() throws MessagingException {
+        return wrapped.getMessageID();
+    }
+
+    /**
+     * @see javax.mail.Part#getFileName()
+     */
+    public String getFileName() throws MessagingException {
+        return wrapped.getFileName();
+    }
+
+    /**
+     * @see javax.mail.Part#getInputStream()
+     */
+    public InputStream getInputStream() throws IOException, MessagingException {
+        return wrapped.getInputStream();
+    }
+
+    /**
+     * @see javax.mail.Part#getDataHandler()
+     */
+    public DataHandler getDataHandler() throws MessagingException {
+        return wrapped.getDataHandler();
+    }
+
+    /**
+     * @see javax.mail.Part#getContent()
+     */
+    public Object getContent() throws IOException, MessagingException {
+        return wrapped.getContent();
+    }
+
+    /**
+     * @see javax.mail.Part#getHeader(java.lang.String)
+     */
+    public String[] getHeader(String name) throws MessagingException {
+        return wrapped.getHeader(name);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getHeader(java.lang.String, java.lang.String)
+     */
+    public String getHeader(String name, String delimiter)
+            throws MessagingException {
+        return wrapped.getHeader(name, delimiter);
+    }
+
+    /**
+     * @see javax.mail.Part#getAllHeaders()
+     */
+    public Enumeration getAllHeaders() throws MessagingException {
+        return wrapped.getAllHeaders();
+    }
+
+    /**
+     * @see javax.mail.Part#getMatchingHeaders(java.lang.String[])
+     */
+    public Enumeration getMatchingHeaders(String[] names)
+            throws MessagingException {
+        return wrapped.getMatchingHeaders(names);
+    }
+
+    /**
+     * @see javax.mail.Part#getNonMatchingHeaders(java.lang.String[])
+     */
+    public Enumeration getNonMatchingHeaders(String[] names)
+            throws MessagingException {
+        return wrapped.getNonMatchingHeaders(names);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getAllHeaderLines()
+     */
+    public Enumeration getAllHeaderLines() throws MessagingException {
+        return wrapped.getAllHeaderLines();
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getMatchingHeaderLines(java.lang.String[])
+     */
+    public Enumeration getMatchingHeaderLines(String[] names)
+            throws MessagingException {
+        return wrapped.getMatchingHeaderLines(names);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#getNonMatchingHeaderLines(java.lang.String[])
+     */
+    public Enumeration getNonMatchingHeaderLines(String[] names)
+            throws MessagingException {
+        return wrapped.getNonMatchingHeaderLines(names);
+    }
+
+    /**
+     * @see javax.mail.Message#getFlags()
+     */
+    public Flags getFlags() throws MessagingException {
+        return wrapped.getFlags();
+    }
+
+    /**
+     * @see javax.mail.Message#isSet(javax.mail.Flags.Flag)
+     */
+    public boolean isSet(Flags.Flag flag) throws MessagingException {
+        return wrapped.isSet(flag);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#getSender()
+     */
+    public Address getSender() throws MessagingException {
+        return wrapped.getSender();
+    }
+
+    /**
+     * @see javax.mail.Message#match(javax.mail.search.SearchTerm)
+     */
+    public boolean match(SearchTerm arg0) throws MessagingException {
+        return wrapped.match(arg0);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#getRawInputStream()
+     */
+    public InputStream getRawInputStream() throws MessagingException {
+        return wrapped.getRawInputStream();
+    }
+
+    /**
+     * @see javax.mail.Message#getFolder()
+     */
+    public Folder getFolder() {
+        return wrapped.getFolder();
+    }
+
+    /**
+     * @see javax.mail.Message#getMessageNumber()
+     */
+    public int getMessageNumber() {
+        return wrapped.getMessageNumber();
+    }
+
+    /**
+     * @see javax.mail.Message#isExpunged()
+     */
+    public boolean isExpunged() {
+        return wrapped.isExpunged();
+    }
+
+    /**
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object arg0) {
+        return wrapped.equals(arg0);
+    }
+
+    /**
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        return wrapped.hashCode();
+    }
+
+    /**
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return wrapped.toString();
+    }
+
+    /*
+     * Various writer methods
+     */
+
+    /**
+     * @see javax.mail.Message#setFrom(javax.mail.Address)
+     */
+    public void setFrom(Address address) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setFrom(address);
+    }
+
+    /**
+     * @see javax.mail.Message#setFrom()
+     */
+    public void setFrom() throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setFrom();
+    }
+
+    /**
+     * @see javax.mail.Message#addFrom(javax.mail.Address[])
+     */
+    public void addFrom(Address[] addresses) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addFrom(addresses);
+    }
+
+    /**
+     * @see javax.mail.Message#setRecipients(javax.mail.Message.RecipientType, javax.mail.Address[])
+     */
+    public void setRecipients(Message.RecipientType type, Address[] addresses)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setRecipients(type, addresses);
+    }
+
+    /**
+     * @see javax.mail.Message#addRecipients(javax.mail.Message.RecipientType, javax.mail.Address[])
+     */
+    public void addRecipients(Message.RecipientType type, Address[] addresses)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addRecipients(type, addresses);
+    }
+
+    /**
+     * @see javax.mail.Message#setReplyTo(javax.mail.Address[])
+     */
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setReplyTo(addresses);
+    }
+
+    /**
+     * @see javax.mail.Message#setSubject(java.lang.String)
+     */
+    public void setSubject(String subject) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setSubject(subject);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#setSubject(java.lang.String, java.lang.String)
+     */
+    public void setSubject(String subject, String charset)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setSubject(subject, charset);
+    }
+
+    /**
+     * @see javax.mail.Message#setSentDate(java.util.Date)
+     */
+    public void setSentDate(Date d) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setSentDate(d);
+    }
+
+    /**
+     * @see javax.mail.Part#setDisposition(java.lang.String)
+     */
+    public void setDisposition(String disposition) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setDisposition(disposition);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#setContentID(java.lang.String)
+     */
+    public void setContentID(String cid) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setContentID(cid);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#setContentMD5(java.lang.String)
+     */
+    public void setContentMD5(String md5) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setContentMD5(md5);
+    }
+
+    /**
+     * @see javax.mail.Part#setDescription(java.lang.String)
+     */
+    public void setDescription(String description) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setDescription(description);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#setDescription(java.lang.String, java.lang.String)
+     */
+    public void setDescription(String description, String charset)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setDescription(description, charset);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#setContentLanguage(java.lang.String[])
+     */
+    public void setContentLanguage(String[] languages)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setContentLanguage(languages);
+    }
+
+    /**
+     * @see javax.mail.Part#setFileName(java.lang.String)
+     */
+    public void setFileName(String filename) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setFileName(filename);
+    }
+
+    /**
+     * @see javax.mail.Part#setDataHandler(javax.activation.DataHandler)
+     */
+    public void setDataHandler(DataHandler dh) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setDataHandler(dh);
+    }
+
+    /**
+     * @see javax.mail.Part#setContent(java.lang.Object, java.lang.String)
+     */
+    public void setContent(Object o, String type) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setContent(o, type);
+    }
+
+    /**
+     * @see javax.mail.Part#setText(java.lang.String)
+     */
+    public void setText(String text) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setText(text);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#setText(java.lang.String, java.lang.String)
+     */
+    public void setText(String text, String charset) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setText(text, charset);
+    }
+
+    /**
+     * @see javax.mail.Part#setContent(javax.mail.Multipart)
+     */
+    public void setContent(Multipart mp) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setContent(mp);
+    }
+
+    public Message reply(boolean replyToAll) throws MessagingException {
+        checkCopyOnWrite();
+        return wrapped.reply(replyToAll);
+    }
+
+    /**
+     * @see javax.mail.Part#setHeader(java.lang.String, java.lang.String)
+     */
+    public void setHeader(String name, String value) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setHeader(name, value);
+    }
+
+    /**
+     * @see javax.mail.Part#addHeader(java.lang.String, java.lang.String)
+     */
+    public void addHeader(String name, String value) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addHeader(name, value);
+    }
+
+    /**
+     * @see javax.mail.Part#removeHeader(java.lang.String)
+     */
+    public void removeHeader(String name) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.removeHeader(name);
+    }
+
+    /**
+     * @see javax.mail.internet.MimePart#addHeaderLine(java.lang.String)
+     */
+    public void addHeaderLine(String line) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addHeaderLine(line);
+    }
+
+    /**
+     * @see javax.mail.Message#setFlags(javax.mail.Flags, boolean)
+     */
+    public void setFlags(Flags flag, boolean set) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setFlags(flag, set);
+    }
+
+    /**
+     * @see javax.mail.Message#saveChanges()
+     */
+    public void saveChanges() throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.saveChanges();
+    }
+
+    /*
+     * Since JavaMail 1.2
+     */
+
+    /**
+     * @see javax.mail.internet.MimeMessage#addRecipients(javax.mail.Message.RecipientType, java.lang.String)
+     */
+    public void addRecipients(Message.RecipientType type, String addresses)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addRecipients(type, addresses);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#setRecipients(javax.mail.Message.RecipientType, java.lang.String)
+     */
+    public void setRecipients(Message.RecipientType type, String addresses)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setRecipients(type, addresses);
+    }
+
+    /**
+     * @see javax.mail.internet.MimeMessage#setSender(javax.mail.Address)
+     */
+    public void setSender(Address arg0) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setSender(arg0);
+    }
+
+    /**
+     * @see javax.mail.Message#addRecipient(javax.mail.Message.RecipientType, javax.mail.Address)
+     */
+    public void addRecipient(RecipientType arg0, Address arg1)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.addRecipient(arg0, arg1);
+    }
+
+    /**
+     * @see javax.mail.Message#setFlag(javax.mail.Flags.Flag, boolean)
+     */
+    public void setFlag(Flag arg0, boolean arg1) throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setFlag(arg0, arg1);
+    }
+
+    /**
+     * @see javax.mail.Message#setRecipient(javax.mail.Message.RecipientType, javax.mail.Address)
+     */
+    public void setRecipient(RecipientType arg0, Address arg1)
+            throws MessagingException {
+        checkCopyOnWrite();
+        wrapped.setRecipient(arg0, arg1);
+    }
+
+    /**
+     * @see org.apache.avalon.framework.activity.Disposable#dispose()
+     */
+    public synchronized void dispose() {
+        if (wrapped != null) {
+            refCount.decrementReferenceCount();
+            System.out.println(System.identityHashCode(this)+System.identityHashCode(wrapped)+" => "+refCount.getReferenceCount());
+            if (refCount.getReferenceCount()<=0) {
+                if (wrapped instanceof Disposable) {
+                    ((Disposable) wrapped).dispose();
+                }
+            }
+            wrapped = null;
+        }
+    }
+
+    /**
+     * @see java.lang.Object#finalize()
+     */
+    protected void finalize() throws Throwable {
+        dispose();
+        super.finalize();
+    }
+
+    /**
+     * @return the message size
+     * @throws MessagingException 
+     */
+    public long getMessageSize() throws MessagingException {
+        if (wrapped instanceof MimeMessageWrapper) {
+            return ((MimeMessageWrapper) wrapped).getMessageSize();
+        } else {
+            long size = wrapped.getSize();
+            Enumeration e = wrapped.getAllHeaderLines();
+            while (e.hasMoreElements()) {
+                size += ((String) e.nextElement()).length();
+            }
+            return size;
+        }
+    }
+
+    /**
+     * @return
+     */
+    public MimeMessage getWrappedMessage() {
+        return wrapped;
+    }
+
+}

Added: james/server/trunk/src/java/org/apache/james/core/MimeMessageUtil.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/core/MimeMessageUtil.java?rev=365502&view=auto
==============================================================================
--- james/server/trunk/src/java/org/apache/james/core/MimeMessageUtil.java (added)
+++ james/server/trunk/src/java/org/apache/james/core/MimeMessageUtil.java Mon Jan  2 16:58:17 2006
@@ -0,0 +1,179 @@
+/***********************************************************************
+ * Copyright (c) 2000-2005 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.                      *
+ ***********************************************************************/
+package org.apache.james.core;
+
+import org.apache.james.util.InternetPrintWriter;
+import org.apache.james.util.io.IOUtil;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+
+/**
+ * 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
+     */
+    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
+     */
+    public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws IOException, MessagingException {
+        if (message instanceof MimeMessageCopyOnWriteProxy) {
+            MimeMessageCopyOnWriteProxy wr = (MimeMessageCopyOnWriteProxy) message;
+            MimeMessage m = wr.getWrappedMessage();
+            if (m instanceof MimeMessageWrapper) {
+                MimeMessageWrapper wrapper = (MimeMessageWrapper)m;
+                wrapper.writeTo(headerOs, bodyOs, ignoreList);
+                return;
+            }
+        } else if (message instanceof MimeMessageWrapper) {
+            MimeMessageWrapper wrapper = (MimeMessageWrapper)message;
+            wrapper.writeTo(headerOs, bodyOs, ignoreList);
+            return;
+        }
+        if(message.getMessageID() == null) {
+            message.saveChanges();
+        }
+
+        //Write the headers (minus ignored ones)
+        Enumeration headers = message.getNonMatchingHeaderLines(ignoreList);
+        PrintWriter hos = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(headerOs), 512), true);
+        while (headers.hasMoreElements()) {
+            hos.println((String)headers.nextElement());
+        }
+        // Print header/data separator
+        hos.println();
+        hos.flush();
+
+        InputStream bis;
+        OutputStream bos;
+        // Write the body to the output stream
+
+        /*
+        try {
+            bis = message.getRawInputStream();
+            bos = bodyOs;
+        } catch(javax.mail.MessagingException me) {
+            // we may get a "No content" exception
+            // if that happens, try it the hard way
+
+            // Why, you ask?  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.
+
+            bos = MimeUtility.encode(bodyOs, message.getEncoding());
+            bis = message.getInputStream();
+        }
+        */
+
+        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(javax.activation.UnsupportedDataTypeException 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 _) {
+                throw udte;
+            }
+        }
+        catch(javax.mail.MessagingException me) {
+            /* This could be another kind of MessagingException
+             * thrown by MimeMessage.getInputStream(), such as a
+             * javax.mail.internet.ParseException.
+             *
+             * The ParseException is precisely one of the reasons
+             * why the getRawInputStream() method exists, so that we
+             * can continue to stream the content, even if we cannot
+             * handle it.  Again, if we get an exception, we throw
+             * the one that caused us to call getRawInputStream().
+             */
+            try {
+                bis = message.getRawInputStream();
+                bos = bodyOs;
+            } catch(javax.mail.MessagingException _) {
+                throw me;
+            }
+        }
+
+        try {
+            copyStream(bis, bos);
+        }
+        finally {
+            IOUtil.shutdownStream(bis);
+        }
+    }
+
+    /**
+     * Convenience method to copy streams
+     */
+    public static void copyStream(InputStream in, OutputStream out) throws IOException {
+        // TODO: This is really a bad way to do this sort of thing.  A shared buffer to
+        //       allow simultaneous read/writes would be a substantial improvement
+        byte[] block = new byte[1024];
+        int read = 0;
+        while ((read = in.read(block)) > -1) {
+            out.write(block, 0, read);
+        }
+        out.flush();
+    }
+
+
+}

Modified: james/server/trunk/src/java/org/apache/james/core/MimeMessageWrapper.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/core/MimeMessageWrapper.java?rev=365502&r1=365501&r2=365502&view=diff
==============================================================================
--- james/server/trunk/src/java/org/apache/james/core/MimeMessageWrapper.java (original)
+++ james/server/trunk/src/java/org/apache/james/core/MimeMessageWrapper.java Mon Jan  2 16:58:17 2006
@@ -230,7 +230,7 @@
             // and write to this outputstream
             InputStream in = source.getInputStream();
             try {
-                copyStream(in, os);
+                MimeMessageUtil.copyStream(in, os);
             } finally {
                 IOUtil.shutdownStream(in);
             }
@@ -269,128 +269,12 @@
                 }
                 pos.println();
                 pos.flush();
-                copyStream(in, bodyOs);
+                MimeMessageUtil.copyStream(in, bodyOs);
             } finally {
                 IOUtil.shutdownStream(in);
             }
         } else {
-            writeTo(message, headerOs, bodyOs, ignoreList);
-        }
-    }
-
-    /**
-     * Convenience method to take any MimeMessage and write the headers and body to two
-     * different output streams
-     */
-    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
-     */
-    public static void writeTo(MimeMessage message, OutputStream headerOs, OutputStream bodyOs, String[] ignoreList) throws IOException, MessagingException {
-        if (message instanceof MimeMessageWrapper) {
-            MimeMessageWrapper wrapper = (MimeMessageWrapper)message;
-            wrapper.writeTo(headerOs, bodyOs, ignoreList);
-        } else {
-            if(message.getMessageID() == null) {
-                message.saveChanges();
-            }
-
-            //Write the headers (minus ignored ones)
-            Enumeration headers = message.getNonMatchingHeaderLines(ignoreList);
-            PrintWriter hos = new InternetPrintWriter(new BufferedWriter(new OutputStreamWriter(headerOs), 512), true);
-            while (headers.hasMoreElements()) {
-                hos.println((String)headers.nextElement());
-            }
-            // Print header/data separator
-            hos.println();
-            hos.flush();
-
-            InputStream bis = null;
-            OutputStream bos = null;
-            // Write the body to the output stream
-
-            /*
-            try {
-                bis = message.getRawInputStream();
-                bos = bodyOs;
-            } catch(javax.mail.MessagingException me) {
-                // we may get a "No content" exception
-                // if that happens, try it the hard way
-
-                // Why, you ask?  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.
-
-                bos = MimeUtility.encode(bodyOs, message.getEncoding());
-                bis = message.getInputStream();
-            }
-            */
-
-            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(javax.activation.UnsupportedDataTypeException 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 _) {
-                    throw udte;
-                }
-            }
-            catch(javax.mail.MessagingException me) {
-                /* This could be another kind of MessagingException
-                 * thrown by MimeMessage.getInputStream(), such as a
-                 * javax.mail.internet.ParseException.
-                 *
-                 * The ParseException is precisely one of the reasons
-                 * why the getRawInputStream() method exists, so that we
-                 * can continue to stream the content, even if we cannot
-                 * handle it.  Again, if we get an exception, we throw
-                 * the one that caused us to call getRawInputStream().
-                 */
-                try {
-                    bis = message.getRawInputStream();
-                    bos = bodyOs;
-                } catch(javax.mail.MessagingException _) {
-                    throw me;
-                }
-            }
-
-            try {
-                copyStream(bis, bos);
-            }
-            finally {
-                IOUtil.shutdownStream(bis);
-            }
+            MimeMessageUtil.writeTo(message, headerOs, bodyOs, ignoreList);
         }
     }
 
@@ -755,24 +639,10 @@
         }
         InputStream in = getContentStream();
         try {
-            copyStream(in, outs);
+            MimeMessageUtil.copyStream(in, outs);
         } finally {
             IOUtil.shutdownStream(in);
         }
-    }
-
-    /**
-     * Convenience method to copy streams
-     */
-    private static void copyStream(InputStream in, OutputStream out) throws IOException {
-        // TODO: This is really a bad way to do this sort of thing.  A shared buffer to
-        //       allow simultaneous read/writes would be a substantial improvement
-        byte[] block = new byte[1024];
-        int read = 0;
-        while ((read = in.read(block)) > -1) {
-            out.write(block, 0, read);
-        }
-        out.flush();
     }
 
     /*

Modified: james/server/trunk/src/java/org/apache/james/mailrepository/AvalonMailRepository.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/mailrepository/AvalonMailRepository.java?rev=365502&r1=365501&r2=365502&view=diff
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/AvalonMailRepository.java (original)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/AvalonMailRepository.java Mon Jan  2 16:58:17 2006
@@ -29,12 +29,14 @@
 import org.apache.avalon.framework.service.ServiceException;
 import org.apache.avalon.framework.service.ServiceManager;
 import org.apache.avalon.framework.service.Serviceable;
+import org.apache.james.core.MimeMessageCopyOnWriteProxy;
 import org.apache.james.core.MimeMessageWrapper;
 import org.apache.james.services.MailRepository;
 import org.apache.james.util.Lock;
 import org.apache.mailet.Mail;
 
 import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
 
 import java.io.OutputStream;
 import java.util.ArrayList;
@@ -260,8 +262,15 @@
                 }
                 boolean saveStream = true;
 
-                if (mc.getMessage() instanceof MimeMessageWrapper) {
-                    MimeMessageWrapper wrapper = (MimeMessageWrapper) mc.getMessage();
+                MimeMessage message = mc.getMessage();
+                // if the message is a Copy on Write proxy we check the wrapped message
+                // to optimize the behaviour in case of MimeMessageWrapper
+                if (message instanceof MimeMessageCopyOnWriteProxy) {
+                    MimeMessageCopyOnWriteProxy messageCow = (MimeMessageCopyOnWriteProxy) message;
+                    message = messageCow.getWrappedMessage();
+                }
+                if (message instanceof MimeMessageWrapper) {
+                    MimeMessageWrapper wrapper = (MimeMessageWrapper) message;
                     if (DEEP_DEBUG) {
                         System.out.println("Retrieving from: " + wrapper.getSourceId());
                         StringBuffer debugBuffer =
@@ -352,7 +361,7 @@
                 return null;
             }
             MimeMessageAvalonSource source = new MimeMessageAvalonSource(sr, destination, key);
-            mc.setMessage(new MimeMessageWrapper(source));
+            mc.setMessage(new MimeMessageCopyOnWriteProxy(source));
 
             return mc;
         } catch (Exception me) {

Modified: james/server/trunk/src/java/org/apache/james/mailrepository/JDBCMailRepository.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/java/org/apache/james/mailrepository/JDBCMailRepository.java?rev=365502&r1=365501&r2=365502&view=diff
==============================================================================
--- james/server/trunk/src/java/org/apache/james/mailrepository/JDBCMailRepository.java (original)
+++ james/server/trunk/src/java/org/apache/james/mailrepository/JDBCMailRepository.java Mon Jan  2 16:58:17 2006
@@ -35,6 +35,8 @@
 import org.apache.avalon.framework.logger.AbstractLogEnabled;
 import org.apache.james.context.AvalonContextUtilities;
 import org.apache.james.core.MailImpl;
+import org.apache.james.core.MimeMessageCopyOnWriteProxy;
+import org.apache.james.core.MimeMessageUtil;
 import org.apache.james.core.MimeMessageWrapper;
 import org.apache.james.services.MailRepository;
 import org.apache.james.util.JDBCUtil;
@@ -612,7 +614,7 @@
                         ObjectOutputStream oos = new ObjectOutputStream(baos);
                         try {
                             if (mc instanceof MailImpl) {
-                                oos.writeObject(((MailImpl)mc).getAttributesRaw());
+                            oos.writeObject(((MailImpl)mc).getAttributesRaw());
                             } else {
                                 HashMap temp = new HashMap();
                                 for (Iterator i = mc.getAttributeNames(); i.hasNext(); ) {
@@ -649,6 +651,11 @@
                 //  updating the database.
                 MimeMessage messageBody = mc.getMessage();
                 boolean saveBody = false;
+                // if the message is a CopyOnWrite proxy we check the modified wrapped object.
+                if (messageBody instanceof MimeMessageCopyOnWriteProxy) {
+                    MimeMessageCopyOnWriteProxy messageCow = (MimeMessageCopyOnWriteProxy) messageBody;
+                    messageBody = messageCow.getWrappedMessage();
+                }
                 if (messageBody instanceof MimeMessageWrapper) {
                     MimeMessageWrapper message = (MimeMessageWrapper)messageBody;
                     saveBody = message.isModified();
@@ -673,7 +680,7 @@
                             }
         
                             //Write the message to the headerOut and bodyOut.  bodyOut goes straight to the file
-                            MimeMessageWrapper.writeTo(messageBody, headerOut, bodyOut);
+                            MimeMessageUtil.writeTo(mc.getMessage(), headerOut, bodyOut);
         
                             //Store the headers in the database
                             ByteArrayInputStream headerInputStream =
@@ -732,7 +739,7 @@
                         }
         
                         //Write the message to the headerOut and bodyOut.  bodyOut goes straight to the file
-                        MimeMessageWrapper.writeTo(messageBody, headerOut, bodyOut);
+                        MimeMessageUtil.writeTo(messageBody, headerOut, bodyOut);
 
                         ByteArrayInputStream headerInputStream =
                             new ByteArrayInputStream(headerOut.toByteArray());
@@ -748,7 +755,7 @@
                         ObjectOutputStream oos = new ObjectOutputStream(baos);
                         try {
                             if (mc instanceof MailImpl) {
-                                oos.writeObject(((MailImpl)mc).getAttributesRaw());
+                            oos.writeObject(((MailImpl)mc).getAttributesRaw());
                             } else {
                                 HashMap temp = new HashMap();
                                 for (Iterator i = mc.getAttributeNames(); i.hasNext(); ) {
@@ -925,7 +932,7 @@
             mc.setLastUpdated(rsMessage.getTimestamp(7));
 
             MimeMessageJDBCSource source = new MimeMessageJDBCSource(this, key, sr);
-            MimeMessageWrapper message = new MimeMessageWrapper(source);
+            MimeMessageCopyOnWriteProxy message = new MimeMessageCopyOnWriteProxy(source);
             mc.setMessage(message);
             return mc;
         } catch (SQLException sqle) {

Added: james/server/trunk/src/test/org/apache/james/core/MimeMessageCopyOnWriteProxyTest.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/test/org/apache/james/core/MimeMessageCopyOnWriteProxyTest.java?rev=365502&view=auto
==============================================================================
--- james/server/trunk/src/test/org/apache/james/core/MimeMessageCopyOnWriteProxyTest.java (added)
+++ james/server/trunk/src/test/org/apache/james/core/MimeMessageCopyOnWriteProxyTest.java Mon Jan  2 16:58:17 2006
@@ -0,0 +1,184 @@
+/***********************************************************************
+ * Copyright (c) 1999-2005 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.                      *
+ ***********************************************************************/
+package org.apache.james.core;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+public class MimeMessageCopyOnWriteProxyTest extends TestCase {
+
+    MimeMessageWrapper mw = null;
+    String content = "Subject: foo\r\nContent-Transfer-Encoding2: plain";
+    String sep = "\r\n\r\n";
+    String body = "bar\r\n.\r\n";
+    MailImpl mail;
+
+    public MimeMessageCopyOnWriteProxyTest(String arg0) throws MessagingException {
+        super(arg0);
+
+        MimeMessageInputStreamSource mmis = null;
+        try {
+            mmis = new MimeMessageInputStreamSource("test", new ByteArrayInputStream((content+sep+body).getBytes()));
+        } catch (MessagingException e) {
+        }
+        mw = new MimeMessageWrapper(mmis);
+    }
+    
+    public void testMessageCloning1() throws MessagingException, IOException {
+        ArrayList r = new ArrayList();
+        r.add(new MailAddress("recipient@test.com"));
+        mail = new MailImpl("test",new MailAddress("test@test.com"),r,mw);
+        MailImpl m2 = (MailImpl) mail.duplicate();
+        System.out.println("mail: "+getReferences(mail.getMessage())+" m2: "+getReferences(m2.getMessage()));
+        assertNotSame(m2,mail);
+        assertNotSame(m2.getMessage(),mail.getMessage());
+        // test that the wrapped message is the same
+        assertTrue(isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+        // test it is the same after real only operations!
+        mail.getMessage().getSubject();
+        assertTrue(isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+        mail.getMessage().setText("new body");
+        mail.getMessage().saveChanges();
+        // test it is different after a write operation!
+        mail.getMessage().setSubject("new Subject");
+        assertTrue(!isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+    }
+
+    
+    public void testMessageCloning2() throws MessagingException, IOException {
+        ArrayList r = new ArrayList();
+        r.add(new MailAddress("recipient@test.com"));
+        mail = new MailImpl("test",new MailAddress("test@test.com"),r,mw);
+        MailImpl m2 = (MailImpl) mail.duplicate();
+        System.out.println("mail: "+getReferences(mail.getMessage())+" m2: "+getReferences(m2.getMessage()));
+        assertNotSame(m2,mail);
+        assertNotSame(m2.getMessage(),mail.getMessage());
+        // test that the wrapped message is the same
+        assertTrue(isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+        // test it is the same after real only operations!
+        m2.getMessage().getSubject();
+        assertTrue(isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+        m2.getMessage().setText("new body");
+        m2.getMessage().saveChanges();
+        // test it is different after a write operation!
+        m2.getMessage().setSubject("new Subject");
+        assertTrue(!isSameMimeMessage(m2.getMessage(),mail.getMessage()));
+        // check that the subjects are correct on both mails!
+        assertEquals(m2.getMessage().getSubject(),"new Subject");
+        assertEquals(mail.getMessage().getSubject(),"foo");
+        // cloning again the messages
+        Mail m2clone = m2.duplicate();
+        assertTrue(isSameMimeMessage(m2clone.getMessage(),m2.getMessage()));
+        MimeMessage mm = getWrappedMessage(m2.getMessage());
+        assertNotSame(m2.getMessage(),m2clone.getMessage());
+        // test that m2clone has a valid wrapped message
+        MimeMessage mm3 = getWrappedMessage(m2clone.getMessage());
+        assertNotNull(mm3);
+        // dispose m2 and check that the clone has still a valid message and it is the same!
+        ((MailImpl) m2).dispose();
+        assertEquals(mm3,getWrappedMessage(m2clone.getMessage()));
+        // change the message that should be not referenced by m2 that has
+        // been disposed, so it should not clone it!
+        m2clone.getMessage().setSubject("new Subject 2");
+        m2clone.getMessage().setText("new Body 3");
+        assertTrue(isSameMimeMessage(m2clone.getMessage(),mm));
+    }
+    
+    public void testMessageAvoidCloning() throws MessagingException, IOException {
+        ArrayList r = new ArrayList();
+        r.add(new MailAddress("recipient@test.com"));
+        mail = new MailImpl("test",new MailAddress("test@test.com"),r,mw);
+        // cloning the message
+        Mail mailClone = mail.duplicate();
+        assertTrue(isSameMimeMessage(mailClone.getMessage(),mail.getMessage()));
+        MimeMessage mm = getWrappedMessage(mail.getMessage());
+        assertNotSame(mail.getMessage(),mailClone.getMessage());
+        // dispose mail and check that the clone has still a valid message and it is the same!
+        ((MailImpl) mail).dispose();
+        // dumb test
+        assertTrue(isSameMimeMessage(mailClone.getMessage(),mailClone.getMessage()));
+        // change the message that should be not referenced by mail that has
+        // been disposed, so it should not clone it!
+        mailClone.getMessage().setSubject("new Subject 2");
+        mailClone.getMessage().setText("new Body 3");
+        assertTrue(isSameMimeMessage(mailClone.getMessage(),mm));
+    }
+
+    
+    public void testMessageDisposing() throws MessagingException, IOException {
+        ArrayList r = new ArrayList();
+        r.add(new MailAddress("recipient@test.com"));
+        mail = new MailImpl("test",new MailAddress("test@test.com"),r,mw);
+        // cloning the message
+        MailImpl mailClone = (MailImpl) mail.duplicate();
+        mail.dispose();
+
+        assertNotNull(getWrappedMessage(mailClone.getMessage()));
+        assertNull(mail.getMessage());
+
+        mailClone.dispose();
+        
+        assertNull(mailClone.getMessage());
+        assertNull(mail.getMessage());
+    }
+
+    private static String getReferences(MimeMessage m) {
+        StringBuffer ref = new StringBuffer("/");
+        while (m instanceof MimeMessageCopyOnWriteProxy) {
+            ref.append(((MimeMessageCopyOnWriteProxy) m).refCount.getReferenceCount()+"/");
+            m = ((MimeMessageCopyOnWriteProxy) m).getWrappedMessage();
+        }
+        if (m instanceof MimeMessageWrapper) {
+            ref.append("W");
+        } else if (m instanceof MimeMessage) {
+            ref.append("M");
+        } else {
+            ref.append(m.getClass());
+        }
+        return ref.toString();
+    }
+    
+    private static MimeMessage getWrappedMessage(MimeMessage m) {
+        while (m instanceof MimeMessageCopyOnWriteProxy) {
+          m = ((MimeMessageCopyOnWriteProxy) m).getWrappedMessage();
+        }
+        return m;
+    }
+    
+    private static boolean isSameMimeMessage(MimeMessage first, MimeMessage second) {
+        return getWrappedMessage(first) == getWrappedMessage(second);
+        
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+}

Modified: james/server/trunk/src/test/org/apache/james/transport/LinearProcessorTest.java
URL: http://svn.apache.org/viewcvs/james/server/trunk/src/test/org/apache/james/transport/LinearProcessorTest.java?rev=365502&r1=365501&r2=365502&view=diff
==============================================================================
--- james/server/trunk/src/test/org/apache/james/transport/LinearProcessorTest.java (original)
+++ james/server/trunk/src/test/org/apache/james/transport/LinearProcessorTest.java Mon Jan  2 16:58:17 2006
@@ -106,6 +106,7 @@
         System.err.println("Setting body to "+newText);
         message.addHeader("x-Header", newText);
         message.setText(newText);
+        message.setSubject(newText);
         message.saveChanges();
       } catch (javax.mail.MessagingException me) {
          log (me.getMessage());
@@ -151,6 +152,8 @@
             MimeMessage m1 = ((Mail) a.get(0)).getMessage();
             MimeMessage m2 = ((Mail) a.get(1)).getMessage();
             assertNotSame(m1,m2);
+            assertEquals(m1.getSubject(),"new text 1");
+            assertEquals(m2.getSubject(),"new text 2");
         } catch (MessagingException e) {
             // TODO Auto-generated catch block
             e.printStackTrace();



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