Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MailImpl.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MailImpl.java (revision 360063) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MailImpl.java (working copy) @@ -133,7 +133,7 @@ } } } - + /** * @param mail * @param newName @@ -177,7 +177,7 @@ throws MessagingException { this(name, sender, recipients); MimeMessageSource source = new MimeMessageInputStreamSource(name, messageIn); - MimeMessageWrapper wrapper = new MimeMessageWrapper(source); + MimeMessageCopyOnWriteProxy wrapper = new MimeMessageCopyOnWriteProxy(source); this.setMessage(wrapper); } @@ -190,9 +190,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 +289,7 @@ public MimeMessage getMessage() throws MessagingException { return message; } + /** * Set the name of this MailImpl. * @@ -372,6 +373,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). Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageWrapper.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageWrapper.java (revision 359860) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageWrapper.java (working copy) @@ -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,132 +269,16 @@ } pos.println(); pos.flush(); - copyStream(in, bodyOs); + MimeMessageUtil.copyStream(in, bodyOs); } finally { IOUtil.shutdownStream(in); } } else { - writeTo(message, headerOs, bodyOs, ignoreList); + MimeMessageUtil.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); - } - } - } - - /** * Various reader methods */ public Address[] getFrom() throws MessagingException { @@ -755,26 +639,12 @@ } 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(); - } - /* * Various writer methods */ Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/AvalonMailRepository.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/AvalonMailRepository.java (revision 360063) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/AvalonMailRepository.java (working copy) @@ -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) { Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/JDBCMailRepository.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/JDBCMailRepository.java (revision 360063) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/mailrepository/JDBCMailRepository.java (working copy) @@ -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) { Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java (revision 0) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageCopyOnWriteProxy.java (revision 0) @@ -0,0 +1,828 @@ +/*********************************************************************** + * 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 + */ + private class ReferenceCounter { + + /** + * reference counter + */ + private int referenceCount = 0; + + /** + * @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; + } + + } + + protected 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); + } + + /** + * @param original + * MimeMessageWrapper + * @throws MessagingException + */ + public MimeMessageCopyOnWriteProxy(MimeMessage original, boolean writeable) + throws MessagingException { + this(original, null, writeable); + } + + /** + * @param original + * MimeMessageWrapper + * @throws MessagingException + */ + public MimeMessageCopyOnWriteProxy(MimeMessageCopyOnWriteProxy original) + throws MessagingException { + this(original.wrapped, original.refCount, false); + } + + /** + * Private constructor providing an external reference counter. + * + * @param original + * @param refCount + * @throws MessagingException + */ + private MimeMessageCopyOnWriteProxy(MimeMessage original, + ReferenceCounter refCount, boolean writeable) + throws MessagingException { + super(Session.getDefaultInstance(System.getProperties(), null)); + this.wrapped = original; + + if (refCount == null) { + refCount = new ReferenceCounter(); + } + this.refCount = refCount; + if (!writeable) { + this.refCount.incrementReferenceCount(); + } + } + + /** + * Check the number of references over the MimeMessage and clone it if + * needed. + * + * @throws MessagingException + * exception + */ + protected synchronized void checkCopyOnWrite() throws MessagingException { + if (refCount.getReferenceCount() > 0) { + synchronized (wrapped) { + refCount.decrementReferenceCount(); + + refCount = new ReferenceCounter(); + if (wrapped instanceof MimeMessageWrapper) { + wrapped = new MimeMessageWrapper( + ((MimeMessageWrapper) wrapped).source); + } 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(); + 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; + } + +} Index: C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageUtil.java =================================================================== --- C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageUtil.java (revision 0) +++ C:/Lab/VOID/projects/james/src/java/org/apache/james/core/MimeMessageUtil.java (revision 0) @@ -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(); + } + + +}