
org.integratedmodelling.engine.modelling.kbox.ObservationKbox Maven / Gradle / Ivy
/*******************************************************************************
* 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.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.h2gis.utilities.SpatialResultSet;
import org.integratedmodelling.api.auth.IUser;
import org.integratedmodelling.api.knowledge.IConcept;
import org.integratedmodelling.api.metadata.IMetadata;
import org.integratedmodelling.api.modelling.IDirectObserver;
import org.integratedmodelling.api.modelling.INamespace;
import org.integratedmodelling.api.modelling.IScale;
import org.integratedmodelling.api.monitoring.IMonitor;
import org.integratedmodelling.api.persistence.IKbox;
import org.integratedmodelling.api.project.IProject;
import org.integratedmodelling.api.space.ISpatialExtent;
import org.integratedmodelling.api.time.ITemporalExtent;
import org.integratedmodelling.common.ConceptPair;
import org.integratedmodelling.common.command.ServiceCall;
import org.integratedmodelling.common.command.ServiceManager;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.indexing.KnowledgeIndex;
import org.integratedmodelling.common.owl.Knowledge;
import org.integratedmodelling.common.space.IGeometricShape;
import org.integratedmodelling.common.utils.Escape;
import org.integratedmodelling.common.vocabulary.ObservationMetadata;
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.ModelKbox.NamespaceSchema;
import org.integratedmodelling.exceptions.KlabException;
import org.integratedmodelling.exceptions.KlabRuntimeException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.io.WKBWriter;
/**
* Kbox to hold finished observations - the ones specified with 'observe'. Supports
* free text search (using Lucene on metadata) and automatically broadcasts the query
* to the network. The ObservationKbox trades in ObservationMetadata, not in subject
* observers, to prevent failures in retrieval due to projects not loaded at the
* engine side (although only proper subject observers are stored as metadata).
*
* @author ferdinando.villa
*
*/
public class ObservationKbox extends H2Kbox {
KnowledgeIndex index;
boolean needsReindex = false;
/**
* The kbox version, which is used to create the filesystem storage.
* Change this when incompatible changes are made to force kbox reset.
*/
public static final String KBOX_VERSION = "098v3";
static ObservationKbox _this;
/**
* Return the single instance of the observation kbox.
* @return the kbox
*/
public static ObservationKbox get() {
if (_this == null) {
H2Kbox.set("observations_"
+ KBOX_VERSION, new ObservationKbox("observations_" + KBOX_VERSION, KLAB.ENGINE
.getMonitor()));
_this = (ObservationKbox) H2Kbox.get("observations_" + KBOX_VERSION);
KLAB.info("observation kbox created: " + _this.count() + " observations are available");
}
return _this;
}
/*
* TODO - this and the one in ModelKbox
*/
private String timeQuery(ITemporalExtent time) {
String ret = "";
// TODO Auto-generated method stub
return ret;
}
/*
* exposed to allow preallocating connections in big imports.
*/
public H2Database getDatabase() {
return this.database;
}
private String spaceQuery(ISpatialExtent space) {
return "observation.space && '"
+ ((IGeometricShape) (space.getExtent().getShape())).getStandardizedGeometry()
+ "'";
}
private String observableQuery(Collection observables) {
return joinStringConditions("observation.type", observables, "OR");
}
public List query(Collection concepts, IScale scale, Collection scenarios, boolean localOnly)
throws KlabException {
Set local = new HashSet<>();
List ret = new ArrayList<>();
/*
* only query locally if we've seen an observation before.
*/
for (ObservationMetadata md : queryLocal(concepts, scale, scenarios)) {
if (local.add(md)) {
ret.add(md);
}
}
/*
* broadcast call for search function on network.
*/
// if (!localOnly && KLAB.NETWORK.isPersonal() && ((EngineNetwork)
// KLAB.NETWORK).provides("im.search")) {
ServiceCall scl = null;
// ServiceManager
// .getServiceCall("thinklab.search-observations", "type", StringUtils
// .joinObjects(concepts, ','), "scenarios", StringUtils
// .join(scenarios, ','), "scale", Scale.asString(scale));
// if (scl != null) {
//
// scl.setMonitor(KLAB.ENGINE.getMonitor());
// Object mdd = ((EngineNetwork) KLAB.NETWORK).broadcast(scl.post(), scl.getMonitor());
//
// if (mdd instanceof Collection>) {
// for (Object md : ((Collection>) mdd)) {
// if (md instanceof ObservationMetadata) {
// if (local.add((ObservationMetadata) md)) {
// ret.add((ObservationMetadata) md);
// }
// // TODO should have ranks from search somewhere
// }
// }
// }
// }
// }
return ret;
}
public List queryLocal(Collection observable, IScale scale, Collection scenarios)
throws KlabException {
List ret = new ArrayList<>();
if (!database.hasTable("observation")) {
return ret;
}
String query = "SELECT observation.oid FROM observation WHERE (";
// TODO query += "(" + scopeQuery(scenarios) + ")";
query += observableQuery(observable) + ")";
if (scale != null) {
if (scale.getSpace() != null) {
query += " AND (" + spaceQuery(scale.getSpace()) + ")";
}
String tquery = timeQuery(scale.getTime());
if (!tquery.isEmpty()) {
query += " AND (" + tquery + ");";
}
}
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
KLAB.warn("kbox is out of sync with knowledge base: " + e.getMessage());
}
}
KLAB.info(ret.size() + " observations of " + observable + " retrieved");
return ret;
}
public List queryLocal(String queryString) throws KlabException {
List ret = new ArrayList<>();
Set local = new HashSet<>();
for (IMetadata m : index.search(queryString)) {
ObservationMetadata md = new ObservationMetadata();
md.name = m.getString("id");
md.id = m.getString("name");
md.namespaceId = m.getString("namespace");
md.observableName = m.getString("observable");
md.description = m.getString("description");
md.serverId = KLAB.NAME;
if (local.add(md)) {
ret.add(md);
}
}
return ret;
}
public List query(String freeText, boolean localOnly) throws KlabException {
Set local = new HashSet<>();
List ret = new ArrayList<>();
/**
* Quick and dirty: handle the * as a catch-all for anything stored locally; otherwise if the string is a concept, we just
* look for observations of that concept. Otherwise we move on to using the Lucene index.
*/
if (freeText.equals("*")) {
return retrieveAll();
} else if (KLAB.KM.getConcept(freeText) != null) {
return query(Collections.singleton(KLAB.c(freeText)), null, null, localOnly);
}
for (ObservationMetadata md : queryLocal(freeText)) {
local.add(md);
ret.add(md);
}
/*
* broadcast call for search function on network.
*/
// if (!localOnly && KLAB.NETWORK.isPersonal() && ((EngineNetwork)
// KLAB.NETWORK).provides("im.search")) {
// TODO all the rest if we have a context, too.
ServiceCall scl = ServiceManager
.getServiceCall("thinklab.search-observations", "query-string", freeText);
if (scl != null) {
// scl.setMonitor(KLAB.ENGINE.getMonitor());
// Object mdd = ((EngineNetwork) KLAB.NETWORK).broadcast(scl.post(), scl.getMonitor());
//
// if (mdd instanceof Collection>) {
// for (Object md : ((Collection>) mdd)) {
//
// /*
// * Only return those observations whose observable we recognize
// * finished objects should come out but they don't and it's a subtlety for now.
// * FIXME see why.
// */
// if (md instanceof Map && ((Map, ?>) md).containsKey("observable-name")) {
//// ObservationMetadata met = new ObservationMetadata((Map, ?>) md);
//// if (local.add(met)) {
//// ret.add(met);
//// }
// }
//
// }
// }
}
// }
return ret;
}
private String joinStringConditions(String field, Collection> stringValues, String operator) {
String ret = "";
for (Object o : stringValues) {
ret += (ret.isEmpty() ? "" : (" " + operator + " ")) + field + " = '" + o + "'";
}
return ret;
}
public boolean hasObservation(String name) throws KlabException {
if (!database.hasTable("observation")) {
return false;
}
return database.queryIds("SELECT oid FROM observation WHERE name = '" + Escape.forSQL(name) + "';")
.size() > 0;
}
@Override
public long store(Object o) throws KlabException {
if (o instanceof IDirectObserver) {
KLAB.info("storing observation " + o);
index.index(o);
needsReindex = true;
deleteNamed(((IDirectObserver) o).getName());
}
return super.store(o);
}
public String getUrnForStoredObservation(long id, IUser user) {
return KLAB.ENGINE.getName() + ":" + user.getUsername() + ":observations:" + id;
}
protected void deleteObservation(long oid) throws KlabException {
database.execute("DELETE FROM observation WHERE oid = " + oid);
database.execute("DELETE FROM observation WHERE fid = " + oid);
database.execute("DELETE FROM metadata WHERE fid = " + oid);
database.execute("DELETE FROM state WHERE fid = " + oid);
database.execute("DELETE FROM traitpairs WHERE fid = " + oid);
}
public int clearNamespace(String namespaceId) throws KlabException {
if (!database.hasTable("observation")) {
return 0;
}
int n = 0;
for (long oid : database.queryIds("SELECT oid FROM observation where namespaceid = '"
+ Escape.forSQL(namespaceId) + "';")) {
deleteObservation(oid);
n++;
}
database.execute("DELETE FROM namespaces where id = '" + namespaceId + "';");
return n;
}
public void deleteNamed(String name) throws KlabException {
if (!database.hasTable("observation")) {
return;
}
List delenda = new ArrayList<>();
for (long oid : database.queryIds("SELECT oid FROM observation WHERE name ='" + Escape.forSQL(name)
+ "';")) {
delenda.add(oid);
}
for (long oid : delenda) {
deleteObservation(oid);
}
}
public List retrieveAll() throws KlabException {
List ret = new ArrayList<>();
if (!database.hasTable("observation")) {
return ret;
}
for (long oid : database.queryIds("SELECT oid FROM observation;")) {
ret.add(deserialize(oid));
}
return ret;
}
/**
* Count the observations in the db.
*
* @return number of observations
*/
public long count() {
try {
if (!database.hasTable("observation")) {
return 0;
}
List ret = database.queryIds("SELECT COUNT(*) from observation;");
return ret.size() > 0 ? ret.get(0) : 0l;
} catch (KlabException e) {
throw new KlabRuntimeException(e);
}
}
class ObservationSerializer extends H2Serializer {
@Override
public String serialize(Object o, Schema schema, long primaryKey, long foreignKey) {
String ret = null;
if (o instanceof IDirectObserver) {
IDirectObserver md = (IDirectObserver) o;
if (((IDirectObserver) o).getObservable() == null
|| ((IDirectObserver) o).getObservable().getType() == null) {
return null;
}
IScale scale = md.getCoverage(KLAB.ENGINE.getMonitor());
ISpatialExtent space = scale.getSpace();
IProject project = md.getNamespace().getProject();
ITemporalExtent time = scale.getTime();
ITemporalExtent timeExtent = null;
if (time != null) {
timeExtent = time.getExtent();
}
// todo
ArrayList traits = new ArrayList<>();
ret = "INSERT INTO observation VALUES ("
+ primaryKey + ", "
+ 0 + ", " // TODO foreign key
+ "'" + KLAB.NAME + "', "// + "serverid VARCHAR(64), "
+ "'" + Escape.forSQL(md.getId()) + "', "// + "id VARCHAR(128), "
+ "'" + Escape.forSQL(md.getName()) + "', "// + "name VARCHAR(256), "
+ "'" + Escape.forSQL(md.getNamespace().getId()) + "', "// +
// "namespaceid VARCHAR(128),
// "
+ "'" + (project == null ? "" : project.getId()) + "', "// +
// "projectid VARCHAR(128), "
+ "'" + ((Knowledge) md.getObservable().getType()).asText() + "', "// +
// "type
// VARCHAR(256), "
+ (md.getNamespace().isScenario() ? "TRUE" : "FALSE") + ", "// +
// "inscenario BOOLEAN, "
+ (timeExtent == null ? 0l : timeExtent.getStart().getMillis()) + ", "// +
// "timestart
// LONG, "
+ (timeExtent == null ? 0l : timeExtent.getEnd().getMillis()) + ", "// +
// "timeend LONG,
// "
+ (time == null ? 0l : time.getMultiplicity()) + ", "// + "timestep LONG, "
+ (space != null ? "TRUE" : "FALSE") + ", "// + "isspatial BOOLEAN, "
+ (time != null ? "TRUE" : "FALSE") + ", "// +
// "istemporal BOOLEAN, "
+ (time == null ? 0l : time.getMultiplicity()) + ", "// + "timemultiplicity LONG, "
+ (space == null ? 0l : space.getMultiplicity()) + ", "// + "spacemultiplicity LONG, "
+ scale.getMultiplicity() + ", "// + "scalemultiplicity LONG, "
+ "'" + (space == null ? "GEOMETRYCOLLECTION EMPTY" : ((IGeometricShape) space)
.getStandardizedGeometry().toString())// + "space GEOMETRY"
+ "'); ";
if (traits != null) {
for (ConceptPair cp : traits) {
ret += "\nINSERT INTO traitpairs VALUES ("
+ primaryKey + ", "// + "fid LONG, "
+ "'" + cp.getFirst() + "', "// + "basetrait VARCHAR(256), "
+ "'" + cp.getSecond() + "', "// + "traitvalue VARCHAR(256)"
+ "); ";
}
}
if (md.getMetadata() != null) {
for (String s : md.getMetadata().getKeys()) {
// TODO
}
}
} else if (o instanceof INamespace) {
INamespace ns = (INamespace) o;
ret = "INSERT INTO namespaces VALUES ('" + Escape.forSQL(ns.getId()) + "', "
+ ns.getTimeStamp() + ", "
+ (ns.isScenario() ? "TRUE" : "FALSE") + ");";
} else {
throw new KlabRuntimeException("ObservationKbox can only store models or namespaces: "
+ o.getClass().getSimpleName());
}
return ret;
}
private String cn(Object o) {
return o == null ? "" : o.toString();
}
}
class ObservationDeserializer implements DirectDeserializer {
@Override
public void setKbox(IKbox h2Kbox) {
}
@Override
public ObservationMetadata deserialize(ResultSet rs) {
ObservationMetadata ret = null;
WKBWriter wkbWriter = new WKBWriter();
try {
SpatialResultSet srs = rs.unwrap(SpatialResultSet.class);
String name = rs.getString(5);
String concept = srs.getString(8);
Geometry geometry = srs.getGeometry(18); // "space GEOMETRY"
if (geometry.isEmpty()) {
geometry = null;
}
ret = new ObservationMetadata();
ret.name = name;
ret.id = rs.getString(4);
ret.observableName = concept;
ret.serverId = KLAB.NAME;
ret.namespaceId = srs.getString(6);
if (geometry != null) {
ret.geometryWKB = WKBWriter.toHex(wkbWriter.write(geometry));
}
ret.start = srs.getLong(10);
ret.end = srs.getLong(11);
ret.step = srs.getLong(12);
// 1 + "oid LONG, "
// 2 + "fid LONG, "
// 3 + "serverid VARCHAR(64), "
// 4 + "id VARCHAR(128), "
// 5 + "name VARCHAR(256), "
// 6 + "namespaceid VARCHAR(128), "
// 7 + "projectid VARCHAR(128), "
// 8 + "type VARCHAR(256), "
// 9 + "inscenario BOOLEAN, "
// 10 + "timestart LONG, "
// 11 + "timeend LONG, "
// 12 + "timestep LONG, "
// 13 + "isspatial BOOLEAN, "
// 14 + "istemporal BOOLEAN, "
// 15 + "timemultiplicity LONG, "
// 16 + "spacemultiplicity LONG, "
// 17 + "scalemultiplicity LONG, "
// 18 + "space GEOMETRY"
// TODO
} catch (Exception e) {
throw new KlabRuntimeException(e);
}
return ret;
}
}
class ObservationSchema extends CompoundSchema {
public ObservationSchema() {
super(IDirectObserver.class);
}
@Override
public String getCreateSQL() {
return "CREATE TABLE observation ("
+ "oid LONG PRIMARY KEY, "
+ "fid LONG, "
+ "serverid VARCHAR(64), "
+ "id VARCHAR(128), "
+ "name VARCHAR(256), "
+ "namespaceid VARCHAR(128), "
+ "projectid VARCHAR(128), "
+ "type VARCHAR(1024), "
+ "inscenario BOOLEAN, "
+ "timestart LONG, "
+ "timeend LONG, "
+ "timestep LONG, "
+ "isspatial BOOLEAN, "
+ "istemporal BOOLEAN, "
+ "timemultiplicity LONG, "
+ "spacemultiplicity LONG, "
+ "scalemultiplicity LONG, "
+ "space GEOMETRY"
+ "); "
+ "CREATE TABLE state ("
+ "fid LONG, "
+ "type VARCHAR(256), "
+ "ptype VARCHAR(256), "
+ "otype VARCHAR(256), "
+ "stype VARCHAR(256), "
+ "ttype VARCHAR(256), "
+ "value OTHER, "
+ "fname VARCHAR(256)"
+ "); "
+ "CREATE TABLE traitpairs ("
+ "fid LONG, "
+ "basetrait VARCHAR(256), "
+ "traitvalue VARCHAR(256)"
+ "); "
+ "CREATE TABLE metadata ("
+ "fid LONG, "
+ "key VARCHAR(256), "
+ "value OTHER"
+ ");"
+ "CREATE INDEX observation_oid_index ON observation(oid); "
+ "CREATE INDEX observation_fid_index ON observation(fid); "
+ "CREATE INDEX observation_name_index ON observation(name); "
+ "CREATE INDEX metadata_oid_index ON metadata(fid); "
+ "CREATE INDEX traitpairs_oid_index ON traitpairs(fid); "
+ "CREATE SPATIAL INDEX observation_space ON observation(space);";
}
@Override
public String getTableName() {
return "observation";
}
}
public ObservationKbox(String name, IMonitor monitor) {
super(name, monitor);
setSerializer(new ObservationSerializer());
setDeserializer(new ObservationDeserializer());
setSchema(IDirectObserver.class, new ObservationSchema());
setSchema(INamespace.class, new NamespaceSchema(ModelData.class));
try {
index = new KnowledgeIndex(name);
} catch (KlabException e) {
throw new KlabRuntimeException();
}
}
/**
* 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 result code
*/
public int removeIfOlder(INamespace namespace) throws KlabException {
if (!database.hasTable("namespaces")) {
return 1;
}
long dbTimestamp = getNamespaceTimestamp(namespace);
long timestamp = namespace.getTimeStamp();
/*
* 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) {
monitor.debug("Removing all observations in namespace " + namespace.getId());
int removed = clearNamespace(namespace.getId());
monitor.debug("Removed " + removed + " observations.");
}
monitor.debug("Refreshing observations in " + 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;
}
/**
* 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 = '"
+ Escape.forSQL(namespace.getId())
+ "';");
return ret.size() > 0 ? ret.get(0) : 0l;
}
public ObservationMetadata retrieveByName(String name) throws KlabException {
if (!database.hasTable("observation")) {
return null;
}
final ObservationDeserializer deserializer = new ObservationDeserializer();
final ArrayList ret = new ArrayList<>();
database.query("SELECT * FROM observation WHERE name = '" + Escape.forSQL(name)
+ "';", new SQL.SimpleResultHandler() {
@Override
public void onRow(ResultSet rs) {
ret.add(deserializer.deserialize(rs));
}
});
return ret.size() > 0 ? ret.get(0) : null;
}
public ObservationMetadata deserialize(Long oid) throws KlabException {
final ObservationDeserializer deserializer = new ObservationDeserializer();
final ArrayList ret = new ArrayList<>();
database.query("SELECT * FROM observation WHERE oid = " + oid, new SQL.SimpleResultHandler() {
@Override
public void onRow(ResultSet rs) {
ret.add(deserializer.deserialize(rs));
}
});
return ret.size() > 0 ? ret.get(0) : null;
}
private static String nullify(String string) {
if (string == null || string.isEmpty())
return null;
return string;
}
public void reindexLocalObservations() throws KlabException {
if (needsReindex) {
needsReindex = false;
index.reindex();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy