/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Id: XindiceServlet.java 523128 2007-03-28 01:00:00Z vgritsenko $
*/
package org.apache.xindice.server;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.Database;
import org.apache.xindice.server.rpc.RPCMessageInterface;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.util.ConfigurationException;
import org.apache.xindice.xml.dom.DOMParser;
import org.apache.xmlrpc.XmlRpc;
import org.apache.xmlrpc.XmlRpcServer;
import org.w3c.dom.Document;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A <code>HttpServlet</code> that enables XML-RPC access to a Xindice
* database instance.
*
* @author <a href="mailto:kstaken@xmldatabases.org">Kimbro Staken</a>
* @author <a href="mailto:vladimir@apache.org">Vladimir R. Bossicard</a>
* @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
* @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
* @version $Revision: 523128 $, $Date: 2007-03-27 21:00:00 -0400 (Tue, 27 Mar 2007) $
*/
public class XindiceServlet extends HttpServlet {
private static final Log log = LogFactory.getLog(XindiceServlet.class);
private static final String DEFAULT_XMLRPC_DRIVER = "xerces";
protected XmlRpcServer xmlrpcServer;
public void destroy() {
// When the servlet engine goes down we need to close the database instance.
// By the time destroy() is called, no more client requests can come in,
// so no need to worry about multithreading.
String[] databases = Database.listDatabases();
for (int i = 0; i < databases.length; i++) {
String name = databases[i];
try {
Database.getDatabase(name).close();
log.info("Database '" + name + "' successfully closed");
} catch (Exception e) {
log.error("Error closing database '" + name + "'", e);
}
}
}
/**
* Delegate GET requests to the UglyBrowser.
*/
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
UglyBrowser.doGet(request, response);
}
/**
* Sends an XML query to the server and writes the output back. Currenlty
* only XML-RPC query is supported.
*/
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
byte[] result = xmlrpcServer.execute(request.getInputStream());
response.setContentType("text/xml");
response.setContentLength(result.length);
OutputStream output = response.getOutputStream();
output.write(result);
output.flush();
}
/**
* Initializes database
*/
public void init(ServletConfig servletConfig) throws ServletException {
Configuration configuration = loadConfiguration(servletConfig);
//
// The configuration is wrapped in a <xindice> element so we need to get the "root-collection" configuration.
//
try {
Configuration[] rootConfigurations = configuration.getChildren("root-collection");
if (rootConfigurations.length == 0) {
throw new ConfigurationException("The database configuration is missing the <root-collection> element");
}
for (int i = 0; i < rootConfigurations.length; i++) {
Configuration rootConfiguration = rootConfigurations[i];
String name = rootConfiguration.getAttribute(Database.NAME);
//
// We need to ensure that the database points to a place where it makes
// sense. If the path in the system.xml file is an absolute path, then
// honor it. If it's not, we first check for the system property "xindice.db.home"
// and if the lookup is successful we use it as the database root parent. If
// the property is not set, we use /WEB-INF relative to the servlet context, unless
// the war has not been unpacked. In this case, we throw an exception and
// ask the user to specify the location of database root
//
String dbRoot = rootConfiguration.getAttribute(Database.DBROOT, Database.DBROOT_DEFAULT);
//
// If there is no absolute path, we have to perform some checks.
//
if (!new File(dbRoot).isAbsolute()) {
// Stupid hack but spec compliant:
// If getRealPath() returns null the war archive has not been unpacked.
String realPath = servletConfig.getServletContext().getRealPath("/WEB-INF");
// Let's see if the property was specified.
String home = System.getProperty(Xindice.PROP_XINDICE_DB_HOME);
if (log.isDebugEnabled()) {
log.debug(Xindice.PROP_XINDICE_DB_HOME + " is set to " + home);
}
if (home != null) {
dbRoot = new File(home + File.separator + dbRoot).getCanonicalPath();
} else if (realPath != null) {
dbRoot = new File(realPath + File.separator + dbRoot).getCanonicalPath();
log.warn("The database '" + name + "' root directory has been set to " + dbRoot +
". Keep in mind that if a war upgrade will take place the database will be lost.");
} else {
throw new ConfigurationException(
"The database '" + name + "' configuration points to a relative path, "
+ "but there was no " + Xindice.PROP_XINDICE_DB_HOME + " property set. "
+ "Furthermore, the war was not unpacked by the application server "
+ "so Xindice was unable to find a database location "
+ "Please check /WEB-INF/system.xml and set an absolute path "
+ "as the \"dbroot\" attribute of \"root-collection\" "
+ "or specify a suitable " + Xindice.PROP_XINDICE_DB_HOME + " system property.");
}
rootConfiguration.setAttribute(Database.DBROOT, dbRoot);
}
//
// We need to use this method to be consistent between deployments (embed, standalone, etc)
// and let the Database object maintain the set of Databases.
//
Database.getDatabase(rootConfiguration);
log.info("Database '" + name + "' successfully opened");
}
// Setup the XML-RPC impl to support UTF-8 input via Xerces.
XmlRpc.setEncoding("UTF8");
/*
* Setup the SAX parser XML-RPC impl will use.
* The XmlRpc.setDriver() method takes either the classname or a shorthand
* name for the SAX parser it will use. The default (for backwards compatibility
* if nothing else) is xerces.
*/
String xmlrpcDriver = DEFAULT_XMLRPC_DRIVER;
Configuration xmlRpcConfiguration = configuration.getChild("xml-rpc");
if (xmlRpcConfiguration != null) {
Configuration xmlRpcDriverConfiguration = xmlRpcConfiguration.getChild("driver");
if (xmlRpcDriverConfiguration != null) {
// xmlrpcDriver will have non-empty value, guaranteed by providing default value
xmlrpcDriver = xmlRpcDriverConfiguration.getAttribute("name", DEFAULT_XMLRPC_DRIVER);
}
}
try {
XmlRpc.setDriver(xmlrpcDriver);
} catch (Exception e) {
throw new ConfigurationException("Failed to set driver for XmlRpc to: " + xmlrpcDriver, e);
}
// Create the XML-RPC server and add our handler as the default.
this.xmlrpcServer = new XmlRpcServer();
try {
this.xmlrpcServer.addHandler("$default", new RPCMessageInterface());
} catch (Exception e) {
throw new ConfigurationException("Failed to add default handler to XmlRpc server.", e);
}
log.info("Xindice server successfully started");
} catch (Exception e) {
log.fatal("Failed to initialize database, throwing ServletException", e);
// Make sure to close database if it was opened already.
destroy();
throw new ServletException("Error while handling the configuration", e);
}
}
/**
* Loads the Xindice configuration file. The file is searched in the following locations:
* <ul>
* <li>the <i>System.getProperty(Xindice.PROP_XINDICE_CONFIGURATION)</i> system property.
* <li>the <i>ServletConfig.getInitParameter(Xindice.PROP_XINDICE_CONFIGURATION)</i> servlet
* configuration parameter in web.xml file.</li>
* <li>default configuration stored in the <tt>Xindice</tt> class</li>
* </ul>
*
* @return Xindice configuration
* @throws ConfigurationException if unable to read configuration file or parse it
*/
public Configuration loadConfiguration(ServletConfig servletConfig) {
try {
InputStream in = null;
String path = System.getProperty(Xindice.PROP_XINDICE_CONFIGURATION);
if (path != null && path.length() > 0) {
// Configuration file specified by system property
log.info("Loading configuration from file path " + path + " (system property)");
in = new FileInputStream(path);
} else {
path = servletConfig.getInitParameter(Xindice.PROP_XINDICE_CONFIGURATION);
if (path != null && path.length() > 0) {
if (path.startsWith("/")) {
// Absolute file path
log.info("Loading configuration from file path " + path);
in = new FileInputStream(path);
} else {
// Relative (to the context) path
log.info("Loading configuration from context path " + path);
ServletContext context = servletConfig.getServletContext();
in = context.getResourceAsStream("/" + path);
}
}
}
Document doc;
if (in != null) {
try {
doc = DOMParser.toDocument(in);
} finally {
in.close();
}
} else {
log.warn("Loading the standard configuration");
doc = DOMParser.toDocument(Xindice.DEFAULT_CONFIGURATION);
}
return new Configuration(doc, false);
} catch (Exception e) {
throw new ConfigurationException("Failed to load configuration.", e);
}
}
}