/*******************************************************************************
* Copyright (c) 2009 MATERNA Information & Communications. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html. For further
* project-related information visit http://www.ws4d.org. The most recent
* version of the JMEDS framework can be obtained from
* http://sourceforge.net/projects/ws4d-javame.
******************************************************************************/
package org.ws4d.java.communication.protocol.mime;
import java.io.IOException;
import java.io.InputStream;
import org.ws4d.java.communication.ProtocolException;
import org.ws4d.java.communication.protocol.http.HTTPUtil;
import org.ws4d.java.constants.MIMEConstants;
import org.ws4d.java.constants.Specialchars;
import org.ws4d.java.structures.HashMap;
import org.ws4d.java.util.Log;
import org.ws4d.java.util.MIMEUtil;
import org.ws4d.java.util.Search;
import org.ws4d.java.util.Sync;
/**
* The <code>MIMEReader</code> reads the MIME from a given input stream.
*/
public class MIMEReader {
// search stuff
private byte[] boundaryBytes = null;
private int[] boundaryFaultFunction = null;
private int part = 0;
// predefined exception messages.
protected static final String FAULT_UNEXPECTED_END = "Unexpected end of stream.";
protected static final String FAULT_MALFORMED_HEADERFIELD = "Malformed MIME header field.";
protected static final String FAULT_NOT_FINISHED = "Previous part not finished.";
// Parent processor stuff
protected InputStream in = null;
protected MIMEInputStream mimeIn = null;
// header
private MIMEBodyHeader header = null;
private Sync incomingMIMEPartLock = null;
private volatile boolean finished = false;
public MIMEReader(InputStream in, byte[] boundary) throws IOException {
this(in, boundary, null);
}
public MIMEReader(InputStream in, byte[] boundary, Sync incomingMIMEPartLock) throws IOException {
/*
* gets rid of the first boundary!!!
*/
MIMEUtil.readBoundary(in, boundary);
this.in = in;
boundaryBytes = new byte[boundary.length + 2];
System.arraycopy(boundary, 0, boundaryBytes, 2, boundary.length);
// insert hyphen characters.
boundaryBytes[0] = MIMEConstants.BOUNDARY_HYPHEN;
boundaryBytes[1] = MIMEConstants.BOUNDARY_HYPHEN;
boundaryFaultFunction = Search.createFaultFunction(boundaryBytes);
try {
readMIMEPartHeader();
} catch (ProtocolException e) {
Log.error("Cannot read first MIME header. " + e.getMessage());
}
this.incomingMIMEPartLock = incomingMIMEPartLock;
part = 1;
}
/**
* Returns <code>true</code> if there is another part (next
* <code>InputStream</code>) to read. This method throws a
* <code>IOExcpetion</code> if someone tries to invoke this method before
* the previous part is read completely or if another I/O error occurs.
*
* @return <code>true</code> if there is another part. <code>false</code>
* otherwise.
*/
public synchronized boolean nextPart() throws IOException {
try {
if (mimeIn == null && part > 1) {
finished = true;
notifyAll();
return false;
}
if (mimeIn == null) {
mimeIn = new MIMEInputStream();
return true;
}
if (mimeIn.isInUse() && !mimeIn.isClosed()) throw new IOException(FAULT_NOT_FINISHED);
int i = in.read();
if (i == Specialchars.CR) {
i = in.read();
if (i == Specialchars.LF) {
try {
readMIMEPartHeader();
} catch (ProtocolException e) {
Log.error("Cannot read MIME header. " + e.getMessage());
finished = true;
notifyAll();
return false;
}
mimeIn = new MIMEInputStream();
part++;
return true;
}
} else if (i == MIMEConstants.BOUNDARY_HYPHEN) {
i = in.read();
if (i == MIMEConstants.BOUNDARY_HYPHEN) {
// the MIME ends here!
HTTPUtil.readRequestLine(in);
finished = true;
notifyAll();
return false;
}
}
finished = true;
notifyAll();
return false;
} catch (IOException ioe) {
if (!FAULT_NOT_FINISHED.equals(ioe.getMessage())) {
finished = true;
notifyAll();
}
throw ioe;
}
}
/**
* Returns the number of the current part.
*
* @return the number of the current part.
*/
public int getPartNumber() {
return part;
}
/**
* Returns the MIME body header.
*
* @return the MIME body header.
*/
public MIMEBodyHeader getMIMEBodyHeader() {
return header;
}
/**
* Returns the input stream for this processor. Because this is a multipart
* processor, this method will return different streams after the use of
* <code>nextPart()</code>.
*
* @return the <code>InputStream</code>.
*/
public InputStream getInputStream() {
if (mimeIn == null) {
mimeIn = new MIMEInputStream();
}
return mimeIn;
}
public synchronized void waitFor() {
while (!finished) {
try {
wait();
} catch (InterruptedException e) {
// void
}
}
}
/**
* Reads the header field of the MIME part.
*/
private synchronized void readMIMEPartHeader() throws IOException, ProtocolException {
HashMap headerfields = new HashMap();
MIMEUtil.readHeaderFields(in, headerfields);
header = new MIMEBodyHeader(headerfields);
}
/**
* MIME input stream wrapper. This class wraps the input stream and controls
* the length of the data read.
*/
private class MIMEInputStream extends InputStream {
private InputStream inKMP = null;
private boolean closed = false;
private boolean read = false;
public MIMEInputStream() {
inKMP = Search.getSearchPatternWrapper(in, boundaryBytes, boundaryFaultFunction);
}
/*
* (non-Javadoc)
* @see java.io.InputStream#available()
*/
public int available() throws IOException {
try {
return inKMP.available();
} catch (IOException e) {
if (incomingMIMEPartLock != null) {
synchronized (incomingMIMEPartLock) {
incomingMIMEPartLock.notifyNow(e);
}
}
throw e;
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#read()
*/
public int read() throws IOException {
try {
if (closed) return -1;
read = true;
int i = inKMP.read();
if (i == -1) {
if (incomingMIMEPartLock != null) {
synchronized (incomingMIMEPartLock) {
closed = true;
incomingMIMEPartLock.notifyNow();
}
} else {
closed = true;
}
}
return i;
} catch (IOException e) {
if (incomingMIMEPartLock != null) {
synchronized (incomingMIMEPartLock) {
incomingMIMEPartLock.notifyNow(e);
}
}
throw e;
}
}
/*
* (non-Javadoc)
* @see java.io.InputStream#close()
*/
public void close() throws IOException {
try {
if (incomingMIMEPartLock != null) {
synchronized (incomingMIMEPartLock) {
incomingMIMEPartLock.notifyNow();
}
}
inKMP.close();
} catch (IOException e) {
if (incomingMIMEPartLock != null) {
synchronized (incomingMIMEPartLock) {
incomingMIMEPartLock.notifyNow(e);
}
}
throw e;
}
}
/**
* Returns <code>true</code> if someone is already reading from this
* stream, <code>false</code> otherwise.
*
* @return <code>true</code> if someone is already reading from this
* stream, <code>false</code> otherwise.
*/
public boolean isInUse() {
return read;
}
/**
* Returns <code>true</code> if the stream is closed, <code>false</code>
* otherwise.
*
* @return <code>true</code> if the stream is closed, <code>false</code>
* otherwise.
*/
public boolean isClosed() {
return closed;
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + ((inKMP == null) ? 0 : inKMP.hashCode());
return result;
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
MIMEInputStream other = (MIMEInputStream) obj;
if (!getOuterType().equals(other.getOuterType())) return false;
if (inKMP == null) {
if (other.inKMP != null) return false;
} else if (!inKMP.equals(other.inKMP)) return false;
return true;
}
private MIMEReader getOuterType() {
return MIMEReader.this;
}
}
}