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

org.integratedmodelling.engine.modelling.kbox.ModelKbox 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.modelling.kbox;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.h2gis.utilities.SpatialResultSet;
import org.integratedmodelling.api.engine.IModelingEngine;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.knowledge.IKnowledge;
import org.integratedmodelling.api.knowledge.IProperty;
import org.integratedmodelling.api.metadata.IModelMetadata;
import org.integratedmodelling.api.modelling.IModel;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.IObservable;
import org.integratedmodelling.api.modelling.resolution.IModelPrioritizer;
import org.integratedmodelling.api.modelling.resolution.IResolutionScope;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.persistence.IKbox;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.api.time.ITemporalExtent;
import org.integratedmodelling.common.ConceptPair;
import org.integratedmodelling.common.beans.requests.ModelQuery;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.owl.Knowledge;
import org.integratedmodelling.common.space.IGeometricShape;
import org.integratedmodelling.common.utils.Escape;
import org.integratedmodelling.common.vocabulary.NS;
import org.integratedmodelling.engine.geospace.Geospace;
import org.integratedmodelling.engine.geospace.literals.ShapeValue;
import org.integratedmodelling.engine.kbox.sql.SQL;
import org.integratedmodelling.engine.kbox.sql.h2.H2Database;
import org.integratedmodelling.engine.kbox.sql.h2.H2Kbox;
import org.integratedmodelling.engine.kbox.sql.h2.H2Serializer;
import org.integratedmodelling.engine.kbox.sql.h2.schema.CompoundSchema;
import org.integratedmodelling.engine.modelling.kbox.ModelData.Observable;
import org.integratedmodelling.engine.modelling.resolver.ResolutionScope;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;

import com.vividsolutions.jts.geom.Geometry;

/**
 * The kbox that holds model data and searches them based on observable and
 * context. Also dispatches the same search to the network and returns ranked
 * data using the namespace's priorities.
 * 
 * @author ferdinando.villa
 *
 */
public class ModelKbox extends H2Kbox {

	public static final String DUMMY_NAMESPACE_ID = "DUMMY_SEARCH_NS";
	// change this when incompatible changes are made to force kbox reset.
	public static final String KBOX_VERSION = "098v6";

	static ModelKbox _this;

	public static ModelKbox get() {

		if (_this == null) {
			H2Kbox.set("models_" + KBOX_VERSION, new ModelKbox("models_" + KBOX_VERSION, KLAB.ENGINE.getMonitor()));
			_this = (ModelKbox) H2Kbox.get("models_" + KBOX_VERSION);
		}
		return _this;
	}

	public List retrieveAll() throws KlabException {

		List ret = new ArrayList<>();
		if (!database.hasTable("model")) {
			return ret;
		}

		for (long oid : database.queryIds("SELECT oid FROM model;")) {
			ret.add(deserialize(oid));
		}
		return ret;
	}

	/**
	 * Find and deserialize all modeldata matching the parameters. Do not rank
	 * or anything.
	 * 
	 * @param observable
	 * @param context
	 * @throws KlabException
	 */
	public List queryModelData(IObservable observable, IResolutionScope context) throws KlabException {

		List ret = new ArrayList<>();

		if (!database.hasTable("model")) {
			return ret;
		}

		String query = "SELECT model.oid FROM model INNER JOIN observable ON model.oid = observable.fid WHERE ";

		query += "(" + scopeQuery(context, observable) + ")";
		query += " AND (" + observableQuery(observable, context) + ")";
		if (context.getScale().getSpace() != null) {
			String sq = spaceQuery(context.getScale().getSpace());
			if (!sq.isEmpty()) {
				query += " AND (" + sq + ")";
			}
		}
		String tquery = timeQuery(context.getScale().getTime());
		if (!tquery.isEmpty()) {
			query += " AND (" + tquery + ");";
		}

		// Env.logger.info(query);

		final List oids = database.queryIds(query);

		for (long l : oids) {
			try {
				ret.add(deserialize(l));
			} catch (KlabException e) {
				// ontologies do not reflect content any more: concepts not
				// found
				((ResolutionScope) context).getMonitor()
						.warn("kbox is out of sync with knowledge base: " + e.getMessage());
			}
		}

		KLAB.info(KLAB.ENGINE.getName() + ": model query for "
				+ (context.isForInstantiation() ? "instantiation of " : "explanation of ") + observable + " found "
				+ (ret.size() == 1 ? ret.get(0).name : (ret.size() + " models")));

		return ret;
	}

	/**
	 * Like query() but returns ranked metadata, only for the local kbox
	 * (ranking version of queryModelData()).
	 * 
	 * @param observable
	 * @param context
	 * @return ranked metadata, unsorted.
	 * @throws KlabException
	 */
	public List queryMetadata(IObservable observable, IResolutionScope context) throws KlabException {

		IModelPrioritizer prioritizer = context.getPrioritizer();
		List ret = new ArrayList<>();

		/*
		 * only query locally if we've seen a model before.
		 */
		if (database.hasTable("model")) {
			for (ModelData md : queryModelData(observable, context)) {
				md.ranks = prioritizer.getRanks(md);
				ret.add(md);
			}
		}

		return ret;
	}

	/**
	 * Pass the output of queryModelData to a contextual prioritizer and return
	 * the ranked list of IModels. If we're a personal engine, also broadcast
	 * the query to the network and merge results before returning.
	 * 
	 * @param observable
	 * @param context
	 * @return models resulting from query, best first.
	 * @throws KlabException
	 */
	public List query(IObservable observable, IResolutionScope context) throws KlabException {

		IModelPrioritizer prioritizer = context.getPrioritizer();
		ModelQueryResult ret = new ModelQueryResult(prioritizer, ((ResolutionScope) context).getMonitor());
		Set local = new HashSet<>();

		/*
		 * only query locally if we've seen a model before.
		 */
		if (database.hasTable("model")) {
			for (ModelData md : queryModelData(observable, context)) {
				local.add(md);
				ret.addModelData(md);
			}
		}

		/**
		 * If we're a modeling engine, dispatch the request to all nodes that
		 * allow it, which we do simply by using the result list as a
		 * distributed operation.
		 */
		if (KLAB.ENGINE instanceof IModelingEngine) {

			ModelQuery mquery = new ModelQuery();
			mquery.setObservable(
					KLAB.MFACTORY.adapt(observable, org.integratedmodelling.common.beans.Observable.class));
			mquery.setScope(KLAB.MFACTORY.adapt(context, org.integratedmodelling.common.beans.Scope.class));
			ret.setQuery(mquery);

			KLAB.ENGINE.getNetwork().broadcast(ret, ((ResolutionScope) context).getMonitor());
		}

		return ret;
	}

	/*
	 * Entirely TODO. For initialization we should use time only to select for
	 * most current info - either closer to the context or to today if time is
	 * null. For dynamic models we should either not have a context or cover the
	 * context. Guess this is the job of the prioritizer, and we should simply
	 * let anything through except when we look for process models.
	 */
	private String timeQuery(ITemporalExtent time) {

		String ret = "";
		// TODO Auto-generated method stub
		return ret;
	}

	/*
	 * select models that intersect the given space or have no space at all.
	 */
	private String spaceQuery(ISpatialExtent space) {
		if (space.getExtent().getShape().isEmpty()) {
			return "";
		}
		return "model.space && '" + ((IGeometricShape) (space.getExtent().getShape())).getStandardizedGeometry()
				+ "' OR ST_IsEmpty(model.space)";
	}

	/*
	 * TYPE: match exactly for now. OBSERVATION: match exactly; if observable
	 * has trait type and it's not subjective, we can also match that
	 * optionally. TRAITS: if observable has traits (classify by) we match that
	 * optionally ONLY IF NOT SUBJECTIVE. INHERENT: match exactly if in
	 * observable, otherwise match with closure and optionally.
	 */
	private String observableQuery(IObservable observable, IResolutionScope context) {

		String ret = observable.getType() instanceof IProperty
				? "observable.ptype = '" + ((Knowledge) observable.getType()).asText() + "'"
				: "observable.type = '" + ((Knowledge) observable.getType()).asText() + "'";

		String oret = "observable.otype = '" + observable.getObservationType() + "'";
		if (observable.getObservationType().isAbstract()) {
			oret += " OR " + joinStringConditions("observable.otype",
					observable.getObservationType().getSemanticClosure(), "OR");
		}
		/*
		 * TODO add the trait (classify/discretize by) if not subjective,
		 * optionally. At the moment there are so few subjective classifications
		 * that it's not really necessary.
		 */
		if (!oret.isEmpty()) {
			ret += " AND (" + oret + ")";
		}

		String tret = "";

		if (!tret.isEmpty()) {
			ret += " AND (" + tret + ")";
		}

		String iret = "";

		if (observable.getContextType() != null) {
			iret = joinStringConditions("observable.stype", observable.getContextType().getSemanticClosure(), "OR");
		} else {

			IKnowledge contextType = null;
			if (NS.isDirect(observable) && !context.isForInstantiation()) {
				contextType = (((ResolutionScope) context).getContextSubject() == null) ? null
						: ((ResolutionScope) context).getContextSubject().getObservable().getType();
			} else {
				contextType = context.getSubject().getObservable().getType();
			}

			iret = "observable.stype IS NULL OR observable.stype = ''";
			if (contextType != null) {
				iret += " OR observable.stype = '" + ((Knowledge) contextType).asText() + "'";
				// iret += " OR "
				// + joinStringConditions("observable.stype",
				// contextType.getSemanticClosure(), "OR");
			}
		}

		if (!iret.isEmpty()) {
			ret += " AND (" + iret + ")";
		}

		if (observable.getInherentType() != null) {

			iret = joinStringConditions("observable.itype",
					observable.getContextType() == null ? null : observable.getContextType().getSemanticClosure(),
					"OR");

			if (!iret.isEmpty()) {
				ret += " AND (" + iret + ")";
			}
		}

		/*
		 * detail level >= indicated, or -1 which is always the max.
		 */
		ret += " AND (observable.dtlvl < 0";
		if (observable.getDetailLevel() > 0) {
			ret += " OR observable.dtlvl >= " + observable.getDetailLevel();
		}
		ret += ")";

		return ret;
	}

	/*
	 * select models that are [instantiators if required] AND:] [private and in
	 * the home namespace if not dummy OR] (non-private and non-scenario) OR (in
	 * any of the scenarios in the context).
	 */
	private String scopeQuery(IResolutionScope context, IObservable observable) {

		String ret = "";

		String namespaceId = context.getResolutionNamespace() == null ? DUMMY_NAMESPACE_ID
				: context.getResolutionNamespace().getId();
		if (!namespaceId.equals(DUMMY_NAMESPACE_ID)) {
			// ret += "(model.isprivate AND model.namespaceid = '" + namespaceId
			// + "')";
			ret += "(model.namespaceid = '" + namespaceId + "')";
		}

		ret += (ret.isEmpty() ? "" : " OR ") + "((NOT model.isprivate) AND (NOT model.inscenario))";

		if (context.getScenarios() != null && context.getScenarios().size() > 0) {
			ret += " OR (" + joinStringConditions("model.namespaceid", context.getScenarios(), "OR") + ")";
		}

		if (NS.isCountable(observable)) {
			if (context.isForInstantiation()
					|| ((org.integratedmodelling.common.vocabulary.Observable) observable).isInstantiator()) {
				ret = "(" + ret + ") AND model.isreification";
			} else {
				ret = "(" + ret + ") AND (NOT model.isreification)";
			}
		}

		return ret;
	}

	private String joinStringConditions(String field, Collection stringValues, String operator) {

		String ret = "";

		if (stringValues != null) {
			for (Object o : stringValues) {
				if (o instanceof IKnowledge) {
					o = ((Knowledge) o).asText();
				}
				ret += (ret.isEmpty() ? "" : (" " + operator + " ")) + field + " = '" + o + "'";
			}
		}

		return ret;
	}

	// /**
	// * This one is for the query service only.
	// * @param criteria
	// * @param scenarios
	// * @param traits
	// * @param contextType
	// * @param observationType
	// * @param scale
	// * @param types
	// *
	// * @return
	// * @deprecated use JSON beans; move to services
	// * @throws KlabException
	// */
	// @Deprecated
	// public List query(IKnowledge subjectType, Set groups,
	// List types, IScale
	// scale, boolean isInstantiator, IConcept observationType, IConcept
	// contextType,
	// IConcept inherentType,
	// int detailLevel, List traits, String[] scenarios, IMetadata
	// criteria)
	// throws KlabException {
	//
	// List ret = new ArrayList<>();
	//
	// if (!database.hasTable("model")) {
	// return ret;
	// }
	//
	// ResolutionScope context = new ResolutionScope(subjectType, scale, types,
	// isInstantiator,
	// observationType, contextType, inherentType, detailLevel, traits,
	// scenarios,
	// criteria);
	// IModelPrioritizer prioritizer = context.getPrioritizer();
	//
	// for (ModelData md : this.queryModelData(context.getObservable(),
	// context)) {
	//
	// if (md == null)
	// continue;
	//
	// /**
	// * FIXME! If groups == null or empty, NOTHING comes out.
	// */
	// if (groups != null) {
	// /*
	// * skip anything not authorized for the groups
	// */
	// //// if (!KLAB.NETWORK.getResourceCatalog().isAuthorized(md.projectId,
	// groups)) {
	// // KLAB.info("skipping " + md.name + ": unauthorized");
	// // continue;
	// // }
	// }
	//
	// /*
	// * TODO if there is a namespace whitelist/blacklist, filter
	// */
	// md.ranks = prioritizer.computeCriteria(md, context);
	// ret.add(md);
	// }
	//
	// return ret;
	//
	// }

	/**
	 * @param name
	 * @return true if model with given id exists in database
	 * @throws KlabException
	 */
	public boolean hasModel(String name) throws KlabException {

		if (!database.hasTable("model")) {
			return false;
		}

		return database.queryIds("SELECT oid FROM model WHERE name = '" + name + "';").size() > 0;
	}

	@Override
	public long store(Object o) throws KlabException {

		ArrayList toStore = new ArrayList();

		if (o instanceof IModel) {

			KLAB.info("storing model " + ((IModel) o).getName());

			/*
			 * Ensure all concepts derived from composed traits will be
			 * recognized.
			 */
			database.updateKnowledge(((IModel) o).getObservable());

			/*
			 * all the various models that this model produces.
			 */
			for (ModelData md : ModelData.getModelData((IModel) o, monitor)) {
				toStore.add(md);
			}

		} else {
			toStore.add(o);
		}

		long ret = -1;
		for (Object obj : toStore) {
			long r = super.store(obj);
			if (ret < 0)
				ret = r;
		}

		return ret;
	}

	/**
	 * Pass the a namespace to check if its objects need to be stored. If the
	 * stored namespace record does not exist or has a timestamp older than the
	 * passed one, remove all objects that belong to it and return true. Does
	 * not store a new namespace record - this should be done when this has
	 * returned true and there were no errors.
	 * 
	 * Returns: 0 if no need to refresh, 1 if it must be entirely refreshed and
	 * every model and namespace record is removed from the kbox, and 2 if the
	 * models without errors need to be checked again (they may be in or not).
	 * 
	 * 
	 * @param namespace
	 * @return see above
	 */
	public int removeIfOlder(INamespace namespace) throws KlabException {

		if (!database.hasTable("namespaces")) {
			return 1;
		}

		long dbTimestamp = getNamespaceTimestamp(namespace);
		long timestamp = namespace.getTimeStamp();

		// KLAB.info("NAMESPACE " + namespace + " SEEN AT " + dbTimestamp + "
		// HAS " + timestamp);
		/*
		 * if we have stored something and we are younger than the stored ns,
		 * remove all models coming from it so we can add our new ones.
		 */
		if (timestamp > dbTimestamp) {

			if (dbTimestamp > 0) {

				// KLAB.info("NAMESPACE " + namespace + " BEING RECYCLED");

				// DataRecorder.debug("Removing all models in namespace " +
				// namespace.getId());
				int n = 0;
				for (long oid : getIdsInNamespace(namespace.getId())) {
					deleteModelData(oid);
					n++;
				}
				// DataRecorder.debug("Removed " + n + " models");

				database.execute("DELETE FROM namespaces where id = '" + namespace.getId() + "';");
			}

			// DataRecorder.debug("Refreshing " + namespace.getId() + ": stored
			// " + new
			// Date(dbTimestamp)
			// + " < "
			// + new Date(timestamp));

			return 1;
		}

		/*
		 * if we have not changed the source file but models had errors when
		 * stored, return the conservative mode so we can check model by model
		 * and only store those that are no longer in error due to external
		 * reasons.
		 */
		if (namespace != null && namespace.hasErrors()) {
			return 2;
		}

		return 0;
	}

	protected void deleteModelData(long oid) throws KlabException {
		database.execute("DELETE FROM model WHERE oid = " + oid);
		database.execute("DELETE FROM metadata WHERE fid = " + oid);
		database.execute("DELETE FROM traitpairs WHERE fid = " + oid);
		database.execute("DELETE FROM observable WHERE fid = " + oid);
	}

	public int clearNamespace(String namespaceId) throws KlabException {

		if (!database.hasTable("model")) {
			return 0;
		}

		// KLAB.info("CLEARING NAMESPACE " + namespaceId);

		int n = 0;
		for (long oid : database
				.queryIds("SELECT oid FROM model where namespaceid = '" + Escape.forSQL(namespaceId) + "';")) {
			deleteModelData(oid);
			n++;
		}

		database.execute("DELETE FROM namespaces where id = '" + namespaceId + "';");

		return n;
	}

	/**
	 * Return 0 if namespace is not in the kbox, or the (long) timestamp of the
	 * namespace if it is.
	 * 
	 * @return result code
	 * @throws KlabException
	 */
	public long getNamespaceTimestamp(INamespace namespace) throws KlabException {

		if (!database.hasTable("namespaces")) {
			return 0l;
		}
		List ret = database.queryIds("SELECT timestamp FROM namespaces WHERE id = '" + namespace.getId() + "';");
		return ret.size() > 0 ? ret.get(0) : 0l;
	}

	protected List getIdsInNamespace(String namespaceId) throws KlabException {

		if (!database.hasTable("model")) {
			return new ArrayList<>();
		}

		return database.queryIds("SELECT oid FROM model WHERE namespaceid = '" + namespaceId + "';");
	}

	/**
	 * Count the models in the db.
	 * 
	 * @return number of models in database.
	 */
	public long count() {

		try {
			if (!database.hasTable("model")) {
				return 0;
			}
			List ret = database.queryIds("SELECT COUNT(*) from model;");
			return ret.size() > 0 ? ret.get(0) : 0l;
		} catch (KlabException e) {
			throw new KlabRuntimeException(e);
		}
	}

	class ModelSerializer extends H2Serializer {

		@Override
		public String serialize(Object o, Schema schema, long primaryKey, long foreignKey) {

			String ret = null;

			if (o instanceof ModelData) {

				ModelData md = (ModelData) o;

				ret = "INSERT INTO model VALUES (" + primaryKey + ", " + "'" + cn(md.serverId) + "', "// +
																										// "serverid
																										// VARCHAR(64),
																										// "
						+ "'" + cn(md.id) + "', "// + "id VARCHAR(128), "
						+ "'" + cn(md.name) + "', "// + "name VARCHAR(256), "
						+ "'" + cn(md.namespaceId) + "', "// + "namespaceid
															// VARCHAR(128), "
						+ "'" + cn(md.projectId) + "', "// + "projectid
														// VARCHAR(128), "
						+ "'" + cn(checkSemantics(md.type)) + "', "// + "type
																	// VARCHAR(256),
																	// "
						+ "'" + cn(md.oType) + "', "// + "otype VARCHAR(256), "
						+ "'" + cn(checkSemantics(md.cType)) + "', "// + "ctype
																	// VARCHAR(256),
																	// "
						+ (md.namespacePrivate ? "TRUE" : "FALSE") + ", "// +
																			// "isprivate
																			// BOOLEAN,
																			// "
						+ "'" + cn(md.tType) + "', "// + "ttype VARCHAR(256), "
						+ (md.resolved ? "TRUE" : "FALSE") + ", "// +
																	// "isresolved
																	// BOOLEAN,
																	// "
						+ (md.computed ? "TRUE" : "FALSE") + ", "// +
																	// "iscomputed
																	// BOOLEAN,
																	// "
						+ (md.reifying ? "TRUE" : "FALSE") + ", "// +
																	// "isreification
																	// BOOLEAN,
																	// "
						+ (md.inScenario ? "TRUE" : "FALSE") + ", "// +
																	// "inscenario
																	// BOOLEAN,
																	// "
						+ (md.hasDirectObjects ? "TRUE" : "FALSE") + ", "// +
																			// "hasdirectobjects
																			// BOOLEAN,
																			// "
						+ (md.hasDirectData ? "TRUE" : "FALSE") + ", "// +
																		// "hasdirectdata
																		// BOOLEAN,
																		// "
						+ md.timeStart + ", "// + "timestart LONG, "
						+ md.timeEnd + ", "// + "timeend LONG, "
						+ (md.isSpatial ? "TRUE" : "FALSE") + ", "// +
																	// "isspatial
																	// BOOLEAN,
																	// "
						+ (md.isTemporal ? "TRUE" : "FALSE") + ", "// +
																	// "istemporal
																	// BOOLEAN,
																	// "
						+ md.timeMultiplicity + ", "// + "timemultiplicity LONG,
													// "
						+ md.spaceMultiplicity + ", "// + "spacemultiplicity
														// LONG, "
						+ md.scaleMultiplicity + ", "// + "scalemultiplicity
														// LONG, "
						+ "'" + cn(md.dereifyingAttribute) + "', "// +
																	// "dereifyingattribute
																	// VARCHAR(256),
																	// "
						+ md.discreteLevelsCount + ", "// + "discretelevelscount
														// INTEGER, "
						+ "'"
						+ (md.spaceExtent == null ? "GEOMETRYCOLLECTION EMPTY"
								: md.spaceExtent.getStandardizedGeometry().toString())// +
						+ "', '" + cn(checkSemantics(md.iType)) + "', "// +
																		// "itype
																		// VARCHAR(256),
																		// "
						// "space GEOMETRY"
						+ md.downTo // detail-level SHORT
						+ ");";

				for (Observable obs : md.observables) {

					ret += " INSERT INTO observable VALUES (" + primaryKey + ", "// +
																					// "fid
																					// LONG,
																					// "
							+ "'" + cn(checkSemantics(obs.mainType)) + "', "// +
																			// "type
																			// VARCHAR(256),
																			// "
							+ "'" + cn(obs.mainProperty) + "', "// + "ptype
																// VARCHAR(256),
																// "
							+ "'" + cn(obs.obsType) + "', "// + "otype
															// VARCHAR(256), "
							+ "'" + cn(checkSemantics(obs.subjType)) + "', "// +
																			// "stype
																			// VARCHAR(256),
																			// "
							+ "'" + cn(obs.traiType) + "', "// + "ttype
															// VARCHAR(256), "
							+ "'" + cn(obs.formalName) + "', "// + "fname
																// VARCHAR(256)"
							+ "'" + cn(checkSemantics(obs.inhrType)) + "', " // +
																				// "itype
							// VARCHAR(256),
							// "
							+ "" + obs.downTo // + "dtlvl SHORT "
							+ ");";

				}

				if (md.traits != null) {
					for (ConceptPair cp : md.traits) {
						ret += " INSERT INTO traitpairs VALUES (" + primaryKey + ", "// +
																						// "fid
																						// LONG,
																						// "
								+ "'" + cp.getFirst() + "', "// + "basetrait
																// VARCHAR(256),
																// "
								+ "'" + cp.getSecond() + "'"// + "traitvalue
															// VARCHAR(256)"
								+ ");";
					}
				}

				if (md.metadata != null) {
					for (String s : md.metadata.getKeys()) {

						String sql = " INSERT INTO metadata VALUES (" + primaryKey + ", "// +
																							// "fid
																							// LONG,
																							// "
								+ "'" + s + "', "// + "key VARCHAR(256), "
								+ "?"// + "value OTHER"
								+ ")";
						try {
							/*
							 * OK, must execute these right away unfortunately -
							 * so if something goes wrong with the object's
							 * storage these will remain in the DB.
							 */
							PreparedStatement prsql = database.getConnection().prepareStatement(sql);
							prsql.setObject(1, md.metadata.get(s), Types.JAVA_OBJECT);
							prsql.executeUpdate();
						} catch (Exception e) {
							throw new KlabRuntimeException(e);
						}

					}
				}
			} else if (o instanceof INamespace) {

				INamespace ns = (INamespace) o;
				ret = "INSERT INTO namespaces VALUES ('" + ns.getId() + "', " + ns.getTimeStamp() + ", "
						+ (ns.isScenario() ? "TRUE" : "FALSE") + ");";

			} else {
				throw new KlabRuntimeException(
						"ModelKbox can only store models or namespaces: " + o.getClass().getSimpleName());
			}

			return ret;
		}

		private String cn(Object o) {
			return o == null ? "" : o.toString();
		}
	}

	class ModelDeserializer implements DirectDeserializer {

		@Override
		public void setKbox(IKbox h2Kbox) {
		}

		@Override
		public Object deserialize(ResultSet rs) {

			ModelData ret = null;

			try {
				long oid = rs.getLong(1);
				// TODO
			} catch (SQLException e) {
				throw new KlabRuntimeException(e);
			}
			return ret;
		}

	}

	static public class NamespaceSchema extends CompoundSchema {

		public NamespaceSchema(Class cls) {
			super(cls);
		}

		@Override
		public String getCreateSQL() {
			return "CREATE TABLE namespaces (" + "id VARCHAR(256) PRIMARY KEY, " + "timestamp LONG, "
					+ "isscenario BOOLEAN" + "); " + "CREATE INDEX namespace_id_index ON namespaces(id); ";
		}

		@Override
		public String getTableName() {
			return "namespaces";
		}
	}

	/*
	 * exposed to allow preallocating connections in big imports.
	 */
	public H2Database getDatabase() {
		return this.database;
	}

	class ModelSchema extends CompoundSchema {

		public ModelSchema(Class cls) {
			super(cls);
		}

		@Override
		public String getCreateSQL() {
			return "CREATE TABLE model (" + "oid LONG, " + "serverid VARCHAR(64), " + "id VARCHAR(128), "
					+ "name VARCHAR(256), " + "namespaceid VARCHAR(128), " + "projectid VARCHAR(128), "
					+ "type VARCHAR(256), " + "otype VARCHAR(256), " + "ctype VARCHAR(256), " + "isprivate BOOLEAN, "
					+ "ttype VARCHAR(256), " + "isresolved BOOLEAN, " + "iscomputed BOOLEAN, "
					+ "isreification BOOLEAN, " + "inscenario BOOLEAN, " + "hasdirectobjects BOOLEAN, "
					+ "hasdirectdata BOOLEAN, " + "timestart LONG, " + "timeend LONG, " + "isspatial BOOLEAN, "
					+ "istemporal BOOLEAN, " + "timemultiplicity LONG, " + "spacemultiplicity LONG, "
					+ "scalemultiplicity LONG, " + "dereifyingattribute VARCHAR(256), "
					+ "discretelevelscount INTEGER, " + "space GEOMETRY, " + "itype VARCHAR(256), " + "dtlvl INTEGER "
					+ "); " + "CREATE TABLE observable (" + "fid LONG, " + "type VARCHAR(256), "
					+ "ptype VARCHAR(256), " + "otype VARCHAR(256), " + "stype VARCHAR(256), " + "ttype VARCHAR(256), "
					+ "fname VARCHAR(256), " + "itype VARCHAR(256), " + "dtlvl INTEGER" + "); "
					+ "CREATE TABLE traitpairs (" + "fid LONG, " + "basetrait VARCHAR(256), "
					+ "traitvalue VARCHAR(256)" + "); " + "CREATE TABLE metadata (" + "fid LONG, "
					+ "key VARCHAR(256), " + "value OTHER" + ");"

					+ "CREATE INDEX model_oid_index ON model(oid); "
					+ "CREATE INDEX metadata_oid_index ON metadata(fid); "
					+ "CREATE INDEX traitpairs_oid_index ON traitpairs(fid); "
					+ "CREATE INDEX observable_oid_index ON observable(fid); "
					+ "CREATE INDEX model_name_index ON model(name); "
					+ "CREATE SPATIAL INDEX model_space ON model(space);";
		}

		@Override
		public String getTableName() {
			return "model";
		}

	}

	public ModelKbox(String name, IMonitor monitor) {
		super(name, monitor);
		setSerializer(new ModelSerializer());
		setDeserializer(new ModelDeserializer());
		setSchema(ModelData.class, new ModelSchema(ModelData.class));
		setSchema(INamespace.class, new NamespaceSchema(ModelData.class));
	}

	public Object checkSemantics(IKnowledge k) {
		if (k == null) {
			return null;
		}
		return ((Knowledge) k).asText();
	}

	ModelData deserialize(Long oid) throws KlabException {

		final ModelData ret = new ModelData();

		database.query("SELECT * FROM model WHERE oid = " + oid, new SQL.SimpleResultHandler() {
			@Override
			public void onRow(ResultSet rs) {

				try {

					SpatialResultSet srs = rs.unwrap(SpatialResultSet.class);

					// + "oid LONG, "
					ret.serverId = nullify(srs.getString(2)); // + "serverid
																// VARCHAR(64),
					// "
					ret.id = srs.getString(3); // + "id VARCHAR(128), "
					ret.name = srs.getString(4); // + "name VARCHAR(256), "
					ret.namespaceId = srs.getString(5); // + "namespaceid
														// VARCHAR(128), "
					ret.projectId = nullify(srs.getString(6)); // + "projectid
					// VARCHAR(128), "
					ret.type = Knowledge.parse(srs.getString(7)); // + "type
																	// VARCHAR(256),
					// "
					ret.oType = KLAB.c(srs.getString(8));// + "otype
															// VARCHAR(256), "
					ret.cType = Knowledge.parse(srs.getString(9)); // + "ctype
					// VARCHAR(256), "
					ret.namespacePrivate = srs.getBoolean(10); // + "isprivate
																// BOOLEAN, "
					ret.tType = KLAB.c(srs.getString(11)); // + "ttype
															// VARCHAR(256), "
					ret.resolved = srs.getBoolean(12);// + "isresolved BOOLEAN,
														// "
					ret.computed = srs.getBoolean(13);// + "iscomputed BOOLEAN,
														// "
					ret.reifying = srs.getBoolean(14);// + "isreification
														// BOOLEAN, "
					ret.inScenario = srs.getBoolean(15);// + "inscenario
														// BOOLEAN, "
					ret.hasDirectObjects = srs.getBoolean(16);// +
																// "hasdirectobjects
																// BOOLEAN, "
					ret.hasDirectData = srs.getBoolean(17); // + "hasdirectdata
															// BOOLEAN, "
					ret.timeStart = srs.getLong(18); // + "timestart LONG, "
					ret.timeEnd = srs.getLong(19); // + "timeend LONG, "
					ret.isSpatial = srs.getBoolean(20); // + "isspatial BOOLEAN,
														// "
					ret.isTemporal = srs.getBoolean(21);// + "istemporal
														// BOOLEAN, "
					ret.timeMultiplicity = srs.getLong(22); // +
															// "timemultiplicity
															// LONG, "
					ret.spaceMultiplicity = srs.getLong(23); // +
																// "spacemultiplicity
																// LONG,
					// "
					ret.scaleMultiplicity = srs.getLong(24);// +
															// "scalemultiplicity
															// LONG,
															// "
					ret.dereifyingAttribute = nullify(srs.getString(25)); // +
					// "dereifyingattribute
					// VARCHAR(256),
					// "
					ret.discreteLevelsCount = srs.getInt(26);// +
																// "discretelevelscount
																// INTEGER, "
					Geometry geometry = srs.getGeometry(27); // "space GEOMETRY"
					if (!geometry.isEmpty()) {
						ret.spaceExtent = new ShapeValue(geometry, Geospace.get().getDefaultCRS()); // +
					}
					ret.iType = Knowledge.parse(srs.getString(28)); // + "itype
					// VARCHAR(256)
					ret.downTo = srs.getInt(29); // + "dtlvl INTEGER

				} catch (SQLException e) {
					throw new KlabRuntimeException(e);
				}
			}

		});

		database.query("SELECT * FROM observable WHERE fid = " + oid, new SQL.SimpleResultHandler() {
			@Override
			public void onRow(ResultSet rs) {
				try {

					ModelData.Observable o = new ModelData.Observable();

					// + "fid LONG, "
					o.mainType = Knowledge.parse(rs.getString(2)); // + "type
					// VARCHAR(256), "
					o.mainProperty = KLAB.p(rs.getString(3));// + "ptype
																// VARCHAR(256),
																// "
					o.obsType = KLAB.c(rs.getString(4)); // + "otype
															// VARCHAR(256), "
					o.subjType = Knowledge.parse(rs.getString(5)); // + "stype
					// VARCHAR(256), "
					o.traiType = KLAB.c(rs.getString(6));// + "ttype
															// VARCHAR(256), "
					o.formalName = nullify(rs.getString(7)); // + "fname
																// VARCHAR(256)"
					o.inhrType = Knowledge.parse(rs.getString(8)); // + "itype
					// VARCHAR(256), "
					o.downTo = rs.getInt(9); // + "dtlvl INTEGER "

					ret.observables.add(o);

				} catch (SQLException e) {
					throw new KlabRuntimeException(e);
				}
			}
		});

		database.query("SELECT * FROM metadata WHERE fid = " + oid, new SQL.SimpleResultHandler() {
			@Override
			public void onRow(ResultSet rs) {
				try {

					// + "fid LONG, "
					String key = rs.getString(2); // + "key VARCHAR(256), "
					Object value = rs.getObject(3); // + "value OTHER"

					if (key != null && value != null) {
						ret.metadata.put(key, value);
					}

				} catch (SQLException e) {
					throw new KlabRuntimeException(e);
				}
			}
		});

		database.query("SELECT * FROM traitpairs WHERE fid = " + oid, new SQL.SimpleResultHandler() {
			@Override
			public void onRow(ResultSet rs) {
				try {

					IConcept b = null, t = null;
					// + "fid LONG, "
					b = KLAB.c(rs.getString(2)); // + "basetrait VARCHAR(256), "
					t = KLAB.c(rs.getString(3)); // + "traitvalue VARCHAR(256)"

					if (ret.traits == null) {
						ret.traits = new ArrayList<>();
					}

					ret.traits.add(new ConceptPair(b, t));

				} catch (SQLException e) {
					throw new KlabRuntimeException(e);
				}
			}
		});

		return ret;
	}

	private static String nullify(String string) {
		if (string == null || string.isEmpty())
			return null;
		return string;
	}
}