/*
* eXist Open Source Native XML Database Copyright (C) 2001-06 Wolfgang M.
* Meier meier@ifs.tu-darmstadt.de 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;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.log4j.Logger;
import org.exist.memtree.SAXAdapter;
import org.exist.util.ConfigurationHelper;
import org.exist.util.SingleInstanceConfiguration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
/** Webapplication Descriptor
*
* Class representation of an XQuery Web Application Descriptor file
* with some helper functions for performing Descriptor related actions
* Uses the Singleton design pattern.
*
* @author Adam Retter <adam.retter@devon.gov.uk>
* @serial 2006-03-19
* @version 1.71
*/
// TODO: doLogRequestInReplayLog() - add the facility to log HTTP PUT requests, may need changes to HttpServletRequestWrapper
// TODO: doLogRequestInReplayLog() - add the facility to log HTTP POST form file uploads, may need changes to HttpServletRequestWrapper
public class Descriptor implements ErrorHandler
{
private static final String SYSTEM_LINE_SEPARATOR = System.getProperty("line.separator");
//References
private static Descriptor singletonRef;
private final static Logger LOG = Logger.getLogger(Descriptor.class); //Logger
/** descriptor file (descriptor.xml) */
private final static String file = "descriptor.xml";
//Data
private BufferedWriter bufWriteReplayLog = null; //Should a replay log of requests be created
private boolean requestsFiltered = false;
private String allowSourceList[] = null; //Array of xql files to allow source to be viewed
private String mapList[][] = null; //Array of Mappings
/**
* Descriptor Constructor
*
* Class has a Singleton design pattern
* to get an instance, call getDescriptorSingleton()
*/
private Descriptor()
{
try
{
InputStream is = null;
// First, try to read Descriptor from file. Guess the location if necessary
// from the home folder.
File f = ConfigurationHelper.lookup(file);
if(! f.canRead())
{
LOG.warn("Giving up unable to read descriptor file from " + f);
}
else
{
is = new FileInputStream(f);
LOG.info("Reading Descriptor from file " + f);
}
if(is == null)
{
// otherise, secondly
// try to read the Descriptor from a file within the classpath
is = Descriptor.class.getResourceAsStream(file);
if(is != null)
{
LOG.info("Reading Descriptor from classloader in " + this.getClass().getPackage());
}
else
{
LOG.warn("Giving up unable to read descriptor.xml file from classloader in " + this.getClass().getPackage());
return;
}
}
// initialize xml parser
// we use eXist's in-memory DOM implementation to work
// around a bug in Xerces
SAXParserFactory factory = SAXParserFactory.newInstance();
factory.setNamespaceAware(true);
InputSource src = new InputSource(is);
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
SAXAdapter adapter = new SAXAdapter();
reader.setContentHandler(adapter);
reader.parse(src);
Document doc = adapter.getDocument();
//load <xquery-app> attribue settings
if(doc.getDocumentElement().getAttribute("request-replay-log").equals("true"))
{
File logFile = new File("request-replay-log.txt");
bufWriteReplayLog = new BufferedWriter(new FileWriter(logFile));
String attr = doc.getDocumentElement().getAttribute("filtered");
if (attr != null)
requestsFiltered = attr.equals("true");
}
//load <allow-source> settings
NodeList allowsourcexqueries = doc.getElementsByTagName("allow-source");
if(allowsourcexqueries.getLength() > 0)
{
configureAllowSourceXQuery((Element) allowsourcexqueries.item(0));
}
//load <maps> settings
NodeList maps = doc.getElementsByTagName("maps");
if(maps.getLength() > 0)
{
configureMaps((Element) maps.item(0));
}
}
catch(SAXException e)
{
LOG.warn("Error while reading descriptor file: " + file, e);
return;
}
catch(ParserConfigurationException cfg)
{
LOG.warn("Error while reading descriptor file: " + file, cfg);
return;
}
catch(IOException io)
{
LOG.warn("Error while reading descriptor file: " + file, io);
return;
}
}
/**
* Returns a refernce to this (Descriptor) Singleton class
*
* @return The Descriptor object reference
*/
public static synchronized Descriptor getDescriptorSingleton()
{
if(singletonRef == null)
{
singletonRef = new Descriptor();
}
return(singletonRef);
}
/**
* loads <allow-source> settings from the descriptor.xml file
*
* @param allowsourcexqueries The <allow-source> DOM Element from the descriptor.xml file
*/
private void configureAllowSourceXQuery(Element allowsourcexqueries)
{
//Get the xquery element(s)
NodeList nlXQuery = allowsourcexqueries.getElementsByTagName("xquery");
//Setup the hashmap to hold the xquery elements
allowSourceList = new String[nlXQuery.getLength()];
Element elem = null; //temporary holds xquery elements
//Iterate through the xquery elements
for(int i = 0; i < nlXQuery.getLength(); i++)
{
elem = (Element) nlXQuery.item(i); //<xquery>
String path = elem.getAttribute("path"); //@path
//must be a path to allow source for
if(path == null)
{
LOG.warn("Error element 'xquery' requires an attribute 'path'");
return;
}
path=path.replaceAll("\\$\\{WEBAPP_HOME\\}",
SingleInstanceConfiguration.getWebappHome().getAbsolutePath().replace('\\','/') );
//store the path
allowSourceList[i] = path;
}
}
/**
* loads <maps> settings from the descriptor.xml file
*
* @param maps The <maps> DOM Element from the descriptor.xml file
*/
private void configureMaps(Element maps)
{
//TODO: add pattern support for mappings, as an alternative to path - deliriumsky
//Get the map element(s)
NodeList nlMap = maps.getElementsByTagName("map");
//Setup the hashmap to hold the map elements
mapList = new String[nlMap.getLength()][2];
Element elem = null; //temporary holds map elements
//Iterate through the map elements
for(int i = 0; i < nlMap.getLength(); i++)
{
elem = (Element) nlMap.item(i); //<map>
String path = elem.getAttribute("path"); //@path
//String pattern = elem.getAttribute("pattern");//@pattern
String view = elem.getAttribute("view"); //@view
//must be a path or a pattern to map from
if(path == null /*&& pattern == null*/)
{
LOG.warn("Error element 'map' requires an attribute 'path' or an attribute 'pattern'");
return;
}
path=path.replaceAll("\\$\\{WEBAPP_HOME\\}",
SingleInstanceConfiguration.getWebappHome().getAbsolutePath().replace('\\','/') );
//must be a view to map to
if(view == null)
{
LOG.warn("Error element 'map' requires an attribute 'view'");
return;
}
view=view.replaceAll("\\$\\{WEBAPP_HOME\\}",
SingleInstanceConfiguration.getWebappHome().getAbsolutePath().replace('\\','/') );
//store what to map from
/* if(path != null)
{*/
//store the path
mapList[i][0] = path;
/*}
else
{
//store the pattern
mapList[i][0] = pattern;
}*/
//store what to map to
mapList[i][1] = view;
}
}
/**
* Determines whether it is permissible to show the source of an XQuery.
* Takes a path such as that from RESTServer.doGet() as an argument,
* if it finds a matching allowsourcexquery path in the descriptor then it returns true else it returns false
*
* @param path The path of the XQuery (e.g. /db/MyCollection/query.xql)
* @return The boolean value true or false indicating whether it is permissible to show the source
*/
public boolean allowSource(String path)
{
if(allowSourceList != null)
{
//Iterate through the xqueries that source viewing is allowed for
for(int i = 0; i < allowSourceList.length; i++)
{
// DWES: this helps a lot. quickfix not the final solution
path=path.replace('\\','/');
//does the path match the <allow-source><xquery path=""/></allow-source> path
if((allowSourceList[i].equals(path)) || (path.indexOf(allowSourceList[i]) > -1))
{
//yes, return true
return(true);
}
}
}
return(false);
}
/**
* Map's one XQuery or Collection path to another
* Takes a path such as that from RESTServer.doGet() as an argument,
* if it finds a matching map path then it returns the map view else it returns the passed in path
*
* @param path The path of the XQuery or Collection (e.g. /db/MyCollection/query.xql or /db/MyCollection) to map from
* @return The path of the XQuery or Collection (e.g. /db/MyCollection/query.xql or /db/MyCollection) to map to
*/
public String mapPath(String path)
{
if(mapList == null) //has a list of mappings been specified?
return(path);
//Iterate through the mappings
for(int i = 0; i < mapList.length; i++)
{
//does the path or the path/ match the map path
if(mapList[i][0].equals(path) || new String(mapList[i][0] + "/").equals(path))
{
//return the view
return(mapList[i][1]);
}
}
//no match return the original path
return(path);
}
public boolean requestsFiltered() {
return requestsFiltered;
}
/**
* Determines whether it is permissible to Log Requests
*
* Enabled by descriptor.xml <xquery-app request-replay-log="true">
*
* @return The boolean value true or false indicating whether it is permissible to Log Requests
*/
public boolean allowRequestLogging()
{
if(bufWriteReplayLog == null)
{
return(false);
}
else
{
return(true);
}
}
/**
* Logs HTTP Request's in a log file suitable for replaying to eXist later
* Takes a HttpServletRequest or a HttpServletRequestWrapper as an argument for logging.
*
* Enabled by descriptor.xml <xquery-app request-replay-log="true">
*
* @param request The HttpServletRequest to log.
* For Simple HTTP POST Requests - EXistServlet/XQueryServlet - POST parameters (e.g. form data) will only be logged if a HttpServletRequestWrapper is used instead of HttpServletRequest! POST Uploaded files are not yet supported!
* For XML-RPC Requests - RpcServlet - HttpServletRequestWrapper must be used, otherwise the content of the Request will be lost!
* For Cocoon Requests -
*/
public synchronized void doLogRequestInReplayLog(HttpServletRequest request)
{
//Only log if set by the user in descriptor.xml <xquery-app request-replay-log="true">
if(bufWriteReplayLog == null)
{
return;
}
//Log the Request
try
{
//Store the date and time
bufWriteReplayLog.write("Date: ");
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
bufWriteReplayLog.write(formatter.format(new Date()));
bufWriteReplayLog.write(SYSTEM_LINE_SEPARATOR);
//Store the request string excluding the first line
String requestAsString = request.toString();
bufWriteReplayLog.write(requestAsString.substring(requestAsString.indexOf(SYSTEM_LINE_SEPARATOR) + 1));
//End of record indicator
bufWriteReplayLog.write(SYSTEM_LINE_SEPARATOR);
//flush the buffer to file
bufWriteReplayLog.flush();
}
catch(IOException ioe)
{
LOG.warn("Could not write request replay log: " + ioe);
return;
}
}
/**
* Thows a CloneNotSupportedException as this class uses a Singleton design pattern
*
* @return Will never return anything!
*/
public Object clone() throws CloneNotSupportedException
{
//Class is a Singleton, dont allow cloning
throw new CloneNotSupportedException();
}
/**
* @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
*/
public void error(SAXParseException exception) throws SAXException
{
System.err.println("Error occured while reading descriptor file [line: " + exception.getLineNumber() + "]:" + exception.getMessage());
}
/**
* @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
*/
public void fatalError(SAXParseException exception) throws SAXException
{
System.err.println("Error occured while reading descriptor file [line: " + exception.getLineNumber() + "]:" + exception.getMessage());
}
/**
* @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
*/
public void warning(SAXParseException exception) throws SAXException
{
System.err.println("error occured while reading descriptor file [line: " + exception.getLineNumber() + "]:" + exception.getMessage());
}
}