/*
* eXist Open Source Native XML Database
* Copyright (C) 2001-06 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.http.servlets;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URISyntaxException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.transform.OutputKeys;
import org.apache.log4j.Logger;
import org.exist.http.Descriptor;
import org.exist.source.FileSource;
import org.exist.source.Source;
import org.exist.source.StringSource;
import org.exist.xmldb.CollectionImpl;
import org.exist.xmldb.XQueryService;
import org.exist.xmldb.XmldbURI;
import org.exist.xmldb.LocalResourceSet;
import org.exist.xquery.Constants;
import org.exist.xquery.XPathException;
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.util.HTTPUtils;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Item;
import org.exist.debuggee.Debuggee;
import org.exist.dom.XMLUtil;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.ResourceIterator;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
/**
* Servlet to generate HTML output from an XQuery file.
*
* The servlet responds to an URL pattern as specified in the
* WEB-INF/web.xml configuration file of the application. It will
* interpret the path with which it is called as leading to a valid
* XQuery file. The XQuery file is loaded, compiled and executed.
* Any output of the script is sent back to the client.
*
* The servlet accepts the following initialization parameters in web.xml:
*
* <table border="0">
* <tr><td>user</td><td>The user identity with which the script is executed.</td></tr>
* <tr><td>password</td><td>Password for the user.</td></tr>
* <tr><td>uri</td><td>A valid XML:DB URI leading to the root collection used to
* process the request.</td></tr>
* <tr><td>encoding</td><td>The character encoding used for XQuery files.</td></tr>
* <tr><td>container-encoding</td><td>The character encoding used by the servlet
* container.</td></tr>
* <tr><td>form-encoding</td><td>The character encoding used by parameters posted
* from HTML for
* ms.</td></tr>
* </table>
*
* User identity and password may also be specified through the HTTP session attributes
* "user" and "password". These attributes will overwrite any other settings.
*
* @author Wolfgang Meier (wolfgang@exist-db.org)
*/
public class XQueryServlet extends HttpServlet {
private static final Logger LOG = Logger.getLogger(XQueryServlet.class);
public final static String DEFAULT_USER = "guest";
public final static String DEFAULT_PASS = "guest";
public final static XmldbURI DEFAULT_URI = XmldbURI.EMBEDDED_SERVER_URI.append(XmldbURI.ROOT_COLLECTION_URI);
public final static String DEFAULT_ENCODING = "UTF-8";
public final static String DEFAULT_CONTENT_TYPE = "text/html";
public final static String DRIVER = "org.exist.xmldb.DatabaseImpl";
private String user = null;
private String password = null;
private XmldbURI collectionURI = null;
private String containerEncoding = null;
private String formEncoding = null;
private String encoding = null;
private String contentType = null;
/* (non-Javadoc)
* @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
user = config.getInitParameter("user");
if(user == null)
user = DEFAULT_USER;
password = config.getInitParameter("password");
if(password == null)
password = DEFAULT_PASS;
String confCollectionURI = config.getInitParameter("uri");
if(confCollectionURI == null) {
collectionURI = DEFAULT_URI;
} else {
try {
collectionURI = XmldbURI.xmldbUriFor(confCollectionURI);
} catch (URISyntaxException e) {
throw new ServletException("Invalid XmldbURI for parameter 'uri': "+e.getMessage(),e);
}
}
formEncoding = config.getInitParameter("form-encoding");
if(formEncoding == null)
formEncoding = DEFAULT_ENCODING;
LOG.info("form-encoding = " + formEncoding);
containerEncoding = config.getInitParameter("container-encoding");
if(containerEncoding == null)
containerEncoding = DEFAULT_ENCODING;
LOG.info("container-encoding = " + containerEncoding);
encoding = config.getInitParameter("encoding");
if(encoding == null)
encoding = DEFAULT_ENCODING;
LOG.info("encoding = " + encoding);
contentType = config.getInitParameter("content-type");
if(contentType == null)
contentType = DEFAULT_CONTENT_TYPE;
try {
Class driver = Class.forName(DRIVER);
Database database = (Database)driver.newInstance();
database.setProperty("create-database", "true");
DatabaseManager.registerDatabase(database);
} catch(Exception e) {
String errorMessage="Failed to initialize database driver";
LOG.error(errorMessage,e);
throw new ServletException(errorMessage+": " + e.getMessage(), e);
}
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doPost(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
HttpServletRequest request = null;
//For POST request, If we are logging the requests we must wrap HttpServletRequest in HttpServletRequestWrapper
//otherwise we cannot access the POST parameters from the content body of the request!!! - deliriumsky
Descriptor descriptor = Descriptor.getDescriptorSingleton();
if(descriptor != null) {
if(descriptor.allowRequestLogging()) {
request = new HttpServletRequestWrapper(req, formEncoding);
} else {
request = req;
}
} else {
request = req;
}
process(request, response);
}
//-------------------------------
// doPut and doDelete added by Andrzej Taramina (andrzej@chaeron.com)
// Date: Sept/05/2007
//
// These methods were added so that you can issue an HTTP PUT or DELETE request and have it serviced by an XQuery.
// NOTE: The XQuery referenced in the target URL of the request will be executed and the PUT/DELETE request will be passed to it
//
//-------------------------------
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doPut(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException {
HttpServletRequest request = null;
//For POST request, If we are logging the requests we must wrap HttpServletRequest in HttpServletRequestWrapper
//otherwise we cannot access the POST parameters from the content body of the request!!! - deliriumsky
Descriptor descriptor = Descriptor.getDescriptorSingleton();
if(descriptor != null) {
if(descriptor.allowRequestLogging()) {
request = new HttpServletRequestWrapper(req, formEncoding);
} else {
request = req;
}
} else {
request = req;
}
process(request, response);
}
/* (non-Javadoc)
* @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
process(request, response);
}
/**
* Processes incoming HTTP requests for XQuery
*/
protected void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//first, adjust the path
String path = request.getPathTranslated();
if(path == null) {
path = request.getRequestURI().substring(request.getContextPath().length());
int p = path.lastIndexOf(';');
if(p != Constants.STRING_NOT_FOUND)
path = path.substring(0, p);
path = getServletContext().getRealPath(path);
}
//second, perform descriptor actions
Descriptor descriptor = Descriptor.getDescriptorSingleton();
if(descriptor != null && !descriptor.requestsFiltered()) {
//logs the request if specified in the descriptor
descriptor.doLogRequestInReplayLog(request);
//map's the path if a mapping is specified in the descriptor
path = descriptor.mapPath(path);
}
// if (request.getCharacterEncoding() == null)
// try {
// request.setCharacterEncoding(formEncoding);
// } catch (IllegalStateException e) {
// }
ServletOutputStream sout = response.getOutputStream();
PrintWriter output = new PrintWriter(new OutputStreamWriter(sout, formEncoding));
// response.setContentType(contentType + "; charset=" + formEncoding);
response.addHeader( "pragma", "no-cache" );
response.addHeader( "Cache-Control", "no-cache" );
Source source;
Object sourceAttrib = request.getAttribute("xquery.source");
if (sourceAttrib != null) {
String s;
if (sourceAttrib instanceof Item)
try {
s = ((Item) sourceAttrib).getStringValue();
} catch (XPathException e) {
throw new ServletException("Failed to read XQuery source string from " +
"request attribute 'xquery.source': " + e.getMessage(), e);
}
else
s = sourceAttrib.toString();
source = new StringSource(s);
} else {
File f = new File(path);
if(!f.canRead()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
sendError(output, "Cannot read source file", path);
return;
}
source = new FileSource(f, encoding, true);
}
boolean reportErrors = false;
String errorOpt = (String) request.getAttribute("xquery.report-errors");
if (errorOpt != null)
reportErrors = errorOpt.equalsIgnoreCase("YES");
//allow source viewing for GET?
if(request.getMethod().toUpperCase().equals("GET")) {
String option;
boolean allowSource = false;
if((option = request.getParameter("_source")) != null)
allowSource = option.equals("yes");
//Should we display the source of the XQuery or execute it
if(allowSource && descriptor != null) {
//show the source
//check are we allowed to show the xquery source - descriptor.xml
// System.out.println("path="+path);
if(descriptor.allowSource(path)) {
//Show the source of the XQuery
//writeResourceAs(resource, broker, stylesheet, encoding, "text/plain", outputProperties, response);
// response.setContentType("text/plain;charset=" + formEncoding);
output.write(source.getContent());
output.flush();
return;
} else {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Permission to view XQuery source for: " + path + " denied. Must be explicitly defined in descriptor.xml");
return;
}
}
}
//-------------------------------
// Added by Igor Abade (igoravl@cosespseguros.com.br)
// Date: Aug/06/2004
//-------------------------------
// String contentType = this.contentType;
// try {
// contentType = getServletContext().getMimeType(path);
// if (contentType == null)
// contentType = this.contentType;
// } catch (Throwable e) {
// contentType = this.contentType;
// } finally {
// if (contentType.startsWith("text/") || (contentType.endsWith("+xml")))
// contentType += ";charset=" + formEncoding;
// response.setContentType(contentType );
// }
//-------------------------------
// URI baseUri;
// try {
// baseUri = new URI(request.getScheme(),
// null/*user info?*/, request.getLocalName(), request.getLocalPort(),
// request.getRequestURI(), null, null);
// } catch(URISyntaxException e) {
// baseUri = null;
// }
String requestPath = request.getRequestURI();
int p = requestPath.lastIndexOf("/");
if(p != Constants.STRING_NOT_FOUND)
requestPath = requestPath.substring(0, p);
String moduleLoadPath = getServletContext().getRealPath(requestPath.substring(request.getContextPath().length()));
String actualUser = null;
String actualPassword = null;
HttpSession session = request.getSession( false );
if(session != null && request.isRequestedSessionIdValid()) {
actualUser = getSessionAttribute(session, "user");
actualPassword = getSessionAttribute(session, "password");
}
if(actualUser == null) actualUser = user;
if(actualPassword == null) actualPassword = password;
String requestAttr = (String) request.getAttribute("xquery.attribute");
try {
Collection collection = DatabaseManager.getCollection(collectionURI.toString(), actualUser, actualPassword);
XQueryService service = (XQueryService)
collection.getService("XQueryService", "1.0");
service.setProperty("base-uri", collectionURI.toString());
service.setModuleLoadPath(moduleLoadPath);
//service.setNamespace(prefix, RequestModule.NAMESPACE_URI);
if(!((CollectionImpl)collection).isRemoteCollection()) {
service.declareVariable(RequestModule.PREFIX + ":request", new HttpRequestWrapper(request, formEncoding, containerEncoding));
service.declareVariable(ResponseModule.PREFIX + ":response", new HttpResponseWrapper(response));
service.declareVariable(SessionModule.PREFIX + ":session", ( session != null ? new HttpSessionWrapper( session ) : null ) );
}
//if get "start new session" request
String xdebug = request.getParameter("XDEBUG_SESSION_START");
if (xdebug != null)
service.declareVariable(Debuggee.PREFIX+":session", xdebug);
else {
//if have session
xdebug = request.getParameter("XDEBUG_SESSION");
if (xdebug != null) {
service.declareVariable(Debuggee.PREFIX+":session", xdebug);
} else {
//looking for session in cookies (FF XDebug Helper add-ons)
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals("XDEBUG_SESSION")) {
//TODO: check for value?? ("eXistDB_XDebug" ? or leave "default") -shabanovd
service.declareVariable(Debuggee.PREFIX+":session", cookies[i].getValue());
break;
}
}
}
}
}
ResourceSet result = service.execute(source);
String mediaType = service.getProperty(OutputKeys.MEDIA_TYPE);
if (mediaType != null) {
if (!response.isCommitted())
response.setContentType(mediaType + "; charset=" + formEncoding);
}
if (requestAttr != null && !((CollectionImpl)collection).isRemoteCollection()) {
request.setAttribute(requestAttr, ((LocalResourceSet)result).toSequence());
} else {
for(ResourceIterator i = result.getIterator(); i.hasMoreResources(); ) {
Resource res = i.nextResource();
output.println(res.getContent().toString());
}
}
} catch (XMLDBException e) {
LOG.debug(e.getMessage(), e);
if (reportErrors)
writeError(output, e);
else {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
sendError(output, e.getMessage(), e);
}
} catch (Throwable e){
LOG.error(e.getMessage(), e);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
sendError(output, "Error", e.getMessage());
}
output.flush();
}
private String getSessionAttribute(HttpSession session, String attribute) {
Object obj = session.getAttribute(attribute);
if(obj == null)
return null;
if(obj instanceof Sequence)
try {
return ((Sequence)obj).getStringValue();
} catch (XPathException e) {
return null;
}
return obj.toString();
}
private void sendError(PrintWriter out, String message, XMLDBException e) {
out.print("<html><head>");
out.print("<title>XQueryServlet Error</title>");
out.print("<link rel=\"stylesheet\" type=\"text/css\" href=\"error.css\"></link></head>");
out.print("<body><div id=\"container\"><h1>Error found</h1>");
Throwable t = e.getCause();
if (t instanceof XPathException) {
XPathException xe = (XPathException) t;
out.println(xe.getMessageAsHTML());
} else {
out.print("<h2>Message:");
out.print(message);
out.print("</h2>");
}
if(t!=null){
// t can be null
out.print(HTTPUtils.printStackTraceHTML(t));
}
out.print("</div></body></html>");
}
private void writeError(PrintWriter out, XMLDBException e) {
out.print("<error>");
Throwable t = e.getCause();
if (t != null)
out.print(XMLUtil.encodeAttrMarkup(t.getMessage()));
else
out.print(XMLUtil.encodeAttrMarkup(e.getMessage()));
out.println("</error>");
}
private void sendError(PrintWriter out, String message, String description) {
out.print("<html><head>");
out.print("<title>XQueryServlet Error</title>");
out.print("<link rel=\"stylesheet\" type=\"text/css\" href=\"error.css\"></link></head>");
out.println("<body><h1>Error found</h1>");
out.print("<div class='message'><b>Message: </b>");
out.print(message);
out.print("</div><div class='description'>");
out.print(description);
out.print("</div></body></html>");
out.flush();
}
// -jmvanel : never used locally
// private static final class CachedQuery {
//
// long lastModified;
// String sourcePath;
// CompiledExpression expression;
//
// public CachedQuery(File sourceFile, CompiledExpression expression) {
// this.sourcePath = sourceFile.getAbsolutePath();
// this.lastModified = sourceFile.lastModified();
// this.expression = expression;
// }
//
// public boolean isValid() {
// File f = new File(sourcePath);
// if(f.lastModified() > lastModified)
// return false;
// return true;
// }
//
// public CompiledExpression getExpression() {
// return expression;
// }
// }
}