All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.integratedmodelling.engine.kbox.sql.h2.H2Database Maven / Gradle / Ivy

There is a newer version: 0.9.10
Show 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.IObservable;
import org.integratedmodelling.api.monitoring.IMonitor;
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.engine.kbox.sql.h2.schema.CompoundSchema;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabInternalErrorException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import org.integratedmodelling.exceptions.KlabStorageException;

public class H2Database {

    private static Map datastores = new HashMap<>();

    JdbcDataSource      ds;
    // JdbcConnectionPool connections;
    String              url;
    boolean             isNew            = false;
    // H2Serializer serializer;
    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           = 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);
        }
    }

    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.contains(tableName)) {
            return true;
        }

        class RH implements SQL.ResultHandler {

            int n = 0;

            @Override
            public void onRow(ResultSet rs) {
                n++;
            }

            @Override
            public void nResults(int nres) {
            }
        }

        RH rh = new RH();
        this.query("select table_name from information_schema.tables where table_name = '"
                + tableName.toUpperCase() + "';", rh);

        if (rh.n > 0) {
            tables.add(tableName);
            return true;
        }

        return false;
    }

    /**
     * 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);
    }

    /*
     * FIXME the SQL in this gets called way too many times.
     */
    public void createIfAbsent(Schema schema) throws KlabException {

        if (!(schema instanceof CompoundSchema)) {
            throw new KlabInternalErrorException("h2: primary schema is not a compound schema");
        }
        if (!hasTable(((CompoundSchema) schema).getTableName())) {
            execute(schema.getCreateSQL());
        }
    }

    public long storeObject(Object o, long foreignKey, Serializer serializer, IMonitor monitor)
            throws KlabException {
        Schema schema = SchemaFactory.getSchema(o.getClass());
        createIfAbsent(schema);
        long id = getNextId();
        execute(serializer.serialize(o, schema, id, foreignKey));
        return id;

    }

    /**
     * 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(IObservable observable) {

        if (observable == null) {
            return;
        }

        storeTraits(observable.getType());
        storeTraits(observable.getInherentType());
    }

    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() {

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy