# 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;
    };