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 mw...@apache.org on 2008/12/12 17:58:51 UTC

svn commit: r726071 - in /james/mime4j/trunk/src: main/java/org/apache/james/mime4j/message/ main/java/org/apache/james/mime4j/message/storage/ test/java/org/apache/james/mime4j/message/storage/

Author: mwiederkehr
Date: Fri Dec 12 08:58:50 2008
New Revision: 726071

URL: http://svn.apache.org/viewvc?rev=726071&view=rev
Log:
OutputStream support for StorageProvider (MIME4J-91)

Added:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/AbstractStorageProvider.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageOutputStream.java
Modified:
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/BodyFactory.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/CipherStorageProvider.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/MemoryStorageProvider.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageProvider.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/TempFileStorageProvider.java
    james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/ThresholdStorageProvider.java
    james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/storage/StorageProviderTest.java

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/BodyFactory.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/BodyFactory.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/BodyFactory.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/BodyFactory.java Fri Dec 12 08:58:50 2008
@@ -43,10 +43,22 @@
 
     private StorageProvider storageProvider;
 
+    /**
+     * Creates a new <code>BodyFactory</code> instance that uses the default
+     * storage provider for creating message bodies from input streams.
+     */
     public BodyFactory() {
         this.storageProvider = DefaultStorageProvider.getInstance();
     }
 
+    /**
+     * Creates a new <code>BodyFactory</code> instance that uses the given
+     * storage provider for creating message bodies from input streams.
+     * 
+     * @param storageProvider
+     *            a storage provider or <code>null</code> to use the default
+     *            one.
+     */
     public BodyFactory(StorageProvider storageProvider) {
         if (storageProvider == null)
             storageProvider = DefaultStorageProvider.getInstance();
@@ -54,6 +66,17 @@
         this.storageProvider = storageProvider;
     }
 
+    /**
+     * Creates a {@link BinaryBody} that holds the content of the given input
+     * stream.
+     * 
+     * @param is
+     *            input stream to create a message body from.
+     * @return a {@link SingleBody} that implements the {@link BinaryBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
     public BinaryBody binaryBody(InputStream is) throws IOException {
         if (is == null)
             throw new IllegalArgumentException();
@@ -62,6 +85,45 @@
         return new StorageBinaryBody(new MultiReferenceStorage(storage));
     }
 
+    /**
+     * Creates a {@link BinaryBody} that holds the content of the given
+     * {@link Storage}.
+     * <p>
+     * Note that the caller must not invoke {@link Storage#delete() delete()} on
+     * the given <code>Storage</code> object after it has been passed to this
+     * method. Instead the message body created by this method takes care of
+     * deleting the storage when it gets disposed of (see
+     * {@link Disposable#dispose()}).
+     * 
+     * @param storage
+     *            storage to create a message body from.
+     * @return a {@link SingleBody} that implements the {@link BinaryBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    public BinaryBody binaryBody(Storage storage) throws IOException {
+        if (storage == null)
+            throw new IllegalArgumentException();
+
+        return new StorageBinaryBody(new MultiReferenceStorage(storage));
+    }
+
+    /**
+     * Creates a {@link TextBody} that holds the content of the given input
+     * stream.
+     * <p>
+     * &quot;us-ascii&quot; is used to decode the byte content of the
+     * <code>Storage</code> into a character stream when calling
+     * {@link TextBody#getReader() getReader()} on the returned object.
+     * 
+     * @param is
+     *            input stream to create a message body from.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
     public TextBody textBody(InputStream is) throws IOException {
         if (is == null)
             throw new IllegalArgumentException();
@@ -71,6 +133,25 @@
                 MessageUtils.DEFAULT_CHARSET);
     }
 
+    /**
+     * Creates a {@link TextBody} that holds the content of the given input
+     * stream.
+     * <p>
+     * The charset corresponding to the given MIME charset name is used to
+     * decode the byte content of the input stream into a character stream when
+     * calling {@link TextBody#getReader() getReader()} on the returned object.
+     * If the MIME charset has no corresponding Java charset or the Java charset
+     * cannot be used for decoding then &quot;us-ascii&quot; is used instead.
+     * 
+     * @param is
+     *            input stream to create a message body from.
+     * @param mimeCharset
+     *            name of a MIME charset.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
     public TextBody textBody(InputStream is, String mimeCharset)
             throws IOException {
         if (is == null)
@@ -83,6 +164,85 @@
         return new StorageTextBody(new MultiReferenceStorage(storage), charset);
     }
 
+    /**
+     * Creates a {@link TextBody} that holds the content of the given
+     * {@link Storage}.
+     * <p>
+     * &quot;us-ascii&quot; is used to decode the byte content of the
+     * <code>Storage</code> into a character stream when calling
+     * {@link TextBody#getReader() getReader()} on the returned object.
+     * <p>
+     * Note that the caller must not invoke {@link Storage#delete() delete()} on
+     * the given <code>Storage</code> object after it has been passed to this
+     * method. Instead the message body created by this method takes care of
+     * deleting the storage when it gets disposed of (see
+     * {@link Disposable#dispose()}).
+     * 
+     * @param storage
+     *            storage to create a message body from.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    public TextBody textBody(Storage storage) throws IOException {
+        if (storage == null)
+            throw new IllegalArgumentException();
+
+        return new StorageTextBody(new MultiReferenceStorage(storage),
+                MessageUtils.DEFAULT_CHARSET);
+    }
+
+    /**
+     * Creates a {@link TextBody} that holds the content of the given
+     * {@link Storage}.
+     * <p>
+     * The charset corresponding to the given MIME charset name is used to
+     * decode the byte content of the <code>Storage</code> into a character
+     * stream when calling {@link TextBody#getReader() getReader()} on the
+     * returned object. If the MIME charset has no corresponding Java charset or
+     * the Java charset cannot be used for decoding then &quot;us-ascii&quot; is
+     * used instead.
+     * <p>
+     * Note that the caller must not invoke {@link Storage#delete() delete()} on
+     * the given <code>Storage</code> object after it has been passed to this
+     * method. Instead the message body created by this method takes care of
+     * deleting the storage when it gets disposed of (see
+     * {@link Disposable#dispose()}).
+     * 
+     * @param storage
+     *            storage to create a message body from.
+     * @param mimeCharset
+     *            name of a MIME charset.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    public TextBody textBody(Storage storage, String mimeCharset)
+            throws IOException {
+        if (storage == null)
+            throw new IllegalArgumentException();
+        if (mimeCharset == null)
+            throw new IllegalArgumentException();
+
+        Charset charset = toJavaCharset(mimeCharset, false);
+        return new StorageTextBody(new MultiReferenceStorage(storage), charset);
+    }
+
+    /**
+     * Creates a {@link TextBody} that holds the content of the given string.
+     * <p>
+     * &quot;us-ascii&quot; is used to encode the characters of the string into
+     * a byte stream when calling
+     * {@link Body#writeTo(java.io.OutputStream, Mode) writeTo(OutputStream, Mode)}
+     * on the returned object.
+     * 
+     * @param text
+     *            text to create a message body from.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     */
     public TextBody textBody(String text) {
         if (text == null)
             throw new IllegalArgumentException();
@@ -90,6 +250,23 @@
         return new StringTextBody(text, MessageUtils.DEFAULT_CHARSET);
     }
 
+    /**
+     * Creates a {@link TextBody} that holds the content of the given string.
+     * <p>
+     * The charset corresponding to the given MIME charset name is used to
+     * encode the characters of the string into a byte stream when calling
+     * {@link Body#writeTo(java.io.OutputStream, Mode) writeTo(OutputStream, Mode)}
+     * on the returned object. If the MIME charset has no corresponding Java
+     * charset or the Java charset cannot be used for encoding then
+     * &quot;us-ascii&quot; is used instead.
+     * 
+     * @param text
+     *            text to create a message body from.
+     * @param mimeCharset
+     *            name of a MIME charset.
+     * @return a {@link SingleBody} that implements the {@link TextBody}
+     *         interface.
+     */
     public TextBody textBody(String text, String mimeCharset) {
         if (text == null)
             throw new IllegalArgumentException();

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/AbstractStorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/AbstractStorageProvider.java?rev=726071&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/AbstractStorageProvider.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/AbstractStorageProvider.java Fri Dec 12 08:58:50 2008
@@ -0,0 +1,61 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.message.storage;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.james.mime4j.decoder.CodecUtil;
+
+/**
+ * Abstract implementation of {@link StorageProvider} that implements
+ * {@link StorageProvider#store(InputStream) store(InputStream)} by copying the
+ * input stream to a {@link StorageOutputStream} obtained from
+ * {@link StorageProvider#createStorageOutputStream() createStorageOutputStream()}.
+ */
+public abstract class AbstractStorageProvider implements StorageProvider {
+
+    /**
+     * Sole constructor.
+     */
+    protected AbstractStorageProvider() {
+    }
+
+    /**
+     * This implementation creates a {@link StorageOutputStream} by calling
+     * {@link StorageProvider#createStorageOutputStream() createStorageOutputStream()}
+     * and copies the content of the given input stream to that output stream.
+     * It then calls {@link StorageOutputStream#toStorage()} on the output
+     * stream and returns this object.
+     * 
+     * @param in
+     *            stream containing the data to store.
+     * @return a {@link Storage} instance that can be used to retrieve the
+     *         stored content.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    public final Storage store(InputStream in) throws IOException {
+        StorageOutputStream out = createStorageOutputStream();
+        CodecUtil.copy(in, out);
+        return out.toStorage();
+    }
+
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/CipherStorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/CipherStorageProvider.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/CipherStorageProvider.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/CipherStorageProvider.java Fri Dec 12 08:58:50 2008
@@ -26,6 +26,7 @@
 
 import javax.crypto.Cipher;
 import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
 import javax.crypto.KeyGenerator;
 import javax.crypto.spec.SecretKeySpec;
 
@@ -43,7 +44,7 @@
  * DefaultStorageProvider.setInstance(provider);
  * </pre>
  */
-public class CipherStorageProvider implements StorageProvider {
+public class CipherStorageProvider extends AbstractStorageProvider {
 
     private final StorageProvider backend;
     private final String algorithm;
@@ -83,19 +84,50 @@
         }
     }
 
-    public Storage store(InputStream in) throws IOException {
-        try {
-            byte[] raw = keygen.generateKey().getEncoded();
-            SecretKeySpec skeySpec = new SecretKeySpec(raw, algorithm);
+    public StorageOutputStream createStorageOutputStream() throws IOException {
+        return new CipherStorageOutputStream(backend
+                .createStorageOutputStream());
+    }
+
+    private final class CipherStorageOutputStream extends StorageOutputStream {
+        private final StorageOutputStream storageOut;
+        private final SecretKeySpec skeySpec;
+        private final CipherOutputStream cipherOut;
+
+        public CipherStorageOutputStream(StorageOutputStream out)
+                throws IOException {
+            try {
+                this.storageOut = out;
+
+                byte[] raw = keygen.generateKey().getEncoded();
+                skeySpec = new SecretKeySpec(raw, algorithm);
+
+                Cipher cipher = Cipher.getInstance(algorithm);
+                cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
 
-            Cipher cipher = Cipher.getInstance(algorithm);
-            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
+                this.cipherOut = new CipherOutputStream(out, cipher);
+            } catch (GeneralSecurityException e) {
+                throw (IOException) new IOException().initCause(e);
+            }
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+            cipherOut.close();
+        }
+
+        @Override
+        protected void write0(byte[] buffer, int offset, int length)
+                throws IOException {
+            cipherOut.write(buffer, offset, length);
+        }
 
-            InputStream encrypted = new CipherInputStream(in, cipher);
-            Storage storage = backend.store(encrypted);
-            return new CipherStorage(storage, skeySpec);
-        } catch (GeneralSecurityException e) {
-            throw (IOException) new IOException().initCause(e);
+        @Override
+        protected Storage toStorage0() throws IOException {
+            // cipherOut has already been closed because toStorage calls close
+            Storage encrypted = storageOut.toStorage();
+            return new CipherStorage(encrypted, skeySpec);
         }
     }
 

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/MemoryStorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/MemoryStorageProvider.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/MemoryStorageProvider.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/MemoryStorageProvider.java Fri Dec 12 08:58:50 2008
@@ -35,7 +35,7 @@
  * DefaultStorageProvider.setInstance(provider);
  * </pre>
  */
-public class MemoryStorageProvider implements StorageProvider {
+public class MemoryStorageProvider extends AbstractStorageProvider {
 
     /**
      * Creates a new <code>MemoryStorageProvider</code>.
@@ -43,16 +43,24 @@
     public MemoryStorageProvider() {
     }
 
-    public Storage store(InputStream in) throws IOException {
+    public StorageOutputStream createStorageOutputStream() {
+        return new MemoryStorageOutputStream();
+    }
+
+    private static final class MemoryStorageOutputStream extends
+            StorageOutputStream {
         ByteArrayBuffer bab = new ByteArrayBuffer(1024);
 
-        final byte[] buffer = new byte[512];
-        int inputLength;
-        while ((inputLength = in.read(buffer)) != -1) {
-            bab.append(buffer, 0, inputLength);
+        @Override
+        protected void write0(byte[] buffer, int offset, int length)
+                throws IOException {
+            bab.append(buffer, offset, length);
         }
 
-        return new MemoryStorage(bab.buffer(), bab.length());
+        @Override
+        protected Storage toStorage0() throws IOException {
+            return new MemoryStorage(bab.buffer(), bab.length());
+        }
     }
 
     static final class MemoryStorage implements Storage {

Added: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageOutputStream.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageOutputStream.java?rev=726071&view=auto
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageOutputStream.java (added)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageOutputStream.java Fri Dec 12 08:58:50 2008
@@ -0,0 +1,170 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.mime4j.message.storage;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This class implements an output stream that can be used to create a
+ * {@link Storage} object. An instance of this class is obtained by calling
+ * {@link StorageProvider#createStorageOutputStream()}. The user can then write
+ * data to this instance and invoke {@link #toStorage()} to retrieve a
+ * {@link Storage} object that contains the data that has been written.
+ * <p>
+ * Note that the <code>StorageOutputStream</code> does not have to be closed
+ * explicitly because {@link #toStorage()} invokes {@link #close()} if
+ * necessary. Also note that {@link #toStorage()} may be invoked only once. One
+ * <code>StorageOutputStream</code> can create only one <code>Storage</code>
+ * instance.
+ */
+public abstract class StorageOutputStream extends OutputStream {
+
+    private byte[] singleByte;
+    private boolean closed;
+    private boolean usedUp;
+
+    /**
+     * Sole constructor.
+     */
+    protected StorageOutputStream() {
+    }
+
+    /**
+     * Closes this output stream if it has not already been closed and returns a
+     * {@link Storage} object contains the bytes that have been written to this
+     * output stream.
+     * <p>
+     * Note that this method may not be invoked a second time. This is because
+     * for some implementations it is not possible to create another
+     * <code>Storage</code> object that can be read from and deleted
+     * independently (e.g. if the implementation writes to a file).
+     * 
+     * @return a <code>Storage</code> object as described above.
+     * @throws IOException
+     *             if an I/O error occurs.
+     * @throws IllegalStateException
+     *             if this method has already been called.
+     */
+    public final Storage toStorage() throws IOException {
+        if (usedUp)
+            throw new IllegalStateException(
+                    "toStorage may be invoked only once");
+
+        if (!closed)
+            close();
+
+        usedUp = true;
+        return toStorage0();
+    }
+
+    @Override
+    public final void write(int b) throws IOException {
+        if (closed)
+            throw new IOException("StorageOutputStream has been closed");
+
+        if (singleByte == null)
+            singleByte = new byte[1];
+
+        singleByte[0] = (byte) b;
+        write0(singleByte, 0, 1);
+    }
+
+    @Override
+    public final void write(byte[] buffer) throws IOException {
+        if (closed)
+            throw new IOException("StorageOutputStream has been closed");
+
+        if (buffer == null)
+            throw new NullPointerException();
+
+        if (buffer.length == 0)
+            return;
+
+        write0(buffer, 0, buffer.length);
+    }
+
+    @Override
+    public final void write(byte[] buffer, int offset, int length)
+            throws IOException {
+        if (closed)
+            throw new IOException("StorageOutputStream has been closed");
+
+        if (buffer == null)
+            throw new NullPointerException();
+
+        if (offset < 0 || length < 0 || offset + length > buffer.length)
+            throw new IndexOutOfBoundsException();
+
+        if (length == 0)
+            return;
+
+        write0(buffer, offset, length);
+    }
+
+    /**
+     * Closes this output stream. Subclasses that override this method have to
+     * invoke <code>super.close()</code>.
+     * <p>
+     * This implementation never throws an {@link IOException} but a subclass
+     * might.
+     * 
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    @Override
+    public void close() throws IOException {
+        closed = true;
+    }
+
+    /**
+     * Has to implemented by a concrete subclass to write bytes from the given
+     * byte array to this <code>StorageOutputStream</code>. This method gets
+     * called by {@link #write(int)}, {@link #write(byte[])} and
+     * {@link #write(byte[], int, int)}. All the required preconditions have
+     * already been checked by these methods, including the check if the output
+     * stream has already been closed.
+     * 
+     * @param buffer
+     *            buffer containing bytes to write.
+     * @param offset
+     *            start offset in the buffer.
+     * @param length
+     *            number of bytes to write.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    protected abstract void write0(byte[] buffer, int offset, int length)
+            throws IOException;
+
+    /**
+     * Has to be implemented by a concrete subclass to create a {@link Storage}
+     * object from the bytes that have been written to this
+     * <code>StorageOutputStream</code>. This method gets called by
+     * {@link #toStorage()} after the preconditions have been checked. The
+     * implementation can also be sure that this methods gets invoked only once.
+     * 
+     * @return a <code>Storage</code> object as described above.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    protected abstract Storage toStorage0() throws IOException;
+
+}

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageProvider.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageProvider.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/StorageProvider.java Fri Dec 12 08:58:50 2008
@@ -23,7 +23,8 @@
 import java.io.InputStream;
 
 /**
- * Provides a strategy for storing the contents of an <code>InputStream</code>.
+ * Provides a strategy for storing the contents of an <code>InputStream</code>
+ * or retrieving the content written to an <code>OutputStream</code>.
  */
 public interface StorageProvider {
     /**
@@ -36,4 +37,15 @@
      */
     Storage store(InputStream in) throws IOException;
 
+    /**
+     * Creates a {@link StorageOutputStream} where data to be stored can be
+     * written to. Subsequently the user can call
+     * {@link StorageOutputStream#toStorage() toStorage()} on that object to get
+     * a {@link Storage} instance that holds the data that has been written.
+     * 
+     * @return a {@link StorageOutputStream} where data can be written to.
+     * @throws IOException
+     *             if an I/O error occurs.
+     */
+    StorageOutputStream createStorageOutputStream() throws IOException;
 }

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/TempFileStorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/TempFileStorageProvider.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/TempFileStorageProvider.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/TempFileStorageProvider.java Fri Dec 12 08:58:50 2008
@@ -30,8 +30,6 @@
 import java.util.Iterator;
 import java.util.Set;
 
-import org.apache.james.mime4j.decoder.CodecUtil;
-
 /**
  * A {@link StorageProvider} that stores the data in temporary files. The files
  * are stored either in a user-specified directory or the default temporary-file
@@ -45,7 +43,7 @@
  * DefaultStorageProvider.setInstance(provider);
  * </pre>
  */
-public class TempFileStorageProvider implements StorageProvider {
+public class TempFileStorageProvider extends AbstractStorageProvider {
 
     private static final String DEFAULT_PREFIX = "m4j";
 
@@ -102,15 +100,40 @@
         this.directory = directory;
     }
 
-    public Storage store(InputStream in) throws IOException {
+    public StorageOutputStream createStorageOutputStream() throws IOException {
         File file = File.createTempFile(prefix, suffix, directory);
         file.deleteOnExit();
 
-        OutputStream out = new FileOutputStream(file);
-        CodecUtil.copy(in, out);
-        out.close();
+        return new TempFileStorageOutputStream(file);
+    }
+
+    private static final class TempFileStorageOutputStream extends
+            StorageOutputStream {
+        private File file;
+        private OutputStream out;
+
+        public TempFileStorageOutputStream(File file) throws IOException {
+            this.file = file;
+            this.out = new FileOutputStream(file);
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
+            out.close();
+        }
+
+        @Override
+        protected void write0(byte[] buffer, int offset, int length)
+                throws IOException {
+            out.write(buffer, offset, length);
+        }
 
-        return new TempFileStorage(file);
+        @Override
+        protected Storage toStorage0() throws IOException {
+            // out has already been closed because toStorage calls close
+            return new TempFileStorage(file);
+        }
     }
 
     private static final class TempFileStorage implements Storage {

Modified: james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/ThresholdStorageProvider.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/ThresholdStorageProvider.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/ThresholdStorageProvider.java (original)
+++ james/mime4j/trunk/src/main/java/org/apache/james/mime4j/message/storage/ThresholdStorageProvider.java Fri Dec 12 08:58:50 2008
@@ -39,7 +39,7 @@
  * DefaultStorageProvider.setInstance(provider);
  * </pre>
  */
-public class ThresholdStorageProvider implements StorageProvider {
+public class ThresholdStorageProvider extends AbstractStorageProvider {
 
     private final StorageProvider backend;
     private final int thresholdSize;
@@ -74,30 +74,58 @@
         this.thresholdSize = thresholdSize;
     }
 
-    public Storage store(InputStream in) throws IOException {
-        final int bufferSize = Math.min(thresholdSize, 1024);
-        byte[] buffer = new byte[bufferSize];
-        ByteArrayBuffer bab = new ByteArrayBuffer(bufferSize);
-
-        for (int remainingHeadSize = thresholdSize; remainingHeadSize > 0;) {
-            int bytesToRead = Math.min(remainingHeadSize, bufferSize);
-            int nBytes = in.read(buffer, 0, bytesToRead);
-            if (nBytes == -1) {
-                byte[] head = bab.buffer();
-                int headLen = bab.length();
+    public StorageOutputStream createStorageOutputStream() {
+        return new ThresholdStorageOutputStream();
+    }
+
+    private final class ThresholdStorageOutputStream extends
+            StorageOutputStream {
+
+        private final ByteArrayBuffer head;
+        private StorageOutputStream tail;
+
+        public ThresholdStorageOutputStream() {
+            final int bufferSize = Math.min(thresholdSize, 1024);
+            head = new ByteArrayBuffer(bufferSize);
+        }
+
+        @Override
+        public void close() throws IOException {
+            super.close();
 
-                return new MemoryStorageProvider.MemoryStorage(head, headLen);
+            if (tail != null)
+                tail.close();
+        }
+
+        @Override
+        protected void write0(byte[] buffer, int offset, int length)
+                throws IOException {
+            int remainingHeadSize = thresholdSize - head.length();
+            if (remainingHeadSize > 0) {
+                int n = Math.min(remainingHeadSize, length);
+                head.append(buffer, offset, n);
+                offset += n;
+                length -= n;
             }
 
-            bab.append(buffer, 0, nBytes);
-            remainingHeadSize -= nBytes;
+            if (length > 0) {
+                if (tail == null)
+                    tail = backend.createStorageOutputStream();
+
+                tail.write(buffer, offset, length);
+            }
         }
 
-        byte[] head = bab.buffer();
-        int headLen = bab.length();
-        Storage tail = backend.store(in);
+        @Override
+        protected Storage toStorage0() throws IOException {
+            if (tail == null)
+                return new MemoryStorageProvider.MemoryStorage(head.buffer(),
+                        head.length());
+
+            return new ThresholdStorage(head.buffer(), head.length(), tail
+                    .toStorage());
+        }
 
-        return new ThresholdStorage(head, headLen, tail);
     }
 
     private static final class ThresholdStorage implements Storage {

Modified: james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/storage/StorageProviderTest.java
URL: http://svn.apache.org/viewvc/james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/storage/StorageProviderTest.java?rev=726071&r1=726070&r2=726071&view=diff
==============================================================================
--- james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/storage/StorageProviderTest.java (original)
+++ james/mime4j/trunk/src/test/java/org/apache/james/mime4j/message/storage/StorageProviderTest.java Fri Dec 12 08:58:50 2008
@@ -35,6 +35,7 @@
         testReadWrite(provider, 0);
         testReadWrite(provider, 1);
         testReadWrite(provider, 1024);
+        testReadWrite(provider, 20000);
 
         testDelete(provider);
     }
@@ -45,12 +46,13 @@
         testReadWrite(provider, 0);
         testReadWrite(provider, 1);
         testReadWrite(provider, 1024);
+        testReadWrite(provider, 20000);
 
         testDelete(provider);
     }
 
     public void testThresholdStorageProvider() throws Exception {
-        final int threshold = 500;
+        final int threshold = 5000;
         StorageProvider backend = new TempFileStorageProvider();
         StorageProvider provider = new ThresholdStorageProvider(backend,
                 threshold);
@@ -61,6 +63,7 @@
         testReadWrite(provider, threshold);
         testReadWrite(provider, threshold + 1);
         testReadWrite(provider, 2 * threshold);
+        testReadWrite(provider, 10 * threshold);
 
         testDelete(provider);
     }
@@ -72,24 +75,47 @@
         testReadWrite(provider, 0);
         testReadWrite(provider, 1);
         testReadWrite(provider, 1024);
+        testReadWrite(provider, 20000);
 
         testDelete(provider);
     }
 
     private void testReadWrite(StorageProvider provider, int size)
             throws IOException {
+        testStore(provider, size);
+        testCreateStorageOutputStream(provider, size);
+    }
+
+    private void testStore(StorageProvider provider, int size)
+            throws IOException {
         byte[] data = createData(size);
         assertEquals(size, data.length);
 
         Storage storage = provider.store(new ByteArrayInputStream(data));
-        ByteArrayOutputStream out = new ByteArrayOutputStream();
-        CodecUtil.copy(storage.getInputStream(), out);
 
-        byte[] result = out.toByteArray();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CodecUtil.copy(storage.getInputStream(), baos);
+        verifyData(data, baos.toByteArray());
+    }
 
-        assertEquals(size, result.length);
-        for (int i = 0; i < size; i++) {
-            assertEquals(data[i], result[i]);
+    private void testCreateStorageOutputStream(StorageProvider provider,
+            int size) throws IOException {
+        byte[] data = createData(size);
+        assertEquals(size, data.length);
+
+        StorageOutputStream out = provider.createStorageOutputStream();
+        CodecUtil.copy(new ByteArrayInputStream(data), out);
+        Storage storage = out.toStorage();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        CodecUtil.copy(storage.getInputStream(), baos);
+        verifyData(data, baos.toByteArray());
+    }
+
+    private void verifyData(byte[] expected, byte[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
         }
     }
 



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