Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ 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):

```java
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:

Expand Down
24 changes: 23 additions & 1 deletion src/main/java/bi/saiku/ossie/model/OssieDocument.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
package bi.saiku.ossie.model;

import bi.saiku.ossie.model.ontology.OntologyEntry;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
Expand All @@ -19,7 +20,7 @@
* up the value from the emitted YAML to route to the right parser.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonPropertyOrder({"version", "semantic_model"})
@JsonPropertyOrder({"version", "ontology", "semantic_model"})
public class OssieDocument {
/**
* Ossie spec version this document conforms to. Bump when the exporter starts emitting
Expand Down Expand Up @@ -51,6 +52,19 @@ public class OssieDocument {
@JsonProperty("ontology_mappings")
private List<OntologyMapping> ontologyMappings = new ArrayList<>();

/**
* Top-level {@code ontology:} block from OSI v0.2.x. Each entry pairs a concept (named entity)
* with its out-bound relationships — attributes to primitive types, associations to other
* concepts, and derived relationships. Unlike {@code semantic_model:} (SQL-adjacent) and
* {@code ontology_mappings:} (bridging), this block is the pure knowledge-graph shape and
* can appear standalone in documents produced by ontology-first modelling tools.
*
* <p>Empty for documents that don't declare an ontology — Jackson's {@code NON_EMPTY}
* inclusion drops the field on serialisation in that case.
*/
@JsonProperty("ontology")
private List<OntologyEntry> ontology = new ArrayList<>();

public String getVersion() {
return version;
}
Expand All @@ -75,6 +89,14 @@ public void setOntologyMappings(List<OntologyMapping> v) {
this.ontologyMappings = v == null ? new ArrayList<>() : v;
}

public List<OntologyEntry> getOntology() {
return ontology;
}

public void setOntology(List<OntologyEntry> v) {
this.ontology = v == null ? new ArrayList<>() : v;
}

/**
* Return every semantic model the document publishes — the top-level {@code semantic_model:}
* entries plus any nested inside {@code ontology_mappings[*].semantic_model:}. Consumers
Expand Down
54 changes: 54 additions & 0 deletions src/main/java/bi/saiku/ossie/model/ontology/OntologyConcept.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2026 Spicule Ltd
* Apache License, Version 2.0.
*/
package bi.saiku.ossie.model.ontology;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;

/**
* A named entity in the OSI ontology.
*
* <p>{@code type} is a string in the wire format (typically {@code EntityType}, but the OSI draft
* leaves room for other kinds — {@code AttributeType} etc. — so the model round-trips whatever
* value is present rather than pinning an enum).
*
* <p>{@code identify_by} lists the relationship names on this concept that together identify a
* unique instance (analogous to a primary key on a table). Empty when the concept is a value type
* without an intrinsic identifier.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class OntologyConcept {
private String name;
private String type;

@JsonProperty("identify_by")
private List<String> identifyBy = new ArrayList<>();

public String getName() {
return name;
}

public void setName(String v) {
this.name = v;
}

public String getType() {
return type;
}

public void setType(String v) {
this.type = v;
}

public List<String> getIdentifyBy() {
return identifyBy;
}

public void setIdentifyBy(List<String> v) {
this.identifyBy = v == null ? new ArrayList<>() : v;
}
}
49 changes: 49 additions & 0 deletions src/main/java/bi/saiku/ossie/model/ontology/OntologyEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2026 Spicule Ltd
* Apache License, Version 2.0.
*/
package bi.saiku.ossie.model.ontology;

import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.ArrayList;
import java.util.List;

/**
* One entry in the top-level {@code ontology:} list. Each entry pairs a {@link OntologyConcept}
* with the relationships out-bound from it — attributes to primitive concepts (String, Float),
* associations to other named entities, or derived relationships. Both members are optional so
* partial documents round-trip; a well-formed OSI ontology entry declares both.
*
* <p>The nested shape follows the apache/ossie flights example verbatim:
*
* <pre>{@code
* ontology:
* - concept: { name: Example_Runway, type: EntityType, identify_by: [id] }
* relationships:
* - name: id
* roles: [ { concept: String } ]
* verbalizes: [ '{Example_Runway} id {String}' ]
* multiplicity: ManyToOne
* }</pre>
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class OntologyEntry {
private OntologyConcept concept;
private List<OntologyRelationship> relationships = new ArrayList<>();

public OntologyConcept getConcept() {
return concept;
}

public void setConcept(OntologyConcept v) {
this.concept = v;
}

public List<OntologyRelationship> getRelationships() {
return relationships;
}

public void setRelationships(List<OntologyRelationship> v) {
this.relationships = v == null ? new ArrayList<>() : v;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2026 Spicule Ltd
* Apache License, Version 2.0.
*/
package bi.saiku.ossie.model.ontology;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;

/**
* An out-bound relationship from one {@link OntologyConcept} to one or more targets.
*
* <p>Covers three shapes that all sit under the same wire structure in OSI:
*
* <ul>
* <li><em>Attributes</em> — relationships whose {@link OntologyRole#getConcept() role concept}
* is a primitive type ({@code String}, {@code Float}, ...). One role, one verbalize line,
* usually {@code ManyToOne} multiplicity.
* <li><em>Associations</em> — relationships whose role concept is another named entity. Same
* shape, but the role concept is the target's {@code name}.
* <li><em>Derived</em> — relationships that declare a {@code derived_by} expression pointing at
* already-existing attribute values. The expression is a plain string (typically an equality
* like {@code Runway.airportid == Airport.airportid}); OSI leaves parsing to the consumer.
* </ul>
*
* <p>{@code multiplicity} is a string — the spec allows {@code ManyToOne}, {@code OneToMany},
* {@code ManyToMany}, {@code OneToOne}, but future values would silently round-trip if we pinned
* an enum, so a string is safer.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class OntologyRelationship {
private String name;
private List<OntologyRole> roles = new ArrayList<>();
private List<String> verbalizes = new ArrayList<>();
private String multiplicity;

@JsonProperty("derived_by")
private List<String> derivedBy = new ArrayList<>();

public String getName() {
return name;
}

public void setName(String v) {
this.name = v;
}

public List<OntologyRole> getRoles() {
return roles;
}

public void setRoles(List<OntologyRole> v) {
this.roles = v == null ? new ArrayList<>() : v;
}

public List<String> getVerbalizes() {
return verbalizes;
}

public void setVerbalizes(List<String> v) {
this.verbalizes = v == null ? new ArrayList<>() : v;
}

public String getMultiplicity() {
return multiplicity;
}

public void setMultiplicity(String v) {
this.multiplicity = v;
}

public List<String> getDerivedBy() {
return derivedBy;
}

public void setDerivedBy(List<String> v) {
this.derivedBy = v == null ? new ArrayList<>() : v;
}
}
25 changes: 25 additions & 0 deletions src/main/java/bi/saiku/ossie/model/ontology/OntologyRole.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2026 Spicule Ltd
* Apache License, Version 2.0.
*/
package bi.saiku.ossie.model.ontology;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* The target of an {@link OntologyRelationship}. Almost always contains a single {@code concept}
* field naming either a primitive type ({@code String}, {@code Float}, ...) or another
* {@link OntologyConcept#getName() named concept} in the same ontology.
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class OntologyRole {
private String concept;

public String getConcept() {
return concept;
}

public void setConcept(String v) {
this.concept = v;
}
}
Loading
Loading