Author: scheu
Date: Wed Jul 11 12:51:52 2007
New Revision: 555379

URL: http://svn.apache.org/viewvc?view=rev&rev=555379
Log:
JIRA AXIS2-2946
Contributor: Rich Scheuerle
Added code to use the message content-length as a heuristic to improve 
attachment performance.
The content-length is used instead of building large internal byte buffers.
Also added a method to the Attachments class to get the attachments in order.
This can be useful in SWA "raw" situations.  In such scenarios the xml does not 
have a reference cid
to the attachment.  Thus the receiver may wish to view the attachments in the 
order that they appeared
on the wire.  One use case is JAX-WS RPC/LIT support with SWA attachments.

Modified:
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/Attachments.java

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/Attachments.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/Attachments.java?view=diff&rev=555379&r1=555378&r2=555379
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/Attachments.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/Attachments.java
 Wed Jul 11 12:51:52 2007
@@ -1,529 +1,609 @@
-/*
- * 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.axiom.attachments;
-
-import org.apache.axiom.om.OMException;
-import org.apache.axiom.om.impl.MTOMConstants;
-import org.apache.axiom.om.util.UUIDGenerator;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import javax.activation.DataHandler;
-import javax.mail.MessagingException;
-import javax.mail.internet.ContentType;
-import javax.mail.internet.ParseException;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PushbackInputStream;
-import java.io.UnsupportedEncodingException;
-import java.util.Set;
-import java.util.TreeMap;
-
-public class Attachments {
-
-    /** <code>ContentType</code> of the MIME message */
-    ContentType contentType;
-
-    /** Mime <code>boundary</code> which separates mime parts */
-    byte[] boundary;
-
-    /**
-     * <code>applicationType</code> used to distinguish between MTOM & SWA If 
the message is MTOM
-     * optimised type is application/xop+xml If the message is SWA, type is 
??have to find out
-     */
-    String applicationType;
-
-    /**
-     * <code>pushbackInStream</code> stores the reference to the incoming 
stream A PushbackStream
-     * has the ability to "push back" or "unread" one byte.
-     */
-    PushbackInputStream pushbackInStream;
-    int PUSHBACK_SIZE = 1000;
-
-    /**
-     * <code>attachmentsMap</code> stores the Data Handlers of the already 
parsed Mime Body Parts.
-     * This ordered Map is keyed using the content-ID's.
-     */
-    TreeMap attachmentsMap;
-
-    /** <code>partIndex</code>- Number of Mime parts parsed */
-    int partIndex = 0;
-
-    /** Container to hold streams for direct access */
-    IncomingAttachmentStreams streams = null;
-
-    /** <code>boolean</code> Indicating if any streams have been directly 
requested */
-    private boolean streamsRequested = false;
-
-    /** <code>boolean</code> Indicating if any data handlers have been 
directly requested */
-    private boolean partsRequested = false;
-
-    /**
-     * <code>endOfStreamReached</code> flag which is to be set by 
MIMEBodyPartStream when MIME
-     * message terminator is found.
-     */
-    private boolean endOfStreamReached;
-
-
-    /**
-     * <code>noStreams</code> flag which is to be set when this class is 
instantiated by the SwA API
-     * to handle programatic added attachements. An InputStream with 
attachments is not present at
-     * that occation.
-     */
-    private boolean noStreams = false;
-
-    private String firstPartId;
-
-    private boolean fileCacheEnable;
-
-    private String attachmentRepoDir;
-
-    private int fileStorageThreshold;
-
-    protected static Log log = LogFactory.getLog(Attachments.class);
-
-
-    /**
-     * Moves the pointer to the beginning of the first MIME part. Reads till 
first MIME boundary is
-     * found or end of stream is reached.
-     *
-     * @param inStream
-     * @param contentTypeString
-     * @param fileCacheEnable
-     * @param attachmentRepoDir
-     * @throws OMException
-     */
-    public Attachments(InputStream inStream, String contentTypeString,
-                       boolean fileCacheEnable, String attachmentRepoDir,
-                       String fileThreshold) throws OMException {
-        this.attachmentRepoDir = attachmentRepoDir;
-        this.fileCacheEnable = fileCacheEnable;
-        if (fileThreshold != null && (!"".equals(fileThreshold))) {
-            this.fileStorageThreshold = Integer.parseInt(fileThreshold);
-        } else {
-            this.fileStorageThreshold = 1;
-        }
-        attachmentsMap = new TreeMap();
-        try {
-            contentType = new ContentType(contentTypeString);
-        } catch (ParseException e) {
-            throw new OMException(
-                    "Invalid Content Type Field in the Mime Message"
-                    , e);
-        }
-        // REVIEW: This conversion is hard-coded to UTF-8.
-        // The complete solution is to respect the charset setting of the 
message.
-        // However this may cause problems in BoundaryDelimittedStream and 
other
-        // lower level classes.
-
-        // Boundary always have the prefix "--".
-        try {
-            String encoding = contentType.getParameter("charset");
-            if(encoding == null || encoding.length()==0){
-                encoding = "UTF-8";
-            }
-            this.boundary = ("--" + contentType.getParameter("boundary"))
-                    .getBytes(encoding);
-        } catch (UnsupportedEncodingException e) {
-            throw new OMException(e);
-        }
-
-        // do we need to wrap InputStream from a BufferedInputStream before
-        // wrapping from PushbackStream
-        pushbackInStream = new PushbackInputStream(inStream,
-                                                   PUSHBACK_SIZE);
-
-        // Move the read pointer to the beginning of the first part
-        // read till the end of first boundary
-        while (true) {
-            int value;
-            try {
-                value = pushbackInStream.read();
-                if ((byte) value == boundary[0]) {
-                    int boundaryIndex = 0;
-                    while ((boundaryIndex < boundary.length)
-                            && ((byte) value == boundary[boundaryIndex])) {
-                        value = pushbackInStream.read();
-                        if (value == -1) {
-                            throw new OMException(
-                                    "Unexpected End of Stream while searching 
for first Mime Boundary");
-                        }
-                        boundaryIndex++;
-                    }
-                    if (boundaryIndex == boundary.length) { // boundary found
-                        pushbackInStream.read();
-                        break;
-                    }
-                } else if ((byte) value == -1) {
-                    throw new OMException(
-                            "Mime parts not found. Stream ended while 
searching for the boundary");
-                }
-            } catch (IOException e1) {
-                throw new OMException("Stream Error" + e1.toString(), e1);
-            }
-        }
-
-        // Read the SOAP part and cache it
-        getDataHandler(getSOAPPartContentID());
-
-        // Now reset partsRequested. SOAP part is a special case which is 
always 
-        // read beforehand, regardless of request.
-        partsRequested = false;
-    }
-
-    /**
-     * Sets file cache to false.
-     *
-     * @param inStream
-     * @param contentTypeString
-     * @throws OMException
-     */
-    public Attachments(InputStream inStream, String contentTypeString)
-            throws OMException {
-        this(inStream, contentTypeString, false, null, null);
-    }
-
-    /**
-     * Use this constructor when instantiating this to store the attachments 
set programatically
-     * through the SwA API.
-     */
-    public Attachments() {
-        attachmentsMap = new TreeMap();
-        noStreams = true;
-    }
-
-    /**
-     * @return whether Message Type is SOAP with Attachments or MTOM 
optimized, by checking the
-     *         application type parameter in the Content Type.
-     */
-    public String getAttachmentSpecType() {
-        if (this.applicationType == null) {
-            applicationType = contentType.getParameter("type");
-            if ((MTOMConstants.MTOM_TYPE).equalsIgnoreCase(applicationType)) {
-                this.applicationType = MTOMConstants.MTOM_TYPE;
-            } else if 
((MTOMConstants.SWA_TYPE).equalsIgnoreCase(applicationType)) {
-                this.applicationType = MTOMConstants.SWA_TYPE;
-            } else if 
((MTOMConstants.SWA_TYPE_12).equalsIgnoreCase(applicationType)) {
-                this.applicationType = MTOMConstants.SWA_TYPE_12;
-            } else {
-                throw new OMException(
-                        "Invalid Application type. Support available for MTOM 
& SwA only.");
-            }
-        }
-        return this.applicationType;
-    }
-
-    /**
-     * Checks whether the MIME part is already parsed by checking the 
attachments HashMap. If it is
-     * not parsed yet then call the getNextPart() till the required part is 
found.
-     *
-     * @param blobContentID (without the surrounding angle brackets and "cid:" 
prefix)
-     * @return The DataHandler of the mime part referred by the Content-Id or 
*null* if the mime
-     *         part referred by the content-id does not exist
-     */
-    public DataHandler getDataHandler(String blobContentID) {
-        DataHandler dataHandler;
-        if (attachmentsMap.containsKey(blobContentID)) {
-            dataHandler = (DataHandler) attachmentsMap.get(blobContentID);
-            return dataHandler;
-        } else if (!noStreams) {
-            //This loop will be terminated by the Exceptions thrown if the Mime
-            // part searching was not found
-            while ((dataHandler = this.getNextPartDataHandler()) != null) {
-                if (attachmentsMap.containsKey(blobContentID)) {
-                    dataHandler = (DataHandler) 
attachmentsMap.get(blobContentID);
-                    return dataHandler;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Programatically adding an SOAP with Attachments(SwA) Attachment. These 
attachments will get
-     * serialized only if SOAP with Attachments is enabled.
-     *
-     * @param contentID
-     * @param dataHandler
-     */
-    public void addDataHandler(String contentID, DataHandler dataHandler) {
-        attachmentsMap.put(contentID, dataHandler);
-    }
-
-    /**
-     * Removes the DataHandler corresponding to the given contenID. If it is 
not present, then
-     * trying to find it calling the getNextPart() till the required part is 
found.
-     *
-     * @param blobContentID
-     */
-    public void removeDataHandler(String blobContentID) {
-        DataHandler dataHandler;
-        if (attachmentsMap.containsKey(blobContentID)) {
-            attachmentsMap.remove(blobContentID);
-        } else if (!noStreams) {
-            //This loop will be terminated by the Exceptions thrown if the Mime
-            // part searching was not found
-            while ((dataHandler = this.getNextPartDataHandler()) != null) {
-                if (attachmentsMap.containsKey(blobContentID)) {
-                    attachmentsMap.remove(blobContentID);
-                }
-            }
-        }
-    }
-
-    /**
-     * @return the InputStream which includes the SOAP Envelope. It assumes 
that the root mime part
-     *         is always pointed by "start" parameter in content-type.
-     */
-    public InputStream getSOAPPartInputStream() throws OMException {
-        DataHandler dh;
-        if (noStreams) {
-            throw new OMException("Invalid operation. Attachments are created 
programatically.");
-        }
-        try {
-            dh = getDataHandler(getSOAPPartContentID());
-            if (dh == null) {
-                throw new OMException(
-                        "Mandatory Root MIME part containing the SOAP Envelope 
is missing");
-            }
-            return dh.getInputStream();
-        } catch (IOException e) {
-            throw new OMException(
-                    "Problem with DataHandler of the Root Mime Part. ", e);
-        }
-    }
-
-    /**
-     * @return the Content-ID of the SOAP part It'll be the value Start 
Parameter of Content-Type
-     *         header if given in the Content type of the MIME message. Else 
it'll be the content-id
-     *         of the first MIME part of the MIME message
-     */
-    public String getSOAPPartContentID() {
-        String rootContentID = contentType.getParameter("start");
-
-        // to handle the Start parameter not mentioned situation
-        if (rootContentID == null) {
-            if (partIndex == 0) {
-                getNextPartDataHandler();
-            }
-            rootContentID = firstPartId;
-        } else {
-            rootContentID = rootContentID.trim();
-
-            if ((rootContentID.indexOf("<") > -1)
-                    & (rootContentID.indexOf(">") > -1)) {
-                rootContentID = rootContentID.substring(1, (rootContentID
-                        .length() - 1));
-            }
-        }
-        // Strips off the "cid" part from content-id
-        if ("cid".equalsIgnoreCase(rootContentID.substring(0, 3))) {
-            rootContentID = rootContentID.substring(4);
-        }
-        return rootContentID;
-    }
-
-    public String getSOAPPartContentType() {
-        if (!noStreams) {
-            DataHandler soapPart = getDataHandler(getSOAPPartContentID());
-            return soapPart.getContentType();
-        } else {
-            throw new OMException(
-                    "The attachments map was created programatically. 
Unsupported operation.");
-        }
-    }
-
-    /**
-     * Stream based access
-     *
-     * @return The stream container of type 
<code>IncomingAttachmentStreams</code>
-     * @throws IllegalStateException if application has alreadt started using 
Part's directly
-     */
-    public IncomingAttachmentStreams getIncomingAttachmentStreams()
-            throws IllegalStateException {
-        if (partsRequested) {
-            throw new IllegalStateException(
-                    "The attachments stream can only be accessed once; either 
by using the IncomingAttachmentStreams class or by getting a " +
-                            "collection of AttachmentPart objects. They cannot 
both be called within the life time of the same service request.");
-        }
-        if (noStreams) {
-            throw new IllegalStateException(
-                    "The attachments map was created programatically. No 
streams are available.");
-        }
-
-        streamsRequested = true;
-
-        if (this.streams == null) {
-            BoundaryDelimitedStream boundaryDelimitedStream =
-                    new BoundaryDelimitedStream(pushbackInStream,
-                                                boundary, 1024);
-
-            this.streams = new 
MultipartAttachmentStreams(boundaryDelimitedStream);
-        }
-
-        return this.streams;
-    }
-
-    public String[] getAllContentIDs() {
-        Set keys = getContentIDSet();
-        return (String[]) keys.toArray(new String[keys.size()]);
-    }
-
-    public Set getContentIDSet() {
-        DataHandler dataHandler;
-        while (!noStreams) {
-            dataHandler = this.getNextPartDataHandler();
-            if (dataHandler == null) {
-                break;
-            }
-        }
-        return attachmentsMap.keySet();
-    }
-
-    /**
-     * endOfStreamReached will be set to true if the message ended in MIME 
Style having "--" suffix
-     * with the last mime boundary
-     *
-     * @param value
-     */
-    protected void setEndOfStream(boolean value) {
-        this.endOfStreamReached = value;
-    }
-
-    /**
-     * @return the Next valid MIME part + store the Part in the Parts List
-     * @throws OMException throw if content id is null or if two MIME parts 
contain the same
-     *                     content-ID & the exceptions throws by getPart()
-     */
-    private DataHandler getNextPartDataHandler() throws OMException {
-        if (endOfStreamReached) {
-            return null;
-        }
-        Part nextPart;
-        nextPart = getPart();
-        if (nextPart == null) {
-            return null;
-        } else
-            try {
-                if (nextPart.getSize() > 0) {
-                    String partContentID;
-                    try {
-                        partContentID = nextPart.getContentID();
-
-                        if (partContentID == null & partIndex == 1) {
-                            String id = "firstPart_" + UUIDGenerator.getUUID();
-                            attachmentsMap.put(id, nextPart.getDataHandler());
-                            firstPartId = id;
-                            return nextPart.getDataHandler();
-                        }
-                        if (partContentID == null) {
-                            throw new OMException(
-                                    "Part content ID cannot be blank for non 
root MIME parts");
-                        }
-                        if ((partContentID.indexOf("<") > -1)
-                                & (partContentID.indexOf(">") > -1)) {
-                            partContentID = partContentID.substring(1, 
(partContentID
-                                    .length() - 1));
-
-                        } else if (partIndex == 1) {
-                            firstPartId = partContentID;
-                        }
-                        if (attachmentsMap.containsKey(partContentID)) {
-                            throw new OMException(
-                                    "Two MIME parts with the same Content-ID 
not allowed.");
-                        }
-                        attachmentsMap.put(partContentID, 
nextPart.getDataHandler());
-                        return nextPart.getDataHandler();
-                    } catch (MessagingException e) {
-                        throw new OMException("Error reading Content-ID from 
the Part."
-                                + e);
-                    }
-                } // This will take care if stream ended without having MIME
-                // message terminator
-                else {
-                    return null;
-                }
-            } catch (MessagingException e) {
-                throw new OMException(e);
-            }
-    }
-
-    /**
-     * @return This will return the next available MIME part in the stream.
-     * @throws OMException if Stream ends while reading the next part...
-     */
-    private Part getPart() throws OMException {
-
-        if (streamsRequested) {
-            throw new IllegalStateException(
-                    "The attachments stream can only be accessed once; either 
by using the IncomingAttachmentStreams class or by getting a collection of 
AttachmentPart objects. They cannot both be called within the life time of the 
same service request.");
-        }
-
-        partsRequested = true;
-
-        Part part;
-
-        try {
-            if (fileCacheEnable) {
-                try {
-                    MIMEBodyPartInputStream partStream;
-                    byte[] buffer = new byte[fileStorageThreshold];
-                    partStream = new MIMEBodyPartInputStream(pushbackInStream,
-                                                             boundary, this, 
PUSHBACK_SIZE);
-                    int count = 0;
-                    do {
-                        int len;
-                        int off = 0;
-                        int rem = fileStorageThreshold;
-                        while ((len = partStream.read(buffer, off, rem)) > 0) {
-                            off = off + len;
-                            rem = rem - len;
-                        }
-                        count += off;
-                    } while (partStream.available() > 0);
-
-                    if (count == fileStorageThreshold) {
-                        PushbackFilePartInputStream filePartStream =
-                                new PushbackFilePartInputStream(
-                                        partStream, buffer);
-                        part = new PartOnFile(filePartStream, 
attachmentRepoDir);
-                    } else {
-                        ByteArrayInputStream byteArrayInStream = new 
ByteArrayInputStream(buffer,
-                                                                               
           0, count);
-                        part = new PartOnMemory(byteArrayInStream);
-                    }
-                } catch (Exception e) {
-                    throw new OMException("Error creating temporary File.", e);
-                }
-            } else {
-                MIMEBodyPartInputStream partStream;
-                partStream = new MIMEBodyPartInputStream(pushbackInStream,
-                                                         boundary, this, 
PUSHBACK_SIZE);
-                part = new PartOnMemory(partStream);
-            }
-
-        } catch (MessagingException e) {
-            throw new OMException(e);
-        }
-        partIndex++;
-        return part;
-    }
+/*
+ * 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.axiom.attachments;
+
+import org.apache.axiom.om.OMException;
+import org.apache.axiom.om.impl.MTOMConstants;
+import org.apache.axiom.om.util.UUIDGenerator;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.activation.DataHandler;
+import javax.mail.MessagingException;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.ParseException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeMap;
+
+public class Attachments {
+
+    /** <code>ContentType</code> of the MIME message */
+    ContentType contentType;
+    
+    int contentLength; // Content Length
+
+    /** Mime <code>boundary</code> which separates mime parts */
+    byte[] boundary;
+
+    /**
+     * <code>applicationType</code> used to distinguish between MTOM & SWA If 
the message is MTOM
+     * optimised type is application/xop+xml If the message is SWA, type is 
??have to find out
+     */
+    String applicationType;
+
+    /**
+     * <code>pushbackInStream</code> stores the reference to the incoming 
stream A PushbackStream
+     * has the ability to "push back" or "unread" one byte.
+     */
+    PushbackInputStream pushbackInStream;
+    int PUSHBACK_SIZE = 1000;
+
+    /**
+     * <code>attachmentsMap</code> stores the Data Handlers of the already 
parsed Mime Body Parts.
+     * This ordered Map is keyed using the content-ID's.
+     */
+    TreeMap attachmentsMap;
+    
+    /**
+     * <code>cids</code> stores the content ids in the order that the 
attachments
+     * occur in the message
+     */
+    ArrayList cids = new ArrayList(); 
+
+    /** <code>partIndex</code>- Number of Mime parts parsed */
+    int partIndex = 0;
+
+    /** Container to hold streams for direct access */
+    IncomingAttachmentStreams streams = null;
+
+    /** <code>boolean</code> Indicating if any streams have been directly 
requested */
+    private boolean streamsRequested = false;
+
+    /** <code>boolean</code> Indicating if any data handlers have been 
directly requested */
+    private boolean partsRequested = false;
+
+    /**
+     * <code>endOfStreamReached</code> flag which is to be set by 
MIMEBodyPartStream when MIME
+     * message terminator is found.
+     */
+    private boolean endOfStreamReached;
+
+
+    /**
+     * <code>noStreams</code> flag which is to be set when this class is 
instantiated by the SwA API
+     * to handle programatic added attachements. An InputStream with 
attachments is not present at
+     * that occation.
+     */
+    private boolean noStreams = false;
+
+    private String firstPartId;
+
+    private boolean fileCacheEnable;
+
+    private String attachmentRepoDir;
+
+    private int fileStorageThreshold;
+
+    protected static Log log = LogFactory.getLog(Attachments.class);
+
+
+    /**
+     * Moves the pointer to the beginning of the first MIME part. Reads till 
first MIME boundary is
+     * found or end of stream is reached.
+     *
+     * @param inStream
+     * @param contentTypeString
+     * @param fileCacheEnable
+     * @param attachmentRepoDir
+     * @throws OMException
+     */
+    public Attachments(InputStream inStream, String contentTypeString,
+                       boolean fileCacheEnable, String attachmentRepoDir,
+                       String fileThreshold) throws OMException {
+        this(inStream, contentTypeString, fileCacheEnable, attachmentRepoDir, 
fileThreshold, 0);
+    }
+        
+        /**
+     * Moves the pointer to the beginning of the first MIME part. Reads
+     * till first MIME boundary is found or end of stream is reached.
+     *
+     * @param inStream
+     * @param contentTypeString
+     * @param fileCacheEnable
+     * @param attachmentRepoDir
+     * @param fileThreshold
+     * @param contentLength
+     * @throws OMException
+     */
+    public Attachments(InputStream inStream, String contentTypeString, boolean 
fileCacheEnable,
+            String attachmentRepoDir, String fileThreshold, int contentLength) 
throws OMException {
+        this.contentLength = contentLength;
+        this.attachmentRepoDir = attachmentRepoDir;
+        this.fileCacheEnable = fileCacheEnable;
+        if (fileThreshold != null && (!"".equals(fileThreshold))) {
+            this.fileStorageThreshold = Integer.parseInt(fileThreshold);
+        } else {
+            this.fileStorageThreshold = 1;
+        }
+        attachmentsMap = new TreeMap();
+        try {
+            contentType = new ContentType(contentTypeString);
+        } catch (ParseException e) {
+            throw new OMException(
+                    "Invalid Content Type Field in the Mime Message"
+                    , e);
+        }
+        // REVIEW: This conversion is hard-coded to UTF-8.
+        // The complete solution is to respect the charset setting of the 
message.
+        // However this may cause problems in BoundaryDelimittedStream and 
other
+        // lower level classes.
+
+        // Boundary always have the prefix "--".
+        try {
+            String encoding = contentType.getParameter("charset");
+            if(encoding == null || encoding.length()==0){
+                encoding = "UTF-8";
+            }
+            this.boundary = ("--" + contentType.getParameter("boundary"))
+                    .getBytes(encoding);
+        } catch (UnsupportedEncodingException e) {
+            throw new OMException(e);
+        }
+
+        // do we need to wrap InputStream from a BufferedInputStream before
+        // wrapping from PushbackStream
+        pushbackInStream = new PushbackInputStream(inStream,
+                                                   PUSHBACK_SIZE);
+
+        // Move the read pointer to the beginning of the first part
+        // read till the end of first boundary
+        while (true) {
+            int value;
+            try {
+                value = pushbackInStream.read();
+                if ((byte) value == boundary[0]) {
+                    int boundaryIndex = 0;
+                    while ((boundaryIndex < boundary.length)
+                            && ((byte) value == boundary[boundaryIndex])) {
+                        value = pushbackInStream.read();
+                        if (value == -1) {
+                            throw new OMException(
+                                    "Unexpected End of Stream while searching 
for first Mime Boundary");
+                        }
+                        boundaryIndex++;
+                    }
+                    if (boundaryIndex == boundary.length) { // boundary found
+                        pushbackInStream.read();
+                        break;
+                    }
+                } else if ((byte) value == -1) {
+                    throw new OMException(
+                            "Mime parts not found. Stream ended while 
searching for the boundary");
+                }
+            } catch (IOException e1) {
+                throw new OMException("Stream Error" + e1.toString(), e1);
+            }
+        }
+
+        // Read the SOAP part and cache it
+        getDataHandler(getSOAPPartContentID());
+
+        // Now reset partsRequested. SOAP part is a special case which is 
always 
+        // read beforehand, regardless of request.
+        partsRequested = false;
+    }
+
+    /**
+     * Sets file cache to false.
+     *
+     * @param inStream
+     * @param contentTypeString
+     * @throws OMException
+     */
+    public Attachments(InputStream inStream, String contentTypeString)
+            throws OMException {
+        this(inStream, contentTypeString, false, null, null);
+    }
+
+    /**
+     * Use this constructor when instantiating this to store the attachments 
set programatically
+     * through the SwA API.
+     */
+    public Attachments() {
+        attachmentsMap = new TreeMap();
+        noStreams = true;
+    }
+
+    /**
+     * @return whether Message Type is SOAP with Attachments or MTOM 
optimized, by checking the
+     *         application type parameter in the Content Type.
+     */
+    public String getAttachmentSpecType() {
+        if (this.applicationType == null) {
+            applicationType = contentType.getParameter("type");
+            if ((MTOMConstants.MTOM_TYPE).equalsIgnoreCase(applicationType)) {
+                this.applicationType = MTOMConstants.MTOM_TYPE;
+            } else if 
((MTOMConstants.SWA_TYPE).equalsIgnoreCase(applicationType)) {
+                this.applicationType = MTOMConstants.SWA_TYPE;
+            } else if 
((MTOMConstants.SWA_TYPE_12).equalsIgnoreCase(applicationType)) {
+                this.applicationType = MTOMConstants.SWA_TYPE_12;
+            } else {
+                throw new OMException(
+                        "Invalid Application type. Support available for MTOM 
& SwA only.");
+            }
+        }
+        return this.applicationType;
+    }
+
+    /**
+     * Checks whether the MIME part is already parsed by checking the 
attachments HashMap. If it is
+     * not parsed yet then call the getNextPart() till the required part is 
found.
+     *
+     * @param blobContentID (without the surrounding angle brackets and "cid:" 
prefix)
+     * @return The DataHandler of the mime part referred by the Content-Id or 
*null* if the mime
+     *         part referred by the content-id does not exist
+     */
+    public DataHandler getDataHandler(String blobContentID) {
+        DataHandler dataHandler;
+        if (attachmentsMap.containsKey(blobContentID)) {
+            dataHandler = (DataHandler) attachmentsMap.get(blobContentID);
+            return dataHandler;
+        } else if (!noStreams) {
+            //This loop will be terminated by the Exceptions thrown if the Mime
+            // part searching was not found
+            while ((dataHandler = this.getNextPartDataHandler()) != null) {
+                if (attachmentsMap.containsKey(blobContentID)) {
+                    dataHandler = (DataHandler) 
attachmentsMap.get(blobContentID);
+                    return dataHandler;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Programatically adding an SOAP with Attachments(SwA) Attachment. These 
attachments will get
+     * serialized only if SOAP with Attachments is enabled.
+     *
+     * @param contentID
+     * @param dataHandler
+     */
+    public void addDataHandler(String contentID, DataHandler dataHandler) {
+        attachmentsMap.put(contentID, dataHandler);
+        if (!cids.contains(contentID)) {
+            cids.add(contentID);
+        }
+    }
+
+    /**
+     * Removes the DataHandler corresponding to the given contenID. If it is 
not present, then
+     * trying to find it calling the getNextPart() till the required part is 
found.
+     *
+     * @param blobContentID
+     */
+    public void removeDataHandler(String blobContentID) {
+        DataHandler dataHandler;
+        if (attachmentsMap.containsKey(blobContentID)) {
+            attachmentsMap.remove(blobContentID);
+        } else if (!noStreams) {
+            //This loop will be terminated by the Exceptions thrown if the Mime
+            // part searching was not found
+            while ((dataHandler = this.getNextPartDataHandler()) != null) {
+                if (attachmentsMap.containsKey(blobContentID)) {
+                    attachmentsMap.remove(blobContentID);
+                }
+            }
+        }
+        if (!cids.contains(blobContentID)) {
+            cids.remove(blobContentID);
+        }
+    }
+
+    /**
+     * @return the InputStream which includes the SOAP Envelope. It assumes 
that the root mime part
+     *         is always pointed by "start" parameter in content-type.
+     */
+    public InputStream getSOAPPartInputStream() throws OMException {
+        DataHandler dh;
+        if (noStreams) {
+            throw new OMException("Invalid operation. Attachments are created 
programatically.");
+        }
+        try {
+            dh = getDataHandler(getSOAPPartContentID());
+            if (dh == null) {
+                throw new OMException(
+                        "Mandatory Root MIME part containing the SOAP Envelope 
is missing");
+            }
+            return dh.getInputStream();
+        } catch (IOException e) {
+            throw new OMException(
+                    "Problem with DataHandler of the Root Mime Part. ", e);
+        }
+    }
+
+    /**
+     * @return the Content-ID of the SOAP part It'll be the value Start 
Parameter of Content-Type
+     *         header if given in the Content type of the MIME message. Else 
it'll be the content-id
+     *         of the first MIME part of the MIME message
+     */
+    public String getSOAPPartContentID() {
+        String rootContentID = contentType.getParameter("start");
+
+        // to handle the Start parameter not mentioned situation
+        if (rootContentID == null) {
+            if (partIndex == 0) {
+                getNextPartDataHandler();
+            }
+            rootContentID = firstPartId;
+        } else {
+            rootContentID = rootContentID.trim();
+
+            if ((rootContentID.indexOf("<") > -1)
+                    & (rootContentID.indexOf(">") > -1)) {
+                rootContentID = rootContentID.substring(1, (rootContentID
+                        .length() - 1));
+            }
+        }
+        // Strips off the "cid" part from content-id
+        if ("cid".equalsIgnoreCase(rootContentID.substring(0, 3))) {
+            rootContentID = rootContentID.substring(4);
+        }
+        return rootContentID;
+    }
+
+    public String getSOAPPartContentType() {
+        if (!noStreams) {
+            DataHandler soapPart = getDataHandler(getSOAPPartContentID());
+            return soapPart.getContentType();
+        } else {
+            throw new OMException(
+                    "The attachments map was created programatically. 
Unsupported operation.");
+        }
+    }
+
+    /**
+     * Stream based access
+     *
+     * @return The stream container of type 
<code>IncomingAttachmentStreams</code>
+     * @throws IllegalStateException if application has alreadt started using 
Part's directly
+     */
+    public IncomingAttachmentStreams getIncomingAttachmentStreams()
+            throws IllegalStateException {
+        if (partsRequested) {
+            throw new IllegalStateException(
+                    "The attachments stream can only be accessed once; either 
by using the IncomingAttachmentStreams class or by getting a " +
+                            "collection of AttachmentPart objects. They cannot 
both be called within the life time of the same service request.");
+        }
+        if (noStreams) {
+            throw new IllegalStateException(
+                    "The attachments map was created programatically. No 
streams are available.");
+        }
+
+        streamsRequested = true;
+
+        if (this.streams == null) {
+            BoundaryDelimitedStream boundaryDelimitedStream =
+                    new BoundaryDelimitedStream(pushbackInStream,
+                                                boundary, 1024);
+
+            this.streams = new 
MultipartAttachmentStreams(boundaryDelimitedStream);
+        }
+
+        return this.streams;
+    }
+
+    public String[] getAllContentIDs() {
+        Set keys = getContentIDSet();
+        return (String[]) keys.toArray(new String[keys.size()]);
+    }
+
+    public Set getContentIDSet() {
+        DataHandler dataHandler;
+        while (!noStreams) {
+            dataHandler = this.getNextPartDataHandler();
+            if (dataHandler == null) {
+                break;
+            }
+        }
+        return attachmentsMap.keySet();
+    }
+    
+    /**
+     * @return List of content ids in order of appearance in message
+     */
+    public List getContentIDList() {
+        return cids;
+    }
+
+    /**
+     * endOfStreamReached will be set to true if the message ended in MIME 
Style having "--" suffix
+     * with the last mime boundary
+     *
+     * @param value
+     */
+    protected void setEndOfStream(boolean value) {
+        this.endOfStreamReached = value;
+    }
+
+    /**
+     * @return the Next valid MIME part + store the Part in the Parts List
+     * @throws OMException throw if content id is null or if two MIME parts 
contain the same
+     *                     content-ID & the exceptions throws by getPart()
+     */
+    private DataHandler getNextPartDataHandler() throws OMException {
+        if (endOfStreamReached) {
+            return null;
+        }
+        Part nextPart;
+        nextPart = getPart();
+        if (nextPart == null) {
+            return null;
+        } else
+            try {
+                if (nextPart.getSize() > 0) {
+                    String partContentID;
+                    try {
+                        partContentID = nextPart.getContentID();
+
+                        if (partContentID == null & partIndex == 1) {
+                            String id = "firstPart_" + UUIDGenerator.getUUID();
+                            firstPartId = id;
+                            addDataHandler(id, nextPart.getDataHandler());
+                            return nextPart.getDataHandler();
+                        }
+                        if (partContentID == null) {
+                            throw new OMException(
+                                    "Part content ID cannot be blank for non 
root MIME parts");
+                        }
+                        if ((partContentID.indexOf("<") > -1)
+                                & (partContentID.indexOf(">") > -1)) {
+                            partContentID = partContentID.substring(1, 
(partContentID
+                                    .length() - 1));
+
+                        } else if (partIndex == 1) {
+                            firstPartId = partContentID;
+                        }
+                        if (attachmentsMap.containsKey(partContentID)) {
+                            throw new OMException(
+                                    "Two MIME parts with the same Content-ID 
not allowed.");
+                        }
+                        addDataHandler(partContentID, 
nextPart.getDataHandler());
+                        return nextPart.getDataHandler();
+                    } catch (MessagingException e) {
+                        throw new OMException("Error reading Content-ID from 
the Part."
+                                + e);
+                    }
+                } // This will take care if stream ended without having MIME
+                // message terminator
+                else {
+                    return null;
+                }
+            } catch (MessagingException e) {
+                throw new OMException(e);
+            }
+    }
+
+    /**
+     * @return This will return the next available MIME part in the stream.
+     * @throws OMException if Stream ends while reading the next part...
+     */
+    private Part getPart() throws OMException {
+
+        if (streamsRequested) {
+            throw new IllegalStateException("The attachments stream can only 
be accessed once; either by using the IncomingAttachmentStreams class or by 
getting a collection of AttachmentPart objects. They cannot both be called 
within the life time of the same service request.");
+        }
+
+        partsRequested = true;
+
+        Part part;
+
+        try {
+            if (fileCacheEnable) {
+                try {
+                    if (contentLength != 0 &&
+                            contentLength <= fileStorageThreshold) {
+                        // Since the content-length is less than the 
threshold, we can safely 
+                        // store it in memory.
+                        if (log.isDebugEnabled()) {
+                            log.debug("Creating an Attachment Part (in 
memory)");
+                        }
+                        MIMEBodyPartInputStream partStream =
+                            new MIMEBodyPartInputStream(pushbackInStream,
+                                                        boundary,
+                                                        this,
+                                                        PUSHBACK_SIZE);
+                        part = new PartOnMemory(partStream);
+                    } else if (contentLength != 0 && 
+                            contentLength > fileStorageThreshold * 2) {  
+                        if (log.isDebugEnabled()) {
+                            log.debug("Creating an Attachment Part (in a 
temporary file in " + attachmentRepoDir + ")");
+                        }
+                        // The content-length is much bigger than the 
threshold, then always
+                        // store the attachments in a file.  This prevents 
unnecessary buffering.
+                        // REVIEW Arbitrary heuristic.  Does this need to be 
fine-tuned.
+                        MIMEBodyPartInputStream partStream =
+                            new MIMEBodyPartInputStream(pushbackInStream,
+                                                        boundary,
+                                                        this,
+                                                        PUSHBACK_SIZE);
+                        PushbackFilePartInputStream filePartStream =
+                            new PushbackFilePartInputStream(partStream, new 
byte[0]);
+                        part = new PartOnFile(filePartStream, 
attachmentRepoDir);
+                        
+                    } else {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Buffering attachment part to determine 
if it should be stored in memory");
+                        }
+                        // Read chunks of data to determine the size
+                        // of the attachment.  This can wasteful because we 
need to gc the buffers.
+                        // TODO We could look at the content-length of the 
individual attachment; however
+                        // this is seldom provided.
+                        
+                        MIMEBodyPartInputStream partStream;
+                        byte[] buffer = new byte[fileStorageThreshold];
+                        partStream =
+                            new MIMEBodyPartInputStream(pushbackInStream,
+                                                        boundary,
+                                                        this,
+                                                        PUSHBACK_SIZE);
+                        int count = 0;
+                        int value;
+                        count = partStream.read(buffer);
+                        
+                        if (count == fileStorageThreshold) {
+                            if (log.isDebugEnabled()) {
+                                log.debug("The calculated attachment size is " 
+ count + ". Storing Part in file.");
+                            }
+                            PushbackFilePartInputStream filePartStream =
+                                new PushbackFilePartInputStream(partStream, 
buffer);
+                            part = new PartOnFile(filePartStream, 
attachmentRepoDir);
+                        } else {
+                            if (log.isDebugEnabled()) {
+                                log.debug("The calculated attachment size is " 
+ count + ". Storing Part in memory.");
+                            }
+                            ByteArrayInputStream byteArrayInStream =
+                                new ByteArrayInputStream(buffer, 0, count);
+                            part = new PartOnMemory(byteArrayInStream);
+                        }
+                    } 
+                } catch (Exception e) {
+                    throw new OMException("Error creating temporary File.", e);
+                }
+            } else {
+                MIMEBodyPartInputStream partStream;
+                partStream =
+                        new MIMEBodyPartInputStream(pushbackInStream, 
boundary, this, PUSHBACK_SIZE);
+                part = new PartOnMemory(partStream);
+            }
+
+        } catch (MessagingException e) {
+            throw new OMException(e);
+        }
+        partIndex++;
+        return part;
+    }
 }



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to