feat: Support blank nodes in attribute editor for fixed and default values (RDFA-321)#22
Merged
spah-soptim merged 7 commits intomainfrom May 6, 2026
Merged
Conversation
e463e38 to
95ced91
Compare
d3a4163 to
f5b701b
Compare
f5b701b to
d8ac3d3
Compare
rema-soptim
requested changes
Apr 30, 2026
905867d to
9b76973
Compare
rema-soptim
requested changes
May 6, 2026
Fixed and default attribute values can be persisted in two equivalent shapes in the source RDF: as a direct literal triple, or as a blank-node wrapper (subject -> predicate -> _:blank ; _:blank -> rdfs:Literal -> literal). Until now the model layer collapsed both shapes into a plain literal, so editing an attribute backed by a blank-node value silently dropped that blank node on write. Introduced a shared `AttributeValueNode` base for `CIMSIsFixed` and `CIMSIsDefault` carrying a `blankNode` flag, and a `ValueNodeParser` utility that turns an `RDFNode` into a `ParsedValue(value, dataType, blankNode)` while validating the blank-node shape (exactly one statement with `rdfs:Literal` predicate and a literal object). `CIMQuerySolutionParser` takes an optional `Model` so it can re-resolve blank-node properties from the source dataset; `CIMObjectFactory.createCIMAttributeList` and `CIMObjectFetcher.fetchCIMAttributeList` thread that model through. `CIMUpdates.appendValueNode` emits either a direct literal or a fresh blank-node wrapper depending on `blankNode`. Because removing an attribute via `?attr ?p ?o` does not reach the inner triples of an attached blank-node value, `replaceAttribute`/`replaceAttributes` now return an `UpdateRequest` composed of a separate blank-node cleanup, the SPO delete and the insert; `deleteClass` performs the same cleanup before deleting its attributes. `InMemorySparqlExecutor` gains an `executeSingleUpdate(UpdateRequest)` overload to run those compound requests in a single write transaction.
Adds the resolver that decides, for every attribute being created or replaced, whether its fixed/default value should be persisted as a direct literal or as a blank-node wrapper: - if the attribute already exists in the graph, the existing shape wins (a blank-node value stays a blank-node value, a literal stays a literal), - otherwise the new `attributes.newValuesBlankNode` config flag decides; defaults to `false` so behaviour is unchanged for fresh installs. shape as `AssociationsService` — a single `resolve()` followed by a `AttributeFixedDefaultResolver` is a `@Service` with the config injected via `@Value` constructor parameter. It self-manages a READ transaction when none is active and reads inline when called from inside an existing WRITE transaction (e.g. `UpdateClassService.replaceClass`), so callers don't need transaction boilerplate. `AttributesService` keeps the same single `executeSingleUpdate()`. `AttributeMapper` now resolves the canonical XSD datatype URI (`xsd:string` instead of `xsd:String`) for fixed/default values via `XSDDatatypeMapper`; the resolver re-resolves it from the graph for non-primitive attribute datatypes. Frontend: `mapAttributeDtoToReactiveAttribute` passes `fixedValue` and `defaultValue` through so they are available on the reactive object. Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
…te insert Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
…TOCTOU
- CIMUpdates.deleteAttributeValueNodes{,ForClass} now build their SPARQL
via UpdateBuilder + ExprFactory.isBlank instead of raw string concat,
so user-controlled classUUID and graphURI flow through Jena's node
coercion and can no longer be used to inject SPARQL.
- InMemorySparqlExecutor gains an executeSingleUpdate overload that
takes a Function<Graph, UpdateRequest> and runs build + execute in a
single WRITE transaction. AttributesService uses it so the
fixed/default shape resolution and the resulting update happen
atomically — the previous read-then-write split could see another
writer change the existing value's shape between the two
transactions.
- AttributeFixedDefaultResolver no longer self-manages a READ
transaction; the caller's open transaction is required (and is now
always a WRITE).
- ValueNodeParser.parseLiteral takes a Literal directly so the contract
is enforced at the call site.
- Add a comment explaining why insertAttribute uses BIND to force the
WHERE clause to match exactly once.
Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
…ne shape resolution - Extend the attribute SELECT with isFixedInner / isDefaultInner so blank- node fixed/default values are unwrapped by SPARQL itself. CIMQuerySolutionParser drops its Model field; ValueNodeParser is gone. - CIMObjectFactory drops the Model-aware createCIMAttribute(*) overloads; CIMObjectFetcher drops getQueryModel. - CIMUpdates.insertAttribute / replaceAttribute / replaceAttributes / replaceClass now take Graph + newValuesAsBlankNode and consult the resolver internally. The resolver moved into the update package as a package-private @UtilityClass — services no longer call it directly. - deleteAttribute / deleteAttributes return UpdateRequests that include the blank-node value-wrapper cleanup, so deleteClass / replaceAttribute / replaceAttributes no longer chain a separate cleanup step. - AttributesService manages a single WRITE transaction per call and runs UpdateExecutionFactory directly, so resolver reads and the update share one transaction (no TOCTOU). - InMemorySparqlExecutor drops the test-only Update overload and the Function<Graph, UpdateRequest> overload. - UpdateClassService loses its resolver field; AttributesService and UpdateClassService inject the newValuesBlankNode flag directly. Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
Signed-off-by: Jan-Hendrik Spahn <jan-hendrik.spahn@soptim.de>
9b76973 to
95b72df
Compare
rema-soptim
approved these changes
May 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Added RDF blank-node support for attribute
cims:isFixed/cims:isDefaultvalues, in addition to literals.CIMSIsFixed/CIMSIsDefault) carriesvalue,dataType, and ablankNodeflag.ValueNodeParserreplaces the previous literal/blank-node extraction inCIMQuerySolutionParser. The fetcher now passes the datasetModelthrough so blank-node properties can be enumerated.AttributeFixedDefaultResolverdecides per-attribute whether the new value should be literal vs. blank-node - preserves existing shape, otherwise defaults toattributes.newValuesBlankNode(default false).CIMUpdatesnow emits a multi-statementUpdateRequestthat first cleans orphan blank-node triples (since?attribute SPOdoes not reach the inner triples of nested resources), then deletes the attribute, then inserts the new one.AttributeMapper.buildXsdDatatypenow goes throughXSDDatatypeMapper(now for examplexsd:stringnotxsd:String).fixedValue/defaultValue.Test Checklist
General Behavior
Global MenuBar
Welcome Page
Editor - MenuBar
Editor - Navigation
Editor - Package View
Editor - Class Editor
Prefixes Page
Changelog Page
Compare Page