/* * eXist Open Source Native XML Database * Copyright (C) 2001 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; er 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.xmldb; import org.apache.log4j.Logger; import org.apache.xmlrpc.client.XmlRpcClient; import org.apache.xmlrpc.client.XmlRpcClientConfigImpl; import org.exist.EXistException; import org.exist.security.SecurityManager; import org.exist.security.User; import org.exist.security.xacml.AccessContext; import org.exist.storage.BrokerPool; import org.exist.util.Configuration; 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; import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; /** * The XMLDB driver class for eXist. This driver manages two different * internal implementations. The first communicates with a remote * database using the XMLRPC protocol. The second has direct access * to an embedded database instance running in the same virtual machine. * The driver chooses an implementation depending on the XML:DB URI passed * to getCollection(). * * When running in embedded mode, the driver can create a new database * instance if none is available yet. It will do so if the property * "create-database" is set to "true" or if there is a system property * "exist.initdb" with value "true". * * You may optionally provide the location of an alternate configuration * file through the "configuration" property. The driver is also able to * address different database instances - which may have been installed at * different places. * * @author Wolfgang Meier */ public class DatabaseImpl implements Database { private final static Logger LOG = Logger.getLogger(DatabaseImpl.class); //TODO : discuss about other possible values protected final static String LOCAL_HOSTNAME = ""; protected final static int UNKNOWN_CONNECTION = -1; protected final static int LOCAL_CONNECTION = 0; protected final static int REMOTE_CONNECTION = 1; /** Default config filename to configure an Instance */ public final static String CONF_XML="conf.xml"; protected boolean autoCreate = false; protected String configuration = null; protected String currentInstanceName = null; private HashMap rpcClients = new HashMap(); protected ShutdownListener shutdown = null; protected int mode = UNKNOWN_CONNECTION; public DatabaseImpl() { String initdb = System.getProperty( "exist.initdb" ); if(initdb != null) autoCreate = initdb.equalsIgnoreCase("true"); } /** * In embedded mode: configure the database instance * *@exception XMLDBException Description of the Exception */ private void configure(String instanceName) throws XMLDBException { // System.out.println("Configuring '" + instanceName + "' using " + Configuration.getPath(configuration, null)); try { Configuration config = new Configuration(configuration, null); BrokerPool.configure(instanceName, 1, 5, config); if (shutdown != null) BrokerPool.getInstance(instanceName).registerShutdownListener(shutdown); } catch (Exception e ) { throw new XMLDBException(ErrorCodes.VENDOR_ERROR, "configuration error: " + e.getMessage(), e ); } currentInstanceName = instanceName; } /* @deprecated Although part of the xmldb API, the design is somewhat inconsistent. * @see org.xmldb.api.base.Database#acceptsURI(java.lang.String) */ public boolean acceptsURI(String uri) throws XMLDBException { XmldbURI xmldbURI = null; try { //Ugly workaround for non-URI compliant collection (resources ?) names (most likely IRIs) String newURIString = XmldbURI.recoverPseudoURIs(uri); //Remember that DatabaseManager (provided in xmldb.jar) trims the leading "xmldb:" !!! //... prepend it to have a real xmldb URI again... xmldbURI = XmldbURI.xmldbUriFor(XmldbURI.XMLDB_URI_PREFIX + newURIString); return acceptsURI(xmldbURI); } catch (URISyntaxException e) { //... even in the error message throw new XMLDBException(ErrorCodes.INVALID_DATABASE, "xmldb URI is not well formed: " + XmldbURI.XMLDB_URI_PREFIX + uri); } } public boolean acceptsURI(XmldbURI xmldbURI) throws XMLDBException { //TODO : smarter processing (resources names, protocols, servers accessibility...) ? -pb return true; } /* Returns a collection from the given "uri". * @deprecated Although part of the xmldb API, the design is somewhat inconsistent. * @see org.exist.xmldb.DatabaseImpl#getCollection(org.exist.xmldb.XmldbURI, java.lang.String, java.lang.String) * @see org.xmldb.api.base.Database#getCollection(java.lang.String, java.lang.String, java.lang.String) */ public Collection getCollection(String uri, String user, String password) throws XMLDBException { XmldbURI xmldbURI = null; try { //Ugly workaround for non-URI compliant collection names (most likely IRIs) String newURIString = XmldbURI.recoverPseudoURIs(uri); //Remember that DatabaseManager (provided in xmldb.jar) trims the leading "xmldb:" !!! //... prepend it to have a real xmldb URI again... xmldbURI = XmldbURI.xmldbUriFor(XmldbURI.XMLDB_URI_PREFIX + newURIString); } catch (URISyntaxException e) { //... even in the error message throw new XMLDBException(ErrorCodes.INVALID_DATABASE, "xmldb URI is not well formed: " + XmldbURI.XMLDB_URI_PREFIX + uri); } return getCollection(xmldbURI, user, password); } public Collection getCollection(XmldbURI xmldbURI, String user, String password) throws XMLDBException { if (XmldbURI.API_LOCAL.equals(xmldbURI.getApiName())) return getLocalCollection(xmldbURI, user, password); else if (XmldbURI.API_XMLRPC.equals(xmldbURI.getApiName())) return getRemoteCollection(xmldbURI, user, password); else throw new XMLDBException(ErrorCodes.INVALID_DATABASE, "Unknown or unparsable API for: " + xmldbURI); } /** * @param xmldbURI * @param user * @param password * @return The collection * @throws XMLDBException */ private Collection getLocalCollection(XmldbURI xmldbURI, String user, String password) throws XMLDBException { mode = LOCAL_CONNECTION; // use local database instance if (!BrokerPool.isConfigured(xmldbURI.getInstanceName())) { if (autoCreate) configure(xmldbURI.getInstanceName()); else throw new XMLDBException(ErrorCodes.COLLECTION_CLOSED, "Local database server is not running"); } BrokerPool pool; try { pool = BrokerPool.getInstance(xmldbURI.getInstanceName()); } catch (EXistException e) { throw new XMLDBException( ErrorCodes.VENDOR_ERROR, "Can not access to local database instance", e); } User u = getUser(user, password, pool); try { Collection current = new LocalCollection(u, pool, xmldbURI.toCollectionPathURI(), AccessContext.XMLDB); return (current != null) ? current : null; } catch (XMLDBException e) { switch (e.errorCode) { case ErrorCodes.NO_SUCH_RESOURCE: case ErrorCodes.NO_SUCH_COLLECTION: case ErrorCodes.INVALID_COLLECTION: case ErrorCodes.INVALID_RESOURCE: LOG.info(e.getMessage()); return null; default: LOG.error(e.getMessage(), e); throw e; } } } /** * @param xmldbURI * @param user * @param password * @return The collection * @throws XMLDBException */ private Collection getRemoteCollection(XmldbURI xmldbURI, String user, String password) throws XMLDBException { mode = REMOTE_CONNECTION; if (user == null) { //TODO : read this from configuration user = "guest"; password = "guest"; } if(password == null) password = ""; try { URL url = new URL("http", xmldbURI.getHost(), xmldbURI.getPort(), xmldbURI.getContext()); XmlRpcClient rpcClient = getRpcClient(user, password, url); return readCollection(xmldbURI.getRawCollectionPath(), rpcClient); } catch (MalformedURLException e) { //Should never happen throw new XMLDBException(ErrorCodes.INVALID_DATABASE, e.getMessage()); } catch (XMLDBException e) { return null; } } public static Collection readCollection(String c, XmlRpcClient rpcClient) throws XMLDBException { XmldbURI path; try { path = XmldbURI.xmldbUriFor(c); } catch (URISyntaxException e) { throw new XMLDBException(ErrorCodes.INVALID_URI,e); } XmldbURI[] components = path.getPathSegments(); if (components.length == 0) throw new XMLDBException(ErrorCodes.NO_SUCH_COLLECTION, "Could not find collection: " + path.toString()); XmldbURI rootName = components[0]; if (XmldbURI.RELATIVE_ROOT_COLLECTION_URI.equals(rootName)) rootName = XmldbURI.ROOT_COLLECTION_URI; Collection current = new RemoteCollection(rpcClient, null, rootName); for (int i = 1 ; i < components.length ; i++) { current = ((RemoteCollection)current).getChildCollection(components[i]); if (current == null) throw new XMLDBException(ErrorCodes.NO_SUCH_COLLECTION , "Could not find collection: " + c); } return current; } /** * @param user * @param pool * @return the User object corresponding to the username in <code>user</code> * @throws XMLDBException */ private User getUser(String user, String password, BrokerPool pool) throws XMLDBException { if (user == null) { user = "guest"; password = "guest"; } SecurityManager securityManager = pool.getSecurityManager(); User u = securityManager.getUser(user); if (u == null) { throw new XMLDBException(ErrorCodes.PERMISSION_DENIED, "User '" + user + "' does not exist"); } if (!u.validate(password, securityManager)) { throw new XMLDBException(ErrorCodes.PERMISSION_DENIED, "Invalid password for user '" + user + "'"); } return u; } /** * RpcClients are cached by address+user. The password is transparently changed. * @param user * @param password * @param url * @throws XMLDBException */ private XmlRpcClient getRpcClient(String user, String password, URL url) throws XMLDBException { String key = user + "@" + url.toString(); XmlRpcClient client = (XmlRpcClient) rpcClients.get(key); XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl(); config.setEnabledForExtensions(true); config.setServerURL(url); config.setBasicUserName(user); config.setBasicPassword(password); if (client == null) { client = new XmlRpcClient(); rpcClients.put(key, client); } client.setConfig(config); return client; } /** * Register a ShutdownListener for the current database instance. The ShutdownListener is called * after the database has shut down. You have to register a listener before any calls to getCollection(). * * @param listener * @throws XMLDBException */ public void setDatabaseShutdownListener(ShutdownListener listener) throws XMLDBException { shutdown = listener; } public String getConformanceLevel() throws XMLDBException { //TODO : what is to be returned here ? -pb return "0"; } //WARNING : returning such a default value is dangerous IMHO ? -pb /** @deprecated */ public String getName() throws XMLDBException { return (currentInstanceName != null) ? currentInstanceName : "exist"; } //WARNING : returning such *a* default value is dangerous IMHO ? -pb public String[] getNames() throws XMLDBException { return new String[] { (currentInstanceName != null) ? currentInstanceName : "exist" }; } public String getProperty(String property) throws XMLDBException { if (property.equals("create-database")) return Boolean.valueOf(autoCreate).toString(); //TODO : rename ? if (property.equals("database-id")) //TODO : consider multivalued property return currentInstanceName; if (property.equals("configuration")) return configuration; return null; } public void setProperty(String property, String value) throws XMLDBException { if (property.equals("create-database")) autoCreate = value.equals("true"); //TODO : rename ? if (property.equals("database-id")) //TODO : consider multivalued property currentInstanceName = value; if (property.equals("configuration")) configuration = value; } }