Source: jala/code/Database.js

/**
 * @fileoverview Fields and methods of the jala.db package.
 */


// Define the global namespace for Jala modules
if (!global.jala) {
   global.jala = {};
}

/**
 * HelmaLib dependencies
 */
app.addRepository("modules/helma/Database.js");

/**
 * Namespace declarations
 */
jala.db = {
   "metadata": {}
};

/**
 * Static helper method that converts the object passed as argument
 * to a connection property string.
 * @param {Object} props The property object to convert
 * @returns A connection property string
 * @type String
 * @private
 */
jala.db.getPropertyString = function(props) {
   if (props != null) {
      res.push();
      for (var i in props) {
         res.write(";");
         res.write(i);
         res.write("=");
         res.write(props[i]);
      }
      return res.pop();
   }
   return null;
};

/**
 * Returns an array of table names. The optional patterns can contain
 * "_" for matching a single character or "%" for any character sequence.
 * @param {java.sql.DatabaseMetaData} dbMetadata The metadata to use for retrieval.
 * @param {String} tablePattern An optional table name pattern (defaults to "%")
 * @param {String} schemaPattern An optional schema name pattern (defaults to "%")
 * @returns An array containing the table names
 * @type Array
 */
jala.db.metadata.getTableNames = function(dbMetadata, tablePattern, schemaPattern) {
   var result = [];
   var tableMeta = null;
   try {
      tableMeta = dbMetadata.getTables(null, (schemaPattern || "%"), (tablePattern || "%"), null);
      while (tableMeta.next()) {
         result.push(tableMeta.getString("TABLE_NAME"));
      }
      return result;
   } finally {
      if (tableMeta !== null) {
         tableMeta.close();
      }
   }
   return null;
};

/**
 * Returns an array containing table metadata.
 * @param {java.sql.DatabaseMetaData} dbMetadata The metadata to use for retrieval
 * @param {String} tablePattern Optional table name pattern
 * @param {String} schemaPattern Optional schema name pattern
 * @returns An array containing table metadata. Each one is represented by a
 * javascript object containing the following properties:
 * <ul>
 * <li>name (String): The name of the table</li>
 * <li>schema (String): The name of the schema the table belongs to</li>
 * <li>columns (Array): An array of column metadata (see {@link #getColumns})</li>
 * <li>keys (Array): An array containing primary key column names (see {@link #getPrimaryKeys}<li>
 * </ul>
 * @type Array
 */
jala.db.metadata.getTables = function(dbMetadata, tablePattern, schemaPattern) {
   var result = [];
   var tableMeta = null;
   try {
      tableMeta = dbMetadata.getTables(null, (schemaPattern || "%"),
            (tablePattern || "%"), null);
      while (tableMeta.next()) {
         var tableName = tableMeta.getString("TABLE_NAME");
         var schemaName = tableMeta.getString("TABLE_SCHEM") || null;
         result.push({
            "name": tableName,
            "schema": schemaName,
            "columns": jala.db.metadata.getColumns(dbMetadata, tableName, schemaName),
            "keys": jala.db.metadata.getPrimaryKeys(dbMetadata, tableName, schemaName)
         });
      }
      return result;
   } finally {
      if (tableMeta != null) {
         tableMeta.close();
      }
   }
   return null;
};

/**
 * Returns the column metadata of a table (or multiple tables, if a tableName
 * pattern matching several tables is specified).
 * @param {java.sql.DatabaseMetaData} dbMetadata The metadata to use for retrieval
 * @param {String} tablePattern Optional table name pattern
 * @param {String} schemaPattern Optional schema name pattern
 * @param {String} columnPattern Optional column name pattern
 * @returns An array containing column metadata. Each one is represented by a
 * javascript object containing the following properties:
 * <ul>
 * <li>name (String): The name of the column</li>
 * <li>type (Number): The data type of the column</li>
 * <li>length (Number): The maximum length of the column</li>
 * <li>nullable (Boolean): True if the column may contain null values, false otherwise</li>
 * <li>default (String): The default value of the column</li>
 * <li>precision (Number): The precision of the column</li>
 * <li>scale (Number): The radix of the column</li>
 * </ul>
 * @type Array
 */
jala.db.metadata.getColumns = function(dbMetadata, tablePattern, schemaPattern, columnPattern) {
   var result = [];
   var columnMeta = null;
   try {
      columnMeta = dbMetadata.getColumns(null, schemaPattern || null,
            tablePattern || null, columnPattern || "%");
      while (columnMeta.next()) {
         result.push({
            "name": columnMeta.getString("COLUMN_NAME"),
            "type": columnMeta.getInt("DATA_TYPE"),
            "length": columnMeta.getInt("COLUMN_SIZE"),
            "nullable": (columnMeta.getInt("NULLABLE") == dbMetadata.typeNoNulls) ? false : true,
            "default": columnMeta.getString("COLUMN_DEF"),
            "precision": columnMeta.getInt("DECIMAL_DIGITS"),
            "scale": columnMeta.getInt("NUM_PREC_RADIX")
         });
      }
      return result;
   } finally {
      if (columnMeta != null) {
         columnMeta.close();
      }
   }
   return null;
};

/**
 * Returns an array containing the primary key names of the specified table.
 * @param {java.sql.DatabaseMetaData} dbMetadata The metadata to use for retrieval
 * @param {String} tableName The name of the table
 * @param {String} schemaName Optional name of the schema
 * @returns An array containing the primary key column names
 * @type Array
 */
jala.db.metadata.getPrimaryKeys = function(dbMetadata, tableName, schemaName) {
   var result = [];
   var keyMeta = null;
   try {
      // retrieve the primary keys of the table
      var keyMeta = dbMetadata.getPrimaryKeys(null, schemaName || null, tableName);
      while (keyMeta.next()) {
         result.push(keyMeta.getString("COLUMN_NAME"));
      }
      return result;
   } finally {
      if (keyMeta != null) {
         keyMeta.close();
      }
   }
   return null;
};


/**
 * Returns the table metadata of the given database. The optional patterns
 * can contain "_" for matching a single character or "%" for any
 * character sequence.
 * @param {helma.Database} database The database to connect to
 * @param {String} schemaPattern An optional schema name pattern
 * @param {String} tablePattern An optional table name pattern
 * @returns An array containing the metadata of all matching tables (see {@link #getTables})
 * @type Array
 */
jala.db.getTableMetadata = function(database, tablePattern, schemaPattern) {
   var conn = null;
   try {
      conn = database.getConnection();
      return jala.db.metadata.getTables(conn.getMetaData(), tablePattern, schemaPattern);
   } finally {
      if (conn != null) {
         conn.close();
      }
   }
   return null;
};




/*****************************************
 ***   D A T A B A S E   S E R V E R   ***
 *****************************************/


/**
 * Returns a new Server instance.
 * @class Instances of this class represent a H2 database listener that
 * allows multiple databases to be accessed via tcp.
 * <br /><strong>Important:</strong> You need the h2.jar in directory "lib/ext"
 * of your helma installation for this library to work, which you can get
 * at http://www.h2database.com/.
 * @param {helma.File} baseDir The directory where the database files
 * are located or should be stored
 * @param {Number} port The port to listen on (defaults to 9001)
 * @param {Boolean} createOnDemand If true this server will create non-existing
 * databases on-the-fly, if false it only accepts connections to already
 * existing databases in the given base directory
 * @param {Boolean} makePublic If true this database is reachable from outside,
 * if false it's only reachable from localhost
 * @param {Boolean} useSsl If true SSL will be used to encrypt the connection
 * @returns A newly created Server instance
 * @constructor
 */
jala.db.Server = function(baseDir, port) {

   /**
    * Private variable containing the h2 server instance
    * @type org.h2.tools.Server
    * @private
    */
   var server = null;

   /**
    * An object containing configuration properties used when creating
    * the server instance
    * @private
    */
   var config = {
      "baseDir": baseDir.getAbsolutePath(),
      "tcpPort": port || 9092,
      "tcpSSL": false,
      "ifExists": true,
      "tcpAllowOthers": false,
      "log": false
   };

   /**
    * Returns the wrapped database server instance
    * @returns The wrapped database server
    * @type org.h2.tools.Server
    * @private
    */
   this.getServer = function() {
      return server;
   };

   /**
    * Returns the directory used by this server instance
    * @returns The directory where the databases used by this
    * server are located in
    * @type helma.File
    */
   this.getDirectory = function() {
      return baseDir;
   };

   /**
    * Returns the port this server listens on
    * @returns The port this server listens on
    * @type Number
    */
   this.getPort = function() {
      return config.tcpPort;
   };

   /**
    * Returns the config of this server
    * @returns The config of this server
    * @private
    */
   this.getConfig = function() {
      return config;
   };

   /**
    * Starts the database server.
    * @returns True in case the server started successfully, false otherwise
    * @type Boolean
    */
   this.start = function() {
      if (server != null && server.isRunning(true)) {
         throw "jala.db.Server: already listening on port " + this.getPort();
      }
      // convert properties into an array
      var config = this.getConfig();
      var args = [];
      for (var propName in config) {
         args.push("-" + propName);
         args.push(config[propName].toString());
      }
      // create the server instance
      server = Packages.org.h2.tools.Server.createTcpServer(args);
      try {
         server.start();
      } catch (e) {
         app.logger.error("jala.db.Server: unable to start server, reason: " + e);
         return false;
      }
      app.logger.info("jala.db.Server: listening on localhost:" + this.getPort());
      return true;
   };


   return this;
};

/** @ignore */
jala.db.Server.prototype.toString = function() {
   return "[Jala Database Server]";
};

/**
 * Stops the database server.
 * @returns True if stopping the server was successful, false otherwise
 * @type Boolean
 */
jala.db.Server.prototype.stop = function() {
   try {
      this.getServer().stop();
      app.logger.info("jala.db.Server: stopped listening on localhost:" +
                      this.getPort());
   } catch (e) {
      app.logger.error("jala.db.Server: Unable to stop, reason: " + e);
      return false;
   }
   return true;
};

/**
 * Returns true if the database server is running.
 * @returns True if the database server is running
 * @type Boolean
 */
jala.db.Server.prototype.isRunning = function() {
   return this.getServer().isRunning(true);
};

/**
 * Toggles the use of Ssl encryption within this server. This should be set
 * before starting the server.
 * @param {Boolean} bool If true SSL encryption will be used, false
 * otherwise. If no argument is given, this method returns the
 * current setting.
 * @returns The current setting if no argument is given, or void
 * @type Boolean
 */
jala.db.Server.prototype.useSsl = function(bool) {
   var config = this.getConfig();
   if (bool != null) {
      config.tcpSSL = (bool === true);
   } else {
      return config.tcpSSL;
   }
   return;
};

/**
 * If called with boolean true as argument, this server creates databases
 * on-the-fly, otherwise it only accepts connections to already existing
 * databases. This should be set before starting the server.
 * @param {Boolean} bool If true this server creates non-existing databases
 * on demand, if false it only allows connections to existing databases.
 * If no argument is given, this method returns the current setting.
 * @returns The current setting if no argument is given, or void
 * @type Boolean
 */
jala.db.Server.prototype.createOnDemand = function(bool) {
   var config = this.getConfig();
   if (bool != null) {
      config.ifExists = (bool === false);
   } else {
      return !config.ifExists;
   }
   return;
};

/**
 * If called with boolean true as argument, this server accepts connections
 * from outside localhost. This should be set before starting the server.
 * @param {Boolean} bool If true this server accepts connections from outside
 * localhost. If no argument is given, this method returns the current setting.
 * @returns The current setting if no argument is given, or void
 * @type Boolean
 */
jala.db.Server.prototype.isPublic = function(bool) {
   var config = this.getConfig();
   if (bool != null) {
      config.tcpAllowOthers = (bool === true);
   } else {
      return config.tcpAllowOthers;
   }
   return;
};

/**
 * Returns the JDBC Url to use for connections to a given database.
 * @param {String} name An optional name of a database running
 * @param {Object} props Optional connection properties to add
 * @returns The JDBC Url to use for connecting to a database
 * within this sever
 * @type String
 */
jala.db.Server.prototype.getUrl = function(name, props) {
   res.push();
   res.write("jdbc:h2:");
   res.write(this.useSsl() ? "ssl" : "tcp");
   res.write("://localhost:");
   res.write(this.getPort());
   res.write("/");
   res.write(name);
   res.write(jala.db.getPropertyString(props))
   return res.pop();
};

/**
 * Returns a properties object containing the connection properties
 * of the database with the given name.
 * @param {String} name The name of the database
 * @param {String} username Optional username to use for this connection
 * @param {String} password Optional password to use for this connection
 * @param {Object} props An optional parameter object containing
 * connection properties to add to the connection Url.
 * @returns A properties object containing the connection properties
 * @type helma.util.ResourceProperties
 */
jala.db.Server.prototype.getProperties = function(name, username, password, props) {
   var rp = new Packages.helma.util.ResourceProperties();
   rp.put(name + ".url", this.getUrl(name, props));
   rp.put(name + ".driver", "org.h2.Driver");
   rp.put(name + ".user", username || "sa");
   rp.put(name + ".password", password || "");
   return rp;
};

/**
 * Returns a connection to a database within this server.
 * @param {String} name The name of the database running
 * within this server
 * @param {String} username Optional username to use for this connection
 * @param {String} password Optional password to use for this connection
 * @param {Object} props An optional parameter object
 * containing connection properties to add to the connection Url.
 * @returns A connection to the specified database
 * @type helma.Database
 */
jala.db.Server.prototype.getConnection = function(name, username, password, props) {
   var rp = this.getProperties(name, username, password, props);
   var dbSource = new Packages.helma.objectmodel.db.DbSource(name, rp);
   return new helma.Database(dbSource);
};



/*****************************
 ***   D A T A   T Y P E   ***
 *****************************/


/**
 * Returns a newly created DataType instance.
 * @class Instances of this class represent a data type. Each instance
 * contains the code number as defined in java.sql.Types, the name of
 * the data type as defined in java.sql.Types and optional creation parameters
 * allowed for this data type.
 * @param {Number} type The sql code number of this data type
 * @param {String} typeName The type name of this data type, as used within sql statements
 * @param {String} params Optional creation parameters allowed for this data type.
 * @returns A newly created instance of DataType.
 * @constructor
 * @private
 */
jala.db.DataType = function(type, typeName, params) {

   /**
    * Returns the sql type code number as defined in java.sql.Types
    * @returns The sql type code number of this data type
    * @type Number
    */
   this.getType = function() {
      return type;
   };

   /**
    * Returns the type name of this data type, which can be
    * used in sql queries.
    * @returns The type name of this data type
    * @type String
    */
   this.getTypeName = function() {
      return typeName;
   };

   /**
    * Returns the creation parameter string of this data type
    * @returns The creation parameter string of this data type
    * @type String
    */
   this.getParams = function() {
      return params;
   };

   /** @ignore */
   this.toString = function() {
      return "[DataType " +
             " CODE: " + code +
             ", SQL: " + sqlType +
             ", PARAMS: " + params + "]";
   };

   return this;
};

/**
 * Returns true if values for this data type should be surrounded
 * by (single) quotes.
 * @returns True if values for this data type should be surrounded
 * by quotes, false if not
 * @type Boolean
 */
jala.db.DataType.prototype.needsQuotes = function() {
   switch (this.getType()) {
      case java.sql.Types.CHAR:
      case java.sql.Types.VARCHAR:
      case java.sql.Types.LONGVARCHAR:
      case java.sql.Types.BINARY:
      case java.sql.Types.VARBINARY:
      case java.sql.Types.LONGVARBINARY:
      case java.sql.Types.DATE:
      case java.sql.Types.TIME:
      case java.sql.Types.TIMESTAMP:
         return true;
      default:
         return false;
   }
};



/***********************************
 ***   R A M   D A T A B A S E   ***
 ***********************************/


/**
 * Returns a newly created RamDatabase instance.
 * @class Instances of this class represent an in-memory sql database.
 * <br /><strong>Important:</strong> You need the h2.jar in directory "lib/ext"
 * of your helma installation for this library to work, which you can get
 * at http://www.h2database.com/.
 * @param {String} name The name of the database. If not given a private
 * un-named database is created, that can only be accessed through this instance
 * of jala.db.RamDatabase
 * @param {String} username Optional username (defaults to "sa"). This username
 * is used when creating the database, so the same should be used when
 * creating subsequent instances of jala.db.RamDatabase pointing to a named
 * database.
 * @param {String} password Optional password (defaults to "").
 * @returns A newly created instance of RamDatabase
 * @constructor
 */
jala.db.RamDatabase = function(name, username, password) {

   /**
    * Returns the name of the database
    * @returns The name of the database
    * @type String
    */
   this.getName = function() {
      return name;
   };

   /**
    * Returns the username of this database
    * @returns The username of this database
    * @type String
    */
   this.getUsername = function() {
      return username || "sa";
   };

   /**
    * Returns the password of this database
    * @returns The password of this database
    * @type String
    */
   this.getPassword = function() {
      return password || "";
   };

   return;
};

/** @ignore */
jala.db.RamDatabase.prototype.toString = function() {
   return "[Jala RamDatabase " + this.getName() + "]";
}

/**
 * Returns the JDBC Url to connect to this database
 * @param {Object} props Optional connection properties to add
 * @returns The JDBC url to use for connecting to this database
 * @type String
 */
jala.db.RamDatabase.prototype.getUrl = function(props) {
   var url = "jdbc:h2:" + this.getDatabasePath();
   if (props != null) {
      url += jala.db.getPropertyString(props);
   }
   return url;
};

/**
 * Returns the path of this database, which is used by jala.db.Server
 * when adding the database to its set of hosted databases.
 * @returns The path of this database within a server instance
 * @type String
 * @private
 */
jala.db.RamDatabase.prototype.getDatabasePath = function() {
   return "mem:" + this.getName();
}

/**
 * Returns a properties object containing the connection properties
 * for this database.
 * @param {Object} props An optional parameter object containing
 * connection properties to add to the connection Url.
 * @returns A properties object containing the connection properties
 * @type helma.util.ResourceProperties
 */
jala.db.RamDatabase.prototype.getProperties = function(props) {
   var name = this.getName();
   var rp = new Packages.helma.util.ResourceProperties();
   rp.put(name + ".url", this.getUrl(props));
   rp.put(name + ".driver", "org.h2.Driver");
   rp.put(name + ".user", this.getUsername());
   rp.put(name + ".password", this.getPassword());
   return rp;
};

/**
 * Returns a connection to this database
 * @param {Object} An optional parameter object containing connection
 * properties to add to the connection Url.
 * @returns A connection to this database
 * @type helma.Database
 */
jala.db.RamDatabase.prototype.getConnection = function(props) {
   var name = this.getName();
   var rp = this.getProperties(props);
   var dbSource = new Packages.helma.objectmodel.db.DbSource(name, rp);
   return new helma.Database(dbSource);
};

/**
 * Stops this in-process database by issueing a "SHUTDOWN" sql command.
 */
jala.db.RamDatabase.prototype.shutdown = function() {
   var conn = this.getConnection();
   conn.execute("SHUTDOWN");
   return;
};

/**
 * Creates a table in this database.
 * @param {String} name The name of the table
 * @param {Array} columns The columns to create in the table. Each column
 * must be described using an object containing the following properties:
 * <ul>
 * <li>name (String): The name of the column</li>
 * <li>type (Number): The type of the column as defined in java.sql.Types</li>
 * <li>nullable (Boolean): If true the column may contain null values (optional, defaults to true)</li>
 * <li>length (Number): The maximum length of the column (optional)</li>
 * <li>precision (Number): The precision to use (optional)</li>
 * <li>unique (Boolean): If true the column may only contain unique values (optional, defaults to false)</li>
 * <li>default (Object): The default value to use (optional)</li>
 * </ul>
 * @param {String} primaryKey The name of the column that contains
 * the primary key
 * @private
 */
jala.db.RamDatabase.prototype.createTable = function(tableName, columns, primaryKey) {
   res.push();
   res.write("CREATE TABLE ");
   res.write(tableName);
   res.write(" (");
   var column, dataType, params;
   for (var i=0;i<columns.length;i++) {
      column = columns[i];
      res.write(column.name);
      res.write(" ");
      dataType = this.getDataType(column.type);
      if (!dataType) {
         throw "Unable to determine data type, code: " + column.type;
      }
      res.write(dataType.getTypeName());
      if ((params = dataType.getParams()) != null) {
         var arr = [];
         switch (params.toLowerCase()) {
            case "length":
               if (column.length) {
                  arr.push(column.length);
               }
               break;
            case "precision":
               if (column.precision) {
                  arr.push(column.precision);
               }
               break;
            case "precision,scale":
               if (column.precision) {
                  arr.push(column.precision);
               }
               if (column.scale) {
                  arr.push(column.scale);
               }
               break;
         }
         if (arr.length > 0) {
            res.write("(");
            res.write(arr.join(","));
            res.write(")");
         }
      }
      if (column["default"]) {
         res.write(" DEFAULT ");
         if (dataType.needsQuotes() === true) {
            res.write("'");
            res.write(column["default"]);
            res.write("'");
         } else {
            res.write(column["default"]);
         }
      }
      if (column.nullable === false) {
         res.write(" NOT NULL");
      }
      if (i < columns.length - 1) {
         res.write(", ");
      }
   }
   if (primaryKey != null) {
      res.write(", PRIMARY KEY (");
      if (primaryKey instanceof Array) {
         res.write(primaryKey.join(", "));
      } else {
         res.write(primaryKey);
      }
      res.write(")");
   }
   res.write(")");
   var sql = res.pop();
   try {
      var conn = this.getConnection();
      conn.execute(sql);
      app.logger.info("Successfully created table " + tableName);
      app.logger.debug("Sql statement used: " + sql);
      return true;
   } catch (e) {
      app.logger.error("Unable to create table " + tableName + ", reason: " + e);
      return false;
   }
};

/**
 * Drops the table with the given name
 * @param {String} tableName The name of the table
 * @returns True if the table was successfully dropped, false otherwise
 * @type Boolean
 */
jala.db.RamDatabase.prototype.dropTable = function(tableName) {
   var conn = this.getConnection();
   var sql = "DROP TABLE " + tableName;
   conn.execute(sql);
   return;
};

/**
 * Returns true if the table exists already in the database
 * @param {String} name The name of the table
 * @returns True if the table exists, false otherwise
 * @type Boolean
 */
jala.db.RamDatabase.prototype.tableExists = function(name) {
   var conn = this.getConnection().getConnection();
   var meta = conn.getMetaData();
   var t = meta.getTables(null, "PUBLIC", "%", null);
   var tableName;
   try {
      while (t.next()) {
         tableName = t.getString(3).toUpperCase();
         if (tableName.toLowerCase() === name.toLowerCase()) {
            return true;
         }
      }
      return false;
   } finally {
      if (t != null) {
         t.close();
      }
   }
};

/**
 * Copies all tables in the database passed as argument into this embedded database.
 * If any of the tables already exists in this database, they will be removed before
 * re-created. Please mind that this method ignores any indexes in the source database,
 * but respects the primary key settings.
 * @param {helma.Database} database The database to copy the tables from
 * @param {Array} tables An optional array containing the names of the tables to copy.
 * If not given all tables are copied
 */
jala.db.RamDatabase.prototype.copyTables = function(database, tables) {
   // retrieve the metadata for all tables in this schema
   var conn = null;
   try {
      conn = database.getConnection();
      var dbMetadata = conn.getMetaData();
      if (tables === null || tables === undefined) {
         // no tables specified, so copy all available
         tables = jala.db.metadata.getTableNames(dbMetadata);
      }

      for (let tableName of tables) {
         // drop the table if it exists
         if (this.tableExists(tableName)) {
            this.dropTable(tableName);
         }
         // retrieve the table metadata and create the table
         var metadata = jala.db.metadata.getTables(dbMetadata, tableName);
         if (metadata !== null && metadata.length > 0) {
            this.createTable(metadata[0].name, metadata[0].columns, metadata[0].keys);
         }
      }
   } finally {
      if (conn != null) {
         conn.close();
      }
   }
   return;
};

/**
 * Returns an array containing all available data types.
 * @returns All available data types
 * @type Array
 * @see jala.db.DataType
 * @private
 */
jala.db.RamDatabase.prototype.getDataTypes = function() {
   // data types are cached for performance reasons
   if (!arguments.callee.cache) {
      // java.sql data types
      arguments.callee.cache = [];
      var con = this.getConnection().getConnection();
      var meta = con.getMetaData();
      var rs = meta.getTypeInfo();
      var code, name, params;
      while (rs.next()) {
         code = rs.getInt("DATA_TYPE");
         name = rs.getString("TYPE_NAME");
         params = rs.getString("CREATE_PARAMS");
         arguments.callee.cache.push(new jala.db.DataType(code, name, params));
      }
   }
   return arguments.callee.cache;
};

/**
 * Returns the data type for the code passed as argument
 * @param {Number} type The type code as defined in java.sql.Types
 * @returns The data type object for the code
 * @type jala.db.DataType
 * @private
 */
jala.db.RamDatabase.prototype.getDataType = function(type) {
   var types = this.getDataTypes();
   var dataType;
   for (var i=0;i<types.length;i++) {
      dataType = types[i];
      if (dataType.getType() === type) {
         return dataType;
      }
   }
   return null;
};

/**
 * Runs the script file passed as argument in the context of this database.
 * Use this method to eg. create and/or populate a database.
 * @param {helma.File} file The script file to run
 * @param {Object} props Optional object containing connection properties
 * @param {String} charset Optional character set to use (defaults to "UTF-8")
 * @param {Boolean} continueOnError Optional flag indicating whether to continue
 * on error or not (defaults to false)
 * @returns True in case the script was executed successfully, false otherwise
 * @type Boolean
 */
jala.db.RamDatabase.prototype.runScript = function(file, props, charset, continueOnError) {
   try {
      Packages.org.h2.tools.RunScript.execute(
         this.getUrl(props),
         this.getUsername(),
         this.getPassword(),
         file.getAbsolutePath(),
         charset || "UTF-8",
         continueOnError === true
      );
      app.logger.info("jala.db: successfully executed SQL script '" +
                      file.getAbsolutePath() + "'");
   } catch (e) {
      app.logger.error("jala.db: executing SQL script failed, reason: " + e);
      return false;
   }
   return true;
};

/**
 * Dumps the database schema and data into a file
 * @param {helma.File} file The file where the database dump will be
 * @param {Object} props Optional object containing connection properties
 * @returns True in case the database was successfully dumped, false otherwise
 * @type Boolean
 */
jala.db.RamDatabase.prototype.dump = function(file, props) {
   try {
      Packages.org.h2.tools.Script.execute(
         this.getUrl(props),
         this.getUsername(),
         this.getPassword(),
         file.getAbsolutePath()
      );
   } catch (e) {
      app.logger.error("jala.db: Unable to dump database to '" + file.getAbsolutePath() +
            ", reason: " + e.toString());
      return false;
   }
   return true;
};


/*************************************
 ***   F I L E   D A T A B A S E   ***
 *************************************/


/**
 * Returns a newly created instance of FileDatabase.
 * @class Instances of this class represent a file based in-process database
 * <br /><strong>Important:</strong> You need the h2.jar in directory "lib/ext"
 * of your helma installation for this library to work, which you can get
 * at http://www.h2database.com/.
 * @param {String} name The name of the database. This name is used as
 * prefix for all database files
 * @param {helma.File} directory The directory where the database files
 * should be stored in.
 * @param {String} username Optional username (defaults to "sa"). This username
 * is used when creating the database, so the same should be used when
 * creating subsequent instances of jala.db.FileDatabase pointing to the
 * same database
 * @param {String} password Optional password (defaults to "").
 * @returns A newly created FileDatabase instance
 * @constructor
 */
jala.db.FileDatabase = function(name, directory, username, password) {

   /**
    * Returns the name of the database. This name is used as prefix
    * for all files of this database in the specified directory
    * @returns The name of the database
    * @type String
    */
   this.getName = function() {
      return name;
   };

   /**
    * Returns the directory where the database files are stored.
    * @returns The directory where this database is stored.
    * @type helma.File
    */
   this.getDirectory = function() {
      return directory;
   };

   /**
    * Returns the username of this database
    * @returns The username of this database
    * @type String
    */
   this.getUsername = function() {
      return username || "sa";
   };

   /**
    * Returns the password of this database
    * @returns The password of this database
    * @type String
    */
   this.getPassword = function() {
      return password || "";
   };

   if (!name || typeof(name) != "string" ||
             !directory || !(directory instanceof helma.File)) {
      throw "jala.db.FileDatabase: Missing or invalid arguments"
   } else if (!directory.exists()) {
      throw "jala.db.FileDatabase: directory '" + directory + "' does not exist";
   }

   return this;
};
// extend RamDatabase
jala.db.FileDatabase.prototype = new jala.db.RamDatabase();

/** @ignore */
jala.db.FileDatabase.prototype.toString = function() {
   return "[Jala FileDatabase '" + this.getName() + "' in "
          + this.getDirectory().getAbsolutePath() + "]";
};

/**
 * Returns the path of this database, which is used when adding
 * the database to a server instance.
 * @returns The path of this database within a server instance
 * @type String
 * @private
 */
jala.db.FileDatabase.prototype.getDatabasePath = function() {
   var directory = new helma.File(this.getDirectory(), this.getName());
   return "file:" + directory.getAbsolutePath();
};

/**
 * Deletes all files of this database on disk. Note that this also
 * closes the database before removing it.
 * @returns True in case the database was removed successfully, false otherwise
 * @type Boolean
 */
jala.db.FileDatabase.prototype.remove = function() {
   var directory = this.getDirectory();
   try {
      // shut down the database
      this.shutdown();
      Packages.org.h2.tools.DeleteDbFiles.execute(
         directory.getAbsolutePath(),
         this.getName(),
         false
      );
   } catch(e) {
      app.logger.error("jala.db: Unable to delete database in " +
            directory.getAbsolutePath() + ", reason: " + e);
      return false;
   }
   return true;
};

/**
 * Creates a backup of this database, using the file passed as argument. The
 * result will be a zipped file containing the database files
 * @param {helma.File} file The file to write the backup to
 * @returns True if the database backup was created successfully, false otherwise
 * @type Boolean
 */
jala.db.FileDatabase.prototype.backup = function(file) {
   try {
      Packages.org.h2.tools.Backup.execute(
         file.getAbsolutePath(),
         this.getDirectory().getAbsolutePath(),
         this.getName(),
         false
      );
   } catch (e) {
      app.logger.error("jala.db: Unable to backup database to '" +
                       file.getAbsolutePath() + ", reason: " + e);
      return false;
   }
   return true;
};

/**
 * Restores this database using a backup on disk.
 * @param {helma.File} backupFile The backup file to use for restore
 * @returns True if the database was successfully restored, false otherwise
 * @type Boolean
 */
jala.db.FileDatabase.prototype.restore = function(backupFile) {
   try {
      Packages.org.h2.tools.Restore.execute(
         backupFile.getAbsolutePath(),
         this.getDirectory().getAbsolutePath(),
         this.getName(),
         false
      );
   } catch (e) {
      app.logger.error("jala.db: Unable to restore database using '" +
                       backupFile.getAbsolutePath() + ", reason: " + e);
      return false;
   }
   return true;
};