/*
* AtomProtocol.java
*
* Created on June 16, 2006, 11:39 AM
*
* (C) R. Alexander Milowski alex@milowski.com
*/
package org.exist.atom.modules;
import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.atom.Atom;
import org.exist.atom.IncomingMessage;
import org.exist.atom.OutgoingMessage;
import org.exist.collections.Collection;
import org.exist.http.BadRequestException;
import org.exist.http.NotFoundException;
import org.exist.http.servlets.HttpRequestWrapper;
import org.exist.http.servlets.HttpResponseWrapper;
import org.exist.http.servlets.RequestWrapper;
import org.exist.http.servlets.ResponseWrapper;
import org.exist.security.PermissionDeniedException;
import org.exist.security.xacml.AccessContext;
import org.exist.source.StringSource;
import org.exist.source.URLSource;
import org.exist.storage.DBBroker;
import org.exist.storage.serializers.Serializer;
import org.exist.util.MimeTable;
import org.exist.util.MimeType;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.CompiledXQuery;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQuery;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.request.RequestModule;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.functions.session.SessionModule;
import org.exist.xquery.value.Sequence;
import org.xml.sax.SAXException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
*
* @author R. Alexander Milowski
*/
public class Query extends AtomModuleBase implements Atom {
protected final static Logger LOG = Logger.getLogger(Query.class);
MimeType xqueryMimeType;
public class MethodConfiguration {
String contentType;
URLSource querySource;
MethodConfiguration() {
querySource = null;
contentType = Atom.MIME_TYPE;
}
public void setContentType(String value) {
this.contentType = value;
}
public String getContentType() {
return contentType;
}
public URL getQuerySource() {
return querySource.getURL();
}
public void setQuerySource(URL source) {
this.querySource = source==null ? null : new URLSource(source);
}
}
boolean allowQueryPost;
Map methods;
MethodConfiguration get;
MethodConfiguration post;
MethodConfiguration put;
MethodConfiguration delete;
MethodConfiguration head;
/** Creates a new instance of AtomProtocol */
public Query() {
xqueryMimeType = MimeTable.getInstance().getContentType("application/xquery");
methods = new HashMap();
methods.put("GET",new MethodConfiguration());
methods.put("POST",new MethodConfiguration());
methods.put("PUT",new MethodConfiguration());
methods.put("DELETE",new MethodConfiguration());
methods.put("HEAD",new MethodConfiguration());
allowQueryPost = false;
}
public MethodConfiguration getMethodConfiguration(String name) {
return (MethodConfiguration)methods.get(name);
}
public void init(Context context)
throws EXistException
{
super.init(context);
get = (MethodConfiguration)methods.get("GET");
post = (MethodConfiguration)methods.get("POST");
put = (MethodConfiguration)methods.get("PUT");
delete = (MethodConfiguration)methods.get("DELETE");
head = (MethodConfiguration)methods.get("HEAD");
}
public void doGet(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
if (get.querySource!=null) {
doQuery(broker,request,response,get);
} else {
super.doGet(broker,request,response);
}
}
public void doPut(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
if (put.querySource!=null) {
// TODO: handle put body
doQuery(broker,request,response,put);
} else {
super.doGet(broker,request,response);
}
}
public void doDelete(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
if (delete.querySource!=null) {
doQuery(broker,request,response,delete);
} else {
super.doGet(broker,request,response);
}
}
public void doHead(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
if (head.querySource!=null) {
doQuery(broker,request,response,head);
} else {
super.doGet(broker,request,response);
}
}
public void doPost(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
if (post.querySource!=null) {
// TODO: handle post body
doQuery(broker,request,response,post);
} else if (allowQueryPost) {
Collection collection = broker.getCollection(XmldbURI.create(request.getPath()));
if (collection == null) {
throw new BadRequestException("Collection "+request.getPath()+" does not exist.");
}
XQuery xquery = broker.getXQueryService();
XQueryContext context = xquery.newContext(AccessContext.REST);
context.setModuleLoadPath(getContext().getModuleLoadPath());
String contentType = request.getHeader("Content-Type");
String charset = getContext().getDefaultCharset();
MimeType mime = MimeType.XML_TYPE;
if (contentType != null) {
int semicolon = contentType.indexOf(';');
if (semicolon>0) {
contentType = contentType.substring(0,semicolon).trim();
}
mime = MimeTable.getInstance().getContentType(contentType);
int equals = contentType.indexOf('=',semicolon);
if (equals>0) {
String param = contentType.substring(semicolon+1,equals).trim();
if (param.compareToIgnoreCase("charset=")==0) {
charset = param.substring(equals+1).trim();
}
}
}
if (!mime.isXMLType() && !mime.equals(xqueryMimeType)) {
throw new BadRequestException("The xquery mime type is not an XML mime type nor application/xquery");
}
CompiledXQuery compiledQuery = null;
try {
StringBuilder builder = new StringBuilder();
Reader r = new InputStreamReader(request.getInputStream(),charset);
char [] buffer = new char[4096];
int len;
int count = 0;
int contentLength = request.getContentLength();
while ((len=r.read(buffer))>=0 && count<contentLength) {
count += len;
builder.append(buffer,0,len);
}
compiledQuery = xquery.compile(context, new StringSource(builder.toString()));
} catch (XPathException ex) {
throw new EXistException("Cannot compile xquery.",ex);
} catch (IOException ex) {
throw new EXistException("I/O exception while compiling xquery.",ex);
}
context.setStaticallyKnownDocuments(new XmldbURI[] { XmldbURI.create(request.getPath()).append(AtomProtocol.FEED_DOCUMENT_NAME) });
try {
Sequence resultSequence = xquery.execute(compiledQuery, null);
if (resultSequence.isEmpty()) {
throw new BadRequestException("No topic was found.");
}
response.setStatusCode(200);
response.setContentType(Atom.MIME_TYPE+"; charset="+charset);
Serializer serializer = broker.getSerializer();
serializer.reset();
try {
Writer w = new OutputStreamWriter(response.getOutputStream(),charset);
SAXSerializer sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
Properties outputProperties = new Properties();
sax.setOutput(w, outputProperties);
serializer.setProperties(outputProperties);
serializer.setSAXHandlers(sax, sax);
serializer.toSAX(resultSequence, 1, 1, false);
SerializerPool.getInstance().returnObject(sax);
w.flush();
w.close();
} catch (IOException ex) {
LOG.fatal("Cannot read resource "+request.getPath(),ex);
throw new EXistException("I/O error on read of resource "+request.getPath(),ex);
} catch (SAXException saxe) {
LOG.warn(saxe);
throw new BadRequestException("Error while serializing XML: " + saxe.getMessage());
}
resultSequence.itemAt(0);
} catch (XPathException ex) {
throw new EXistException("Cannot execute xquery.",ex);
}
} else {
super.doPost(broker,request,response);
}
}
private void declareVariables(XQueryContext context, HttpServletRequest request, HttpServletResponse response) throws XPathException {
RequestWrapper reqw = new HttpRequestWrapper(request, request.getCharacterEncoding(), request.getCharacterEncoding());
ResponseWrapper respw = new HttpResponseWrapper(response);
//context.declareNamespace(RequestModule.PREFIX, RequestModule.NAMESPACE_URI);
context.declareVariable(RequestModule.PREFIX + ":request", reqw);
context.declareVariable(ResponseModule.PREFIX + ":response", respw);
context.declareVariable(SessionModule.PREFIX + ":session", reqw.getSession( false ));
}
public void doQuery(DBBroker broker,IncomingMessage request,OutgoingMessage response,MethodConfiguration config)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
Collection collection = broker.getCollection(XmldbURI.create(request.getPath()));
if (collection == null) {
throw new BadRequestException("Collection "+request.getPath()+" does not exist.");
}
XQuery xquery = broker.getXQueryService();
CompiledXQuery feedQuery = xquery.getXQueryPool().borrowCompiledXQuery(broker,config.querySource);
XQueryContext context;
if (feedQuery==null) {
context = xquery.newContext(AccessContext.REST);
context.setModuleLoadPath(getContext().getModuleLoadPath());
try {
feedQuery = xquery.compile(context, config.querySource);
} catch (XPathException ex) {
throw new EXistException("Cannot compile xquery "+config.querySource.getURL(),ex);
} catch (IOException ex) {
throw new EXistException("I/O exception while compiling xquery "+config.querySource.getURL(),ex);
}
} else {
context = feedQuery.getContext();
context.setModuleLoadPath(getContext().getModuleLoadPath());
}
context.setStaticallyKnownDocuments(new XmldbURI[] { XmldbURI.create(request.getPath()).append(AtomProtocol.FEED_DOCUMENT_NAME) });
try {
declareVariables(context, request.getRequest(), response.getResponse());
Sequence resultSequence = xquery.execute(feedQuery, null);
if (resultSequence.isEmpty()) {
throw new BadRequestException("No topic was found.");
}
String charset = getContext().getDefaultCharset();
response.setStatusCode(200);
response.setContentType(config.contentType+"; charset="+charset);
Serializer serializer = broker.getSerializer();
serializer.reset();
try {
Writer w = new OutputStreamWriter(response.getOutputStream(),charset);
SAXSerializer sax = (SAXSerializer) SerializerPool.getInstance().borrowObject(SAXSerializer.class);
Properties outputProperties = new Properties();
sax.setOutput(w, outputProperties);
serializer.setProperties(outputProperties);
serializer.setSAXHandlers(sax, sax);
serializer.toSAX(resultSequence, 1, 1, false);
SerializerPool.getInstance().returnObject(sax);
w.flush();
w.close();
} catch (IOException ex) {
LOG.fatal("Cannot read resource "+request.getPath(),ex);
throw new EXistException("I/O error on read of resource "+request.getPath(),ex);
} catch (SAXException saxe) {
LOG.warn(saxe);
throw new BadRequestException("Error while serializing XML: " + saxe.getMessage());
}
resultSequence.itemAt(0);
} catch (XPathException ex) {
throw new EXistException("Cannot execute xquery "+config.querySource.getURL(),ex);
} finally {
xquery.getXQueryPool().returnCompiledXQuery(config.querySource, feedQuery);
}
}
public boolean isQueryByPostAllowed() {
return allowQueryPost;
}
public void setQueryByPost(boolean allowed) {
this.allowQueryPost = allowed;
}
}