Using CIMVocabCheck from Java & JUnit
cimvocabcheck-core is a plain Java library. You can build a schema index from your CGMES
profiles once and validate any number of SPARQL queries or SHACL shapes against it — ideal for
unit tests and build-time guards that fail the moment a query or shape stops matching the
schema.
This page is a practical cookbook. For the full type-by-type reference see the API page.
Dependency
<dependency>
<groupId>de.soptim.opencgmes</groupId>
<artifactId>cimvocabcheck-core</artifactId>
<version>1.0.0</version>
</dependency>
testImplementation 'de.soptim.opencgmes:cimvocabcheck-core:1.0.0'
The library pulls in cimxml (for parsing CIM profiles) and Apache Jena.
Build a schema index
The entry point is SparqlValidationApi, constructed with a SchemaIndex. The standard
implementation, RdfsSchemaIndex, is built from CGMES RDFS profiles. The most direct route is via
the CIMXML profile registry, which parses the profile files and resolves their datatypes:
import de.soptim.opencgmes.cimxml.parser.RdfXmlParser;
import de.soptim.opencgmes.cimxml.rdfs.CimProfileRegistry;
import de.soptim.opencgmes.cimxml.rdfs.CimProfileRegistryStd;
import de.soptim.opencgmes.cimvocabcheck.core.SparqlValidationApi;
import de.soptim.opencgmes.cimvocabcheck.core.schema.RdfsSchemaIndex;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
static SparqlValidationApi loadApi(Path rdfsDir) throws IOException {
CimProfileRegistry registry = new CimProfileRegistryStd();
RdfXmlParser parser = new RdfXmlParser();
try (var paths = Files.list(rdfsDir)) {
paths.filter(p -> p.toString().endsWith(".rdf"))
.sorted()
.forEach(p -> {
try {
registry.register(parser.parseCimProfile(p));
} catch (IllegalArgumentException duplicateVersionIri) {
// Some CGMES 2.4 Equipment variants share version IRIs — skip duplicates.
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
return new SparqlValidationApi(RdfsSchemaIndex.fromCimRegistry(registry));
}
Build the SparqlValidationApi once (e.g. in a @BeforeAll / static initializer) and reuse it
across tests — parsing the profiles is the expensive part.
When you don't want to load real profiles, build a minimal index with the builder:
RdfsSchemaIndex index = RdfsSchemaIndex.builder()
.addProfile(
"http://example.org/EQ/1.0",
java.util.List.of("http://iec.ch/TC57/CIM100#ACLineSegment"),
java.util.List.of("http://iec.ch/TC57/CIM100#ACLineSegment.r"))
.build();
SparqlValidationApi api = new SparqlValidationApi(index);
Validate a SPARQL query
import de.soptim.opencgmes.cimvocabcheck.core.SparqlValidationResult;
SparqlValidationResult result = api.validateSparql("""
PREFIX cim: <http://iec.ch/TC57/CIM100#>
SELECT * WHERE {
?line a cim:ACLineSegment ;
cim:IdentifiedObject.name ?name .
}
""");
System.out.println("valid: " + result.isValid());
result.annotations().forEach(a ->
System.out.printf("[%s] %s (line %s) — %s%n",
a.severity(), a.code(), a.line(), a.message()));
validateSparql auto-detects query vs. SPARQL Update. Multiple ;-separated queries in one string
are split and validated independently with line offsets preserved.
Assert in a JUnit test
A query that references the schema correctly should produce no errors; a typo'd property should
produce UNKNOWN_PROPERTY:
import static org.junit.jupiter.api.Assertions.*;
import de.soptim.opencgmes.cimvocabcheck.core.SparqlValidationApi;
import de.soptim.opencgmes.cimvocabcheck.core.SparqlValidationCode;
import de.soptim.opencgmes.cimvocabcheck.core.SparqlValidationResult;
import java.nio.file.Path;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class QueryGuardrailTest {
static SparqlValidationApi api;
@BeforeAll
static void setUp() throws Exception {
api = loadApi(Path.of("src/test/resources/cgmes-3.0"));
}
@Test
void productionQueryMatchesSchema() {
SparqlValidationResult r = api.validateSparql("""
PREFIX cim: <http://iec.ch/TC57/CIM100#>
SELECT ?line ?name WHERE {
?line a cim:ACLineSegment ;
cim:IdentifiedObject.name ?name .
}
""");
assertTrue(r.isValid(), () -> "Unexpected findings:\n" + render(r));
}
@Test
void typoIsCaught() {
SparqlValidationResult r = api.validateSparql("""
PREFIX cim: <http://iec.ch/TC57/CIM100#>
SELECT * WHERE { ?l a cim:ACLineSegment ; cim:DoesNotExist ?x }
""");
assertFalse(r.isValid());
assertTrue(
r.annotations().stream()
.anyMatch(a -> a.code() == SparqlValidationCode.UNKNOWN_PROPERTY),
"expected UNKNOWN_PROPERTY");
}
private static String render(SparqlValidationResult r) {
StringBuilder sb = new StringBuilder();
r.annotations().forEach(a ->
sb.append(a.severity()).append(' ').append(a.code())
.append(" — ").append(a.message()).append('\n'));
return sb.toString();
}
}
Validate SHACL shapes
validateShacl takes a Jena Graph. It validates the shape structure and any embedded
SPARQL in one pass:
import de.soptim.opencgmes.cimvocabcheck.core.shacl.ShaclValidationResult;
import org.apache.jena.graph.Graph;
import org.apache.jena.riot.RDFDataMgr;
Graph shapes = RDFDataMgr.loadGraph("my-shapes.ttl");
ShaclValidationResult r = api.validateShacl(shapes);
// Structural findings (unknown class/property in sh:targetClass, sh:class, sh:path …)
r.shapeAnnotations().forEach(a ->
System.out.printf("[%s] %s — %s%n", a.severity(), a.code(), a.message()));
// One result per embedded SPARQL fragment (sh:sparql, sh:rule, sh:validator, sh:target)
for (var fragment : r.embeddedResults()) {
System.out.println("fragment: " + fragment.embedded().rawQuery());
fragment.result().annotations().forEach(a ->
System.out.printf(" [%s] %s — %s%n", a.severity(), a.code(), a.message()));
}
assertTrue(r.isValid()); // true when neither structure nor any embedded query has errors
$this inside a sh:sparql constraint is automatically typed as the enclosing shape's
sh:targetClass, and $PATH is substituted with the property shape's sh:path — no extra wiring.
Fail CI on warnings (strictness)
For pipelines, treat warnings as failures by passing a StrictnessLevel to isValid:
import de.soptim.opencgmes.cimvocabcheck.core.StrictnessLevel;
SparqlValidationResult r = api.validateSparql(queryText);
// DEFAULT: only ERRORs fail. STRICT: warnings count too. PEDANTIC: infos/hints too.
assertTrue(r.isValid(StrictnessLevel.STRICT));
The same four levels are available on the CLI (--strictness) and in
opencgmes.jsonc.
Discover profile dependencies
Ask which classes, properties, graphs, or profiles a query references — useful for tests that assert a query only touches the profiles it should:
import org.apache.jena.graph.Node;
import de.soptim.opencgmes.cimvocabcheck.core.schema.VersionIri;
import java.util.Collection;
Collection<Node> classes = api.getClassDependencies(query);
Collection<Node> props = api.getPropertyDependencies(query);
Collection<Node> graphs = api.getGraphDependencies(query);
Collection<VersionIri> profiles = api.getProfileDependencies(query);
Equivalent getShacl…Dependencies(Graph) and getUpdate…Dependencies(String) methods exist for
shapes graphs and SPARQL Update — see the API reference.
See also
- API reference — every public type and overload.
- Validation checks — the codes you assert on.
- Endpoints — build the index from a live SPARQL endpoint instead of files.