blob: dff38711793ceb2c9e5f73140bd6c1a3961790e4 [file] [log] [blame]
/*
* Copyright 2004,2005 The Apache Software Foundation.
*
* 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.ws.commons.attachments;
import org.apache.ws.commons.om.OMException;
import org.apache.ws.commons.om.impl.MTOMConstants;
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.util.HashMap;
public class MIMEHelper {
/**
* <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;
/**
* <code>mimeBodyPartsMap</code> stores the already parsed Mime Body
* Parts. This Map will be keyed using the content-ID's
*/
HashMap bodyPartsMap;
/**
* <code>partIndex</code>- Number of Mime parts parsed
*/
int partIndex = 0;
/**
* <code>endOfStreamReached</code> flag which is to be set by
* MIMEBodyPartStream when MIME message terminator is found.
*/
boolean endOfStreamReached;
String firstPartId;
boolean fileCacheEnable;
String attachmentRepoDir;
int fileStorageThreshold;
protected Log log = LogFactory.getLog(getClass());
/**
* 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 MIMEHelper(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;
}
bodyPartsMap = new HashMap();
try {
contentType = new ContentType(contentTypeString);
} catch (ParseException e) {
throw new OMException(
"Invalid Content Type Field in the Mime Message"
,e);
}
// Boundary always have the prefix "--".
this.boundary = ("--" + contentType.getParameter("boundary"))
.getBytes();
// do we need to wrap InputStream from a BufferedInputStream before
// wrapping from PushbackStream
pushbackInStream = new PushbackInputStream(inStream,
(this.boundary.length + 2));
// 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);
}
}
}
/**
* Sets file cache to false.
*
* @param inStream
* @param contentTypeString
* @throws OMException
*/
public MIMEHelper(InputStream inStream, String contentTypeString)
throws OMException {
this(inStream, contentTypeString, false, null, null);
}
/**
* @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 {
throw new OMException(
"Invalid Application type. Support available for MTOM/SOAP 1.2 & SwA/SOAP 1.l only.");
}
}
return this.applicationType;
}
/**
* @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;
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
*/
private String getSOAPPartContentID() {
String rootContentID = contentType.getParameter("start");
// to handle the Start parameter not mentioned situation
if (rootContentID == null) {
if (partIndex == 0) {
getNextPart();
}
rootContentID = firstPartId;
} else {
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() {
Part soapPart = getPart(getSOAPPartContentID());
try {
return soapPart.getContentType();
} catch (MessagingException e) {
log.error(e.getMessage());
throw new OMException(e);
}
}
/**
* @param blobContentID
* (without the surrounding angle brackets and "cid:" prefix)
* @return The DataHandler of the mime part referred by the Content-Id
* @throws OMException
*/
public DataHandler getDataHandler(String blobContentID) throws OMException {
try {
return getPart(blobContentID).getDataHandler();
} catch (MessagingException e) {
throw new OMException("Problem with Mime Body Part No " + partIndex
+ ". ", e);
}
}
/**
* Checks whether the MIME part is already parsed by checking the
* parts HashMap. If it is not parsed yet then call the getNextPart()
* till the required part is found.
*
* @param blobContentID
* @return The Part referred by the Content-Id
* @throws OMException
*
*/
public Part getPart(String blobContentID) {
Part bodyPart;
if (bodyPartsMap.containsKey(blobContentID)) {
bodyPart = (Part) bodyPartsMap.get(blobContentID);
return bodyPart;
} else {
//This loop will be terminated by the Exceptions thrown if the Mime
// part searching was not found
while (true) {
bodyPart = this.getNextPart();
if (bodyPart == null) {
return null;
}
if (bodyPartsMap.containsKey(blobContentID)) {
bodyPart = (Part) bodyPartsMap.get(blobContentID);
return bodyPart;
}
}
}
}
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 Part getNextPart() throws OMException {
Part nextPart;
nextPart = getPart();
if (nextPart != null) {
String partContentID;
try {
partContentID = nextPart.getContentID();
if (partContentID == null & partIndex == 1) {
bodyPartsMap.put("firstPart", nextPart);
firstPartId = "firstPart";
return nextPart;
}
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 (bodyPartsMap.containsKey(partContentID)) {
throw new OMException(
"Two MIME parts with the same Content-ID not allowed.");
}
bodyPartsMap.put(partContentID, nextPart);
return nextPart;
} catch (MessagingException e) {
throw new OMException("Error reading Content-ID from the Part."
+ e);
}
} else
return null;
}
/**
* @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 {
// endOfStreamReached will be set to true if the message ended in MIME
// Style having "--" suffix with the last mime boundary
if (endOfStreamReached)
throw new OMException(
"Referenced MIME part not found.End of Stream reached.");
Part part = null;
try {
if (fileCacheEnable) {
try {
MIMEBodyPartInputStream partStream;
byte[] buffer = new byte[fileStorageThreshold];
partStream = new MIMEBodyPartInputStream(pushbackInStream,
boundary, this);
int count = 0;
int value;
// Make sure not to modify this to a Short Circuit "&". If
// removed a byte will be lost
while (count != fileStorageThreshold
&& (!partStream.getBoundaryStatus())) {
value = partStream.read();
buffer[count] = (byte) value;
count++;
}
if (count == fileStorageThreshold) {
PushbackFilePartInputStream filePartStream = new PushbackFilePartInputStream(
partStream, buffer);
part = new PartOnFile(filePartStream, attachmentRepoDir);
} else {
ByteArrayInputStream byteArrayInStream = new ByteArrayInputStream(
buffer,0,count-1);
part = new PartOnMemory(byteArrayInStream);
}
} catch (Exception e) {
throw new OMException("Error creating temporary File.", e);
}
} else {
MIMEBodyPartInputStream partStream;
partStream = new MIMEBodyPartInputStream(pushbackInStream,
boundary, this);
part = new PartOnMemory(partStream);
}
// This will take care if stream ended without having MIME
// message terminator
if (part.getSize() <= 0) {
throw new OMException(
"Referenced MIME part not found.End of Stream reached.");
}
} catch (MessagingException e) {
throw new OMException(e);
}
partIndex++;
return part;
}
}