You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@manifoldcf.apache.org by kw...@apache.org on 2010/10/09 21:42:06 UTC

svn commit: r1006222 - in /incubator/lcf/trunk/modules: build.xml framework/build.xml framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java lib/hsqldb.jar

Author: kwright
Date: Sat Oct  9 19:42:06 2010
New Revision: 1006222

URL: http://svn.apache.org/viewvc?rev=1006222&view=rev
Log:
Add HSQLDB support; not yet hooked up.

Added:
    incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java   (with props)
    incubator/lcf/trunk/modules/lib/hsqldb.jar   (with props)
Modified:
    incubator/lcf/trunk/modules/build.xml
    incubator/lcf/trunk/modules/framework/build.xml

Modified: incubator/lcf/trunk/modules/build.xml
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/build.xml?rev=1006222&r1=1006221&r2=1006222&view=diff
==============================================================================
--- incubator/lcf/trunk/modules/build.xml (original)
+++ incubator/lcf/trunk/modules/build.xml Sat Oct  9 19:42:06 2010
@@ -67,7 +67,6 @@
                 <include name="commons-io*.jar"/>
                 <include name="commons-logging*.jar"/>
                 <include name="eclipse*.jar"/>
-                <include name="derby*.jar"/>
                 <include name="jasper*.jar"/>
                 <include name="jetty*.jar"/>
                 <include name="jdbcpool-0.99.jar"/>
@@ -85,6 +84,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <ant dir="framework"/>
@@ -671,7 +671,7 @@
         <mkdir dir="build/example"/>
         <manifest file="build/example/manifest">
             <attribute name="Main-Class" value="org.apache.manifoldcf.jettyrunner.ManifoldCFJettyRunner"/>
-            <attribute name="Class-Path" value="lib/commons-codec.jar lib/commons-collections.jar lib/commons-el.jar lib/commons-fileupload.jar lib/commons-httpclient-mcf.jar lib/commons-io.jar lib/commons-logging.jar lib/derbyclient.jar lib/derby.jar lib/derbyLocale_cs.jar lib/derbyLocale_de_DE.jar lib/derbyLocale_es.jar lib/derbyLocale_fr.jar lib/derbyLocale_hu.jar lib/derbyLocale_it.jar lib/derbyLocale_ja_JP.jar lib/derbyLocale_ko_KR.jar lib/derbyLocale_pl.jar lib/derbyLocale_pt_BR.jar lib/derbyLocale_ru.jar lib/derbyLocale_zh_CN.jar lib/derbyLocale_zh_TW.jar lib/derbynet.jar lib/derbyrun.jar lib/derbytools.jar lib/eclipse-ecj.jar lib/jasper-6.0.24.jar lib/jasper-el-6.0.24.jar lib/jdbcpool-0.99.jar lib/jetty-6.1.22.jar lib/jetty-util-6.1.22.jar lib/jsp-api-2.1-glassfish-9.1.1.B60.25.p2.jar lib/json.jar lib/mcf-agents.jar lib/mcf-core.jar lib/mcf-jetty-runner.jar lib/mcf-pull-agent.jar lib/mcf-ui-core.jar lib/log4j-1.2.jar lib/postgresql.jar lib/serializer.jar lib/servlet-
 api-2.5-20081211.jar lib/tomcat-juli-6.0.24.jar lib/xalan2.jar lib/xercesImpl-mcf.jar lib/xml-apis.jar"/>
+            <attribute name="Class-Path" value="lib/commons-codec.jar lib/commons-collections.jar lib/commons-el.jar lib/commons-fileupload.jar lib/commons-httpclient-mcf.jar lib/commons-io.jar lib/commons-logging.jar lib/hsqldb.jar lib/derbyclient.jar lib/derby.jar lib/derbyLocale_cs.jar lib/derbyLocale_de_DE.jar lib/derbyLocale_es.jar lib/derbyLocale_fr.jar lib/derbyLocale_hu.jar lib/derbyLocale_it.jar lib/derbyLocale_ja_JP.jar lib/derbyLocale_ko_KR.jar lib/derbyLocale_pl.jar lib/derbyLocale_pt_BR.jar lib/derbyLocale_ru.jar lib/derbyLocale_zh_CN.jar lib/derbyLocale_zh_TW.jar lib/derbynet.jar lib/derbyrun.jar lib/derbytools.jar lib/eclipse-ecj.jar lib/jasper-6.0.24.jar lib/jasper-el-6.0.24.jar lib/jdbcpool-0.99.jar lib/jetty-6.1.22.jar lib/jetty-util-6.1.22.jar lib/jsp-api-2.1-glassfish-9.1.1.B60.25.p2.jar lib/json.jar lib/mcf-agents.jar lib/mcf-core.jar lib/mcf-jetty-runner.jar lib/mcf-pull-agent.jar lib/mcf-ui-core.jar lib/log4j-1.2.jar lib/postgresql.jar lib/serializer.j
 ar lib/servlet-api-2.5-20081211.jar lib/tomcat-juli-6.0.24.jar lib/xalan2.jar lib/xercesImpl-mcf.jar lib/xml-apis.jar"/>
         </manifest>
         <jar destfile="dist/example/start.jar" manifest="build/example/manifest"/>
     </target>

Modified: incubator/lcf/trunk/modules/framework/build.xml
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/framework/build.xml?rev=1006222&r1=1006221&r2=1006222&view=diff
==============================================================================
--- incubator/lcf/trunk/modules/framework/build.xml (original)
+++ incubator/lcf/trunk/modules/framework/build.xml Sat Oct  9 19:42:06 2010
@@ -197,6 +197,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <copy todir="build/webapp/authority-service/WEB-INF/lib">
@@ -231,6 +232,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <copy todir="build/webapp/api-service/WEB-INF/lib">
@@ -271,6 +273,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <copy todir="build/webapp/crawler-ui/WEB-INF/lib">
@@ -330,6 +333,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <copy todir="dist/processes/jar">
@@ -374,6 +378,7 @@
             <fileset dir="lib">
                 <include name="postgresql*.jar"/>
                 <include name="derby*.jar"/>
+                <include name="hsqldb*.jar"/>
             </fileset>
         </copy>
         <copy todir="dist/example/lib">

Added: incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java?rev=1006222&view=auto
==============================================================================
--- incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java (added)
+++ incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java Sat Oct  9 19:42:06 2010
@@ -0,0 +1,1103 @@
+/* $Id$ */
+
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package org.apache.manifoldcf.core.database;
+
+import org.apache.manifoldcf.core.interfaces.*;
+import org.apache.manifoldcf.core.system.ManifoldCF;
+import org.apache.manifoldcf.core.system.Logging;
+import java.util.*;
+import java.io.*;
+import java.sql.*;
+
+/** This is the HSQLDB implementation of the IDBInterface class.
+*/
+public class DBInterfaceHSQLDB extends Database implements IDBInterface
+{
+  public static final String _rcsid = "@(#)$Id$";
+
+  private static final String _url = "jdbc:hsqldb:file:";
+  private static final String _driver = "org.hsqldb.jdbcDriver";
+
+  public final static String databasePathProperty = "org.apache.manifoldcf.hsqldbdatabasepath";
+
+  protected String cacheKey;
+  // Postgresql serializable transactions are broken in that transactions that occur within them do not in fact work properly.
+  // So, once we enter the serializable realm, STOP any additional transactions from doing anything at all.
+  protected int serializableDepth = 0;
+
+  public DBInterfaceHSQLDB(IThreadContext tc, String databaseName, String userName, String password)
+    throws ManifoldCFException
+  {
+    super(tc,_url+getFullDatabasePath(databaseName),_driver,getFullDatabasePath(databaseName),userName,password);
+    cacheKey = CacheKeyFactory.makeDatabaseKey(this.databaseName);
+    this.userName = userName;
+    this.password = password;
+  }
+
+  protected static String getFullDatabasePath(String databaseName)
+    throws ManifoldCFException
+  {
+    File path = ManifoldCF.getFileProperty(databasePathProperty);
+    if (path == null)
+      throw new ManifoldCFException("HSQLDB database requires '"+databasePathProperty+"' property, containing a relative path");
+    String pathString = path.toString().replace("\\\\","/");
+    if (!pathString.endsWith("/"))
+      pathString = pathString + "/";
+    return pathString + databaseName;
+  }
+
+  /** Initialize.  This method is called once per JVM instance, in order to set up
+  * database communication.
+  */
+  public void openDatabase()
+    throws ManifoldCFException
+  {
+    try
+    {
+      // Force a load of the appropriate JDBC driver
+      Class.forName(_driver).newInstance();
+      DriverManager.getConnection(_url+databaseName,userName,password).close();
+    }
+    catch (Exception e)
+    {
+      throw new ManifoldCFException(e.getMessage(),e,ManifoldCFException.SETUP_ERROR);
+    }
+  }
+  
+  /** Uninitialize.  This method is called during JVM shutdown, in order to close
+  * all database communication.
+  */
+  public void closeDatabase()
+    throws ManifoldCFException
+  {
+    try
+    {
+      // Force a load of the appropriate JDBC driver
+      Class.forName(_driver).newInstance();
+    }
+    catch (Exception e)
+    {
+      throw new ManifoldCFException(e.getMessage(),e);
+    }
+
+    // For the shutdown itself, eat the exception
+    try
+    {
+      DriverManager.getConnection(_url+databaseName+";shutdown=true",userName,password).close();
+    }
+    catch (Exception e)
+    {
+      // Never any exception!
+    }
+  }
+
+  /** Get the database general cache key.
+  *@return the general cache key for the database.
+  */
+  public String getDatabaseCacheKey()
+  {
+    return cacheKey;
+  }
+
+  /** Perform a table lock operation.
+  *@param tableName is the name of the table.
+  */
+  public void performLock(String tableName)
+    throws ManifoldCFException
+  {
+    performModification("LOCK TABLE "+tableName+" IN EXCLUSIVE MODE",null,null);
+  }
+
+  /** Perform an insert operation.
+  *@param tableName is the name of the table.
+  *@param invalidateKeys are the cache keys that should be
+  * invalidated.
+  *@param parameterMap is the map of column name/values to write.
+  */
+  public void performInsert(String tableName, Map parameterMap, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    ArrayList paramArray = new ArrayList();
+
+    StringBuffer bf = new StringBuffer();
+    bf.append("INSERT INTO ");
+    bf.append(tableName);
+    bf.append(" (") ;
+
+    StringBuffer values = new StringBuffer(" VALUES (");
+
+    // loop for cols
+    Iterator it = parameterMap.entrySet().iterator();
+    boolean first = true;
+    while (it.hasNext())
+    {
+      Map.Entry e = (Map.Entry)it.next();
+      String key = (String)e.getKey();
+
+      Object o = e.getValue();
+      if (o != null)
+      {
+        paramArray.add(o);
+
+        if (!first)
+        {
+          bf.append(',');
+          values.append(',');
+        }
+        bf.append(key);
+        values.append('?');
+
+        first = false;
+      }
+    }
+
+    bf.append(')');
+    values.append(')');
+    bf.append(values);
+
+    // Do the modification
+    performModification(bf.toString(),paramArray,invalidateKeys);
+  }
+
+
+  /** Perform an update operation.
+  *@param tableName is the name of the table.
+  *@param invalidateKeys are the cache keys that should be invalidated.
+  *@param parameterMap is the map of column name/values to write.
+  *@param whereClause is the where clause describing the match (including the WHERE), or null if none.
+  *@param whereParameters are the parameters that come with the where clause, if any.
+  */
+  public void performUpdate(String tableName, Map parameterMap, String whereClause, ArrayList whereParameters, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    ArrayList paramArray = new ArrayList();
+
+    StringBuffer bf = new StringBuffer();
+    bf.append("UPDATE ");
+    bf.append(tableName);
+    bf.append(" SET ") ;
+
+    // loop for parameters
+    Iterator it = parameterMap.entrySet().iterator();
+    boolean first = true;
+    while (it.hasNext())
+    {
+      Map.Entry e = (Map.Entry)it.next();
+      String key = (String)e.getKey();
+
+      Object o = e.getValue();
+
+      if (!first)
+      {
+        bf.append(',');
+      }
+      bf.append(key);
+      bf.append('=');
+      if (o == null)
+      {
+        bf.append("NULL");
+      }
+      else
+      {
+        bf.append('?');
+        paramArray.add(o);
+      }
+
+      first = false;
+    }
+
+    if (whereClause != null)
+    {
+      bf.append(' ');
+      bf.append(whereClause);
+      if (whereParameters != null)
+      {
+        for (int i = 0; i < whereParameters.size(); i++)
+        {
+          Object value = whereParameters.get(i);
+          paramArray.add(value);
+        }
+      }
+    }
+
+    // Do the modification
+    performModification(bf.toString(),paramArray,invalidateKeys);
+
+  }
+
+
+  /** Perform a delete operation.
+  *@param tableName is the name of the table to delete from.
+  *@param invalidateKeys are the cache keys that should be invalidated.
+  *@param whereClause is the where clause describing the match (including the WHERE), or null if none.
+  *@param whereParameters are the parameters that come with the where clause, if any.
+  */
+  public void performDelete(String tableName, String whereClause, ArrayList whereParameters, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    StringBuffer bf = new StringBuffer();
+    bf.append("DELETE FROM ");
+    bf.append(tableName);
+    if (whereClause != null)
+    {
+      bf.append(' ');
+      bf.append(whereClause);
+    }
+    else
+      whereParameters = null;
+
+    // Do the modification
+    performModification(bf.toString(),whereParameters,invalidateKeys);
+
+  }
+
+  /** Perform a table creation operation.
+  *@param tableName is the name of the table to create.
+  *@param columnMap is the map describing the columns and types.  NOTE that these are abstract
+  * types, which will be mapped to the proper types for the actual database inside this
+  * layer.
+  *@param invalidateKeys are the cache keys that should be invalidated, if any.
+  */
+  public void performCreate(String tableName, Map columnMap, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    StringBuffer queryBuffer = new StringBuffer("CREATE TABLE ");
+    queryBuffer.append(tableName);
+    queryBuffer.append('(');
+    Iterator iter = columnMap.keySet().iterator();
+    boolean first = true;
+    while (iter.hasNext())
+    {
+      String columnName = (String)iter.next();
+      ColumnDescription cd = (ColumnDescription)columnMap.get(columnName);
+      if (!first)
+        queryBuffer.append(',');
+      else
+        first = false;
+      appendDescription(queryBuffer,columnName,cd,false);
+    }
+    queryBuffer.append(')');
+
+    performModification(queryBuffer.toString(),null,invalidateKeys);
+
+  }
+
+  protected static void appendDescription(StringBuffer queryBuffer, String columnName, ColumnDescription cd, boolean forceNull)
+  {
+    queryBuffer.append(columnName);
+    queryBuffer.append(' ');
+    queryBuffer.append(mapType(cd.getTypeString()));
+    if (forceNull || cd.getIsNull())
+      queryBuffer.append(" NULL");
+    else
+      queryBuffer.append(" NOT NULL");
+    if (cd.getIsPrimaryKey())
+      queryBuffer.append(" PRIMARY KEY");
+    if (cd.getReferenceTable() != null)
+    {
+      queryBuffer.append(" REFERENCES ");
+      queryBuffer.append(cd.getReferenceTable());
+      queryBuffer.append('(');
+      queryBuffer.append(cd.getReferenceColumn());
+      queryBuffer.append(") ON DELETE");
+      if (cd.getReferenceCascade())
+        queryBuffer.append(" CASCADE");
+      else
+        queryBuffer.append(" RESTRICT");
+    }
+  }
+
+  /** Perform a table alter operation.
+  *@param tableName is the name of the table to alter.
+  *@param columnMap is the map describing the columns and types to add.  These
+  * are in the same form as for performCreate.
+  *@param columnModifyMap is the map describing the columns to be changed.  The key is the
+  * existing column name, and the value is the new type of the column.  Data will be copied from
+  * the old column to the new.
+  *@param columnDeleteList is the list of column names to delete.
+  *@param invalidateKeys are the cache keys that should be invalidated, if any.
+  */
+  public void performAlter(String tableName, Map columnMap, Map columnModifyMap, ArrayList columnDeleteList,
+    StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    beginTransaction(TRANSACTION_ENCLOSING);
+    try
+    {
+      if (columnDeleteList != null)
+      {
+        int i = 0;
+        while (i < columnDeleteList.size())
+        {
+          String columnName = (String)columnDeleteList.get(i++);
+          performModification("ALTER TABLE "+tableName+" DROP "+columnName+" RESTRICT",null,invalidateKeys);
+        }
+      }
+
+      // Do the modifies.  This involves renaming each column to a temp column, then creating a new one, then copying
+      if (columnModifyMap != null)
+      {
+        Iterator iter = columnModifyMap.keySet().iterator();
+        while (iter.hasNext())
+        {
+          StringBuffer sb;
+          String columnName = (String)iter.next();
+          ColumnDescription cd = (ColumnDescription)columnModifyMap.get(columnName);
+          String renameColumn = "__temp__";
+          sb = new StringBuffer();
+          appendDescription(sb,renameColumn,cd,true);
+          // Rename current column.  This too involves a copy.
+          performModification("ALTER TABLE "+tableName+" ADD "+sb.toString(),null,invalidateKeys);
+          performModification("UPDATE "+tableName+" SET "+renameColumn+"="+columnName,null,invalidateKeys);
+          performModification("ALTER TABLE "+tableName+" DROP "+columnName+" RESTRICT",null,invalidateKeys);
+          // Create new column
+          sb = new StringBuffer();
+          appendDescription(sb,columnName,cd,true);
+          performModification("ALTER TABLE "+tableName+" ADD "+sb.toString(),null,invalidateKeys);
+          // Copy old data to new
+          performModification("UPDATE "+tableName+" SET "+columnName+"="+renameColumn,null,invalidateKeys);
+          // Make the column null, if it needs it
+          if (cd.getIsNull() == false)
+            performModification("ALTER TABLE "+tableName+" ALTER "+columnName+" SET NOT NULL",null,invalidateKeys);
+          // Drop old column
+          performModification("ALTER TABLE "+tableName+" DROP "+renameColumn+" RESTRICT",null,invalidateKeys);
+        }
+      }
+
+      // Now, do the adds
+      if (columnMap != null)
+      {
+        Iterator iter = columnMap.keySet().iterator();
+        while (iter.hasNext())
+        {
+          String columnName = (String)iter.next();
+          ColumnDescription cd = (ColumnDescription)columnMap.get(columnName);
+          StringBuffer sb = new StringBuffer();
+          appendDescription(sb,columnName,cd,false);
+          performModification("ALTER TABLE "+tableName+" ADD "+sb.toString(),null,invalidateKeys);
+        }
+      }
+    }
+    catch (ManifoldCFException e)
+    {
+      signalRollback();
+      throw e;
+    }
+    catch (Error e)
+    {
+      signalRollback();
+      throw e;
+    }
+    finally
+    {
+      endTransaction();
+    }
+
+  }
+
+
+  /** Map a standard type into a postgresql type.
+  *@param inputType is the input type.
+  *@return the output type.
+  */
+  protected static String mapType(String inputType)
+  {
+    if (inputType.equalsIgnoreCase("longtext"))
+      return "clob";
+    return inputType;
+  }
+
+  /** Add an index to a table.
+  *@param tableName is the name of the table to add the index for.
+  *@param unique is a boolean that if true describes a unique index.
+  *@param columnList is the list of columns that need to be included
+  * in the index, in order.
+  */
+  public void addTableIndex(String tableName, boolean unique, ArrayList columnList)
+    throws ManifoldCFException
+  {
+    String[] columns = new String[columnList.size()];
+    int i = 0;
+    while (i < columns.length)
+    {
+      columns[i] = (String)columnList.get(i);
+      i++;
+    }
+    performAddIndex(null,tableName,new IndexDescription(unique,columns));
+  }
+
+  /** Add an index to a table.
+  *@param tableName is the name of the table to add the index for.
+  *@param indexName is the optional name of the table index.  If null, a name will be chosen automatically.
+  *@param description is the index description.
+  */
+  public void performAddIndex(String indexName, String tableName, IndexDescription description)
+    throws ManifoldCFException
+  {
+    String[] columnNames = description.getColumnNames();
+    if (columnNames.length == 0)
+      return;
+
+    if (indexName == null)
+      // Build an index name
+      indexName = "I"+IDFactory.make(context);
+    StringBuffer queryBuffer = new StringBuffer("CREATE ");
+    if (description.getIsUnique())
+      queryBuffer.append("UNIQUE ");
+    queryBuffer.append("INDEX ");
+    queryBuffer.append(indexName);
+    queryBuffer.append(" ON ");
+    queryBuffer.append(tableName);
+    queryBuffer.append(" (");
+    int i = 0;
+    while (i < columnNames.length)
+    {
+      String colName = columnNames[i];
+      if (i > 0)
+        queryBuffer.append(',');
+      queryBuffer.append(colName);
+      i++;
+    }
+    queryBuffer.append(')');
+
+    performModification(queryBuffer.toString(),null,null);
+  }
+
+  /** Remove an index.
+  *@param indexName is the name of the index to remove.
+  */
+  public void performRemoveIndex(String indexName)
+    throws ManifoldCFException
+  {
+    performModification("DROP INDEX "+indexName,null,null);
+  }
+
+  /** Analyze a table.
+  *@param tableName is the name of the table to analyze/calculate statistics for.
+  */
+  public void analyzeTable(String tableName)
+    throws ManifoldCFException
+  {
+    // Nothing to do.
+  }
+
+  /** Reindex a table.
+  *@param tableName is the name of the table to rebuild indexes for.
+  */
+  public void reindexTable(String tableName)
+    throws ManifoldCFException
+  {
+    // Nothing to do.
+  }
+
+  /** Perform a table drop operation.
+  *@param tableName is the name of the table to drop.
+  *@param invalidateKeys are the cache keys that should be invalidated, if any.
+  */
+  public void performDrop(String tableName, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    performModification("DROP TABLE "+tableName,null,invalidateKeys);
+  }
+
+  /** Create user and database.
+  *@param adminUserName is the admin user name.
+  *@param adminPassword is the admin password.
+  *@param invalidateKeys are the cache keys that should be invalidated, if any.
+  */
+  public void createUserAndDatabase(String adminUserName, String adminPassword, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    String fullDatabasePath = getFullDatabasePath(databaseName);
+    // Create a connection to the master database, using the credentials supplied
+    Database masterDatabase = new Database(context,_url+fullDatabasePath,_driver,fullDatabasePath,adminUserName,adminPassword);
+    try
+    {
+      // Create user
+      ArrayList params = new ArrayList();
+      params.add(userName);
+      IResultSet set = masterDatabase.executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYSTEM_USERS WHERE USER_NAME=?",params,null,null,null,true,-1,null,null);
+      if (set.getRowCount() == 0)
+      {
+	masterDatabase.executeQuery("CREATE USER \""+userName+"\" PASSWORD "+
+	  quoteSQLString(password),null,null,invalidateKeys,null,false,0,null,null);
+      }
+      
+    }
+    catch (ManifoldCFException e)
+    {
+      throw reinterpretException(e);
+    }
+  }
+
+  /** Drop user and database.
+  *@param adminUserName is the admin user name.
+  *@param adminPassword is the admin password.
+  *@param invalidateKeys are the cache keys that should be invalidated, if any.
+  */
+  public void dropUserAndDatabase(String adminUserName, String adminPassword, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    File f = new File(databaseName);
+    if (f.exists())
+    {
+      // Try to guarantee that all connections are discarded before we shut the database down.  Otherwise we get pool warnings from bitstream.
+      ConnectionFactory.releaseAll();
+      // Make sure database is shut down.
+      closeDatabase();
+      // Now, it's OK to delete
+      recursiveDelete(f);
+    }
+  }
+  
+  protected static void recursiveDelete(File f)
+  {
+    File[] files = f.listFiles();
+    if (files != null)
+    {
+      int i = 0;
+      while (i < files.length)
+      {
+        File newf = files[i++];
+        if (newf.isDirectory())
+          recursiveDelete(newf);
+        else
+          newf.delete();
+      }
+    }
+    if (!f.delete())
+      System.out.println("Failed to delete file "+f.toString());
+  }
+
+  /** Reinterpret an exception tossed by the database layer.  We need to disambiguate the various kinds of exception that
+  * should be thrown.
+  *@param theException is the exception to reinterpret
+  *@return the reinterpreted exception to throw.
+  */
+  protected ManifoldCFException reinterpretException(ManifoldCFException theException)
+  {
+    if (Logging.db.isDebugEnabled())
+      Logging.db.debug("Reinterpreting exception '"+theException.getMessage()+"'.  The exception type is "+Integer.toString(theException.getErrorCode()));
+    if (theException.getErrorCode() != ManifoldCFException.DATABASE_CONNECTION_ERROR)
+      return theException;
+    Throwable e = theException.getCause();
+    if (!(e instanceof java.sql.SQLException))
+      return theException;
+    if (Logging.db.isDebugEnabled())
+      Logging.db.debug("Exception "+theException.getMessage()+" is possibly a transaction abort signal");
+    java.sql.SQLException sqlException = (java.sql.SQLException)e;
+    String message = sqlException.getMessage();
+    String sqlState = sqlException.getSQLState();
+    // Could not serialize
+    if (sqlState != null && sqlState.equals("40001"))
+      return new ManifoldCFException(message,e,ManifoldCFException.DATABASE_TRANSACTION_ABORT);
+    // Deadlock detected
+    if (sqlState != null && sqlState.equals("40P01"))
+      return new ManifoldCFException(message,e,ManifoldCFException.DATABASE_TRANSACTION_ABORT);
+    // Note well: We also have to treat 'duplicate key' as a transaction abort, since this is what you get when two threads attempt to
+    // insert the same row.  (Everything only works, then, as long as there is a unique constraint corresponding to every bad insert that
+    // one could make.)
+    if (sqlState != null && sqlState.equals("23505"))
+      return new ManifoldCFException(message,e,ManifoldCFException.DATABASE_TRANSACTION_ABORT);
+    if (Logging.db.isDebugEnabled())
+      Logging.db.debug("Exception "+theException.getMessage()+" is NOT a transaction abort signal");
+    return theException;
+  }
+
+  /** Perform a general database modification query.
+  *@param query is the query string.
+  *@param params are the parameterized values, if needed.
+  *@param invalidateKeys are the cache keys to invalidate.
+  */
+  public void performModification(String query, ArrayList params, StringSet invalidateKeys)
+    throws ManifoldCFException
+  {
+    try
+    {
+      executeQuery(query,params,null,invalidateKeys,null,false,0,null,null);
+    }
+    catch (ManifoldCFException e)
+    {
+      throw reinterpretException(e);
+    }
+  }
+
+  /** Get a table's schema.
+  *@param tableName is the name of the table.
+  *@param cacheKeys are the keys against which to cache the query, or null.
+  *@param queryClass is the name of the query class, or null.
+  *@return a map of column names and ColumnDescription objects, describing the schema, or null if the
+  * table doesn't exist.
+  */
+  public Map getTableSchema(String tableName, StringSet cacheKeys, String queryClass)
+    throws ManifoldCFException
+  {
+    StringBuffer query = new StringBuffer();
+    ArrayList list = new ArrayList();
+    list.add(tableName.toUpperCase());
+    query.append("SELECT column_name, is_nullable, data_type, character_maximum_length ")
+      .append("FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema='PUBLIC' AND table_name=?");
+    IResultSet set = performQuery(query.toString(),list,cacheKeys,queryClass);
+    if (set.getRowCount() == 0)
+      return null;
+
+    query = new StringBuffer();
+    query.append("SELECT column_name ")
+      .append("FROM INFORMATION_SCHEMA.SYSTEM_PRIMARYKEYS WHERE table_schem='PUBLIC' AND table_name=?");
+    IResultSet primarySet = performQuery(query.toString(),list,cacheKeys,queryClass);
+    String primaryKey = "";
+    if (primarySet.getRowCount() != 0)
+      primaryKey = primarySet.getRow(0).getValue("column_name").toString();
+    
+    // Digest the result
+    HashMap rval = new HashMap();
+    int i = 0;
+    while (i < set.getRowCount())
+    {
+      IResultRow row = set.getRow(i++);
+      String fieldName = (String)row.getValue("column_name");
+      String type = (String)row.getValue("data_type");
+      Long width = (Long)row.getValue("character_maximum_length");
+      String isNullable = (String)row.getValue("is_nullable");
+      boolean isPrimaryKey = primaryKey.equals(fieldName);
+      boolean isNull = isNullable.equals("YES");
+      String dataType;
+      if (type.equals("CHARACTER VARYING"))
+        dataType = "VARCHAR("+width.toString()+")";
+      else if (type.equals("CLOB"))
+        dataType = "LONGVARCHAR";
+      else
+        dataType = type;
+      rval.put(fieldName,new ColumnDescription(type,isPrimaryKey,isNull,null,null,false));
+    }
+
+    return rval;
+  }
+
+  /** Get a table's indexes.
+  *@param tableName is the name of the table.
+  *@param cacheKeys are the keys against which to cache the query, or null.
+  *@param queryClass is the name of the query class, or null.
+  *@return a map of index names and IndexDescription objects, describing the indexes.
+  */
+  public Map getTableIndexes(String tableName, StringSet cacheKeys, String queryClass)
+    throws ManifoldCFException
+  {
+    Map rval = new HashMap();
+
+    String query = "SELECT index_name,column_name,non_unique,ordinal_position FROM INFORMATION_SCHEMA.SYSTEM_INDEXINFO "+
+      "WHERE table_schem='PUBLIC' AND TABLE_NAME=? ORDER BY index_name,ordinal_position ASC";
+    ArrayList list = new ArrayList();
+    list.add(tableName.toUpperCase());
+    IResultSet result = performQuery(query,null,cacheKeys,queryClass);
+    String lastIndexName = null;
+    ArrayList indexColumns = null;
+    boolean isUnique = false;
+    int i = 0;
+    while (i < result.getRowCount())
+    {
+      IResultRow row = result.getRow(i++);
+      String indexName = (String)row.getValue("index_name");
+      String columnName = (String)row.getValue("column_name");
+      String nonUnique = row.getValue("non_unique").toString();
+      
+      if (lastIndexName != null && !lastIndexName.equals(indexName))
+      {
+        addIndex(rval,lastIndexName,isUnique,indexColumns);
+        lastIndexName = null;
+        indexColumns = null;
+        isUnique = false;
+      }
+      
+      if (lastIndexName == null)
+      {
+        lastIndexName = indexName;
+        indexColumns = new ArrayList();
+        isUnique = false;
+      }
+      indexColumns.add(columnName);
+      isUnique = nonUnique.equals("false");
+    }
+    
+    if (lastIndexName != null)
+      addIndex(rval,lastIndexName,isUnique,indexColumns);
+    
+    return rval;
+  }
+
+  protected void addIndex(Map rval, String indexName, boolean isUnique, ArrayList indexColumns)
+  {
+    if (indexName.indexOf("SYS_IDX_SYS_PK") != -1)
+      return;
+    String[] columnNames = new String[indexColumns.size()];
+    int i = 0;
+    while (i < columnNames.length)
+    {
+      columnNames[i] = (String)indexColumns.get(i);
+      i++;
+    }
+    rval.put(indexName,new IndexDescription(isUnique,columnNames));
+  }
+  
+  /** Get a database's tables.
+  *@param cacheKeys are the cache keys for the query, or null.
+  *@param queryClass is the name of the query class, or null.
+  *@return the set of tables.
+  */
+  public StringSet getAllTables(StringSet cacheKeys, String queryClass)
+    throws ManifoldCFException
+  {
+    IResultSet set = performQuery("SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema='PUBLIC'",null,cacheKeys,queryClass);
+    StringSetBuffer ssb = new StringSetBuffer();
+    String columnName = "table_name";
+
+    int i = 0;
+    while (i < set.getRowCount())
+    {
+      IResultRow row = set.getRow(i++);
+      String value = row.getValue(columnName).toString();
+      ssb.add(value);
+    }
+    return new StringSet(ssb);
+  }
+
+  /** Perform a general "data fetch" query.
+  *@param query is the query string.
+  *@param params are the parameterized values, if needed.
+  *@param cacheKeys are the cache keys, if needed (null if no cache desired).
+  *@param queryClass is the LRU class name against which this query would be cached,
+  * or null if no LRU behavior desired.
+  *@return a resultset.
+  */
+  public IResultSet performQuery(String query, ArrayList params, StringSet cacheKeys, String queryClass)
+    throws ManifoldCFException
+  {
+    try
+    {
+      return executeQuery(query,params,cacheKeys,null,queryClass,true,-1,null,null);
+    }
+    catch (ManifoldCFException e)
+    {
+      throw reinterpretException(e);
+    }
+  }
+
+  /** Perform a general "data fetch" query.
+  *@param query is the query string.
+  *@param params are the parameterized values, if needed.
+  *@param cacheKeys are the cache keys, if needed (null if no cache desired).
+  *@param queryClass is the LRU class name against which this query would be cached,
+  * or null if no LRU behavior desired.
+  *@param maxResults is the maximum number of results returned (-1 for all).
+  *@param returnLimit is a description of how to limit the return result, or null if no limit.
+  *@return a resultset.
+  */
+  public IResultSet performQuery(String query, ArrayList params, StringSet cacheKeys, String queryClass,
+    int maxResults, ILimitChecker returnLimit)
+    throws ManifoldCFException
+  {
+    try
+    {
+      return executeQuery(query,params,cacheKeys,null,queryClass,true,maxResults,null,returnLimit);
+    }
+    catch (ManifoldCFException e)
+    {
+      throw reinterpretException(e);
+    }
+  }
+
+  /** Perform a general "data fetch" query.
+  *@param query is the query string.
+  *@param params are the parameterized values, if needed.
+  *@param cacheKeys are the cache keys, if needed (null if no cache desired).
+  *@param queryClass is the LRU class name against which this query would be cached,
+  * or null if no LRU behavior desired.
+  *@param maxResults is the maximum number of results returned (-1 for all).
+  *@param resultSpec is a result specification, or null for the standard treatment.
+  *@param returnLimit is a description of how to limit the return result, or null if no limit.
+  *@return a resultset.
+  */
+  public IResultSet performQuery(String query, ArrayList params, StringSet cacheKeys, String queryClass,
+    int maxResults, ResultSpecification resultSpec, ILimitChecker returnLimit)
+    throws ManifoldCFException
+  {
+    try
+    {
+      return executeQuery(query,params,cacheKeys,null,queryClass,true,maxResults,resultSpec,returnLimit);
+    }
+    catch (ManifoldCFException e)
+    {
+      throw reinterpretException(e);
+    }
+  }
+
+  /** Construct a regular-expression match clause.
+  * This method builds both the text part of a regular-expression match.
+  *@param column is the column specifier string.
+  *@param regularExpression is the properly-quoted regular expression string, or "?" if a parameterized value is to be used.
+  *@param caseInsensitive is true of the regular expression match is to be case insensitive.
+  *@return the query chunk needed, not padded with spaces on either side.
+  */
+  public String constructRegexpClause(String column, String regularExpression, boolean caseInsensitive)
+  {
+    return "REGEXP_MATCHES("+column+","+quoteSQLString(regularExpression)+")";
+  }
+
+  /** Construct a regular-expression substring clause.
+  * This method builds an expression that extracts a specified string section from a field, based on
+  * a regular expression.
+  *@param column is the column specifier string.
+  *@param regularExpression is the properly-quoted regular expression string, or "?" if a parameterized value is to be used.
+  *@param caseInsensitive is true if the regular expression match is to be case insensitive.
+  *@return the expression chunk needed, not padded with spaces on either side.
+  */
+  public String constructSubstringClause(String column, String regularExpression, boolean caseInsensitive)
+  {
+    // MHL
+    return "''";
+  }
+
+  /** Construct an offset/limit clause.
+  * This method constructs an offset/limit clause in the proper manner for the database in question.
+  *@param offset is the starting offset number.
+  *@param limit is the limit of result rows to return.
+  *@return the proper clause, with no padding spaces on either side.
+  */
+  public String constructOffsetLimitClause(int offset, int limit)
+  {
+    StringBuffer sb = new StringBuffer();
+    if (offset != 0)
+      sb.append("OFFSET ").append(Integer.toString(offset));
+    if (limit != -1)
+    {
+      if (offset != 0)
+        sb.append(" ");
+      sb.append("LIMIT ").append(Integer.toString(limit));
+    }
+    return sb.toString();
+  }
+
+  /** Construct a 'distinct on (x)' filter.
+  * This filter wraps a query and returns a new query whose results are similar to POSTGRESQL's DISTINCT-ON feature.
+  * Specifically, for each combination of the specified distinct fields in the result, only the first such row is included in the final
+  * result.
+  *@param outputParameters is a blank arraylist into which to put parameters.  Null may be used if the baseParameters parameter is null.
+  *@param baseQuery is the base query, which is another SELECT statement, without parens,
+  * e.g. "SELECT ..."
+  *@param baseParameters are the parameters corresponding to the baseQuery.
+  *@param distinctFields are the fields to consider to be distinct.  These should all be keys in otherFields below.
+  *@param otherFields are the rest of the fields to return, keyed by the AS name, value being the base query column value, e.g. "value AS key"
+  *@return a revised query that performs the necessary DISTINCT ON operation.  The arraylist outputParameters will also be appropriately filled in.
+  */
+  public String constructDistinctOnClause(ArrayList outputParameters, String baseQuery, ArrayList baseParameters, String[] distinctFields, Map otherFields)
+  {
+    // HSQLDB does not really support this functionality.
+    // We could hack a workaround, along the following lines:
+    //
+    // SELECT
+    //   t1.bucket, t1.bytecount, t1.windowstart, t1.windowend
+    // FROM
+    //   (xxx) t1
+    // WHERE
+    //   t1.bytecount=( SELECT t2.bytecount FROM (xxx) t2 WHERE
+    //     t2.bucket = t1.bucket LIMIT 1 ) AND
+    //   t1.windowstart=( SELECT t2.windowstart FROM (xxx) t2 WHERE
+    //     t2.bucket = t1.bucket LIMIT 1 ) AND
+    //   t1.windowend=( SELECT t2.windowend FROM (xxx) t2 WHERE
+    //     t2.bucket = t1.bucket LIMIT 1 )
+    //
+    // However, the cost of doing 3 identical and very costly queries is likely to be too high for this to be viable.
+
+    // Copy arguments
+    if (baseParameters != null)
+      outputParameters.addAll(baseParameters);
+
+    StringBuffer sb = new StringBuffer("SELECT ");
+    boolean needComma = false;
+    Iterator iter = otherFields.keySet().iterator();
+    while (iter.hasNext())
+    {
+      String fieldName = (String)iter.next();
+      String columnValue = (String)otherFields.get(fieldName);
+      if (needComma)
+        sb.append(",");
+      needComma = true;
+      sb.append("txxx1.").append(columnValue).append(" AS ").append(fieldName);
+    }
+    sb.append(" FROM (").append(baseQuery).append(") txxx1");
+    return sb.toString();
+  }
+
+  /** Quote a sql string.
+  * This method quotes a sql string in the proper manner for the database in question.
+  *@param string is the input string.
+  *@return the properly quoted (and escaped) output string.
+  */
+  public String quoteSQLString(String string)
+  {
+    StringBuffer rval = new StringBuffer();
+    char quoteChar = '\'';
+    rval.append(quoteChar);
+    int i = 0;
+    while (i < string.length())
+    {
+      char x = string.charAt(i++);
+      if (x == quoteChar)
+        rval.append(quoteChar);
+      rval.append(x);
+    }
+    rval.append(quoteChar);
+    return rval.toString();
+  }
+
+  /** Prepare a sql date for use in a query.
+  * This method prepares a query constant using the sql date string passed in.
+  * The date passed in is presumed to be in "standard form", or something that might have
+  * come back from a resultset of a query.
+  *@param date is the date in standard form.
+  *@return the sql date expression to use for date comparisons.
+  */
+  public String prepareSQLDate(String date)
+  {
+    // MHL
+    return null;
+  }
+
+  /** Obtain the maximum number of individual items that should be
+  * present in an IN clause.  Exceeding this amount will potentially cause the query performance
+  * to drop.
+  *@return the maximum number of IN clause members.
+  */
+  public int getMaxInClause()
+  {
+    return 100;
+  }
+
+  /** Begin a database transaction.  This method call MUST be paired with an endTransaction() call,
+  * or database handles will be lost.  If the transaction should be rolled back, then signalRollback() should
+  * be called before the transaction is ended.
+  * It is strongly recommended that the code that uses transactions be structured so that a try block
+  * starts immediately after this method call.  The body of the try block will contain all direct or indirect
+  * calls to executeQuery().  After this should be a catch for every exception type, including Error, which should call the
+  * signalRollback() method, and rethrow the exception.  Then, after that a finally{} block which calls endTransaction().
+  */
+  public void beginTransaction()
+    throws ManifoldCFException
+  {
+    beginTransaction(TRANSACTION_ENCLOSING);
+  }
+
+  /** Begin a database transaction.  This method call MUST be paired with an endTransaction() call,
+  * or database handles will be lost.  If the transaction should be rolled back, then signalRollback() should
+  * be called before the transaction is ended.
+  * It is strongly recommended that the code that uses transactions be structured so that a try block
+  * starts immediately after this method call.  The body of the try block will contain all direct or indirect
+  * calls to executeQuery().  After this should be a catch for every exception type, including Error, which should call the
+  * signalRollback() method, and rethrow the exception.  Then, after that a finally{} block which calls endTransaction().
+  *@param transactionType is the kind of transaction desired.
+  */
+  public void beginTransaction(int transactionType)
+    throws ManifoldCFException
+  {
+    if (getCurrentTransactionType() == TRANSACTION_SERIALIZED)
+    {
+      serializableDepth++;
+      return;
+    }
+
+    if (transactionType == TRANSACTION_ENCLOSING)
+    {
+      transactionType = getCurrentTransactionType();
+    }
+
+    switch (transactionType)
+    {
+    case TRANSACTION_READCOMMITTED:
+      super.beginTransaction(TRANSACTION_READCOMMITTED);
+      break;
+    case TRANSACTION_SERIALIZED:
+      super.beginTransaction(TRANSACTION_SERIALIZED);
+      try
+      {
+        performModification("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE",null,null);
+      }
+      catch (Error e)
+      {
+        super.signalRollback();
+        super.endTransaction();
+        throw e;
+      }
+      catch (ManifoldCFException e)
+      {
+        super.signalRollback();
+        super.endTransaction();
+        throw e;
+      }
+      break;
+    default:
+      throw new ManifoldCFException("Bad transaction type: "+Integer.toString(transactionType));
+    }
+  }
+
+  /** Signal that a rollback should occur on the next endTransaction().
+  */
+  public void signalRollback()
+  {
+    if (serializableDepth == 0)
+      super.signalRollback();
+  }
+
+  /** End a database transaction, either performing a commit or a rollback (depending on whether
+  * signalRollback() was called within the transaction).
+  */
+  public void endTransaction()
+    throws ManifoldCFException
+  {
+    if (serializableDepth > 0)
+    {
+      serializableDepth--;
+      return;
+    }
+
+    super.endTransaction();
+  }
+
+  /** Abstract method to start a transaction */
+  protected void startATransaction()
+    throws ManifoldCFException
+  {
+    executeViaThread(connection,"START TRANSACTION",null,false,0,null,null);
+  }
+
+  /** Abstract method to commit a transaction */
+  protected void commitCurrentTransaction()
+    throws ManifoldCFException
+  {
+    executeViaThread(connection,"COMMIT",null,false,0,null,null);
+  }
+  
+  /** Abstract method to roll back a transaction */
+  protected void rollbackCurrentTransaction()
+    throws ManifoldCFException
+  {
+    executeViaThread(connection,"ROLLBACK",null,false,0,null,null);
+  }
+  
+}
+

Propchange: incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: incubator/lcf/trunk/modules/framework/core/src/main/java/org/apache/manifoldcf/core/database/DBInterfaceHSQLDB.java
------------------------------------------------------------------------------
    svn:keywords = Id

Added: incubator/lcf/trunk/modules/lib/hsqldb.jar
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/modules/lib/hsqldb.jar?rev=1006222&view=auto
==============================================================================
Binary file - no diff available.

Propchange: incubator/lcf/trunk/modules/lib/hsqldb.jar
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream