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