/*
* 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: DatabaseImpl.java 511426 2007-02-25 03:25:02Z vgritsenko $
*/
package org.apache.xindice.client.xmldb.xmlrpc;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.client.xmldb.CommonConfigurable;
import org.apache.xmlrpc.XmlRpc;
import org.apache.xmlrpc.XmlRpcClient;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.Database;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.XMLDBException;
import java.net.MalformedURLException;
/**
* Implements XML:DB's <code>Database</code> interface using XML-RPC to communicate
* with the Xindice XML-RPC server. Usually, this class is not used
* directly, but {@link org.apache.xindice.client.xmldb.DatabaseImpl} is used instead.
*
* Note this class is a database <em>driver</em>, and one class of this database
* could be used to connect to <em>many</em> different databases, on one or
* different Xindice XML-RPC servers.
*
* XML-RPC database driver uses following configuration parameters:
* <ul>
* <li><strong>service-location</strong>: Specifies path to the Xindice server WebApp</li>
* <li><strong>xmlrpc-driver</strong>: Specifies name of the SAX parser to use</li>
* <li><strong>xmlrpc-user</strong>: If server protected with HTTP Basic Auth, specifies user name</li>
* <li><strong>xmlrpc-password</strong>: If server protected with HTTP Basic Auth, specifies password</li>
* </ul>
*
* If one of these parameters is not specified, system properties will be used.
*
* @author <a href="mailto:james.bates@amplexor.com">James Bates</a>
* @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
* @version $Revision: 511426 $, $Date: 2007-02-24 22:25:02 -0500 (Sat, 24 Feb 2007) $
*/
public class DatabaseImpl extends CommonConfigurable implements Database {
private static final Log log = LogFactory.getLog(DatabaseImpl.class);
/**
* Driver property name for the xml-rpc service location.
*/
public static final String PROP_SERVICE_LOCATION = "service-location";
/**
* System property name for the service location
*/
public static final String SYSPROP_SERVICE_LOCATION = "xindice.xmlrpc.service-location";
/**
* Driver property name for the SAX parser xml-rpc will use.
*/
public static final String PROP_XMLRPC_DRIVER = "xmlrpc-driver";
/**
* System property name for the SAX parser xml-rpc will use in case
* there were no configuration property passed
*/
public static final String SYSPROP_XMLRPC_DRIVER = "xindice.xmlrpc.driver";
/**
* Driver property name for the basic authentication user name.
*/
public static final String PROP_XMLRPC_USER = "xmlrpc-user";
/**
* System property name for the basic authentication user name in case
* there were no configuration property passed
*/
public static final String SYSPROP_XMLRPC_USER = "xindice.xmlrpc.user";
/**
* Driver property name for the basic authentication password.
*/
public static final String PROP_XMLRPC_PASSWORD = "xmlrpc-password";
/**
* System property name for the basic authentication password in case
* there were no configuration property passed
*/
public static final String SYSPROP_XMLRPC_PASSWORD = "xindice.xmlrpc.password";
/**
* Default value of the xmlrpc-driver property
*/
private static final String DEFAULT_XMLRPC_DRIVER = "xerces";
/**
* Default path to the XML-RPC service in the web server
*/
private static final String DEFAULT_SERVICE_LOCATION = "/xindice/";
/**
* Prefix used to denote XML:DB URI's that should use this driver
*/
public static final String DRIVER_NAME = "xindice";
/**
* XML:DB conformance level of this driver
*/
private static final String CONFORMANCE_LEVEL = "0";
/**
* Ensures that XML-RPC static properties initialized just once
*/
private static boolean xmlRpcInitialized;
/**
* Location of the XML-RPC service in the web server
*/
private String serviceLocation;
private String basicUser;
private String basicPassword;
/**
* Create a new DatabaseImpl object.
*/
public DatabaseImpl() {
super();
}
/**
* Create a new DatabaseImpl object with a copy of the properties
* from the DatabaseImpl parameter.
*
* @param config from which the initial parameters for this
* DatabaseImpl object are copied.
*/
public DatabaseImpl(CommonConfigurable config) {
super(config);
}
/**
* Determines whether this <code>Database</code> implementation can handle
* the URI. It should return true if the Database instance knows how to
* handle the URI and false otherwise.
*
* @param uri the URI to check for.
* @return true if the URI can be handled, false otherwise.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrroCodes.INVALID_URI</code> If the URI is not in a valid format. <br />
*/
public boolean acceptsURI(String uri) throws XMLDBException {
return ((uri != null) && uri.startsWith(getName() + "://"));
}
/**
* Initialize XML-RPC library static properties: encoding, keep-alive, SAX driver.
*
* @throws XMLDBException if specified (or default, if none specified) SAX driver
* class could not be loaded
*/
private void initialize() throws XMLDBException {
synchronized (getClass()) {
if (!xmlRpcInitialized) {
XmlRpc.setEncoding("UTF8");
XmlRpc.setKeepAlive(true);
/*
* Determine the SAXparser the xmlrpc client will use.
*
* In priority order:
* DatabaseImpl xmlrpc-driver property
* (passed in the xmlrpc-driver parameter)
* System property "xindice.xmlrpc.driver"
* Default value "xerces"
*/
String xmlrpcDriver = getProperty(PROP_XMLRPC_DRIVER);
if (xmlrpcDriver == null) {
xmlrpcDriver = System.getProperty(SYSPROP_XMLRPC_DRIVER);
if (xmlrpcDriver == null) {
xmlrpcDriver = DEFAULT_XMLRPC_DRIVER;
}
}
try {
if (log.isDebugEnabled()) {
log.debug("Using SAX Driver: '" + xmlrpcDriver + "'");
}
XmlRpc.setDriver(xmlrpcDriver);
} catch (Exception e) {
throw new XMLDBException(ErrorCodes.VENDOR_ERROR,
"SAX Driver " + xmlrpcDriver + " is not available", e);
}
}
xmlRpcInitialized = true;
}
}
/**
* Initialize XML-RPC service location, basic authentication.
* Create XML-RPC client.
*
* @param hostPort of the Xindice XML-RPC server
* @return XML-RPC client connected to the Xindice server
* @throws XMLDBException
*/
private XmlRpcClient connect(String hostPort) throws XMLDBException {
// Initialize XML-RPC static properties
initialize();
synchronized (this) {
if (serviceLocation == null) {
/*
* Determine the path in the web server to the XML-RPC service.
*
* In priority order:
* DatabaseImpl service-location property
* (passed in the serviceLocation parameter)
* System property "xindice.xmlrpc.service-location"
* Default value "/xindice/"
*/
serviceLocation = getProperty(PROP_SERVICE_LOCATION);
if (serviceLocation == null) {
serviceLocation = System.getProperty(SYSPROP_SERVICE_LOCATION);
if (serviceLocation == null) {
serviceLocation = DEFAULT_SERVICE_LOCATION;
}
}
if (!serviceLocation.startsWith("/")) {
serviceLocation = "/" + serviceLocation;
}
if (!serviceLocation.endsWith("/")) {
serviceLocation = serviceLocation + "/";
}
if (log.isDebugEnabled()) {
log.debug("Using Service Location: '" + serviceLocation + "'");
}
/*
* Determine basic authentication parameters
*/
basicUser = getProperty(PROP_XMLRPC_USER);
if (basicUser == null) {
basicUser = System.getProperty(SYSPROP_XMLRPC_USER);
}
if (basicUser != null) {
basicPassword = getProperty(PROP_XMLRPC_PASSWORD);
if (basicPassword == null) {
basicPassword = System.getProperty(SYSPROP_XMLRPC_PASSWORD);
}
if (log.isDebugEnabled()) {
log.debug("Using Basic authentication. User: '" + basicUser + "', password: '" + basicPassword + "'");
}
}
}
}
String xmlRpcURL = "http://" + hostPort + serviceLocation;
try {
if (log.isDebugEnabled()) {
log.debug("Using URI: '" + xmlRpcURL + "'");
}
XmlRpcClient client = new XmlRpcClient(xmlRpcURL);
if (basicUser != null) {
client.setBasicAuthentication(basicUser, basicPassword);
}
return client;
} catch (MalformedURLException e) {
throw new XMLDBException(ErrorCodes.INVALID_URI, e);
}
}
/**
* Retrieves a <code>Collection</code> instance based on the URI provided
* in the <code>uri</code> parameter. The format of the URI is defined in the
* documentation for DatabaseManager.getCollection().<p/>
*
* Authentication is handled via username and password however it is not
* required that the database support authentication. Databases that do not
* support authentication MUST ignore the
* <code>username</code> and <code>password</code> if those provided are not
* null.
*
* @param uri the URI to use to locate the collection.
* @param password The password to use for authentication to the database or
* null if the database does not support authentication.
* @return A <code>Collection</code> instance for the requested collection or
* null if the collection could not be found.
* @return The <code>Collection</code> instance
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
* <code>ErrroCodes.INVALID_URI</code> If the URI is not in a valid format. <br />
* <code>ErrroCodes.PERMISSION_DENIED</code> If the <code>username</code>
* and <code>password</code> were not accepted by the database.
*/
public Collection getCollection(String uri, String userName, String password) throws XMLDBException {
/* TODO: introduce authentication some day */
if (!acceptsURI(uri)) {
throw new XMLDBException(ErrorCodes.INVALID_URI,
"Invalid URL: " + uri);
}
/* Chop off driver prefix, and '://' */
uri = uri.substring(getName().length() + 3);
/* Extract host name & port, if present */
int firstSlash = uri.indexOf('/');
if (firstSlash == -1) {
throw new XMLDBException(ErrorCodes.INVALID_URI,
"Invalid URL (must have '/'): " + uri);
}
/* Extract collection name */
String collPath = uri.substring(firstSlash);
if (!collPath.startsWith("/")) {
throw new XMLDBException(ErrorCodes.INVALID_URI,
"Invalid URL (collection name must start with '/'): " + uri);
}
String hostPort = uri.substring(0, firstSlash);
/* Absent host defaults to localhost and standard Xindice HTTP port */
if (hostPort.equals("")) {
hostPort = "127.0.0.1:8888";
}
try {
return new CollectionImpl(connect(hostPort), collPath);
} catch (XMLDBException e) {
if (e.errorCode == ErrorCodes.NO_SUCH_COLLECTION) {
// per getCollection contract, return null if not found
return null;
}
throw e;
}
}
/**
* Returns the prefix used in XML:DB to denote URI's that this driver can
* handle.
*
* @return the prefix driver name
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
*/
public String getName() throws XMLDBException {
return DRIVER_NAME;
}
/**
* Returns an array of names associated with the Database instance.
*
* @return the array of name of the object.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
*/
public String[] getNames() throws XMLDBException {
return new String[]{ getName() };
}
/**
* Returns the XML:DB API Conformance level for the implementation. This can
* be used by client programs to determine what functionality is available to
* them.
*
* @return the XML:DB API conformance level for this implementation.
* @exception XMLDBException with expected error codes.<br />
* <code>ErrorCodes.VENDOR_ERROR</code> for any vendor
* specific errors that occur.<br />
*/
public String getConformanceLevel() throws XMLDBException {
return CONFORMANCE_LEVEL;
}
}