Skip to content
This repository was archived by the owner on Dec 18, 2025. It is now read-only.

Commit a91ff94

Browse files
Add support for EObject creation and deletion
This commit implements the creation and deletion of EObjects in the resource, including the recursive creation and deletion of their descendants in the containment hierarchy. The documentation has been updated to describe the required tweaks to the .rdfres file when using the resource to create new RDF files. The user should either set a default namespace IRI for the new instances (which use EMF-generated UUIDs), whether through the .rdfres, or by setting the namespace IRI of the empty prefix (e.g. `PREFIX : <http://foo/bar/> .`). If none of the above are set, the resource will fall back to Jena's default behaviour, which is to use the absolute IRI of the RDF model (e.g. `file://a/b/c/file.ttl#`). --------- Co-authored-by: Antonio Garcia-Dominguez <a.garcia-dominguez@york.ac.uk>
1 parent 50470e5 commit a91ff94

File tree

47 files changed

+1272
-328
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1272
-328
lines changed

RESOURCE.md

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ This repository includes a prototype implementation of an EMF resource for RDF g
44

55
It can be installed from the repository's update site: see [`README`](./README.md) for details.
66

7+
This RDF-EMF resource produces an EMF model representation of an RDF model; this could be an EMF model saved as RDF, or an RDF model from another program.
8+
One or more RDF model files and schemas can be combined/reasoned before being deserialised against an EMF Ecore metamodel.
9+
An EMF model instance is composed of `EObject`s for the RDF model element that have `rdf:type` statements that match an `EClass` in the configured Ecore metamodel(s).
10+
711
## Differences with emf-triple
812

913
This implementation has some major differences with [emf-triple](https://github.com/ghillairet/emftriple):
@@ -15,21 +19,25 @@ These differences are achieved by loading an intermediary `.rdfres` file with al
1519

1620
## Current limitations
1721

18-
The only modifications that are supported at the moment are:
22+
Saving has only been tested against file-based locations. We have not tested saving into triple stores.
23+
24+
The resource assumes that the EPackage nsURI and the RDF nsURI used for `rdf:type` subjects and for property statements (e.g. `metamodel:featureName`) are a close match to each other.
25+
Specifically, we support two options:
1926

20-
* Setting/unsetting single-valued `EAttribute`s with the pre-defined `EDataType`s in Ecore.
27+
* RDF namespace IRI = EPackage nsURI (including any trailing separator, such as `#` or `/`).
28+
* RDF namespace IRI = EPackage nsURI + "#".
2129

22-
Saving has only been tested against file-based locations.
23-
We have not tested saving into triple stores.
30+
Namespaces for creating EMF models must be configured in the `.rdfres` (see Default model namespace section below).
31+
At the moment, the same namespace URI is used for every EObject created by the resource.
2432

2533
## .rdfres file format
2634

2735
Suppose you have a `model.ttl` Turtle file with some statements of interest, written against an ontology in `schema.ttl`.
2836

2937
Suppose as well that the RDF resources in `model.ttl` follows certain conventions that relate them to an Ecore metamodel, in the [MOF2RDF](https://www.omg.org/spec/MOF2RDF/1.0/About-MOF2RDF) style:
3038

31-
* There are `rdf:type` predicates from the RDF resource to another RDF resource whose URI is `ePackageNamespaceURI#eClassName`.
32-
* Statements use predicates with URIs of the form `ePackageNamespaceURI#eStructuralFeatureName`:
39+
* There are `rdf:type` predicates from the RDF resource to another RDF resource whose namespace URI is `ePackageNamespaceURI`, and local name is `eClassName`.
40+
* Statements use predicates whose namespace URIs are `ePackageNamespaceURI` and local names are `eStructuralFeatureName`:
3341
* Predicate objects can be other RDF resources (in the case of `EReference`s), or literals (in the case of `EAttribute`s).
3442
* RDF lists are supported for many-valued features.
3543

@@ -83,3 +91,19 @@ schemaModels:
8391
- schema.ttl
8492
multiValueAttributeMode: List
8593
```
94+
95+
### Default model namespace
96+
97+
A default model namespace URI is required when adding new resources to an RDF model.
98+
The namespace URI can be configured in the `.rdfres` file as shown below.
99+
100+
```yaml
101+
defaultModelNamespace: http://foo/bar/example#
102+
```
103+
The URI must be absolute, e.g. by starting with `file://` (`/` will become file) or `http://`.
104+
If you provide an invalid URI, then Jena will revert to using the URI of the named model it loaded the RDF from as the name space.
105+
106+
The example above results in new RDF node with an IRI composed of the default name space and a UUID for the new EObject. E.g. `http://foo/bar/example#_6sDDMIgJEfC2g6UdYdL1hg`
107+
108+
If a default model namespace is not provided in the `.rdfres` file, the resource will fall back to the blank prefix `PREFIX : <URI>` in the RDF model.
109+
The last resort is to let Jena decide, which will use the URI that the RDF model was loaded from.

bundles/org.eclipse.epsilon.rdf.emf/src/org/eclipse/epsilon/rdf/emf/RDFDeserializer.java

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,44 @@ public class RDFDeserializer {
6363
private final Map<EObject, Resource> eobToResource = new IdentityHashMap<>();
6464
private final Multimap<Resource, EObject> resourceToEob = HashMultimap.create();
6565

66+
private final Map<EObject, Resource> deregisteredEObject = new IdentityHashMap<>();
67+
6668
public RDFDeserializer(Supplier<EPackage.Registry> packageRegistry) {
6769
this.packageRegistry = packageRegistry;
6870
}
69-
71+
72+
/**
73+
* <p>
74+
* Normalise EMF package URI to end in '#' if they don't end in '#' or '/'.
75+
* </p>
76+
*
77+
* <p>
78+
* The resource assumes that the EPackage nsURI and the RDF nsURI used for
79+
* `rdf:type` subjects and for property statements (e.g.
80+
* `metamodel:featureName`) are a close match to each other.
81+
* </p>
82+
*
83+
* <p>
84+
* Specifically, we support two options:
85+
* </p>
86+
*
87+
* <ul>
88+
* <li>RDF namespace IRI = EPackage nsURI (including any trailing separator,
89+
* such as `#` or `/`).</li>
90+
* <li>RDF namespace IRI = EPackage nsURI + "#".</li>
91+
* </ul>
92+
*
93+
* @param namespaceURI EPackage namespace URI to be normalised.
94+
* @return EPackage namespace URI normalised to have a trailing separator (e.g.
95+
* # or /).
96+
*/
97+
public String normaliseEPackageNSURI(String namespaceURI) {
98+
if (!namespaceURI.endsWith("#") && !namespaceURI.endsWith("/")) {
99+
namespaceURI += "#";
100+
}
101+
return namespaceURI;
102+
}
103+
70104
/**
71105
* Populates the {@link #getEObjectToResourceMap()} from the contents of
72106
* the {@code ontModel}.
@@ -138,17 +172,15 @@ protected void deserializeProperty(Resource node, EObject eob, EStructuralFeatur
138172
}
139173
}
140174

175+
/**
176+
* The resource assumes that the EPackage nsURI and the RDF nsURI used for `rdf:type` subjects and for property statements (e.g. `metamodel:featureName`) are a close match to each other.
177+
* Specifically, we support two options:
178+
* <br>- RDF namespace IRI = EPackage nsURI (including any trailing separator, such as `#` or `/`).
179+
* <br>- RDF namespace IRI = EPackage nsURI + "#".
180+
*/
141181
@SuppressWarnings("unchecked")
142182
protected Object deserializeProperty(Resource node, EStructuralFeature sf) {
143-
String sfPackageURI = sf.getEContainingClass().getEPackage().getNsURI();
144-
if (!sfPackageURI.endsWith("#") && !sfPackageURI.endsWith("/")) {
145-
/*
146-
* We assume that when the EPackage nsURI does not end in # or /, the RDF nsURI ends in #.
147-
*
148-
* TODO make this mapping configurable?
149-
*/
150-
sfPackageURI += "#";
151-
}
183+
String sfPackageURI = normaliseEPackageNSURI(sf.getEContainingClass().getEPackage().getNsURI());
152184

153185
List<Object> values = new ArrayList<>();
154186
for (StmtIterator itValue = node.listProperties(new PropertyImpl(sfPackageURI, sf.getName())); itValue.hasNext(); ) {
@@ -305,4 +337,24 @@ protected void deserializeObjectAttributes(Resource node) {
305337
}
306338
}
307339

340+
public void deregisterEObject(EObject eob) {
341+
Resource node = getRDFResource(eob);
342+
eobToResource.remove(eob);
343+
resourceToEob.remove(eob, node);
344+
deregisteredEObject.put(eob, node);
345+
}
346+
347+
public void registerNewEObject(EObject eob, Resource node) {
348+
eobToResource.put(eob, node);
349+
resourceToEob.put(node, eob);
350+
}
351+
352+
public Resource restoreEObjectResource(EObject eObject) {
353+
Resource node = deregisteredEObject.remove(eObject);
354+
if (node != null) {
355+
registerNewEObject(eObject, node);
356+
}
357+
return node;
358+
}
359+
308360
}

bundles/org.eclipse.epsilon.rdf.emf/src/org/eclipse/epsilon/rdf/emf/RDFGraphResourceImpl.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050

5151
public class RDFGraphResourceImpl extends ResourceImpl {
5252

53-
private static final boolean NOTIFICATION_TRACE = true;
53+
private static final boolean NOTIFICATION_TRACE = false;
5454

5555
private RDFResourceConfiguration config;
5656
private RDFDeserializer deserializer;
@@ -62,6 +62,16 @@ public class RDFGraphResourceImpl extends ResourceImpl {
6262
private Model rdfSchemaModel;
6363
private Model rdfDataModel;
6464
private OntModel rdfOntModel;
65+
66+
private String defaultModelNamespace;
67+
68+
public String getDefaultModelNamespace() {
69+
return defaultModelNamespace;
70+
}
71+
72+
public void setDefaultModelNamespace(String defaultModelNamespace) {
73+
this.defaultModelNamespace = defaultModelNamespace;
74+
}
6575

6676
public static enum MultiValueAttributeMode {
6777
LIST("List"), CONTAINER("Container");
@@ -108,6 +118,7 @@ protected void doLoad(InputStream inputStream, Map<?, ?> options) throws IOExcep
108118
CustomClassLoaderConstructor constructor = new CustomClassLoaderConstructor(this.getClass().getClassLoader(), new LoaderOptions());
109119
this.config = new Yaml(constructor).loadAs(inputStream, RDFResourceConfiguration.class);
110120
loadRDFModels();
121+
setDefaultModelNamespace(config.getDefaultModelNamespace());
111122

112123
validationMode = config.getRawValidationMode();
113124

@@ -124,9 +135,9 @@ protected void doLoad(InputStream inputStream, Map<?, ?> options) throws IOExcep
124135
// Apply eAdapters for notifications of changes, and setup the Graph Resource updater
125136
if (NOTIFICATION_TRACE) {
126137
// Produce a console trace for debugging and development
127-
this.eAdapters().add(new RDFGraphResourceNotificationAdapterTrace());
138+
this.eAdapters().add(new RDFGraphResourceNotificationAdapterTrace(this));
128139
}
129-
this.eAdapters().add(new RDFGraphResourceNotificationAdapterChangeRDF());
140+
this.eAdapters().add(new RDFGraphResourceNotificationAdapterChangeRDF(this));
130141
rdfGraphUpdater = new RDFGraphResourceUpdate(deserializer, this, multiValueAttributeMode);
131142
}
132143

@@ -239,7 +250,7 @@ public List<Resource> getResourcesForNamedModelsContaining(EObject eObject) {
239250

240251
public List<Resource> getResourcesForNamedModelsContaining(Resource res) {
241252
List<Resource> resources = new ArrayList<Resource>();
242-
if (null != dataModelSet) {
253+
if (null != dataModelSet && null != res) {
243254
Iterator<Resource> namedModels = dataModelSet.listModelNames();
244255
namedModels.forEachRemaining(m -> {
245256
Model model = dataModelSet.getNamedModel(m);

bundles/org.eclipse.epsilon.rdf.emf/src/org/eclipse/epsilon/rdf/emf/RDFGraphResourceNotificationAdapterChangeRDF.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,20 @@
2222
import org.eclipse.emf.ecore.util.EContentAdapter;
2323

2424
public class RDFGraphResourceNotificationAdapterChangeRDF extends EContentAdapter {
25+
26+
private final RDFGraphResourceImpl initialRDFGraphResource;
27+
28+
public RDFGraphResourceNotificationAdapterChangeRDF(RDFGraphResourceImpl rdfGraphResource) {
29+
this.initialRDFGraphResource = rdfGraphResource;
30+
}
31+
2532
@Override
2633
public void notifyChanged(Notification notification) {
2734
Object feature = notification.getFeature();
2835
if (null != feature) {
2936
featureNotification(feature, notification);
3037
}
38+
super.notifyChanged(notification);
3139
}
3240

3341
private void featureNotification (Object feature, Notification notification){
@@ -47,6 +55,11 @@ private void eStructuralFeatureNotification(EStructuralFeature eStructuralFeatur
4755
int position = notification.getPosition();
4856

4957
RDFGraphResourceImpl graphResource = (RDFGraphResourceImpl) onEObject.eResource();
58+
if (null == graphResource) {
59+
System.err.println("The Graph resource has been removed, using the initial graph resource instead");
60+
graphResource = initialRDFGraphResource;
61+
}
62+
5063
RDFGraphResourceUpdate rdfUpdater = graphResource.getRDFGraphUpdater();
5164
List<Resource> namedModelURIs = graphResource.getResourcesForNamedModelsContaining(onEObject);
5265

@@ -76,7 +89,7 @@ private void eStructuralFeatureNotification(EStructuralFeature eStructuralFeatur
7689
namedModelURIs.add(first);
7790
}
7891
}
79-
rdfUpdater.newSingleValueEStructuralFeatureStatements(namedModelURIs, onEObject,
92+
rdfUpdater.addSingleValueEStructuralFeatureStatements(namedModelURIs, onEObject,
8093
changedFeature, newValue);
8194
}
8295
} else {
@@ -96,8 +109,7 @@ private void eStructuralFeatureNotification(EStructuralFeature eStructuralFeatur
96109
break;
97110
case Notification.REMOVE_MANY:
98111
case Notification.REMOVE:
99-
rdfUpdater.removeMultiEStructuralFeature(namedModelURIs, onEObject, changedFeature,
100-
newValue, oldValue);
112+
rdfUpdater.removeMultiEStructuralFeature(namedModelURIs, onEObject, changedFeature, oldValue);
101113
break;
102114
case Notification.UNSET:
103115
// Single values, don't need to worry about order

0 commit comments

Comments
 (0)