Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 436   Methods: 7
NCLOC: 279   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
XindiceServlet.java 25% 47.5% 85.7% 42.4%
coverage coverage
 1    /*
 2    * Licensed to the Apache Software Foundation (ASF) under one or more
 3    * contributor license agreements. See the NOTICE file distributed with
 4    * this work for additional information regarding copyright ownership.
 5    * The ASF licenses this file to You under the Apache License, Version 2.0
 6    * (the "License"); you may not use this file except in compliance with
 7    * the License. You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    *
 17    * $Id: XindiceServlet.java 636596 2008-03-13 01:20:52Z natalia $
 18    */
 19   
 20    package org.apache.xindice.server;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.core.Collection;
 25    import org.apache.xindice.core.DBException;
 26    import org.apache.xindice.core.Database;
 27    import org.apache.xindice.server.rpc.RPCMessageInterface;
 28    import org.apache.xindice.util.Configuration;
 29    import org.apache.xindice.webadmin.Location;
 30    import org.apache.xindice.webadmin.WebAdminManager;
 31    import org.apache.xindice.webadmin.util.MimeTable;
 32    import org.apache.xindice.webadmin.viewer.HtmlCollectionViewer;
 33    import org.apache.xindice.webadmin.viewer.HtmlDatabaseViewer;
 34    import org.apache.xindice.webadmin.viewer.HtmlResourceViewer;
 35    import org.apache.xindice.webadmin.webdav.DAVRequest;
 36    import org.apache.xindice.webadmin.webdav.DAVResponse;
 37    import org.apache.xindice.webadmin.webdav.WebdavStatus;
 38    import org.apache.xindice.webadmin.webdav.components.DAVComponent;
 39    import org.apache.xindice.xml.dom.DOMParser;
 40    import org.apache.xmlrpc.XmlRpcException;
 41    import org.apache.xmlrpc.XmlRpcHandler;
 42    import org.apache.xmlrpc.server.XmlRpcHandlerMapping;
 43    import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
 44    import org.apache.xmlrpc.webserver.XmlRpcServletServer;
 45   
 46    import org.w3c.dom.Document;
 47   
 48    import javax.servlet.ServletConfig;
 49    import javax.servlet.ServletContext;
 50    import javax.servlet.ServletException;
 51    import javax.servlet.http.HttpServlet;
 52    import javax.servlet.http.HttpServletRequest;
 53    import javax.servlet.http.HttpServletResponse;
 54   
 55    import java.io.File;
 56    import java.io.FileInputStream;
 57    import java.io.IOException;
 58    import java.io.InputStream;
 59   
 60    /**
 61    * A <code>HttpServlet</code> that enables XML-RPC access to a Xindice
 62    * database instance.
 63    *
 64    * @author <a href="mailto:kstaken@xmldatabases.org">Kimbro Staken</a>
 65    * @author <a href="mailto:vladimir@apache.org">Vladimir R. Bossicard</a>
 66    * @author <a href="mailto:gianugo@apache.org">Gianugo Rabellino</a>
 67    * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
 68    * @version $Revision: 636596 $, $Date: 2008-03-13 01:20:52 +0000 (Thu, 13 Mar 2008) $
 69    */
 70    public class XindiceServlet extends HttpServlet {
 71   
 72    private static final Log log = LogFactory.getLog(XindiceServlet.class);
 73   
 74    private static final String WEBADMIN_CONFIGURATION = "webadmin.configuration";
 75    private static final String MIMETABLE_CONFIGURATION = "mimetable.configuration";
 76   
 77    protected XmlRpcServletServer server;
 78    protected WebAdminManager webAdmin;
 79   
 80   
 81    /**
 82    * Initializes database
 83    */
 84  1 public void init(ServletConfig servletConfig) throws ServletException {
 85  1 super.init(servletConfig);
 86   
 87  1 Configuration configuration = loadConfiguration(servletConfig);
 88   
 89    //
 90    // The configuration is wrapped in a <xindice> element so we need to get the "root-collection" configuration.
 91    //
 92  1 try {
 93  1 Configuration[] rootConfigurations = configuration.getChildren("root-collection");
 94  1 if (rootConfigurations.length == 0) {
 95  0 throw new ServletException("The database configuration is missing the <root-collection> element");
 96    }
 97   
 98  1 for (int i = 0; i < rootConfigurations.length; i++) {
 99  1 Configuration rootConfiguration = rootConfigurations[i];
 100  1 String name = rootConfiguration.getAttribute(Database.NAME);
 101   
 102    //
 103    // We need to ensure that the database points to a place where it makes
 104    // sense. If the path in the system.xml file is an absolute path, then
 105    // honor it. If it's not, we first check for the system property "xindice.db.home"
 106    // and if the lookup is successful we use it as the database root parent. If
 107    // the property is not set, we use /WEB-INF relative to the servlet context, unless
 108    // the war has not been unpacked. In this case, we throw an exception and
 109    // ask the user to specify the location of database root
 110    //
 111  1 String dbRoot = rootConfiguration.getAttribute(Database.DBROOT, Database.DBROOT_DEFAULT);
 112   
 113    //
 114    // If there is no absolute path, we have to perform some checks.
 115    //
 116  1 if (!new File(dbRoot).isAbsolute()) {
 117   
 118    // Stupid hack but spec compliant:
 119    // If getRealPath() returns null the war archive has not been unpacked.
 120  1 String realPath = servletConfig.getServletContext().getRealPath("/WEB-INF");
 121   
 122    // Let's see if the property was specified.
 123  1 String home = System.getProperty(Xindice.PROP_XINDICE_DB_HOME);
 124  1 if (log.isDebugEnabled()) {
 125  0 log.debug(Xindice.PROP_XINDICE_DB_HOME + " is set to " + home);
 126    }
 127   
 128  1 if (home != null) {
 129  1 dbRoot = new File(home + File.separator + dbRoot).getCanonicalPath();
 130  0 } else if (realPath != null) {
 131  0 dbRoot = new File(realPath + File.separator + dbRoot).getCanonicalPath();
 132  0 log.warn("The database '" + name + "' root directory has been set to " + dbRoot +
 133    ". Keep in mind that if a war upgrade will take place the database will be lost.");
 134    } else {
 135  0 throw new ServletException(
 136    "The database '" + name + "' configuration points to a relative path, "
 137    + "but there was no " + Xindice.PROP_XINDICE_DB_HOME + " property set. "
 138    + "Furthermore, the war was not unpacked by the application server "
 139    + "so Xindice was unable to find a database location "
 140    + "Please check /WEB-INF/system.xml and set an absolute path "
 141    + "as the \"dbroot\" attribute of \"root-collection\" "
 142    + "or specify a suitable " + Xindice.PROP_XINDICE_DB_HOME + " system property.");
 143    }
 144   
 145  1 rootConfiguration.setAttribute(Database.DBROOT, dbRoot);
 146    }
 147   
 148    //
 149    // We need to use this method to be consistent between deployments (embed, standalone, etc)
 150    // and let the Database object maintain the set of Databases.
 151    //
 152  1 Database.getDatabase(rootConfiguration);
 153  1 log.info("Database '" + name + "' successfully opened");
 154    }
 155   
 156    } catch (Exception e) {
 157  0 log.fatal("Failed to initialize database, throwing ServletException", e);
 158    // Make sure to close database if it was opened already.
 159  0 destroy();
 160  0 throw new ServletException("Error while handling the configuration", e);
 161    }
 162   
 163    // Initialize XML-RPC
 164  1 Configuration xmlRpcConfiguration = configuration.getChild("xml-rpc");
 165  1 if (xmlRpcConfiguration != null) {
 166  1 XmlRpcServerConfigImpl cfg = new XmlRpcServerConfigImpl();
 167    // Setup the XML-RPC impl to support UTF-8 encoding
 168  1 cfg.setEncoding("utf-8");
 169  1 cfg.setKeepAliveEnabled(true);
 170   
 171    // Create the XML-RPC server and add our handler as the default.
 172  1 this.server = new XmlRpcServletServer();
 173  1 this.server.setConfig(cfg);
 174  1 this.server.setHandlerMapping(new XmlRpcHandlerMapping() {
 175  2381 public XmlRpcHandler getHandler(String s) throws XmlRpcException {
 176  2381 return RPCMessageInterface.getHandler();
 177    }
 178    });
 179   
 180  1 log.info("XML-RPC interface initialized.");
 181    }
 182   
 183    // Initialize WebAdmin
 184  1 try {
 185  1 String path = servletConfig.getInitParameter(WEBADMIN_CONFIGURATION);
 186  1 Document doc = loadFile(path, servletConfig);
 187  1 webAdmin = new WebAdminManager(doc.getDocumentElement());
 188    } catch(Exception e) {
 189  0 throw new ServletException("Could not process WebAdmin configuration", e);
 190    }
 191   
 192  1 try {
 193  1 String path = servletConfig.getInitParameter(MIMETABLE_CONFIGURATION);
 194  1 Document doc = loadFile(path, servletConfig);
 195  1 MimeTable.addMimeConfig(doc);
 196    } catch(Exception e) {
 197  0 log.error("Could not process Mime Table configuration", e);
 198    }
 199   
 200  1 log.info("Xindice server successfully started");
 201    }
 202   
 203    /**
 204    * Loads the Xindice configuration file. The file is searched in the following locations:
 205    * <ul>
 206    * <li>the <i>System.getProperty(Xindice.PROP_XINDICE_CONFIGURATION)</i> system property.
 207    * <li>the <i>ServletConfig.getInitParameter(Xindice.PROP_XINDICE_CONFIGURATION)</i> servlet
 208    * configuration parameter in web.xml file.</li>
 209    * <li>default configuration stored in the <tt>Xindice</tt> class</li>
 210    * </ul>
 211    *
 212    * @param servletConfig servlet configuration
 213    * @return Xindice configuration
 214    * @throws ServletException if unable to load configuration
 215    */
 216  1 public Configuration loadConfiguration(ServletConfig servletConfig) throws ServletException {
 217  1 try {
 218  1 InputStream in = null;
 219  1 String path = System.getProperty(Xindice.PROP_XINDICE_CONFIGURATION);
 220  1 if (path != null && path.length() > 0) {
 221    // Configuration file specified by system property
 222  1 log.info("Loading configuration from file path " + path + " (system property)");
 223  1 in = new FileInputStream(path);
 224    } else {
 225  0 path = servletConfig.getInitParameter(Xindice.PROP_XINDICE_CONFIGURATION);
 226  0 if (path != null && path.length() > 0) {
 227  0 if (path.startsWith("/")) {
 228    // Absolute file path
 229  0 log.info("Loading configuration from file path " + path);
 230  0 in = new FileInputStream(path);
 231    } else {
 232    // Relative (to the context) path
 233  0 log.info("Loading configuration from context path " + path);
 234  0 ServletContext context = servletConfig.getServletContext();
 235  0 in = context.getResourceAsStream("/" + path);
 236    }
 237    }
 238    }
 239   
 240  1 Document doc;
 241  1 if (in != null) {
 242  1 try {
 243  1 doc = DOMParser.toDocument(in);
 244    } finally {
 245  1 in.close();
 246    }
 247    } else {
 248  0 log.warn("Loading the standard configuration");
 249  0 doc = DOMParser.toDocument(Xindice.DEFAULT_CONFIGURATION);
 250    }
 251   
 252  1 return new Configuration(doc, false);
 253    } catch (Exception e) {
 254  0 throw new ServletException("Failed to load configuration.", e);
 255    }
 256    }
 257   
 258  2 private Document loadFile(String path, ServletConfig config) throws Exception {
 259  2 InputStream inputStream = null;
 260   
 261  2 try {
 262  2 if (path.startsWith("/")) { // Absolute file path
 263  0 log.debug("Loading configuration from filesystem path " + path);
 264  0 inputStream = new FileInputStream(path);
 265    } else {
 266    // Relative (to the context) path
 267  2 log.debug("Loading configuration from context path " + path);
 268  2 ServletContext context = config.getServletContext();
 269  2 inputStream = context.getResourceAsStream("/" + path);
 270    }
 271   
 272  2 Document configuration = null;
 273  2 if (inputStream != null) {
 274  2 configuration = DOMParser.toDocument(inputStream);
 275    }
 276   
 277  2 return configuration;
 278    } finally {
 279  2 try {
 280  2 if (inputStream != null) {
 281  2 inputStream.close();
 282    }
 283    } catch (IOException ignored) {
 284    // ignore
 285    }
 286    }
 287    }
 288   
 289  0 public void destroy() {
 290    // When the servlet engine goes down we need to close the database instance.
 291    // By the time destroy() is called, no more client requests can come in,
 292    // so no need to worry about multithreading.
 293  0 String[] databases = Database.listDatabases();
 294  0 for (int i = 0; i < databases.length; i++) {
 295  0 String name = databases[i];
 296  0 try {
 297  0 Database.getDatabase(name).close();
 298  0 log.info("Database '" + name + "' successfully closed");
 299    } catch (Exception e) {
 300  0 log.error("Error closing database '" + name + "'", e);
 301    }
 302    }
 303    }
 304   
 305    /**
 306    * Process request. Determine type of a request and delegate for processing
 307    * either to XML-RPC server, WebDAV, or WebAdmin.
 308    *
 309    * @param request a HttpServletRequest instance
 310    * @param response a HttpServletResponse instance
 311    * @exception IOException if an IO error occurs
 312    * @exception ServletException if a servlet error occurs
 313    */
 314  2381 public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
 315  2381 request.setCharacterEncoding("utf-8");
 316   
 317    // get requested information
 318  2381 String path = getPath(request);
 319  2381 String method = request.getMethod();
 320   
 321    // xmlrpc requests do not have path (always '/') and are always POST
 322  2381 if ("/".equals(path) && method.equalsIgnoreCase("POST")) {
 323  2381 if (server != null) {
 324  2381 server.execute(request, response);
 325    } else {
 326  0 response.sendError(HttpServletResponse.SC_NOT_FOUND);
 327    }
 328  2381 return;
 329    }
 330   
 331    // get viewer parameter (missing if DAV request)
 332  0 String viewer = request.getParameter("viewer");
 333  0 String requestURI = request.getRequestURI();
 334   
 335    // empty path - redirect to initial page
 336  0 if (path.length() == 0 && viewer == null && method.equalsIgnoreCase("GET")) {
 337  0 String redirect = requestURI + "/?viewer=default";
 338  0 response.sendRedirect(redirect);
 339  0 return;
 340    }
 341   
 342    // get request target
 343  0 Location target;
 344  0 try {
 345  0 target = new Location(path);
 346    } catch (DBException e) {
 347  0 log.error("Unable to process request '" + path + "'", e);
 348  0 throw new ServletException(e);
 349    }
 350   
 351    // WebDAV requests do not have viewer parameter, nor can not GET a collection
 352  0 if (viewer == null && !(target.getName() == null && method.equalsIgnoreCase("GET"))) {
 353  0 DAVComponent m = webAdmin.getMethod(method);
 354  0 if (m == null) {
 355    // method is not supported
 356  0 if (log.isInfoEnabled()) {
 357  0 log.info("Method " + method + " is not supported.");
 358    }
 359  0 response.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
 360  0 return;
 361    }
 362   
 363  0 m.execute(new DAVRequest(request), new DAVResponse(response), target);
 364  0 return;
 365    }
 366   
 367    // HTML requests are all the rest
 368  0 Collection col = target.getCollection();
 369  0 String resource = target.getName();
 370  0 if (viewer == null) {
 371  0 viewer = "default";
 372    }
 373   
 374  0 if (target.isRoot()) {
 375    // redirect if path is not '/'
 376  0 if (!path.endsWith("/")) {
 377  0 String redirect = requestURI + "/?viewer=" + viewer;
 378  0 response.sendRedirect(redirect);
 379  0 return;
 380    }
 381   
 382  0 HtmlDatabaseViewer v = webAdmin.getDatabaseViewer(viewer);
 383  0 if (v == null) {
 384  0 response.sendError(WebdavStatus.SC_NOT_IMPLEMENTED);
 385  0 return;
 386    }
 387   
 388  0 v.execute(request, response);
 389   
 390  0 } else if (col == null) {
 391  0 response.sendError(WebdavStatus.SC_NOT_FOUND);
 392   
 393  0 } else if (resource == null) {
 394    // redirect if path does not end with '/'
 395  0 if (!path.endsWith("/")) {
 396  0 String redirect = requestURI + "/?viewer=" + viewer;
 397  0 response.sendRedirect(redirect);
 398  0 return;
 399    }
 400   
 401  0 HtmlCollectionViewer v = webAdmin.getCollectionViewer(viewer);
 402  0 if (v == null) {
 403  0 response.sendError(WebdavStatus.SC_NOT_IMPLEMENTED, "No action defined for this viewer");
 404  0 return;
 405    }
 406   
 407  0 v.execute(request, response, col);
 408   
 409    } else {
 410  0 HtmlResourceViewer v = webAdmin.getResourceViewer(viewer);
 411  0 if (v == null) {
 412  0 response.sendError(WebdavStatus.SC_NOT_IMPLEMENTED, "No action defined for this viewer");
 413  0 return;
 414    }
 415   
 416  0 v.execute(request, response, col, resource);
 417    }
 418    }
 419   
 420  2381 private String getPath(HttpServletRequest request) {
 421  2381 StringBuffer pathBuf = new StringBuffer(request.getServletPath());
 422  2381 String pathInfo = request.getPathInfo();
 423  2381 if (pathInfo != null) {
 424  2381 pathBuf.append(pathInfo);
 425    }
 426   
 427    // workaround for web containers incorrectly returning empty pathInfo for path '/'
 428  2381 int len = pathBuf.length();
 429  2381 if (request.getRequestURI().endsWith("/") && (len == 0 || pathBuf.charAt(len - 1) != '/')) {
 430  0 pathBuf.append("/");
 431    }
 432   
 433  2381 return pathBuf.toString();
 434    }
 435   
 436    }