org.integratedmodelling.engine.kbox.sql.h2.H2Database Maven / Gradle / Ivy
The newest version!
/*******************************************************************************
* Copyright (C) 2007, 2015:
*
* - Ferdinando Villa - integratedmodelling.org - any
* other authors listed in @author annotations
*
* All rights reserved. This file is part of the k.LAB software suite, meant to enable
* modular, collaborative, integrated development of interoperable data and model
* components. For details, see http://integratedmodelling.org.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the Affero General Public License Version 3 or 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 Affero General Public License for more details.
*
* You should have received a copy of the Affero General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite
* 330, Boston, MA 02111-1307, USA. The license is also available at:
* https://www.gnu.org/licenses/agpl.html
*******************************************************************************/
package org.integratedmodelling.engine.kbox.sql.h2;
import java.io.File;
import java.net.MalformedURLException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.sql.PooledConnection;
import org.h2.jdbcx.JdbcDataSource;
import org.h2.tools.Recover;
import org.h2gis.h2spatial.CreateSpatialExtension;
import org.h2gis.utilities.SFSUtilities;
import org.integratedmodelling.api.knowledge.IKnowledge;
import org.integratedmodelling.api.modelling.IObservableSemantics;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.collections.Pair;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.owl.Knowledge;
import org.integratedmodelling.engine.kbox.sql.SQL;
import org.integratedmodelling.engine.kbox.sql.h2.H2Kbox.Schema;
import org.integratedmodelling.engine.kbox.sql.h2.H2Kbox.Serializer;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import org.integratedmodelling.exceptions.KlabStorageException;
public class H2Database {
private static Map datastores = new HashMap<>();
JdbcDataSource ds;
String url;
boolean isNew = false;
AtomicLong oid = new AtomicLong(1);
// directory containing the database files.
File directory;
String name;
// cache for still unknown trait-assembled concepts that were saved before.
Map derivedConcepts = new HashMap<>();
PooledConnection pooledConnection = null;
// we leave it to the user to decide whether to reuse a connection (for single-user
// repetitive operations) or get one from the pool at every use (default).
Connection connection = null;
Set tables = null;
protected Map, Schema> schemata = new HashMap<>();
private Set> initializedSchemata = new HashSet<>();
private H2Database(String kboxName) {
this(kboxName, KLAB.CONFIG.getDataPath("kbox"));
}
private H2Database(String kboxName, File directory) {
this.directory = directory;
this.name = kboxName;
directory.mkdirs();
JdbcDataSource dds = new JdbcDataSource();
/*
* FIXME weak check. On Win, only the .mv.db get created anyway; on Linux, the h2
* is created and the mv only exists after the db contains anything.
*/
File f1 = new File(directory + File.separator + kboxName + ".mv.db");
File f2 = new File(directory + File.separator + kboxName + ".h2.db");
this.isNew = !f1.exists() && !f2.exists();
try {
String fileUrl = directory.toURI().toURL().toString();
this.url = "jdbc:h2:" + fileUrl + kboxName + ";AUTO_SERVER=TRUE";
/*
* TODO This creates an in-memory database We could use it for models, and
* that would likely help with corruption issues and speed, at the cost of RAM
* and startup time when many models are available.
*
* Can use an option to set this from user preferences.
*/
// this.url = "jdbc:h2:mem:" + kboxName /* + ";AUTO_SERVER=TRUE" */;
} catch (MalformedURLException e1) {
throw new KlabRuntimeException(e1);
}
// this.url = "jdbc:h2:file:" + directory.toString().replaceAll("\\\\", "/") + "/"
// + kboxName
// + ";AUTO_SERVER=TRUE";
dds.setURL(url);
dds.setUser("sa");
dds.setPassword("sa");
// Context ctx;
// try {
// ctx = new InitialContext();
// ctx.bind("jdbc/" + name, dds);
// } catch (NamingException e1) {
// throw new ThinklabRuntimeException("error during kbox initialization: " +
// e1.getMessage());
// }
this.ds = /* SFSUtilities.wrapSpatialDataSource( */dds; // );
// this.connections = JdbcConnectionPool.create(url, "sa", "sa");
try {
pooledConnection = dds.getPooledConnection();
} catch (SQLException e) {
throw new KlabRuntimeException(e);
}
if (isNew) {
try {
CreateSpatialExtension.initSpatialExtension(dds.getConnection());
execute("CREATE TABLE hids (id LONG)");
execute("INSERT INTO hids VALUES (1)");
execute("CREATE TABLE knowledge_structure (knowledge VARCHAR(256) PRIMARY KEY, structure VARCHAR(4096))");
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
} else {
try {
oid.set(queryIds("SELECT id FROM hids").get(0));
/*
* TODO fill derivedConcept cache
*/
} catch (KlabException e) {
throw new KlabRuntimeException(e);
}
}
datastores.put(kboxName, this);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
deallocateConnection();
pooledConnection.close();
} catch (SQLException e) {
// poh
}
}
});
sanityCheck();
// serializer = new H2Serializer();
}
public void preallocateConnection() {
if (connection == null) {
try {
connection = SFSUtilities.wrapConnection(pooledConnection.getConnection());
} catch (SQLException e) {
// just leave null
}
}
}
public void deallocateConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
} finally {
connection = null;
}
}
}
private void sanityCheck() {
KLAB.info("sanity check started on kbox " + url);
/*
* TODO 2. Run sanity checks: 1. check that installed schemata are in sync with
* API 2. compute current statistics 3. if new, install default schemata
*/
}
protected void recover() throws KlabException {
try {
Recover.execute(directory.toString(), name);
} catch (SQLException e) {
throw new KlabStorageException(e);
}
}
public synchronized long getNextId() {
long ret = oid.getAndIncrement();
try {
execute("UPDATE hids SET id = " + (ret + 1));
} catch (KlabException e) {
throw new KlabRuntimeException(e);
}
return ret;
}
public boolean hasTable(String tableName) throws KlabException {
if (tables == null) {
tables = new HashSet<>();
class RH implements SQL.ResultHandler {
int n = 0;
@Override
public void onRow(ResultSet rs) {
try {
tables.add(rs.getString(1));
} catch (SQLException e) {
throw new KlabRuntimeException(e);
}
}
@Override
public void nResults(int nres) {
}
}
RH rh = new RH();
this.query("select table_name from information_schema.tables;", rh);
}
return tables.contains(tableName) || tables.contains(tableName.toUpperCase());
}
/**
* Get a connection, turning any exception into a Thinklab one.
*
* @return connection
* @throws KlabStorageException
*/
public Connection getConnection() throws KlabStorageException {
if (connection != null) {
return connection;
}
try {
// FIXME must close the pooledconnection, not the wrapped connection
return SFSUtilities.wrapConnection(pooledConnection.getConnection());
} catch (SQLException e) {
throw new KlabStorageException(e);
}
}
public void execute(String sql) throws KlabException {
if (sql == null) {
// FIXME check if an exception should be thrown or if this shouldn't be
// checked at all.
return;
}
Connection connection = null;
try {
connection = getConnection();
connection.createStatement().execute(sql);
// connection.close();
} catch (SQLException e) {
throw new KlabStorageException(e);
} finally {
try {
if (connection != null) {
// connection.close();
}
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
}
}
public void query(String sql, SQL.ResultHandler handler) throws KlabException {
if (sql.contains("POINT EMPTY")) {
System.out.println("WHAT?");
}
Connection connection = null;
Statement stmt = null;
ResultSet result = null;
try {
connection = getConnection();
stmt = connection.createStatement();
result = stmt.executeQuery(sql);
int res = 0;
while (result.next()) {
res++;
handler.onRow(result);
}
handler.nResults(res);
} catch (SQLException e) {
throw new KlabStorageException(e);
} finally {
// jesus christ
try {
if (result != null) {
result.close();
}
if (stmt != null) {
stmt.close();
}
if (connection != null) {
// connection.close();
}
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
}
}
public static H2Database get(String kboxName) {
if (datastores.get(kboxName) != null) {
return datastores.get(kboxName);
}
return new H2Database(kboxName);
}
public static H2Database get(File directory, String kboxName) {
if (datastores.get(kboxName) != null) {
return datastores.get(kboxName);
}
return new H2Database(kboxName, directory);
}
public long storeObject(T o, long foreignKey, Serializer serializer, IMonitor monitor)
throws KlabException {
Pair, Schema> schema = getSchema(o.getClass());
if (schema == null) {
throw new KlabStorageException("cannot find schema for " + o.getClass().getCanonicalName());
}
if (!initializedSchemata.contains(schema.getFirst())) {
if (schema.getSecond().getTableName() != null) {
if (!hasTable(schema.getSecond().getTableName())) {
execute(schema.getSecond().getCreateSQL());
tables = null;
}
}
initializedSchemata.add(schema.getFirst());
}
long id = getNextId();
String sql = serializer.serialize(o, schema.getSecond(), id, foreignKey);
if (sql != null && !sql.isEmpty()) {
execute(sql);
}
return id;
}
private Pair, Schema> getSchema(Class cls) {
for (Class cl : schemata.keySet()) {
if (cl.isAssignableFrom(cls)) {
return new Pair<>(cl, schemata.get(cl));
}
}
return null;
}
public void setSchema(Class cls, Schema schema) {
schemata.put(cls, schema);
}
/**
* Returns a list of IDs as a result of running a query, subject to the assumption
* that the id is field 1 of the result.
*
* @param query
* @return the list of IDs resulting, or empty
* @throws KlabException
*/
public List queryIds(String query) throws KlabException {
final List ret = new ArrayList<>();
query(query, new SQL.SimpleResultHandler() {
@Override
public void onRow(ResultSet rs) {
try {
ret.add(rs.getLong(1));
} catch (SQLException e) {
}
}
});
return ret;
}
/**
* TODO store all concepts that depend on complex trait composition as their signature
* from base concepts, so they can be reconstructed at initialization to avoid
* surprises when models are searched for that are based on concepts that haven't been
* loaded.
*
* @param observable
*/
public void updateKnowledge(IObservableSemantics observable) {
if (observable == null) {
return;
}
storeTraits(observable.getType());
}
private void storeTraits(IKnowledge c) {
if (c == null) {
return;
}
String k = c.toString();
String s = ((Knowledge) c).asText();
if (!k.equals(s)) {
/*
* search for k; if not there, insert k,s into knowledge_structure.
*/
}
}
/**
* Call after component initialization and each project loading: assembles any concept
* that was decomposed into traits before and is still unknown, ensuring that all
* knowledge is as synchronized as possible.
*/
public void synchronizeKnowledge() {
}
}