/*
* eXist Open Source Native XML Database
* Copyright (C) 2007 The eXist Project
* http://exist-db.org
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* $Id$
*
* @author Pierrick Brihaye <pierrick.brihaye@free.fr>
*/
package org.exist.indexing.spatial;
import java.io.File;
import java.io.FilenameFilter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.log4j.Logger;
import org.exist.indexing.IndexWorker;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.btree.DBException;
import org.exist.util.DatabaseConfigurationException;
import org.w3c.dom.Element;
/**
*/
public class GMLHSQLIndex extends AbstractGMLJDBCIndex {
private final static Logger LOG = Logger.getLogger(GMLHSQLIndex.class);
public static String db_file_name_prefix = "spatial_index";
//Keep this upper case ;-)
public static String TABLE_NAME = "SPATIAL_INDEX_V1";
private DBBroker connectionOwner = null;
private long connectionTimeout = 100000L;
public GMLHSQLIndex() {
}
public void configure(BrokerPool pool, String dataDir, Element config) throws DatabaseConfigurationException {
super.configure(pool, dataDir, config);
String param = ((Element)config).getAttribute("connectionTimeout");
if (param != null) {
try {
connectionTimeout = Long.parseLong(param);
} catch (NumberFormatException e) {
LOG.error("Invalid value for 'connectionTimeout'", e);
}
}
param = ((Element)config).getAttribute("max_docs_in_context_to_refine_query");
if (param != null) {
try {
max_docs_in_context_to_refine_query = Integer.parseInt(param);
} catch (NumberFormatException e) {
LOG.error("Invalid value for 'max_docs_in_context_to_refine_query', using default:" + max_docs_in_context_to_refine_query, e);
}
}
if (LOG.isDebugEnabled())
LOG.debug("max_docs_in_context_to_refine_query = " + max_docs_in_context_to_refine_query);
}
public IndexWorker getWorker(DBBroker broker) {
GMLHSQLIndexWorker worker = (GMLHSQLIndexWorker)workers.get(broker);
if (worker == null) {
worker = new GMLHSQLIndexWorker(this, broker);
workers.put(broker, worker);
}
return worker;
}
protected void checkDatabase() throws ClassNotFoundException, SQLException {
//Test to see if we have a HSQL driver in the classpath
Class.forName("org.hsqldb.jdbcDriver");
}
protected void shutdownDatabase() throws DBException {
try {
//No need to shutdown if we haven't opened anything
if (conn != null) {
Statement stmt = conn.createStatement();
stmt.executeQuery("SHUTDOWN");
stmt.close();
conn.close();
if (LOG.isDebugEnabled())
LOG.debug("GML index: " + getDataDir() + "/" + db_file_name_prefix + " closed");
}
} catch (SQLException e) {
throw new DBException(e.getMessage());
} finally {
conn = null;
}
}
protected void deleteDatabase() throws DBException {
File directory = new File(getDataDir());
File[] files = directory.listFiles(
new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(db_file_name_prefix);
}
}
);
boolean deleted = true;
for (int i = 0; i < files.length ; i++) {
deleted &= files[i].delete();
}
//TODO : raise an error if deleted == false ?
}
protected void removeIndexContent() throws DBException {
try {
//Let's be lazy here : we only delete the index content if we have a connection
//deleteDatabase() should be far more efficient ;-)
if (conn != null) {
Statement stmt = conn.createStatement();
int nodeCount = stmt.executeUpdate("DELETE FROM " + GMLHSQLIndex.TABLE_NAME + ";");
stmt.close();
if (LOG.isDebugEnabled())
LOG.debug("GML index: " + getDataDir() + "/" + db_file_name_prefix + ". " + nodeCount + " nodes removed");
}
} catch (SQLException e) {
throw new DBException(e.getMessage());
}
}
//Horrible "locking" mechanism
protected Connection acquireConnection(DBBroker broker) throws SQLException {
synchronized (this) {
if (connectionOwner == null) {
connectionOwner = broker;
if (conn == null)
initializeConnection();
return conn;
} else {
long timeOut_ = connectionTimeout;
long waitTime = timeOut_;
long start = System.currentTimeMillis();
try {
for (;;) {
wait(waitTime);
if (connectionOwner == null) {
connectionOwner = broker;
if (conn == null)
//We should never get there since the connection should have been initialized
//by the first request from a worker
initializeConnection();
return conn;
} else {
waitTime = timeOut_ - (System.currentTimeMillis() - start);
if (waitTime <= 0) {
LOG.error("Time out while trying to get connection");
}
}
}
} catch (InterruptedException ex) {
notify();
throw new RuntimeException("interrupted while waiting for lock");
}
}
}
}
protected synchronized void releaseConnection(DBBroker broker) throws SQLException {
if (connectionOwner == null)
throw new SQLException("Attempted to release a connection that wasn't acquired");
connectionOwner = null;
}
private void initializeConnection() throws SQLException {
System.setProperty("hsqldb.cache_scale", "11");
System.setProperty("hsqldb.cache_size_scale", "12");
System.setProperty("hsqldb.default_table_type", "cached");
//Get a connection to the DB... and keep it
this.conn = DriverManager.getConnection("jdbc:hsqldb:" + getDataDir() + "/" + db_file_name_prefix /* + ";shutdown=true" */, "sa", "");
ResultSet rs = null;
try {
rs = this.conn.getMetaData().getTables(null, null, TABLE_NAME, new String[] { "TABLE" });
rs.last();
if (rs.getRow() == 1) {
if (LOG.isDebugEnabled())
LOG.debug("Opened GML index: " + getDataDir() + "/" + db_file_name_prefix);
//Create the data structure if it doesn't exist
} else if (rs.getRow() == 0) {
Statement stmt = conn.createStatement();
stmt.executeUpdate("CREATE TABLE " + TABLE_NAME + "(" +
/*1*/ "DOCUMENT_URI VARCHAR, " +
/*2*/ "NODE_ID_UNITS INTEGER, " +
/*3*/ "NODE_ID BINARY, " +
/*4*/ "GEOMETRY_TYPE VARCHAR, " +
/*5*/ "SRS_NAME VARCHAR, " +
/*6*/ "WKT VARCHAR, " +
/*7*/ "WKB BINARY, " +
/*8*/ "MINX DOUBLE, " +
/*9*/ "MAXX DOUBLE, " +
/*10*/ "MINY DOUBLE, " +
/*11*/ "MAXY DOUBLE, " +
/*12*/ "CENTROID_X DOUBLE, " +
/*13*/ "CENTROID_Y DOUBLE, " +
/*14*/ "AREA DOUBLE, " +
//Boundary ?
/*15*/ "EPSG4326_WKT VARCHAR, " +
/*16*/ "EPSG4326_WKB BINARY, " +
/*17*/ "EPSG4326_MINX DOUBLE, " +
/*18*/ "EPSG4326_MAXX DOUBLE, " +
/*19*/ "EPSG4326_MINY DOUBLE, " +
/*20*/ "EPSG4326_MAXY DOUBLE, " +
/*21*/ "EPSG4326_CENTROID_X DOUBLE, " +
/*22*/ "EPSG4326_CENTROID_Y DOUBLE, " +
/*23*/ "EPSG4326_AREA DOUBLE, " +
//Boundary ?
/*24*/ "IS_CLOSED BOOLEAN, " +
/*25*/ "IS_SIMPLE BOOLEAN, " +
/*26*/ "IS_VALID BOOLEAN, " +
//Enforce uniqueness
"UNIQUE (" +
"DOCUMENT_URI, NODE_ID_UNITS, NODE_ID" +
")" +
")"
);
stmt.executeUpdate("CREATE INDEX DOCUMENT_URI ON " + TABLE_NAME + " (DOCUMENT_URI);");
stmt.executeUpdate("CREATE INDEX NODE_ID ON " + TABLE_NAME + " (NODE_ID);");
stmt.executeUpdate("CREATE INDEX GEOMETRY_TYPE ON " + TABLE_NAME + " (GEOMETRY_TYPE);");
stmt.executeUpdate("CREATE INDEX SRS_NAME ON " + TABLE_NAME + " (SRS_NAME);");
stmt.executeUpdate("CREATE INDEX WKB ON " + TABLE_NAME + " (WKB);");
stmt.executeUpdate("CREATE INDEX EPSG4326_WKB ON " + TABLE_NAME + " (EPSG4326_WKB);");
stmt.executeUpdate("CREATE INDEX EPSG4326_MINX ON " + TABLE_NAME + " (EPSG4326_MINX);");
stmt.executeUpdate("CREATE INDEX EPSG4326_MAXX ON " + TABLE_NAME + " (EPSG4326_MAXX);");
stmt.executeUpdate("CREATE INDEX EPSG4326_MINY ON " + TABLE_NAME + " (EPSG4326_MINY);");
stmt.executeUpdate("CREATE INDEX EPSG4326_MAXY ON " + TABLE_NAME + " (EPSG4326_MAXY);");
stmt.executeUpdate("CREATE INDEX EPSG4326_CENTROID_X ON " + TABLE_NAME + " (EPSG4326_CENTROID_X);");
stmt.executeUpdate("CREATE INDEX EPSG4326_CENTROID_Y ON " + TABLE_NAME + " (EPSG4326_CENTROID_Y);");
//AREA ?
stmt.close();
if (LOG.isDebugEnabled())
LOG.debug("Created GML index: " + getDataDir() + "/" + db_file_name_prefix);
} else {
throw new SQLException("2 tables with the same name ?");
}
} finally {
if (rs != null)
rs.close();
}
}
}