/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-09 Wolfgang M. Meier
* wolfgang@exist-db.org
* http://exist.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package org.exist.xquery.functions.request;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.io.input.CloseShieldInputStream;
import org.apache.log4j.Logger;
import org.exist.Namespaces;
import org.exist.dom.QName;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.exist.http.servlets.RequestWrapper;
import org.exist.memtree.DocumentBuilderReceiver;
import org.exist.memtree.MemTreeBuilder;
import org.exist.util.Configuration;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.util.io.CachingFilterInputStream;
import org.exist.util.io.FilterInputStreamCache;
import org.exist.util.io.FilterInputStreamCacheFactory;
import org.exist.util.io.FilterInputStreamCacheFactory.FilterInputStreamCacheConfiguration;
import org.exist.xquery.*;
import org.exist.xquery.value.*;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
/**
* @author Wolfgang Meier <wolfgang@exist-db.org>
* @author Adam retter <adam@exist-db.org>
*/
public class GetData extends BasicFunction {
protected static final Logger logger = Logger.getLogger(GetData.class);
public final static FunctionSignature signature = new FunctionSignature(
new QName("get-data", RequestModule.NAMESPACE_URI, RequestModule.PREFIX),
"Returns the content of a POST request. " +
"If the HTTP Content-Type header in the request identifies it as a binary document, then xs:base64Binary is returned. " +
"If its not a binary document, we attempt to parse it as XML and return a document-node(). " +
"If its not a binary or XML document, any other data type is returned as an xs:string representation or " +
"an empty sequence if there is no data to be read.",
null,
new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_ONE, "the content of a POST request")
);
public GetData(XQueryContext context) {
super(context, signature);
}
/* (non-Javadoc)
* @see org.exist.xquery.BasicFunction#eval(org.exist.xquery.value.Sequence[], org.exist.xquery.value.Sequence)
*/
@Override
public Sequence eval(Sequence[] args, Sequence contextSequence)throws XPathException {
final RequestModule myModule = (RequestModule) context.getModule(RequestModule.NAMESPACE_URI);
// request object is read from global variable $request
final Variable var = myModule.resolveVariable(RequestModule.REQUEST_VAR);
if(var == null || var.getValue() == null) {
throw new XPathException(this, "No request object found in the current XQuery context.");
}
if(var.getValue().getItemType() != Type.JAVA_OBJECT) {
throw new XPathException(this, "Variable $request is not bound to an Java object.");
}
final JavaObjectValue value = (JavaObjectValue) var.getValue().itemAt(0);
if(!(value.getObject() instanceof RequestWrapper)) {
throw new XPathException(this, "Variable $request is not bound to a Request object.");
}
final RequestWrapper request = (RequestWrapper)value.getObject();
//if the content length is unknown or 0, return
if(request.getContentLength() == -1 || request.getContentLength() == 0) {
return Sequence.EMPTY_SEQUENCE;
}
InputStream isRequest = null;
Sequence result = Sequence.EMPTY_SEQUENCE;
try {
isRequest = request.getInputStream();
//was there any POST content?
/**
* There is a bug in HttpInput.available() in Jetty 7.2.2.v20101205
* This has been filed as Bug 333415 - https://bugs.eclipse.org/bugs/show_bug.cgi?id=333415
* It is expected to be fixed in the Jetty 7.3.0 release
*/
//TODO reinstate call to .available() when Jetty 7.3.0 is released, use of .getContentLength() is not reliable because of http mechanics
//if(is != null && is.available() > 0) {
if(isRequest != null && request.getContentLength() > 0) {
// 1) determine if exists mime database considers this binary data
String contentType = request.getContentType();
if(contentType != null) {
//strip off any charset encoding info
if(contentType.indexOf(";") > -1) {
contentType = contentType.substring(0, contentType.indexOf(";"));
}
final MimeType mimeType = MimeTable.getInstance().getContentType(contentType);
if(mimeType != null && !mimeType.isXMLType()) {
//binary data
result = BinaryValueFromInputStream.getInstance(context, new Base64BinaryValueType(), isRequest);
}
}
if(result == Sequence.EMPTY_SEQUENCE) {
//2) not binary, try and parse as an XML documemnt, otherwise 3) return a string representation
//parsing will consume the stream so we must cache!
InputStream is = null;
FilterInputStreamCache cache = null;
try {
//we have to cache the input stream, so we can reread it, as we may use it twice (once for xml attempt and once for string attempt)
cache = FilterInputStreamCacheFactory.getCacheInstance(new FilterInputStreamCacheConfiguration(){
@Override
public String getCacheClass() {
return (String) context.getBroker().getConfiguration().getProperty(Configuration.BINARY_CACHE_CLASS_PROPERTY);
}
});
is = new CachingFilterInputStream(cache, isRequest);
//mark the start of the stream
is.mark(Integer.MAX_VALUE);
//2) try and parse as XML
result = parseAsXml(is);
if(result == Sequence.EMPTY_SEQUENCE) {
// 3) not a valid XML document, return a string representation of the document
String encoding = request.getCharacterEncoding();
if(encoding == null) {
encoding = "UTF-8";
}
try {
//reset the stream, as we need to reuse for string parsing after the XML parsing happened
is.reset();
result = parseAsString(is, encoding);
} catch(final IOException ioe) {
throw new XPathException(this, "An IO exception occurred: " + ioe.getMessage(), ioe);
}
}
} finally {
if(cache != null) {
try {
cache.invalidate();
} catch(final IOException ioe) {
LOG.error(ioe.getMessage(), ioe);
}
}
if(is != null) {
try {
is.close();
} catch(final IOException ioe) {
LOG.error(ioe.getMessage(), ioe);
}
}
}
}
//NOTE we do not close isRequest, because it may be needed further by the caching input stream wrapper
}
} catch(final IOException ioe) {
throw new XPathException(this, "An IO exception occurred: " + ioe.getMessage(), ioe);
}
return result;
}
private Sequence parseAsXml(InputStream is) {
Sequence result = Sequence.EMPTY_SEQUENCE;
XMLReader reader = null;
context.pushDocumentContext();
try {
//try and construct xml document from input stream, we use eXist's in-memory DOM implementation
//we have to use CloseShieldInputStream otherwise the parser closes the stream and we cant later reread
final InputSource src = new InputSource(new CloseShieldInputStream(is));
reader = context.getBroker().getBrokerPool().getParserPool().borrowXMLReader();
final MemTreeBuilder builder = context.getDocumentBuilder();
final DocumentBuilderReceiver receiver = new DocumentBuilderReceiver(builder, true);
reader.setContentHandler(receiver);
reader.setProperty(Namespaces.SAX_LEXICAL_HANDLER, receiver);
reader.parse(src);
final Document doc = receiver.getDocument();
result = (NodeValue)doc;
} catch(final SAXException saxe) {
//do nothing, we will default to trying to return a string below
} catch(final IOException ioe) {
//do nothing, we will default to trying to return a string below
} finally {
context.popDocumentContext();
if(reader != null) {
context.getBroker().getBrokerPool().getParserPool().returnXMLReader(reader);
}
}
return result;
}
private Sequence parseAsString(InputStream is, String encoding) throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final byte[] buf = new byte[4096];
int read = -1;
while ((read = is.read(buf)) > -1) {
bos.write(buf, 0, read);
}
final String s = new String(bos.toByteArray(), encoding);
return new StringValue(s);
}
}