JVM libraries for Apache Ossie / Open Semantic Interchange.
Two artifacts:
- ossie-core — DTOs + YAML/JSON reader / writer. Zero dependencies beyond Jackson. Use this when you need to read, emit, or round-trip OSI semantic model documents on the JVM.
- ossie-sql — Apache Calcite adapter + shelf-state query engine. Point it at an OSI YAML plus a JDBC URL for any relational warehouse and get a query surface (typed shelf-state builder OR raw SQL passthrough) with cross-dataset joins, metric composition, and dialect handling.
Both are Apache 2.0, JDK 21+, and designed to be used outside the Saiku project.
Read, write, and round-trip Ossie semantic model documents. Zero dependencies beyond Jackson.
- Read OSI YAML or JSON documents into a typed DTO tree. Jackson's
YAMLMapper accepts both formats, so you can point it at dbt Core
1.12's
target/osi_document.jsonunmodified. - Write an OSI YAML document from the same DTO tree, ready for any OSI-compatible consumer.
- Round-trip losslessly, so a read followed by a write produces semantically identical output.
Designed to be used outside the Saiku project — no Spring, no JAX-RS, no Mondrian, no Calcite. If your JVM tool needs to understand or emit OSI semantic models, this is the smallest thing that will do it.
Add the dependency (Maven Central release is pending; snapshots are
published to GitHub Packages under spiculedata/ossie in the meantime):
<dependency>
<groupId>bi.saiku.ossie</groupId>
<artifactId>ossie-core</artifactId>
<version>0.1.0</version>
</dependency>Read a document — YAML or JSON, same call:
import bi.saiku.ossie.OssieYamlReader;
import bi.saiku.ossie.model.OssieDocument;
var doc = new OssieYamlReader().read(Paths.get("target/osi_document.json"));
System.out.println(doc.getVersion()); // "0.1.1"
System.out.println(doc.getSemanticModel().size()); // 1
System.out.println(doc.getSemanticModel().get(0).getDatasets().size());Traverse the tree:
for (var sm : doc.getEffectiveSemanticModels()) {
for (var ds : sm.getDatasets()) {
System.out.println("dataset: " + ds.getName() + " → " + ds.getSource());
for (var f : ds.getFields()) {
System.out.println(" field: " + f.getName() + " (label: " + f.getLabel() + ")");
}
}
for (var m : sm.getMetrics()) {
System.out.println("metric: " + m.getName());
}
}Emit a document:
import bi.saiku.ossie.OssieYamlWriter;
var doc = new OssieDocument();
// ... build up the tree ...
new OssieYamlWriter().write(doc, Paths.get("my-model.ossie.yaml"));Resolve AI-context synonyms to canonical entity names, without writing your own tree walk:
import bi.saiku.ossie.OssieSynonymIndex;
var model = doc.getSemanticModel().get(0);
var metrics = OssieSynonymIndex.buildMetricIndex(model);
// If the LLM (or the user) said "revenue" or "top-line" or "REVENUE",
// resolve it to the canonical metric name declared in the YAML.
String canonical = OssieSynonymIndex.resolve("revenue", metrics);
// → "net_revenue" (assuming the metric declared that synonym)Walk the knowledge-graph half of the spec — concepts, their attributes, and their associations to other concepts. Useful for consumers that want an entity-level view of the model (an LLM prompt-builder that names the entities before drilling into their SQL representation, or a schema browser that surfaces the concept graph alongside the tables):
for (var entry : doc.getOntology()) {
var concept = entry.getConcept();
System.out.println("concept: " + concept.getName() +
" (id-by: " + concept.getIdentifyBy() + ")");
for (var rel : entry.getRelationships()) {
System.out.println(" " + rel.getName() + " → " +
rel.getRoles().get(0).getConcept() +
" (" + rel.getMultiplicity() + ")");
}
}Reject documents at unknown spec versions — useful in CI or when you want to fail fast on drift:
var doc = new OssieYamlReader()
.setStrictVersion(true)
.read(Paths.get("model.ossie.yaml"));
// Throws UnsupportedOssieVersionException if the version isn't in
// OssieYamlReader.SUPPORTED_VERSIONS.- OSI spec versions — the DTO tree covers the v0.1.x and v0.2.x
wire formats.
OssieYamlReader.SUPPORTED_VERSIONSnames the exact versions the reader knows about. Unknown top-level and per-entity fields are silently ignored (@JsonIgnoreProperties(ignoreUnknown = true)), so additive spec changes round-trip without breaking older consumers. - JDK — 21+.
- dbt Core — reads
target/osi_document.jsonfrom dbt 1.12 and up verbatim.
Execute queries against an OSI semantic model, backed by any JDBC warehouse. Apache Calcite adapter + shelf-state query engine.
Depends on ossie-core (for the DTOs) plus Calcite. Compiles the
semantic model into virtual Calcite tables at runtime; user queries
authored either as typed shelf-state (OssieQuery) or as raw SQL get
planned against those virtual tables, auto-joined via the
relationship declarations, and executed on the underlying warehouse.
<dependency>
<groupId>bi.saiku.ossie</groupId>
<artifactId>ossie-sql</artifactId>
<version>0.1.0</version>
</dependency>Point the engine at your Ossie YAML plus the warehouse where the underlying tables live. Build a shelf-state query and execute:
import bi.saiku.ossie.sql.OssieEngine;
import bi.saiku.ossie.sql.OssieQuery;
import bi.saiku.ossie.sql.OssieResult;
try (var engine = OssieEngine.builder()
.semanticModel(Path.of("orders.ossie.yaml"))
.jdbcUrl("jdbc:postgresql://warehouse:5432/analytics")
.credentials("saiku_reader", "***")
.build()) {
var query = OssieQuery.builder()
.model("Orders")
.factDataset("orders")
.rows("customers", "customer_country")
.values("total_revenue")
.sortByMetric("total_revenue", "DESC")
.limit(10)
.build();
OssieResult result = engine.execute(query);
for (var row : result.getRecords()) {
System.out.println(row);
}
System.out.println("Generated SQL: " + result.getGeneratedSql());
}String sql = engine.compile(query);
// SELECT "customers"."COUNTRY" AS "customers.customer_country",
// SUM("orders"."ORDER_TOTAL") AS "total_revenue"
// FROM "orders", "customers"
// GROUP BY "customers"."COUNTRY"
// ORDER BY "total_revenue" DESC
// LIMIT 10The engine also exposes openConnection() — a plain JDBC connection
whose datasets appear as virtual tables. Any BI tool, ORM, or LLM
code path that speaks JDBC gets a semantic-aware query surface:
try (var engine = OssieEngine.builder()
.semanticModel(doc)
.jdbcUrl("jdbc:postgresql://warehouse/analytics")
.credentials("reader", "***")
.build();
var conn = engine.openConnection();
var stmt = conn.createStatement();
var rs = stmt.executeQuery(
"SELECT \"customers\".\"COUNTRY\", COUNT(*) " +
"FROM \"customers\", \"orders\" " +
"GROUP BY \"customers\".\"COUNTRY\"")) {
// ... iterate ...
}customers and orders are Ossie datasets, not warehouse tables.
Calcite's auto-join rule wires the join between them from the
relationships block in the YAML — you don't write the ON clause.
- Dimensions on Rows and Columns shelves, mapped to arbitrary column
expressions via
field.expression.dialects.ANSI_SQL - Metrics with inline SQL expressions (aggregations, ratios,
arbitrary scalar arithmetic — TPC-DS's
SUM(sales) / COUNT(DISTINCT customer)shape works verbatim) - Aggregation override at query time (
.values("net_revenue", "AVG")) - Filters: EQ, NEQ, LT, LTE, GT, GTE, IN, BETWEEN, IS_NULL, IS_NOT_NULL
- Sorts by metric alias or by dimension
- Row limits
- Cross-dataset joins driven by the
relationshipsblock, injected by Calcite's planner viaOssieAutoJoinRule - Metric views (
SELECT * FROM "total_revenue"where metric names are addressable as views — useful for tools that want the aggregated columns as first-class relations)
Anything with a JDBC driver + a Calcite dialect: Postgres, MySQL /
MariaDB, Oracle, MSSQL, DuckDB, H2, HSQLDB, Snowflake, BigQuery,
ClickHouse. Dialect resolution flows through Calcite's
SqlDialectFactoryImpl; unknown warehouses fall back to the
ANSI-SQL dialect with a warning.
- Apache Ossie — the OSI specification and reference schema.
- Open Semantic Interchange initiative.
- Saiku — the reference JVM consumer this library was extracted from.
Issues and PRs welcome at github.com/spiculedata/ossie.
mvn test runs the round-trip suite. mvn verify also runs
Spotless using Palantir Java
Format — apply with mvn spotless:apply before opening a PR.
Apache License 2.0.