Clover coverage report -
Coverage timestamp: Sun Aug 24 2008 23:05:06 GMT
file stats: LOC: 464   Methods: 16
NCLOC: 269   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
Database.java 69.6% 87.3% 87.5% 83.2%
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: Database.java 594145 2007-11-12 13:58:46Z vgritsenko $
 18    */
 19   
 20    package org.apache.xindice.core;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.core.cache.DocumentCache;
 25    import org.apache.xindice.core.cache.DocumentCacheImpl;
 26    import org.apache.xindice.core.query.QueryEngine;
 27    import org.apache.xindice.server.Xindice;
 28    import org.apache.xindice.util.Configuration;
 29    import org.apache.xindice.util.Named;
 30    import org.apache.xindice.util.XindiceException;
 31   
 32    import org.w3c.dom.Document;
 33    import org.w3c.dom.Element;
 34   
 35    import javax.xml.parsers.DocumentBuilder;
 36    import javax.xml.parsers.DocumentBuilderFactory;
 37   
 38    import java.io.File;
 39    import java.io.FileOutputStream;
 40    import java.io.IOException;
 41    import java.util.Date;
 42    import java.util.HashMap;
 43    import java.util.Map;
 44    import java.util.Timer;
 45   
 46    /**
 47    * Database is the primary container for the Xindice Database Engine.
 48    *
 49    * @version $Revision: 594145 $, $Date: 2007-11-12 13:58:46 +0000 (Mon, 12 Nov 2007) $
 50    */
 51    public final class Database extends Collection
 52    implements Named {
 53   
 54    private static final Log log = LogFactory.getLog(Database.class);
 55   
 56    // Configuration elements in config/system.xml file
 57    public static final String DBROOT = "dbroot";
 58    public static final String NAME = "name";
 59   
 60    private static final String QUERYENGINE = "queryengine";
 61    private static final String DATABASE = "database";
 62    private static final String METADATA = "use-metadata";
 63   
 64    public static final String DBROOT_DEFAULT = "./db/";
 65   
 66    // Configuration documents stored in SysConfigs collection
 67    private static final String COLKEY = "database.xml";
 68    private static final String METAKEY = "meta.xml";
 69   
 70    private static final Map databases = new HashMap(); // String to Database
 71    private static final DatabaseShutdownHandler shutdownHandler = new DatabaseShutdownHandler();
 72   
 73    static {
 74    // sets up our golbal observer. will automatically flush document
 75    // changes to disk.
 76  19 DBObserver.setInstance(new DatabaseChangeObserver());
 77    }
 78   
 79    /**
 80    * This will return an instance of a Database for the given
 81    * name if one has already been loaded, otherwise it will
 82    * create and load a new database instance.
 83    *
 84    * @param config Database configuration
 85    * @return Database instance
 86    * @throws DBException if database name is missing in the configuration,
 87    * or unable to load a database.
 88    */
 89  44 public static Database getDatabase(Configuration config) throws DBException {
 90  44 String name = config.getAttribute(Database.NAME);
 91   
 92    // No name in the config file ... can't get the database
 93  44 if (name == null) {
 94  0 throw new DBException(FaultCodes.DBE_CANNOT_READ,
 95    "Database configuration didn't contain a database name");
 96    }
 97   
 98  44 Database database = (Database) databases.get(name);
 99  44 if (database == null) {
 100    // In case it's currently being added (only pay the sync hit on a miss)
 101  44 synchronized (databases) {
 102    // Was it created while we waited?
 103  44 database = (Database) databases.get(name);
 104  44 if (database == null) {
 105  44 database = new Database();
 106  44 try {
 107  44 database.setConfig(config);
 108    } catch (XindiceException x) {
 109  0 throw new DBException(FaultCodes.DBE_CANNOT_READ,
 110    "XindiceException: " + x.getMessage(), x);
 111    }
 112   
 113  44 databases.put(database.getName(), database);
 114    }
 115    }
 116    }
 117   
 118  44 return database;
 119    }
 120   
 121    /**
 122    * This will merely return an instance of a Database for the given
 123    * name if one has already been loaded.
 124    *
 125    * @param name Database name
 126    * @return Database
 127    */
 128  4355 public static Database getDatabase(String name) {
 129  4355 Database database = (Database) databases.get(name);
 130  4355 if (database == null) {
 131    // in case it's currently being added (only pay the sync hit on a miss)
 132  11 synchronized (databases) {
 133  11 database = (Database) databases.get(name);
 134    }
 135    }
 136   
 137  4355 return database;
 138    }
 139   
 140  0 public static String[] listDatabases() {
 141  1 synchronized (databases) {
 142  1 return (String[]) databases.keySet().toArray(new String[0]);
 143    }
 144    }
 145   
 146   
 147    //
 148    // Instance...
 149    //
 150   
 151    private DocumentCache docCache;
 152    private QueryEngine engine;
 153    private boolean metaEnabled;
 154    private boolean metaInit;
 155    private MetaSystemCollection metaSystemCollection;
 156    private boolean sysInit;
 157    private SystemCollection systemCollection;
 158    private FileOutputStream lock;
 159    private boolean closed;
 160   
 161    /** Shared timer instance for this database's IndexManagers */
 162    private Timer timer;
 163   
 164   
 165  123 public Database() {
 166  123 super();
 167  123 docCache = new DocumentCacheImpl();
 168  123 engine = new QueryEngine(this);
 169  123 closed = true;
 170    }
 171   
 172    /**
 173    * Checks to see if it has been closed to insure that it doesn't try to do it
 174    * twice.
 175    *
 176    * @param removeFromShutdown If true removes its self from the shutdown hook
 177    * @return true if closed
 178    * @throws DBException if unable to close
 179    */
 180  122 protected synchronized boolean close(boolean removeFromShutdown) throws DBException {
 181  123 if (removeFromShutdown) {
 182    // we have already been closed so no need to do this again.
 183  119 shutdownHandler.removeDatabase(this);
 184    }
 185   
 186    // check to see if we have already been closed.
 187  123 if (!closed) {
 188  123 if(log.isDebugEnabled()) {
 189  0 log.debug("Shutting down database: '" + getName() + "'");
 190    }
 191   
 192  123 flushConfig();
 193  123 super.close();
 194   
 195    // Release database lock
 196  123 try {
 197  123 this.lock.close();
 198  123 new File(getCollectionRoot(), "db.lock").delete();
 199    } catch (Exception e) {
 200    // Ignore IO exception
 201    }
 202  123 this.lock = null;
 203   
 204  123 synchronized (databases) {
 205  123 databases.remove(getName());
 206    }
 207   
 208    // Stop the timer thread
 209  123 timer.cancel();
 210   
 211  123 closed = true;
 212    }
 213   
 214  123 return true;
 215    }
 216   
 217    /**
 218    * @see org.apache.xindice.core.DBObject#close()
 219    * @see org.apache.xindice.core.Database#close(boolean removeFromShutdown)
 220    */
 221  119 public boolean close() throws DBException {
 222  119 return close(true);
 223    }
 224   
 225    /**
 226    * flushConfig ensures that the Collection configuration has been
 227    * properly flushed to disk after a modification.
 228    */
 229  3910 public void flushConfig() {
 230  3911 if (getConfig().isDirty()) {
 231  1315 try {
 232  1315 Document d = getConfig().getElement().getOwnerDocument();
 233  1315 systemCollection.getCollection(SystemCollection.CONFIGS).setDocument(COLKEY, d);
 234  1315 getConfig().resetDirty();
 235    } catch (Exception e) {
 236  0 log.error("Error Writing Configuration '" + COLKEY + "', for database " + getName(), e);
 237    }
 238   
 239    // Observer
 240  1315 DBObserver.getInstance().flushDatabaseConfig(this, getConfig());
 241    }
 242   
 243  3911 if (isMetaEnabled()) {
 244  3907 Configuration config = metaSystemCollection.getConfig();
 245  3907 if (config.isDirty()) {
 246  1028 try {
 247  1028 Document d = config.getElement().getOwnerDocument();
 248  1028 systemCollection.getCollection(SystemCollection.CONFIGS).setDocument(METAKEY, d);
 249  1028 config.resetDirty();
 250    } catch (Exception e) {
 251  0 log.error("Error writing configuration '" + METAKEY + "', for database " + getName(), e);
 252    }
 253    }
 254    }
 255    }
 256   
 257    /**
 258    * @see org.apache.xindice.core.Collection#getDatabase()
 259    */
 260  45526 public Database getDatabase() {
 261  45526 return this;
 262    }
 263   
 264    /**
 265    * getDocumentCache returns the Database-level Document Cache.
 266    *
 267    * @return The DocumentCache
 268    */
 269  1888 public DocumentCache getDocumentCache() {
 270  1888 return docCache;
 271    }
 272   
 273    /**
 274    * Return the MetaSystem collection for this database.
 275    *
 276    * It will return null if metadata is not enabled on this database.
 277    * @return MetaSystemCollection
 278    */
 279  17613 public MetaSystemCollection getMetaSystemCollection() {
 280  17613 return metaSystemCollection;
 281    }
 282   
 283    /**
 284    * getQueryEngine returns a reference to the Database's current
 285    * operating QueryEngine implementation.
 286    *
 287    * @return The QueryEngine instance
 288    */
 289  201 public QueryEngine getQueryEngine() {
 290  201 return engine;
 291    }
 292   
 293    /**
 294    * @see org.apache.xindice.core.Collection#getSystemCollection()
 295    */
 296  3225 public SystemCollection getSystemCollection() {
 297  3225 return systemCollection;
 298    }
 299   
 300    /**
 301    * Return database's timer instance.
 302    * @return Database timer instance
 303    */
 304  1275 protected Timer getTimer() {
 305  1275 return timer;
 306    }
 307   
 308    /**
 309    * Return whether or not metadata is enabled on this database.
 310    * @return boolean
 311    */
 312  21568 public boolean isMetaEnabled() {
 313  21569 return metaEnabled;
 314    }
 315   
 316    /**
 317    * @see org.apache.xindice.util.Configurable#setConfig(org.apache.xindice.util.Configuration)
 318    */
 319  123 public void setConfig(Configuration config) throws XindiceException {
 320    // FIXME Get rid of super.setConfig here?
 321  123 super.setConfig(config);
 322  123 setCanonicalName('/' + getName());
 323   
 324    // Determine database root directory
 325  123 String dbroot = config.getAttribute(DBROOT);
 326  123 File dbrootDir = new File(dbroot);
 327  123 if (!dbrootDir.isAbsolute()) {
 328    // Here, DB root already should be absolute. XMLRPC, Embed, and Managed drivers take care of it.
 329  120 log.warn("The specified database root directory '" + dbroot + "' is relative. " +
 330    "Using property " + Xindice.PROP_XINDICE_DB_HOME + " to resolve.");
 331   
 332  120 String home = System.getProperty(Xindice.PROP_XINDICE_DB_HOME);
 333  120 if (home == null) {
 334  0 log.warn("The specified database root directory '" + dbroot + "' is relative " +
 335    "and there was no " + Xindice.PROP_XINDICE_DB_HOME + " property set, " +
 336    "so Xindice was unable to determine a database location. " +
 337    "Database will be created relative to the current working directory.");
 338   
 339  0 home = ".";
 340    }
 341  120 try {
 342    // In case home has been specified as relative path convert it to absolute path
 343  120 dbrootDir = new File(home, dbroot).getCanonicalFile();
 344    } catch (IOException e) {
 345  0 throw new XindiceException("Can't get canonical path", e);
 346    }
 347    }
 348  123 setCollectionRoot(dbrootDir);
 349  123 if (log.isInfoEnabled()) {
 350  1 log.info("Database points to " + dbrootDir.getAbsolutePath());
 351    }
 352   
 353    // Put a lock (at least attempt to) on the database
 354  123 File lock = new File(getCollectionRoot(), "db.lock");
 355  123 try {
 356  123 this.lock = new FileOutputStream(lock);
 357   
 358  123 if (this.lock.getChannel().tryLock() != null) {
 359  123 this.lock.write(new Date().toString().getBytes());
 360    } else {
 361  0 throw new IOException("Unable to acquire file lock.");
 362    }
 363    } catch (IOException e) {
 364  0 throw new XindiceException("Unable to open lock file " + lock + ". " +
 365    "Make sure database is not open by another process.",
 366    e);
 367    }
 368   
 369    // Now we are ready to open it up
 370  123 shutdownHandler.registerDatabase(this);
 371  123 timer = new Timer(false);
 372  123 closed = false;
 373   
 374    // Initialize query engine
 375  123 try {
 376  123 Configuration queryCfg = config.getChild(QUERYENGINE);
 377  123 if (queryCfg != null) {
 378  123 engine.setConfig(queryCfg);
 379    }
 380    } catch (Exception e) {
 381  0 if (log.isWarnEnabled()) {
 382  0 log.warn("ignored exception", e);
 383    }
 384    }
 385   
 386    // Initialize system collection
 387  123 if (!sysInit) {
 388  123 systemCollection = new SystemCollection(this);
 389  123 systemCollection.init();
 390  123 super.addCollection(systemCollection);
 391  123 this.sysInit = true;
 392    }
 393   
 394  123 Collection sysConfigCollection = systemCollection.getCollection(SystemCollection.CONFIGS);
 395  123 try {
 396    // Bootstrap from the database itself... This is accomplished
 397    // by intercepting the setConfig call and using a Configuration
 398    // retrieved from the database instead of the standard config
 399  123 Document colDoc = sysConfigCollection.getDocument(COLKEY);
 400  123 if (colDoc == null) {
 401  4 DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
 402  4 colDoc = db.newDocument();
 403  4 Element root = colDoc.createElement(DATABASE);
 404  4 root.setAttribute(NAME, getName());
 405  4 colDoc.appendChild(root);
 406  4 sysConfigCollection.setDocument(COLKEY, colDoc);
 407    }
 408   
 409  123 super.setConfig(new Configuration(colDoc.getDocumentElement(), false));
 410    } catch (Exception e) {
 411  0 if (log.isWarnEnabled()) {
 412  0 log.warn("ignored exception", e);
 413    }
 414    }
 415   
 416    // Register the Database with the VM
 417    // databases.put(getName(), this);
 418   
 419    // initialize the meta collection
 420    // but only if it's turned on in the config.
 421  123 String metaCfg = config.getAttribute(METADATA);
 422  123 if (metaCfg.equalsIgnoreCase("on")) {
 423  122 metaEnabled = true;
 424    }
 425   
 426  123 if (metaEnabled && !metaInit) {
 427  122 try {
 428  122 metaSystemCollection = new MetaSystemCollection(this);
 429   
 430  122 Document colDoc = sysConfigCollection.getDocument(METAKEY);
 431  122 if (colDoc == null) {
 432  4 metaSystemCollection.init();
 433  4 Document metaConfig = metaSystemCollection.getConfig().getElement().getOwnerDocument();
 434  4 sysConfigCollection.setDocument(METAKEY, metaConfig);
 435    } else {
 436  118 metaSystemCollection.setConfig(new Configuration(colDoc, false));
 437    }
 438   
 439  122 super.addCollection(metaSystemCollection);
 440  122 metaInit = true;
 441  122 if (log.isDebugEnabled()) {
 442  0 log.debug("Meta collection is initialized");
 443    }
 444    } catch (Exception e) {
 445  0 log.error("Meta collection was not initialized", e);
 446    }
 447    }
 448   
 449    // observer
 450  123 DBObserver.getInstance().setDatabaseConfig(this, getCollections(), config);
 451    }
 452   
 453   
 454    /**
 455    * Database can not be dropped.
 456    *
 457    * @return nothing.
 458    * @throws DBException Always throws DBE_CANNOT_DROP fault code.
 459    */
 460  0 public boolean drop() throws DBException {
 461  0 throw new DBException(FaultCodes.DBE_CANNOT_DROP,
 462    "You cannot drop the database");
 463    }
 464    }