From d28239e254581e5be10769a5c58f29203ac57ae8 Mon Sep 17 00:00:00 2001 From: Ivan Subotic Date: Thu, 13 Sep 2018 12:02:50 +0200 Subject: [PATCH] Add support for shared ontologies (#987) * feature (webapi): add support for shared ontologies * fix (webapi): typo * feature (StringFormatter): Handle shared ontology and entity IRIs. * feature (OntologyResponderV2): Support knora-base:isShared when loading, creating, and updating ontology metadata. * feature (OntologyResponderV2): Enforce restrictions on references to non-shared ontologies. - Add example of a shared ontology. - Add tests. * feature (OntologyResponderV2): Allow shared ontologies to be changed (for development). - Remove tests that are no longer relevant. * feature (OntologyResponderV2): Check inter-ontology references on startup. - Add more tests. * test (OntologyResponderV2): Add tests of shared ontologies. * docs (admin): Fix shared ontologies project shortcode. * test (admin): Fix shared ontologies project shortcode. * feature (webapi): Don't create a resource whose class is from a non-shared ontology in another project. - Add tests. * feature (webapi): Rework shared ontology IRIs to potentially support multiple shared ontology projects. - Fix tests. - Add more tests. - Add docs. - Update release notes. * docs (knora-ontologies): Mention shared ontologies in knora-base doc. --- docs/src/paradox/00-release-notes/v1.8.0.md | 1 + .../paradox/02-knora-ontologies/knora-base.md | 4 + docs/src/paradox/03-apis/api-v2/knora-iris.md | 70 +- .../03-apis/api-v2/ontology-information.md | 12 + docs/src/paradox/faq.md | 15 +- knora-ontologies/knora-admin.ttl | 12 +- webapi/_test_data/ontologies/example-box.ttl | 51 + .../parole-religieuse-dummy-onto.ttl | 41 + webapi/_test_data/project_shortcodes.md | 6 +- .../class-with-non-shared-base-class.ttl | 26 + .../class-with-non-shared-cardinality.ttl | 32 + .../prop-with-non-shared-base-prop.ttl | 25 + .../prop-with-non-shared-occ.ttl | 34 + .../prop-with-non-shared-scc.ttl | 27 + .../org/knora/webapi/OntologyConstants.scala | 6 + .../usersmessages/UsersMessagesADM.scala | 6 +- .../KnoraApiV2WithValueObjects.scala | 42 + .../ontologymessages/OntologyMessagesV2.scala | 35 +- .../admin/ProjectsResponderADM.scala | 4 +- .../responders/v1/OntologyResponderV1.scala | 2 +- .../responders/v1/ProjectsResponderV1.scala | 2 +- .../responders/v1/ResourcesResponderV1.scala | 53 +- .../responders/v2/OntologyResponderV2.scala | 213 +- .../webapi/routing/v2/OntologiesRouteV2.scala | 8 +- .../knora/webapi/util/StringFormatter.scala | 138 +- .../sparql/v2/createOntology.scala.txt | 2 + .../ontologyR2RV2/allOntologyMetadata.jsonld | 12 + .../ontologyR2RV2/allOntologyMetadata.rdf | 9 + .../ontologyR2RV2/allOntologyMetadata.ttl | 9 + .../boxOntologyWithValueObjects.jsonld | 139 ++ .../boxOntologyWithValueObjects.rdf | 115 + .../boxOntologyWithValueObjects.ttl | 83 + .../incunabulaOntologySimple.rdf | 200 +- .../incunabulaOntologyWithValueObjects.rdf | 328 +-- .../incunabulaPageAndBookWithValueObjects.rdf | 200 +- .../test-data/ontologyR2RV2/knoraApiDate.rdf | 4 +- .../ontologyR2RV2/knoraApiDateValue.jsonld | 1 + .../ontologyR2RV2/knoraApiDateValue.rdf | 69 +- .../ontologyR2RV2/knoraApiDateValue.ttl | 1 + .../ontologyR2RV2/knoraApiOntologySimple.rdf | 344 +-- .../knoraApiOntologyWithValueObjects.jsonld | 17 + .../knoraApiOntologyWithValueObjects.rdf | 2073 +++++++++-------- .../knoraApiOntologyWithValueObjects.ttl | 11 + .../knoraApiWithValueObjectsHasColor.jsonld | 1 + .../knoraApiWithValueObjectsHasColor.rdf | 1 + .../knoraApiWithValueObjectsHasColor.ttl | 1 + .../ontologyR2RV2/salsahGuiOntology.jsonld | 1 + .../ontologyR2RV2/salsahGuiOntology.rdf | 1 + .../ontologyR2RV2/salsahGuiOntology.ttl | 1 + .../standoffOntologyWithValueObjects.jsonld | 1 + .../standoffOntologyWithValueObjects.rdf | 973 ++++---- .../standoffOntologyWithValueObjects.ttl | 1 + .../org/knora/webapi/SharedTestDataADM.scala | 16 + .../webapi/e2e/admin/ProjectsADME2ESpec.scala | 2 +- .../webapi/e2e/v1/ProjectsV1E2ESpec.scala | 3 +- .../webapi/e2e/v1/ResourcesV1R2RSpec.scala | 73 + .../webapi/e2e/v2/OntologyV2R2RSpec.scala | 102 +- .../webapi/e2e/v2/ValuesRouteV2R2RSpec.scala | 16 +- .../other/v1/DrawingsGodsV1E2ESpec.scala | 11 +- .../v1/ResourcesResponderV1Spec.scala | 32 + .../v2/OntologyResponderV2Spec.scala | 842 ++++++- .../webapi/util/StringFormatterSpec.scala | 280 +++ 62 files changed, 4518 insertions(+), 2322 deletions(-) create mode 100644 webapi/_test_data/ontologies/example-box.ttl create mode 100644 webapi/_test_data/other.v1.DrawingsGodsV1Spec/parole-religieuse-dummy-onto.ttl create mode 100644 webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-base-class.ttl create mode 100644 webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-cardinality.ttl create mode 100644 webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-base-prop.ttl create mode 100644 webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-occ.ttl create mode 100644 webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-scc.ttl create mode 100644 webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.jsonld create mode 100644 webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.rdf create mode 100644 webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.ttl diff --git a/docs/src/paradox/00-release-notes/v1.8.0.md b/docs/src/paradox/00-release-notes/v1.8.0.md index 1ae3bb6c39..33242c6aae 100644 --- a/docs/src/paradox/00-release-notes/v1.8.0.md +++ b/docs/src/paradox/00-release-notes/v1.8.0.md @@ -20,6 +20,7 @@ See the - update Dockerfiles for `webapi` and `salsah1` (@github[#979](#979)) - Follow subClassOf when including ontologies in XML import schemas (@github[#991](#991)) - add support for adding list child nodes (@github[#991](#990)) +- Add support for shared ontologies (@github[#987](#987)) ## Bugfixes: diff --git a/docs/src/paradox/02-knora-ontologies/knora-base.md b/docs/src/paradox/02-knora-ontologies/knora-base.md index b573ebe095..b0432b4234 100644 --- a/docs/src/paradox/02-knora-ontologies/knora-base.md +++ b/docs/src/paradox/02-knora-ontologies/knora-base.md @@ -1458,6 +1458,10 @@ this simplified example: ## Summary of Restrictions on Project-Specific Ontologies +An ontology can refer to a Knora ontology in another project only if the other +ontology is built-in or shared +(see @ref:[Shared Ontologies](../03-apis/api-v2/knora-iris.md#shared-ontologies)). + ### Restrictions on Classes - Each class must be a subclass of either `kb:Resource` or diff --git a/docs/src/paradox/03-apis/api-v2/knora-iris.md b/docs/src/paradox/03-apis/api-v2/knora-iris.md index 1de0051762..9edea07c0c 100644 --- a/docs/src/paradox/03-apis/api-v2/knora-iris.md +++ b/docs/src/paradox/03-apis/api-v2/knora-iris.md @@ -28,14 +28,17 @@ certain conventions. A project short-code is a hexadecimal number of at least four digits, assigned by the [DaSCH](http://dasch.swiss/) to uniquely identify a -Knora project regardless of where it is hosted. Project short-codes are -currently optional. It is recommended that new projects request a -project code and use it in their ontology IRIs, to avoid possible future -naming conflicts. +Knora project regardless of where it is hosted. The IRIs of ontologies that +are built into Knora do not contain shortcodes; these ontologies implicitly +belong to the Knora system project. + +A project-specific ontology IRI must always include its project shortcode. + +Project ID `0000` is reserved for shared ontologies +(see @ref:[Shared Ontologies](#shared-ontologies)). The range of project IDs from `0001` to `00FF` inclusive is reserved for -local testing, and also the ID `0000` is reserved for future use by the -system. Thus, the first useful project will be `0100`. +local testing. Thus, the first useful project will be `0100`. In the beginning, Unil will use the IDs `0100` to `07FF`, and Unibas `0800` to `08FF`. @@ -53,13 +56,13 @@ ontologies based on project-specific internal ontologies. Each internal ontology has an IRI, which is also the IRI of the named graph that contains the ontology in the triplestore. An internal -project-specific ontology IRI has the form: +ontology IRI has the form: ``` http://www.knora.org/ontology/PROJECT_SHORTCODE/ONTOLOGY_NAME ``` -For example, the ontology IRI based on project code `0001` and ontology +For example, the internal ontology IRI based on project code `0001` and ontology name `example` would be: ``` @@ -82,6 +85,7 @@ words: - `knora` - `ontology` - `simple` + - `shared` ### External Ontology IRIs @@ -96,14 +100,14 @@ The IRI of an external Knora ontology has the form: http://HOST[:PORT]/ontology/PROJECT_SHORTCODE/ONTOLOGY_NAME/API_VERSION ``` -For built-in ontologies, the host is always `api.knora.org`. Otherwise, +For built-in and shared ontologies, the host is always `api.knora.org`. Otherwise, the hostname and port configured in `application.conf` under `app.http.knora-api.host` and `app.http.knora-api.http-port` are used (the port is omitted if it is 80). -This means that when a built-in external ontology IRI is dereferenced, +This means that when a built-in or shared external ontology IRI is dereferenced, the ontology can be served by a Knora API server running at -`api.knora.org`. When a project-specific external ontology IRI is +`api.knora.org`. When the external IRI of a non-shared, project-specific ontology is dereferenced, the ontology can be served by Knora that hosts the project. During development and testing, this could be `localhost`. @@ -134,9 +138,9 @@ For example, suppose a Knora API server is running at `http://www.knora.org/ontology/0001/example`. That ontology can then be requested using either of these IRIs: - - `http://knora.example.org/ontology/0001/example/v2` (for the complex + - `http://knora.example.org/ontology/0001/example/v2` (in the complex schema) - - `http://knora.example.org/ontology/0001/example/simple/v2` (for the + - `http://knora.example.org/ontology/0001/example/simple/v2` (in the simple schema) While the internal `example` ontology refers to definitions in @@ -144,9 +148,9 @@ While the internal `example` ontology refers to definitions in refers instead to a `knora-api` ontology, whose IRI depends on the schema being used: - - `http://api.knora.org/ontology/knora-api/v2` (for the complex + - `http://api.knora.org/ontology/knora-api/v2` (in the complex schema) - - `http://api.knora.org/ontology/knora-api/simple/v2` (for the simple + - `http://api.knora.org/ontology/knora-api/simple/v2` (in the simple schema) ### Ontology Entity IRIs @@ -169,6 +173,42 @@ has the following IRIs: - `http://HOST[:PORT]/ontology/0001/example/simple/v2#ExampleThing` (in the API v2 simple schema) +### Shared Ontologies + +Knora does not normally allow a project to use classes or properties defined in +an ontology that belongs to another project. Each project must be free to change +its own ontologies, but this is not possible if they have been used in ontologies +or data created by other projects. + +However, an ontology can be defined as shared, meaning that it can be used by +multiple projects, and that its creators will not change it in ways that could +affect other ontologies or data that are based on it. Specifically, in a shared +ontology, existing classes and properties cannot safely be changed, but new ones +can be added. (It is not even safe to add an optional cardinality to an existing +class, because this could cause subclasses to violate the rule that a class cannot +have a cardinality on property P as well as a cardinality on a subproperty of P; +see @ref:[Restrictions on Classes](../../02-knora-ontologies/knora-base.md#restrictions-on-classes).) + +A standardisation process for shared ontologies is planned (issue @github[#523](#523)). + +There is currently one project for shared ontologies: + +``` +http://www.knora.org/ontology/knora-base#DefaultSharedOntologiesProject +``` + +Its project code is `0000`. Additional projects for shared ontologies may be supported +in future. + +The internal and external IRIs of shared ontologies always use the hostname +`api.knora.org`, and have an additional segment, `shared`, after `ontology`. +The project code can be omitted, in which case the default shared ontology +project, `0000`, is assumed. The sample shared ontology, `example-box`, has these IRIs: + + - `http://www.knora.org/ontology/shared/example-box` (internal) + - `http://api.knora.org/ontology/shared/example-box/v2` (external, complex schema) + - `http://api.knora.org/ontology/shared/example-box/simple/v2` (external, simple schema) + ## IRIs for Data Knora generates IRIs for data that it creates in the triplestore. Each diff --git a/docs/src/paradox/03-apis/api-v2/ontology-information.md b/docs/src/paradox/03-apis/api-v2/ontology-information.md index bc45cac56a..55e9e31e39 100644 --- a/docs/src/paradox/03-apis/api-v2/ontology-information.md +++ b/docs/src/paradox/03-apis/api-v2/ontology-information.md @@ -938,6 +938,18 @@ HTTP POST to http://host/v2/ontologies The ontology name must follow the rules given in @ref:[Knora IRIs](knora-iris.md). +If the ontology is to be shared by multiple projects, it must be +created in the default shared ontologies project, +`http://www.knora.org/ontology/knora-base#DefaultSharedOntologiesProject`, +and the request must have this additional boolean property: + +``` +"knora-api:isShared" : true +``` + +See @ref:[Shared Ontologies](knora-iris.md#shared-ontologies) for details about +shared ontologies. + A successful response will be a JSON-LD document providing only the ontology's metadata, which includes the ontology's IRI. When the client makes further requests to create entities (classes and properties) in diff --git a/docs/src/paradox/faq.md b/docs/src/paradox/faq.md index 059c534a3b..c44e0b4e86 100644 --- a/docs/src/paradox/faq.md +++ b/docs/src/paradox/faq.md @@ -34,12 +34,17 @@ has been partly implemented, but is not currently supported. ### Can a project use classes or properties defined in another project's ontology? -No, and Knora API v2 will forbid this. Each project must be free to change its -own ontologies, but this is not possible if they have been used in ontologies or -data created by other projects. +Knora does not allow this to be done with ordinary project-specific ontologies. +Each project must be free to change its own ontologies, but this is not possible +if they have been used in ontologies or data created by other projects. -Instead, there will be a process for standardising ontologies that can be shared -by multiple projects (issue @github[#523](#523)). +An ontology can be defined as shared, meaning that it can be used by multiple +projects, and that its creators promise not to change it in ways that could +affect other ontologies or data that are based on it. See +@ref:[Shared Ontologies](03-apis/api-v2/knora-iris.md#shared-ontologies) for details. + +There will be a standardisation process for shared ontologies +(issue @github[#523](#523)). ### Why doesn't Knora use `rdfs:domain` and `rdfs:range` for consistency checking? diff --git a/knora-ontologies/knora-admin.ttl b/knora-ontologies/knora-admin.ttl index f8f79c1673..0f609cb5d1 100644 --- a/knora-ontologies/knora-admin.ttl +++ b/knora-ontologies/knora-admin.ttl @@ -733,7 +733,17 @@ :projectShortcode "FFFF" ; :projectLongname "Knora System Project" ; :projectDescription "Knora System Project"@en ; - :projectOntology ; + :status "true"^^xsd:boolean ; + :hasSelfJoinEnabled "false"^^xsd:boolean . + + +### http://www.knora.org/ontology/knora-base#SharedOntologiesProject +:DefaultSharedOntologiesProject rdf:type :knoraProject ; + rdfs:comment "The default project for shared ontologies."@en ; + :projectShortname "DefaultSharedOntologiesProject" ; + :projectShortcode "0000" ; + :projectLongname "Knora Default Shared Ontologies Project" ; + :projectDescription "Knora Shared Ontologies Project"@en ; :status "true"^^xsd:boolean ; :hasSelfJoinEnabled "false"^^xsd:boolean . diff --git a/webapi/_test_data/ontologies/example-box.ttl b/webapi/_test_data/ontologies/example-box.ttl new file mode 100644 index 0000000000..d76709899f --- /dev/null +++ b/webapi/_test_data/ontologies/example-box.ttl @@ -0,0 +1,51 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix foaf: . +@prefix knora-base: . +@prefix salsah-gui: . + +@base . + +# An example of a shared ontology. + +@prefix : . + + rdf:type owl:Ontology ; + rdfs:label "An example of a shared ontology" ; + knora-base:attachedToProject ; + knora-base:lastModificationDate "2018-09-10T14:53:00.000Z"^^xsd:dateTimeStamp . + +:Box rdf:type owl:Class ; + + rdfs:subClassOf knora-base:Resource , + [ + rdf:type owl:Restriction ; + owl:onProperty :hasName ; + owl:maxCardinality "1"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "0"^^xsd:nonNegativeInteger + ] ; + + knora-base:resourceIcon "thing.png" ; + + rdfs:label "shared thing"@en ; + + rdfs:comment """A shared thing."""@en . + + +:hasName rdf:type owl:ObjectProperty ; + + rdfs:label "has name"@en ; + + rdfs:comment """Has name."""@en ; + + rdfs:subPropertyOf knora-base:hasValue ; + + knora-base:objectClassConstraint knora-base:TextValue ; + + salsah-gui:guiElement salsah-gui:SimpleText ; + + salsah-gui:guiAttribute "size=80" , + "maxlength=255" . diff --git a/webapi/_test_data/other.v1.DrawingsGodsV1Spec/parole-religieuse-dummy-onto.ttl b/webapi/_test_data/other.v1.DrawingsGodsV1Spec/parole-religieuse-dummy-onto.ttl new file mode 100644 index 0000000000..88e25621f1 --- /dev/null +++ b/webapi/_test_data/other.v1.DrawingsGodsV1Spec/parole-religieuse-dummy-onto.ttl @@ -0,0 +1,41 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix foaf: . +@prefix knora-base: . +@prefix salsah-gui: . +@base . + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "A dummy ontology for DrawingsGodsV1E2ESpec" ; + knora-base:attachedToProject . + +:hasInteger rdf:type owl:ObjectProperty ; + rdfs:subPropertyOf knora-base:hasValue ; + rdfs:label "Ganzzahl"@de , + "Nombre entier"@fr , + "Intero"@it , + "Integer"@en ; + knora-base:subjectClassConstraint :Thing ; + knora-base:objectClassConstraint knora-base:IntValue ; + salsah-gui:guiElement salsah-gui:Spinbox ; + salsah-gui:guiAttribute "min=0" , + "max=-1" . + +:Thing rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource , + [ + rdf:type owl:Restriction ; + owl:onProperty :hasInteger ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "4"^^xsd:nonNegativeInteger + ] ; + knora-base:resourceIcon "thing.png" ; + rdfs:label "Ding"@de , + "Chose"@fr , + "Cosa"@it , + "Thing"@en ; + rdfs:comment """Just for testing"""@en . diff --git a/webapi/_test_data/project_shortcodes.md b/webapi/_test_data/project_shortcodes.md index ed124a6196..be7e43f663 100644 --- a/webapi/_test_data/project_shortcodes.md +++ b/webapi/_test_data/project_shortcodes.md @@ -1,13 +1,13 @@ ### An overview of project `shortcodes` in use. Every project needs to have a unique shortcode assigned. - #### Default range - `0000` - `00FF` - - system: `0000` + - default shared ontologies project: `0000` - anything: `0001` - images: `00FF` + - system: `FFFF` + - #### Lausanne range - `0100` to `07FF` - drawings gods: `0106` diff --git a/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-base-class.ttl b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-base-class.ttl new file mode 100644 index 0000000000..a9b8be28d7 --- /dev/null +++ b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-base-class.ttl @@ -0,0 +1,26 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix knora-base: . +@prefix salsah-gui: . +@prefix incunabula: . +@base . + +# An ontology that has a class whose base class is defined in a non-shared ontology in another project. + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "The invalid ontology" ; + knora-base:attachedToProject . + +:InvalidThing rdf:type owl:Class ; + + rdfs:subClassOf incunabula:book ; + + knora-base:resourceIcon "thing.png" ; + + rdfs:label "Invalid Thing"@en ; + + rdfs:comment """An invalid thing"""@de . diff --git a/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-cardinality.ttl b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-cardinality.ttl new file mode 100644 index 0000000000..9599730087 --- /dev/null +++ b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-cardinality.ttl @@ -0,0 +1,32 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix knora-base: . +@prefix salsah-gui: . +@prefix incunabula: . +@base . + +# An ontology that has a class with a cardinality on a property defined in a non-shared ontology in another project. + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "The invalid ontology" ; + knora-base:attachedToProject . + +:InvalidThing rdf:type owl:Class ; + + rdfs:subClassOf knora-base:Resource , + [ + rdf:type owl:Restriction ; + owl:onProperty incunabula:description ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger + ] ; + + knora-base:resourceIcon "thing.png" ; + + rdfs:label "Invalid Thing"@en ; + + rdfs:comment """An invalid thing"""@de . diff --git a/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-base-prop.ttl b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-base-prop.ttl new file mode 100644 index 0000000000..9070779ba7 --- /dev/null +++ b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-base-prop.ttl @@ -0,0 +1,25 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix knora-base: . +@prefix salsah-gui: . +@prefix incunabula: . +@base . + +# An ontology that has a property whose base property is defined in a non-shared ontology in another project. + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "The invalid ontology" ; + knora-base:attachedToProject . + + +:hasFoo rdf:type owl:ObjectProperty ; + + rdfs:subPropertyOf incunabula:description ; + + rdfs:label "has foo"@en ; + + knora-base:objectClassConstraint knora-base:TextValue . diff --git a/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-occ.ttl b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-occ.ttl new file mode 100644 index 0000000000..52cb6c3c79 --- /dev/null +++ b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-occ.ttl @@ -0,0 +1,34 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix knora-base: . +@prefix salsah-gui: . +@prefix incunabula: . +@base . + +# An ontology that has a property whose object class constraint is defined in a non-shared ontology in another project. + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "The invalid ontology" ; + knora-base:attachedToProject . + + +:hasIncunabulaBook rdf:type owl:ObjectProperty ; + + rdfs:subPropertyOf knora-base:hasLinkTo ; + + rdfs:label "has incunabula book"@en ; + + knora-base:objectClassConstraint incunabula:book . + + +:hasIncunabulaBookValue rdf:type owl:ObjectProperty ; + + rdfs:subPropertyOf knora-base:hasLinkToValue ; + + rdfs:label "has incunabula book"@en ; + + knora-base:objectClassConstraint knora-base:LinkValue . diff --git a/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-scc.ttl b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-scc.ttl new file mode 100644 index 0000000000..5b9e11f96e --- /dev/null +++ b/webapi/_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-scc.ttl @@ -0,0 +1,27 @@ +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix knora-base: . +@prefix salsah-gui: . +@prefix incunabula: . +@base . + +# An ontology that has a property whose subject class constraint is defined in a non-shared ontology in another project. + +@prefix : . + rdf:type owl:Ontology ; + rdfs:label "The invalid ontology" ; + knora-base:attachedToProject . + + +:hasFoo rdf:type owl:ObjectProperty ; + + rdfs:subPropertyOf knora-base:hasValue ; + + rdfs:label "has foo"@en ; + + knora-base:subjectClassConstraint incunabula:book ; + + knora-base:objectClassConstraint knora-base:TextValue . diff --git a/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala b/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala index a4273cc59f..a6d9fee01c 100644 --- a/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala +++ b/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala @@ -86,6 +86,7 @@ object OntologyConstants { * Classes defined by OWL that can be used as knora-base:subjectClassConstraint or knora-base:objectClassConstraint. */ val ClassesThatCanBeKnoraClassConstraints: Set[IRI] = Set( + Ontology, Class, Restriction ) @@ -148,6 +149,7 @@ object OntologyConstants { val KnoraBasePrefix: String = KnoraBaseOntologyLabel + ":" val KnoraBasePrefixExpansion: IRI = KnoraBaseOntologyIri + "#" + val IsShared: IRI = KnoraBasePrefixExpansion + "isShared" val CanBeInstantiated: IRI = KnoraBasePrefixExpansion + "canBeInstantiated" val IsEditable: IRI = KnoraBasePrefixExpansion + "isEditable" @@ -469,6 +471,7 @@ object OntologyConstants { val ForProperty: IRI = KnoraBasePrefixExpansion + "forProperty" val SystemProject: IRI = KnoraBasePrefixExpansion + "SystemProject" + val DefaultSharedOntologiesProject: IRI = KnoraBasePrefixExpansion + "DefaultSharedOntologiesProject" /** * The system user is the owner of objects that are created by the system, rather than directly by the user, @@ -665,6 +668,9 @@ object OntologyConstants { val Result: IRI = KnoraApiV2PrefixExpansion + "result" + val IsShared: IRI = KnoraApiV2PrefixExpansion + "isShared" + val IsBuiltIn: IRI = KnoraApiV2PrefixExpansion + "isBuiltIn" + val SubjectType: IRI = KnoraApiV2PrefixExpansion + "subjectType" val ObjectType: IRI = KnoraApiV2PrefixExpansion + "objectType" diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala index cd3df5e604..935daa4a1d 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/usersmessages/UsersMessagesADM.scala @@ -598,8 +598,8 @@ case class UserADM(id: IRI, val v1Groups: Seq[IRI] = groups.map(_.id) - val projectesWithoutSystemProject = projects.filter(_.id != OntologyConstants.KnoraBase.SystemProject) - val projectInfosV1 = projectesWithoutSystemProject.map(_.asProjectInfoV1) + val projectsWithoutBuiltinProjects = projects.filter(_.id != OntologyConstants.KnoraBase.SystemProject).filter(_.id != OntologyConstants.KnoraBase.DefaultSharedOntologiesProject) + val projectInfosV1 = projectsWithoutBuiltinProjects.map(_.asProjectInfoV1) val projects_info_v1: Map[IRI, ProjectInfoV1] = projectInfosV1.map(_.id).zip(projectInfosV1).toMap[IRI, ProjectInfoV1] UserProfileV1( @@ -771,4 +771,4 @@ object UsersADMJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol wi implicit val userProjectAdminMembershipsGetResponseADMFormat: RootJsonFormat[UserProjectAdminMembershipsGetResponseADM] = jsonFormat1(UserProjectAdminMembershipsGetResponseADM) implicit val userGroupMembershipsGetResponseADMFormat: RootJsonFormat[UserGroupMembershipsGetResponseADM] = jsonFormat1(UserGroupMembershipsGetResponseADM) implicit val userOperationResponseADMFormat: RootJsonFormat[UserOperationResponseADM] = jsonFormat1(UserOperationResponseADM) -} \ No newline at end of file +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraApiV2WithValueObjects.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraApiV2WithValueObjects.scala index e3b94f2902..4912a758ac 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraApiV2WithValueObjects.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraApiV2WithValueObjects.scala @@ -61,6 +61,46 @@ object KnoraApiV2WithValueObjects { objectType = Some(OntologyConstants.Xsd.String) ) + private val IsShared: ReadPropertyInfoV2 = makeProperty( + propertyIri = OntologyConstants.KnoraApiV2WithValueObjects.IsShared, + propertyType = OntologyConstants.Owl.DatatypeProperty, + predicates = Seq( + makePredicate( + predicateIri = OntologyConstants.Rdfs.Label, + objectsWithLang = Map( + LanguageCodes.EN -> "is shared", + ) + ), + makePredicate( + predicateIri = OntologyConstants.Rdfs.Comment, + objectsWithLang = Map( + LanguageCodes.EN -> "Indicates whether an ontology can be shared by multiple projects" + ) + ) + ), + objectType = Some(OntologyConstants.Xsd.Boolean) + ) + + private val IsBuiltIn: ReadPropertyInfoV2 = makeProperty( + propertyIri = OntologyConstants.KnoraApiV2WithValueObjects.IsBuiltIn, + propertyType = OntologyConstants.Owl.DatatypeProperty, + predicates = Seq( + makePredicate( + predicateIri = OntologyConstants.Rdfs.Label, + objectsWithLang = Map( + LanguageCodes.EN -> "is shared", + ) + ), + makePredicate( + predicateIri = OntologyConstants.Rdfs.Comment, + objectsWithLang = Map( + LanguageCodes.EN -> "Indicates whether an ontology is built into Knora" + ) + ) + ), + objectType = Some(OntologyConstants.Xsd.Boolean) + ) + private val IsEditable: ReadPropertyInfoV2 = makeProperty( propertyIri = OntologyConstants.KnoraApiV2WithValueObjects.IsEditable, propertyType = OntologyConstants.Owl.AnnotationProperty, @@ -1373,6 +1413,8 @@ object KnoraApiV2WithValueObjects { */ val KnoraApiPropertiesToAdd: Map[SmartIri, ReadPropertyInfoV2] = Set( Result, + IsShared, + IsBuiltIn, IsResourceClass, IsStandoffClass, IsValueClass, diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala index f677d56e4c..6d513e684a 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/OntologyMessagesV2.scala @@ -65,6 +65,7 @@ case class LoadOntologiesRequestV2(requestingUser: UserADM) extends OntologiesRe */ case class CreateOntologyRequestV2(ontologyName: String, projectIri: SmartIri, + isShared: Boolean = false, label: String, apiRequestID: UUID, requestingUser: UserADM) extends OntologiesResponderRequestV2 @@ -107,10 +108,12 @@ object CreateOntologyRequestV2 extends KnoraJsonLDRequestReaderV2[CreateOntology val ontologyName: String = jsonLDDocument.requireStringWithValidation(OntologyConstants.KnoraApiV2WithValueObjects.OntologyName, stringFormatter.validateProjectSpecificOntologyName) val label: String = jsonLDDocument.requireStringWithValidation(OntologyConstants.Rdfs.Label, stringFormatter.toSparqlEncodedString) val projectIri: SmartIri = jsonLDDocument.requireIriInObject(OntologyConstants.KnoraApiV2WithValueObjects.AttachedToProject, stringFormatter.toSmartIriWithErr) + val isShared: Boolean = jsonLDDocument.maybeBoolean(OntologyConstants.KnoraApiV2WithValueObjects.IsShared).exists(_.value) CreateOntologyRequestV2( ontologyName = ontologyName, projectIri = projectIri, + isShared = isShared, label = label, apiRequestID = apiRequestID, requestingUser = requestingUser @@ -965,13 +968,22 @@ case class SubClassesGetResponseV2(subClasses: Seq[SubClassInfoV2]) case class OntologyKnoraEntityIrisGetRequestV2(ontologyIri: SmartIri, requestingUser: UserADM) extends OntologiesResponderRequestV2 /** - * Requests metadata about ontologies. + * Requests metadata about ontologies by project. * * @param projectIris the IRIs of the projects for which ontologies should be returned. If this set is empty, information * about all ontologies is returned. * @param requestingUser the user making the request. */ -case class OntologyMetadataGetRequestV2(projectIris: Set[SmartIri] = Set.empty[SmartIri], requestingUser: UserADM) extends OntologiesResponderRequestV2 +case class OntologyMetadataGetByProjectRequestV2(projectIris: Set[SmartIri] = Set.empty[SmartIri], requestingUser: UserADM) extends OntologiesResponderRequestV2 + +/** + * Requests metadata about ontologies by ontology IRI. + * + * @param ontologyIris the IRIs of the ontologies to be queried. If this set is empty, information + * about all ontologies is returned. + * @param requestingUser the user making the request. + */ +case class OntologyMetadataGetByIriRequestV2(ontologyIris: Set[SmartIri] = Set.empty[SmartIri], requestingUser: UserADM) extends OntologiesResponderRequestV2 /** * Requests entity definitions for the given ontology. @@ -1335,9 +1347,9 @@ object InputOntologyV2 { throw BadRequestException(s"Invalid ontology IRI: $externalOntologyIri") } - val projectIri = ontologyObj.maybeIriInObject(OntologyConstants.KnoraApiV2WithValueObjects.AttachedToProject, stringFormatter.toSmartIriWithErr) + val projectIri: Option[SmartIri] = ontologyObj.maybeIriInObject(OntologyConstants.KnoraApiV2WithValueObjects.AttachedToProject, stringFormatter.toSmartIriWithErr) - val ontologyLabel = ontologyObj.maybeStringWithValidation(OntologyConstants.Rdfs.Label, stringFormatter.toSparqlEncodedString) + val ontologyLabel: Option[String] = ontologyObj.maybeStringWithValidation(OntologyConstants.Rdfs.Label, stringFormatter.toSparqlEncodedString) val lastModificationDate: Option[Instant] = ontologyObj.maybeStringWithValidation(OntologyConstants.KnoraApiV2Simple.LastModificationDate, stringFormatter.toInstant). @@ -2913,6 +2925,7 @@ case class SubClassInfoV2(id: SmartIri, label: String) * * @param ontologyIri the IRI of the ontology. * @param projectIri the IRI of the project that the ontology belongs to. + * @param isShared `true` if this is a shared ontology. * @param label the label of the ontology, if any. * @param lastModificationDate the ontology's last modification date, if any. */ @@ -2960,6 +2973,18 @@ case class OntologyMetadataV2(ontologyIri: SmartIri, None } + val isSharedStatement: Option[(IRI, JsonLDBoolean)] = if (ontologyIri.isKnoraSharedDefinitionIri && targetSchema == ApiV2WithValueObjects) { + Some(OntologyConstants.KnoraApiV2WithValueObjects.IsShared -> JsonLDBoolean(true)) + } else { + None + } + + val isBuiltInStatement: Option[(IRI, JsonLDBoolean)] = if (ontologyIri.isKnoraBuiltInDefinitionIri && targetSchema == ApiV2WithValueObjects) { + Some(OntologyConstants.KnoraApiV2WithValueObjects.IsBuiltIn -> JsonLDBoolean(true)) + } else { + None + } + val labelStatement: Option[(IRI, JsonLDString)] = label.map { labelStr => OntologyConstants.Rdfs.Label -> JsonLDString(labelStr) } @@ -2976,6 +3001,6 @@ case class OntologyMetadataV2(ontologyIri: SmartIri, Map(JsonLDConstants.ID -> JsonLDString(ontologyIri.toString), JsonLDConstants.TYPE -> JsonLDString(OntologyConstants.Owl.Ontology) - ) ++ projectIriStatement ++ labelStatement ++ lastModDateStatement + ) ++ projectIriStatement ++ labelStatement ++ lastModDateStatement ++ isSharedStatement ++ isBuiltInStatement } } diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala index 8fe09e0977..16c47eb3fd 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala @@ -29,7 +29,7 @@ import org.knora.webapi.messages.admin.responder.projectsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.{UserADM, UserGetADM, UserInformationTypeADM} import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.v1.responder.projectmessages._ -import org.knora.webapi.messages.v2.responder.ontologymessages.{OntologyMetadataGetRequestV2, OntologyMetadataV2, ReadOntologyMetadataV2} +import org.knora.webapi.messages.v2.responder.ontologymessages.{OntologyMetadataGetByProjectRequestV2, OntologyMetadataV2, ReadOntologyMetadataV2} import org.knora.webapi.responders.{IriLocker, Responder} import org.knora.webapi.util.ActorUtil._ import org.knora.webapi.util.{KnoraIdUtil, StringFormatter} @@ -115,7 +115,7 @@ class ProjectsResponderADM extends Responder { */ private def getOntologiesForProjects(projectIris: Set[IRI], requestingUser: UserADM): Future[Map[IRI, Seq[IRI]]] = { for { - ontologyMetadataResponse: ReadOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetRequestV2(projectIris = projectIris.map(_.toSmartIri), requestingUser = requestingUser)).mapTo[ReadOntologyMetadataV2] + ontologyMetadataResponse: ReadOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetByProjectRequestV2(projectIris = projectIris.map(_.toSmartIri), requestingUser = requestingUser)).mapTo[ReadOntologyMetadataV2] } yield ontologyMetadataResponse.ontologies.map { ontology => val ontologyIri: IRI = ontology.ontologyIri.toString diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala b/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala index eab3dbe3ca..57d7d52a6a 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v1/OntologyResponderV1.scala @@ -259,7 +259,7 @@ class OntologyResponderV1 extends Responder { for { projectsResponse <- (responderManager ? ProjectsGetRequestADM(userProfile)).mapTo[ProjectsGetResponseADM] - readOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetRequestV2(projectIris = projectIris.map(_.toSmartIri), requestingUser = userProfile)).mapTo[ReadOntologyMetadataV2] + readOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetByProjectRequestV2(projectIris = projectIris.map(_.toSmartIri), requestingUser = userProfile)).mapTo[ReadOntologyMetadataV2] projectsMap: Map[IRI, ProjectADM] = projectsResponse.projects.map { project => project.id -> project diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v1/ProjectsResponderV1.scala b/webapi/src/main/scala/org/knora/webapi/responders/v1/ProjectsResponderV1.scala index f3db40b158..fd9f81b97a 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v1/ProjectsResponderV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v1/ProjectsResponderV1.scala @@ -28,7 +28,7 @@ import org.knora.webapi.messages.store.triplestoremessages._ import org.knora.webapi.messages.v1.responder.ontologymessages.{NamedGraphV1, NamedGraphsGetRequestV1, NamedGraphsResponseV1} import org.knora.webapi.messages.v1.responder.projectmessages._ import org.knora.webapi.messages.v1.responder.usermessages._ -import org.knora.webapi.messages.v2.responder.ontologymessages.{OntologyMetadataGetRequestV2, ReadOntologyMetadataV2} +import org.knora.webapi.messages.v2.responder.ontologymessages.{OntologyMetadataGetByProjectRequestV2, ReadOntologyMetadataV2} import org.knora.webapi.responders.Responder import org.knora.webapi.util.ActorUtil._ import org.knora.webapi.util.KnoraIdUtil diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala b/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala index cc4f674f49..ed87bba9b1 100755 --- a/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v1/ResourcesResponderV1.scala @@ -34,13 +34,14 @@ import org.knora.webapi.messages.v1.responder.projectmessages._ import org.knora.webapi.messages.v1.responder.resourcemessages.{MultipleResourceCreateResponseV1, _} import org.knora.webapi.messages.v1.responder.sipimessages._ import org.knora.webapi.messages.v1.responder.valuemessages._ -import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality +import org.knora.webapi.messages.v2.responder.ontologymessages.{Cardinality, OntologyMetadataGetByIriRequestV2, OntologyMetadataV2, ReadOntologyMetadataV2} import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo import org.knora.webapi.responders.v1.GroupedProps._ import org.knora.webapi.responders.{IriLocker, Responder} import org.knora.webapi.twirl.SparqlTemplateResourceToCreate import org.knora.webapi.util.ActorUtil._ import org.knora.webapi.util._ +import org.knora.webapi.util.IriConversions._ import scala.collection.immutable import scala.concurrent.Future @@ -1266,6 +1267,16 @@ class ResourcesResponderV1 extends Responder { ) }.mapTo[ProjectInfoResponseV1] + // Ensure that the project isn't the system project or the shared ontologies project. + + resourceProjectIri: IRI = projectInfoResponse.project_info.id + + _ = if (resourceProjectIri == OntologyConstants.KnoraBase.SystemProject || resourceProjectIri == OntologyConstants.KnoraBase.DefaultSharedOntologiesProject) { + throw BadRequestException(s"Resources cannot be created in project $resourceProjectIri") + } + + // Ensure that the resource class isn't from a non-shared ontology in another project. + namedGraph = StringFormatter.getGeneralInstance.projectDataNamedGraph(projectInfoResponse.project_info) // Create random IRIs for resources, collect in Map[clientResourceID, IRI] @@ -1286,6 +1297,19 @@ class ResourcesResponderV1 extends Responder { resourceClasses: Set[IRI] = resourcesToCreate.map(_.resourceTypeIri).toSet + // Ensure that none of the resource classes is from a non-shared ontology in another project. + + resourceClassOntologyIris: Set[SmartIri] = resourceClasses.map(_.toSmartIri.getOntologyFromEntity) + readOntologyMetadataV2: ReadOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetByIriRequestV2(resourceClassOntologyIris, userProfile)).mapTo[ReadOntologyMetadataV2] + + _ = for (ontologyMetadata <- readOntologyMetadataV2.ontologies) { + val ontologyProjectIri: IRI = ontologyMetadata.projectIri.getOrElse(throw InconsistentTriplestoreDataException(s"Ontology ${ontologyMetadata.ontologyIri} has no project")).toString + + if (resourceProjectIri != ontologyProjectIri && !(ontologyMetadata.ontologyIri.isKnoraBuiltInDefinitionIri || ontologyMetadata.ontologyIri.isKnoraSharedDefinitionIri)) { + throw BadRequestException(s"Cannot create a resource in project $resourceProjectIri with a resource class from ontology ${ontologyMetadata.ontologyIri}, which belongs to another project and is not shared") + } + } + resourceClassesEntityInfoResponse: EntityInfoGetResponseV1 <- (responderManager ? EntityInfoGetRequestV1( resourceClassIris = resourceClasses, propertyIris = Set.empty[IRI], @@ -1896,20 +1920,39 @@ class ResourcesResponderV1 extends Responder { ) }.mapTo[ProjectInfoResponseV1] + // Ensure that the project isn't the system project or the shared ontologies project. + + resourceProjectIri: IRI = projectInfoResponse.project_info.id + + _ = if (resourceProjectIri == OntologyConstants.KnoraBase.SystemProject || resourceProjectIri == OntologyConstants.KnoraBase.DefaultSharedOntologiesProject) { + throw BadRequestException(s"Resources cannot be created in project $resourceProjectIri") + } + + // Ensure that the resource class isn't from a non-shared ontology in another project. + + resourceClassOntologyIri: SmartIri = resourceClassIri.toSmartIri.getOntologyFromEntity + readOntologyMetadataV2: ReadOntologyMetadataV2 <- (responderManager ? OntologyMetadataGetByIriRequestV2(Set(resourceClassOntologyIri), userProfile)).mapTo[ReadOntologyMetadataV2] + ontologyMetadata: OntologyMetadataV2 = readOntologyMetadataV2.ontologies.headOption.getOrElse(throw BadRequestException(s"Ontology $resourceClassOntologyIri not found")) + ontologyProjectIri: IRI = ontologyMetadata.projectIri.getOrElse(throw InconsistentTriplestoreDataException(s"Ontology $resourceClassOntologyIri has no project")).toString + + _ = if (resourceProjectIri != ontologyProjectIri && !(ontologyMetadata.ontologyIri.isKnoraBuiltInDefinitionIri || ontologyMetadata.ontologyIri.isKnoraSharedDefinitionIri)) { + throw BadRequestException(s"Cannot create a resource in project $resourceProjectIri with resource class $resourceClassIri, which is defined in a non-shared ontology in another project") + } + namedGraph = StringFormatter.getGeneralInstance.projectDataNamedGraph(projectInfoResponse.project_info) resourceIri: IRI = knoraIdUtil.makeRandomResourceIri(projectInfoResponse.project_info) // Check user's PermissionProfile (part of UserADM) to see if the user has the permission to // create a new resource in the given project. - _ = if (!userProfile.permissions.hasPermissionFor(ResourceCreateOperation(resourceClassIri), projectIri, None)) { - throw ForbiddenException(s"User $userIri does not have permissions to create a resource in project $projectIri") + _ = if (!userProfile.permissions.hasPermissionFor(ResourceCreateOperation(resourceClassIri), resourceProjectIri, None)) { + throw ForbiddenException(s"User $userIri does not have permissions to create a resource in project $resourceProjectIri") } result: ResourceCreateResponseV1 <- IriLocker.runWithIriLock( apiRequestID, resourceIri, () => createResourceAndCheck(resourceClassIri, - projectIri, + resourceProjectIri, label, resourceIri, values, @@ -1927,7 +1970,7 @@ class ResourcesResponderV1 extends Responder { sipiConversionRequest match { case Some(conversionRequest) => conversionRequest match { - case (conversionPathRequest: SipiResponderConversionPathRequestV1) => + case conversionPathRequest: SipiResponderConversionPathRequestV1 => // a tmp file has been created by the resources route (non GUI-case), delete it FileUtil.deleteFileFromTmpLocation(conversionPathRequest.source, log) case _ => () diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala index 6236097cb6..80ff0ab7b4 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/OntologyResponderV2.scala @@ -89,7 +89,8 @@ class OntologyResponderV2 extends Responder { case OntologyEntitiesGetRequestV2(ontologyIri, allLanguages, requestingUser) => future2Message(sender(), getOntologyEntitiesV2(ontologyIri, allLanguages, requestingUser), log) case ClassesGetRequestV2(resourceClassIris, allLanguages, requestingUser) => future2Message(sender(), getClassDefinitionsFromOntologyV2(resourceClassIris, allLanguages, requestingUser), log) case PropertiesGetRequestV2(propertyIris, allLanguages, requestingUser) => future2Message(sender(), getPropertyDefinitionsFromOntologyV2(propertyIris, allLanguages, requestingUser), log) - case OntologyMetadataGetRequestV2(projectIris, requestingUser) => future2Message(sender(), getOntologyMetadataForProjectsV2(projectIris, requestingUser), log) + case OntologyMetadataGetByProjectRequestV2(projectIris, requestingUser) => future2Message(sender(), getOntologyMetadataForProjectsV2(projectIris, requestingUser), log) + case OntologyMetadataGetByIriRequestV2(ontologyIris, requestingUser) => future2Message(sender(), getOntologyMetadataByIriV2(ontologyIris, requestingUser), log) case createOntologyRequest: CreateOntologyRequestV2 => future2Message(sender(), createOntology(createOntologyRequest), log) case changeOntologyMetadataRequest: ChangeOntologyMetadataRequestV2 => future2Message(sender(), changeOntologyMetadata(changeOntologyMetadataRequest), log) case createClassRequest: CreateClassRequestV2 => future2Message(sender(), createClass(createClassRequest), log) @@ -409,10 +410,136 @@ class OntologyResponderV2 extends Responder { } } + // Check references between ontologies. + checkReferencesBetweenOntologies(ontologyCacheData) + // Update the cache. storeCacheData(ontologyCacheData) } + /** + * Checks a reference between an ontology entity and another ontology entity to see if the target + * is in a non-shared ontology in another project. + * + * @param ontologyCacheData the ontology cache data. + * @param sourceEntityIri the entity whose definition contains the reference. + * @param targetEntityIri the entity that's the target of the reference. + * @param errorFun a function that throws an exception with the specified message if the reference is invalid. + */ + private def checkOntologyReferenceInEntity(ontologyCacheData: OntologyCacheData, sourceEntityIri: SmartIri, targetEntityIri: SmartIri, errorFun: String => Nothing): Unit = { + if (targetEntityIri.isKnoraDefinitionIri) { + val sourceOntologyIri = sourceEntityIri.getOntologyFromEntity + val sourceOntologyMetadata = ontologyCacheData.ontologies(sourceOntologyIri).ontologyMetadata + + val targetOntologyIri = targetEntityIri.getOntologyFromEntity + val targetOntologyMetadata = ontologyCacheData.ontologies(targetOntologyIri).ontologyMetadata + + if (sourceOntologyMetadata.projectIri != targetOntologyMetadata.projectIri) { + if (!(targetOntologyIri.isKnoraBuiltInDefinitionIri || targetOntologyIri.isKnoraSharedDefinitionIri)) { + errorFun(s"Entity $sourceEntityIri refers to entity $targetEntityIri, which is in a non-shared ontology that belongs to another project") + } + } + } + } + + /** + * Checks a property definition to ensure that it doesn't refer to any other non-shared ontologies. + * + * @param ontologyCacheData the ontology cache data. + * @param propertyDef the property definition. + * @param errorFun a function that throws an exception with the specified message if the property definition is invalid. + */ + private def checkOntologyReferencesInPropertyDef(ontologyCacheData: OntologyCacheData, propertyDef: PropertyInfoContentV2, errorFun: String => Nothing): Unit = { + // Ensure that the property isn't a subproperty of any property in a non-shared ontology in another project. + + for (subPropertyOf <- propertyDef.subPropertyOf) { + checkOntologyReferenceInEntity( + ontologyCacheData = ontologyCacheData, + sourceEntityIri = propertyDef.propertyIri, + targetEntityIri = subPropertyOf, + errorFun = errorFun + ) + } + + // Ensure that the property doesn't have subject or object constraints pointing to a non-shared ontology in another project. + + propertyDef.getPredicateIriObject(OntologyConstants.KnoraBase.SubjectClassConstraint.toSmartIri) match { + case Some(subjectClassConstraint) => + checkOntologyReferenceInEntity( + ontologyCacheData = ontologyCacheData, + sourceEntityIri = propertyDef.propertyIri, + targetEntityIri = subjectClassConstraint, + errorFun = errorFun + ) + + case None => () + } + + propertyDef.getPredicateIriObject(OntologyConstants.KnoraBase.ObjectClassConstraint.toSmartIri) match { + case Some(objectClassConstraint) => + checkOntologyReferenceInEntity( + ontologyCacheData = ontologyCacheData, + sourceEntityIri = propertyDef.propertyIri, + targetEntityIri = objectClassConstraint, + errorFun = errorFun + ) + + case None => () + } + } + + /** + * Checks a class definition to ensure that it doesn't refer to any non-shared ontologies in other projects. + * + * @param ontologyCacheData the ontology cache data. + * @param classDef the class definition. + * @param errorFun a function that throws an exception with the specified message if the property definition is invalid. + */ + private def checkOntologyReferencesInClassDef(ontologyCacheData: OntologyCacheData, classDef: ClassInfoContentV2, errorFun: String => Nothing): Unit = { + for (subClassOf <- classDef.subClassOf) { + checkOntologyReferenceInEntity( + ontologyCacheData = ontologyCacheData, + sourceEntityIri = classDef.classIri, + targetEntityIri = subClassOf, + errorFun = errorFun + ) + } + + for (cardinalityPropIri <- classDef.directCardinalities.keys) { + checkOntologyReferenceInEntity( + ontologyCacheData = ontologyCacheData, + sourceEntityIri = classDef.classIri, + targetEntityIri = cardinalityPropIri, + errorFun = errorFun + ) + } + } + + /** + * Checks references between ontologies to ensure that they do not refer to non-shared ontologies in other projects. + * + * @param ontologyCacheData the ontology cache data. + */ + private def checkReferencesBetweenOntologies(ontologyCacheData: OntologyCacheData): Unit = { + for (ontology <- ontologyCacheData.ontologies.values) { + for (propertyInfo <- ontology.properties.values) { + checkOntologyReferencesInPropertyDef( + ontologyCacheData = ontologyCacheData, + propertyDef = propertyInfo.entityInfoContent, + errorFun = { msg: String => throw InconsistentTriplestoreDataException(msg) } + ) + } + + for (classInfo <- ontology.classes.values) { + checkOntologyReferencesInClassDef( + ontologyCacheData = ontologyCacheData, + classDef = classInfo.entityInfoContent, + errorFun = { msg: String => throw InconsistentTriplestoreDataException(msg) } + ) + } + } + } + /** * Given a list of ontology graphs, finds the IRIs of all subjects whose `rdf:type` is contained in a given set of types. * @@ -1287,6 +1414,36 @@ class OntologyResponderV2 extends Responder { ) } + /** + * Gets the metadata describing the specified ontologies, or all ontologies. + * + * @param ontologyIris the IRIs of the ontologies selected, or an empty set if all ontologies are selected. + * @param requestingUser the user making the request. + * @return a [[ReadOntologyMetadataV2]]. + */ + private def getOntologyMetadataByIriV2(ontologyIris: Set[SmartIri], requestingUser: UserADM): Future[ReadOntologyMetadataV2] = { + for { + cacheData <- getCacheData + returnAllOntologies: Boolean = ontologyIris.isEmpty + + ontologyMetadata: Set[OntologyMetadataV2] = if (returnAllOntologies) { + cacheData.ontologies.values.map(_.ontologyMetadata).toSet + } else { + val missingOntologies = ontologyIris -- cacheData.ontologies.keySet + + if (missingOntologies.nonEmpty) { + throw BadRequestException(s"One or more requested ontologies were not found: ${missingOntologies.mkString(", ")}") + } + + cacheData.ontologies.filterKeys(ontologyIris).values.map { + ontology => ontology.ontologyMetadata + }.toSet + } + } yield ReadOntologyMetadataV2( + ontologies = ontologyMetadata + ) + } + /** * Requests the entities defined in the given ontology. * @@ -1420,7 +1577,7 @@ class OntologyResponderV2 extends Responder { } } - val projectIris = statementMap.getOrElse(OntologyConstants.KnoraBase.AttachedToProject, throw InconsistentTriplestoreDataException(s"Ontology $internalOntologyIri has no knora-base:attachedToProject")) + val projectIris: Seq[String] = statementMap.getOrElse(OntologyConstants.KnoraBase.AttachedToProject, throw InconsistentTriplestoreDataException(s"Ontology $internalOntologyIri has no knora-base:attachedToProject")) val labels: Seq[String] = statementMap.getOrElse(OntologyConstants.Rdfs.Label, Seq.empty[String]) val lastModDates: Seq[String] = statementMap.getOrElse(OntologyConstants.KnoraBase.LastModificationDate, Seq.empty[String]) @@ -1430,6 +1587,16 @@ class OntologyResponderV2 extends Responder { projectIris.head.toSmartIri } + if (!internalOntologyIri.isKnoraBuiltInDefinitionIri) { + if (projectIri.toString == OntologyConstants.KnoraBase.SystemProject) { + throw InconsistentTriplestoreDataException(s"Ontology $internalOntologyIri cannot be in project ${OntologyConstants.KnoraBase.SystemProject}") + } + + if (internalOntologyIri.isKnoraSharedDefinitionIri && projectIri.toString != OntologyConstants.KnoraBase.DefaultSharedOntologiesProject) { + throw InconsistentTriplestoreDataException(s"Shared ontology $internalOntologyIri must be in project ${OntologyConstants.KnoraBase.DefaultSharedOntologiesProject}") + } + } + val label: String = if (labels.size > 1) { throw InconsistentTriplestoreDataException(s"Ontology $internalOntologyIri has more than one rdfs:label") } else if (labels.isEmpty) { @@ -1478,6 +1645,16 @@ class OntologyResponderV2 extends Responder { throw BadRequestException(s"Ontology ${internalOntologyIri.toOntologySchema(ApiV2WithValueObjects)} cannot be created, because it already exists") } + // If this is a shared ontology, make sure it's in the default shared ontologies project. + _ = if (createOntologyRequest.isShared && createOntologyRequest.projectIri.toString != OntologyConstants.KnoraBase.DefaultSharedOntologiesProject) { + throw BadRequestException(s"Shared ontologies must be created in project <${OntologyConstants.KnoraBase.DefaultSharedOntologiesProject}>") + } + + // If it's in the default shared ontologies project, make sure it's a shared ontology. + _ = if (createOntologyRequest.projectIri.toString == OntologyConstants.KnoraBase.DefaultSharedOntologiesProject && !createOntologyRequest.isShared) { + throw BadRequestException(s"Ontologies created in project <${OntologyConstants.KnoraBase.DefaultSharedOntologiesProject}> must be shared") + } + // Create the ontology. currentTime: Instant = Instant.now @@ -1487,6 +1664,7 @@ class OntologyResponderV2 extends Responder { ontologyNamedGraphIri = internalOntologyIri, ontologyIri = internalOntologyIri, projectIri = createOntologyRequest.projectIri, + isShared = createOntologyRequest.isShared, ontologyLabel = createOntologyRequest.label, currentTime = currentTime ).toString @@ -1540,7 +1718,7 @@ class OntologyResponderV2 extends Responder { validOntologyName = stringFormatter.validateProjectSpecificOntologyName(createOntologyRequest.ontologyName, throw BadRequestException(s"Invalid project-specific ontology name: ${createOntologyRequest.ontologyName}")) // Make the internal ontology IRI. - internalOntologyIri = stringFormatter.makeProjectSpecificInternalOntologyIri(validOntologyName, projectInfo.project.shortcode) + internalOntologyIri = stringFormatter.makeProjectSpecificInternalOntologyIri(validOntologyName, createOntologyRequest.isShared, projectInfo.project.shortcode) // Do the remaining pre-update checks and the update while holding an update lock on the ontology. taskResult <- IriLocker.runWithIriLock( @@ -1693,13 +1871,19 @@ class OntologyResponderV2 extends Responder { } // Check that the cardinalities are valid, and add any inherited cardinalities. - (internalClassDefWithLinkValueProps, cardinalitiesForClassWithInheritance) = checkCardinalitiesBeforeAdding( internalClassDef = internalClassDef, allBaseClassIris = allBaseClassIris, cacheData = cacheData ) + // Check that the class definition doesn't refer to any non-shared ontologies in other projects. + _ = checkOntologyReferencesInClassDef( + ontologyCacheData = cacheData, + classDef = internalClassDefWithLinkValueProps, + errorFun = { msg: String => throw BadRequestException(msg) } + ) + // Prepare to update the ontology cache, undoing the SPARQL-escaping of the input. propertyIrisOfAllCardinalitiesForClass = cardinalitiesForClassWithInheritance.keySet @@ -2049,6 +2233,13 @@ class OntologyResponderV2 extends Responder { cacheData = cacheData ) + // Check that the class definition doesn't refer to any non-shared ontologies in other projects. + _ = checkOntologyReferencesInClassDef( + ontologyCacheData = cacheData, + classDef = newInternalClassDefWithLinkValueProps, + errorFun = { msg: String => throw BadRequestException(msg) } + ) + // Prepare to update the ontology cache. (No need to deal with SPARQL-escaping here, because there // isn't any text to escape in cardinalities.) @@ -2206,6 +2397,13 @@ class OntologyResponderV2 extends Responder { cacheData = cacheData ) + // Check that the class definition doesn't refer to any non-shared ontologies in other projects. + _ = checkOntologyReferencesInClassDef( + ontologyCacheData = cacheData, + classDef = newInternalClassDefWithLinkValueProps, + errorFun = { msg: String => throw BadRequestException(msg) } + ) + // Prepare to update the ontology cache. (No need to deal with SPARQL-escaping here, because there // isn't any text to escape in cardinalities.) @@ -2748,6 +2946,13 @@ class OntologyResponderV2 extends Responder { errorFun = { msg: String => throw BadRequestException(msg) } ) + // Check that the property definition doesn't refer to any non-shared ontologies in other projects. + _ = checkOntologyReferencesInPropertyDef( + ontologyCacheData = cacheData, + propertyDef = internalPropertyDef, + errorFun = { msg: String => throw BadRequestException(msg) } + ) + // Add the property (and the link value property if needed) to the triplestore. currentTime: Instant = Instant.now diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala index e309d9e642..003d2989a6 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/OntologiesRouteV2.scala @@ -102,9 +102,9 @@ object OntologiesRouteV2 extends Authenticator { get { requestContext => { - val requestMessageFuture: Future[OntologyMetadataGetRequestV2] = for { + val requestMessageFuture: Future[OntologyMetadataGetByProjectRequestV2] = for { requestingUser <- getUserADM(requestContext) - } yield OntologyMetadataGetRequestV2(requestingUser = requestingUser) + } yield OntologyMetadataGetByProjectRequestV2(requestingUser = requestingUser) RouteUtilV2.runRdfRouteWithFuture( requestMessageFuture, @@ -147,10 +147,10 @@ object OntologiesRouteV2 extends Authenticator { get { requestContext => { - val requestMessageFuture: Future[OntologyMetadataGetRequestV2] = for { + val requestMessageFuture: Future[OntologyMetadataGetByProjectRequestV2] = for { requestingUser <- getUserADM(requestContext) validatedProjectIris = projectIris.map(iri => iri.toSmartIriWithErr(throw BadRequestException(s"Invalid project IRI: $iri"))).toSet - } yield OntologyMetadataGetRequestV2(projectIris = validatedProjectIris, requestingUser = requestingUser) + } yield OntologyMetadataGetByProjectRequestV2(projectIris = validatedProjectIris, requestingUser = requestingUser) RouteUtilV2.runRdfRouteWithFuture( requestMessageFuture, diff --git a/webapi/src/main/scala/org/knora/webapi/util/StringFormatter.scala b/webapi/src/main/scala/org/knora/webapi/util/StringFormatter.scala index 5cd4a7ddbf..eeb95b472d 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/StringFormatter.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/StringFormatter.scala @@ -291,7 +291,8 @@ object StringFormatter { ontologyName: Option[String] = None, entityName: Option[String] = None, ontologySchema: Option[OntologySchema], - isBuiltInDef: Boolean = false) + isBuiltInDef: Boolean = false, + sharedOntology: Boolean = false) /** * A cache that maps IRI strings to [[SmartIri]] instances. To keep the cache from getting too large, @@ -368,6 +369,11 @@ sealed trait SmartIri extends Ordered[SmartIri] with KnoraContentV2[SmartIri] { */ def isKnoraBuiltInDefinitionIri: Boolean + /** + * Returns `true` if this IRI belongs to a shared ontology. + */ + def isKnoraSharedDefinitionIri: Boolean + /** * Returns `true` if this is an internal Knora ontology or entity IRI. * @@ -537,8 +543,8 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { // The hostname used in internal Knora IRIs. private val InternalIriHostname = "www.knora.org" - // The hostname used in built-in Knora API v2 IRIs. - private val BuiltInKnoraApiHostname = "api.knora.org" + // The hostname used in built-in and shared Knora API v2 IRIs. + private val CentralKnoraApiHostname = "api.knora.org" // The strings that Knora data IRIs can start with. private val DataIriStarts: Set[String] = Set( @@ -546,10 +552,13 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { "http://data.knora.org/" ) + // The project code of the default shared ontologies project. + private val DefaultSharedOntologiesProjectCode = "0000" + // The beginnings of Knora definition IRIs that we know we can cache. private val KnoraDefinitionIriStarts = (Set( InternalIriHostname, - BuiltInKnoraApiHostname + CentralKnoraApiHostname ) ++ knoraApiHostAndPort).map(hostname => "http://" + hostname) // The beginnings of all definition IRIs that we know we can cache. @@ -564,7 +573,7 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { private val versionSegmentWords = Set("simple", "v2") // Reserved words that cannot be used in project-specific ontology names. - private val reservedIriWords = Set("knora", "ontology", "rdf", "rdfs", "owl", "xsd", "schema") ++ versionSegmentWords + private val reservedIriWords = Set("knora", "ontology", "rdf", "rdfs", "owl", "xsd", "schema", "shared") ++ versionSegmentWords // The expected format of a Knora date. // Calendar:YYYY[-MM[-DD]][ EE][:YYYY[-MM[-DD]][ EE]] @@ -640,8 +649,8 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { // A regex for a project-specific XML import namespace. private val ProjectSpecificXmlImportNamespaceRegex: Regex = ( - "^" + OntologyConstants.KnoraXmlImportV1.ProjectSpecificXmlImportNamespace.XmlImportNamespaceStart + "(" + - ProjectIDPattern + ")/(" + NCNamePattern + ")" + + "^" + OntologyConstants.KnoraXmlImportV1.ProjectSpecificXmlImportNamespace.XmlImportNamespaceStart + + "(shared/)?((" + ProjectIDPattern + ")/)?(" + NCNamePattern + ")" + OntologyConstants.KnoraXmlImportV1.ProjectSpecificXmlImportNamespace.XmlImportNamespaceEnd + "$" ).r @@ -760,7 +769,7 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { val (ontologySchema: Option[OntologySchema], hasProjectSpecificHostname: Boolean) = hostname match { case InternalIriHostname => (Some(InternalSchema), false) - case BuiltInKnoraApiHostname => (Some(parseApiV2VersionSegments(segments)), false) + case CentralKnoraApiHostname => (Some(parseApiV2VersionSegments(segments)), false) case _ => // If our StringFormatter instance was initialised with the Knora API server's hostname, @@ -797,34 +806,47 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { case None => throw AssertionException("Unreachable code") } - // Make a Vector containing just the optional project code and the ontology name. - val projectCodeAndOntologyName: Vector[String] = segments.slice(2, segments.length - versionSegmentsLength) + // Make a Vector containing just the optional 'shared' specification, the optional project code, and the ontology name. + val ontologyPath: Vector[String] = segments.slice(2, segments.length - versionSegmentsLength) - if (projectCodeAndOntologyName.isEmpty || projectCodeAndOntologyName.length > 2) { + if (ontologyPath.isEmpty || ontologyPath.length > 3) { errorFun } - if (projectCodeAndOntologyName.exists(segment => versionSegmentWords.contains(segment))) { + if (ontologyPath.exists(segment => versionSegmentWords.contains(segment))) { errorFun } - // Extract the project code. - val projectCode: Option[String] = if (projectCodeAndOntologyName.length == 2) { - Some(validateProjectShortcode(projectCodeAndOntologyName.head, errorFun)) + // Determine whether the ontology is shared, and get its project code, if any. + val (sharedOntology: Boolean, projectCode: Option[String]) = if (ontologyPath.head == "shared") { + if (ontologyPath.length == 2) { + (true, Some(DefaultSharedOntologiesProjectCode)) // default shared ontologies project + } else if (ontologyPath.length == 3) { + (true, Some(validateProjectShortcode(ontologyPath(1), errorFun))) // other shared ontologies project + } else { + errorFun + } + } else if (ontologyPath.length == 2) { + (false, Some(validateProjectShortcode(ontologyPath.head, errorFun))) // non-shared ontology with project code } else { - None + (false, None) // built-in ontology } // Extract the ontology name. - val ontologyName = projectCodeAndOntologyName.last + val ontologyName = ontologyPath.last val hasBuiltInOntologyName = isBuiltInOntologyName(ontologyName) if (!hasBuiltInOntologyName) { validateProjectSpecificOntologyName(ontologyName, errorFun) } - if ((hasProjectSpecificHostname && hasBuiltInOntologyName) || - (hostname == BuiltInKnoraApiHostname && !hasBuiltInOntologyName)) { + // If the IRI has the hostname for project-specific ontologies, it can't refer to a built-in or shared ontology. + if (hasProjectSpecificHostname && (hasBuiltInOntologyName || sharedOntology)) { + errorFun + } + + // If the IRI has the hostname for built-in and shared ontologies, it must refer to a built-in or shared ontology. + if (hostname == CentralKnoraApiHostname && !(hasBuiltInOntologyName || sharedOntology)) { errorFun } @@ -839,7 +861,8 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { ontologyName = Some(ontologyName), entityName = entityName, ontologySchema = ontologySchema, - isBuiltInDef = hasBuiltInOntologyName + isBuiltInDef = hasBuiltInOntologyName, + sharedOntology = sharedOntology ) } else { UnknownIriInfo @@ -857,6 +880,8 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { override def isKnoraDefinitionIri: Boolean = iriInfo.iriType == KnoraDefinitionIri + override def isKnoraSharedDefinitionIri: Boolean = isKnoraDefinitionIri && iriInfo.sharedOntology + override def isKnoraInternalDefinitionIri: Boolean = iriInfo.iriType == KnoraDefinitionIri && iriInfo.ontologySchema.contains(InternalSchema) override def isKnoraInternalEntityIri: Boolean = isKnoraInternalDefinitionIri && isKnoraEntityIri @@ -1012,17 +1037,16 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { private def externalToInternalEntityIri: SmartIri = { // Construct the string representation of this IRI in the target schema. - val ontologyName = getOntologyName + val internalOntologyName = externalToInternalOntologyName(getOntologyName) val entityName = getEntityName - val internalOntologyIri = new StringBuilder(OntologyConstants.KnoraInternal.InternalOntologyStart) - - iriInfo.projectCode match { - case Some(projectCode) => internalOntologyIri.append('/').append(projectCode).append('/') - case None => internalOntologyIri.append('/') - } + val internalOntologyIri = makeInternalOntologyIriStr( + internalOntologyName = internalOntologyName, + isShared = iriInfo.sharedOntology, + projectCode = iriInfo.projectCode + ) - val convertedIriStr = internalOntologyIri.append(externalToInternalOntologyName(ontologyName)).append("#").append(entityName).toString + val convertedIriStr = new StringBuilder(internalOntologyIri).append("#").append(entityName).toString // Get it from the cache, or construct it and cache it if it's not there. getOrCacheSmartIri( @@ -1030,7 +1054,7 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { creationFun = { () => val convertedSmartIriInfo = iriInfo.copy( - ontologyName = Some(externalToInternalOntologyName(getOntologyName)), + ontologyName = Some(internalOntologyName), ontologySchema = Some(InternalSchema) ) @@ -1072,6 +1096,19 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { val convertedIriStr: IRI = if (isKnoraBuiltInDefinitionIri) { OntologyConstants.KnoraApi.ApiOntologyStart + internalToExternalOntologyName(ontologyName) + versionSegment + } else if (isKnoraSharedDefinitionIri) { + val externalOntologyIri = new StringBuilder(OntologyConstants.KnoraApi.ApiOntologyStart).append("shared/") + + iriInfo.projectCode match { + case Some(projectCode) => + if (projectCode != DefaultSharedOntologiesProjectCode) { + externalOntologyIri.append(projectCode).append('/') + } + + case None => () + } + + externalOntologyIri.append(ontologyName).append(versionSegment).toString } else { val projectSpecificApiV2OntologyStart = MaybeProjectSpecificApiV2OntologyStart match { case Some(ontologyStart) => ontologyStart @@ -1108,6 +1145,7 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { private lazy val asInternalOntologyIri: SmartIri = { val convertedIriStr = makeInternalOntologyIriStr( internalOntologyName = externalToInternalOntologyName(getOntologyName), + isShared = iriInfo.sharedOntology, projectCode = iriInfo.projectCode ) @@ -1798,15 +1836,23 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { * @param projectCode the project code. * @return the ontology IRI. */ - private def makeInternalOntologyIriStr(internalOntologyName: String, projectCode: Option[String]): IRI = { + private def makeInternalOntologyIriStr(internalOntologyName: String, isShared: Boolean, projectCode: Option[String]): IRI = { val internalOntologyIri = new StringBuilder(OntologyConstants.KnoraInternal.InternalOntologyStart) + if (isShared) { + internalOntologyIri.append("/shared") + } + projectCode match { - case Some(code) => internalOntologyIri.append('/').append(code).append('/') - case None => internalOntologyIri.append('/') + case Some(code) => + if (code != DefaultSharedOntologiesProjectCode) { + internalOntologyIri.append('/').append(code) + } + + case None => () } - internalOntologyIri.append(internalOntologyName).toString + internalOntologyIri.append('/').append(internalOntologyName).toString } /** @@ -1817,8 +1863,8 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { * @param projectCode the project code. * @return the ontology IRI. */ - def makeProjectSpecificInternalOntologyIri(internalOntologyName: String, projectCode: String): SmartIri = { - toSmartIri(makeInternalOntologyIriStr(internalOntologyName, Some(projectCode))) + def makeProjectSpecificInternalOntologyIri(internalOntologyName: String, isShared: Boolean, projectCode: String): SmartIri = { + toSmartIri(makeInternalOntologyIriStr(internalOntologyName, isShared, Some(projectCode))) } /** @@ -1862,8 +1908,16 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { def internalOntologyIriToXmlNamespaceInfoV1(internalOntologyIri: SmartIri): XmlImportNamespaceInfoV1 = { val namespace = new StringBuilder(OntologyConstants.KnoraXmlImportV1.ProjectSpecificXmlImportNamespace.XmlImportNamespaceStart) + if (internalOntologyIri.isKnoraSharedDefinitionIri) { + namespace.append("shared/") + } + internalOntologyIri.getProjectCode match { - case Some(projectCode) => namespace.append(projectCode).append('/') + case Some(projectCode) => + if (projectCode != DefaultSharedOntologiesProjectCode) { + namespace.append(projectCode).append('/') + } + case None => () } @@ -1882,9 +1936,17 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { */ def xmlImportNamespaceToInternalOntologyIriV1(namespace: String, errorFun: => Nothing): SmartIri = { namespace match { - case ProjectSpecificXmlImportNamespaceRegex(projectCode, ontologyName) if !isBuiltInOntologyName(ontologyName) => + case ProjectSpecificXmlImportNamespaceRegex(Optional(maybeShared), _, Optional(maybeProjectCode), ontologyName) if !isBuiltInOntologyName(ontologyName) => + val isShared = maybeShared.nonEmpty + + val projectCode = maybeProjectCode match { + case Some(code) => code + case None => if (isShared) DefaultSharedOntologiesProjectCode else errorFun + } + makeProjectSpecificInternalOntologyIri( internalOntologyName = externalToInternalOntologyName(ontologyName), + isShared = isShared, projectCode = projectCode ) @@ -2011,6 +2073,4 @@ class StringFormatter private(val knoraApiHostAndPort: Option[String]) { case None => errorFun } } - - } diff --git a/webapi/src/main/twirl/queries/sparql/v2/createOntology.scala.txt b/webapi/src/main/twirl/queries/sparql/v2/createOntology.scala.txt index 615c940b8d..be9fd54a42 100644 --- a/webapi/src/main/twirl/queries/sparql/v2/createOntology.scala.txt +++ b/webapi/src/main/twirl/queries/sparql/v2/createOntology.scala.txt @@ -33,6 +33,7 @@ ontologyNamedGraphIri: SmartIri, ontologyIri: SmartIri, projectIri: SmartIri, + isShared: Boolean, ontologyLabel: String, currentTime: Instant) @@ -46,6 +47,7 @@ INSERT { GRAPH ?ontologyNamedGraph { ?ontology rdf:type owl:Ontology ; knora-base:attachedToProject ?project ; + knora-base:isShared @isShared ; rdfs:label """@ontologyLabel"""^^xsd:string ; knora-base:lastModificationDate "@currentTime"^^xsd:dateTimeStamp . } diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.jsonld index 52f730b3c4..e7384a8558 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.jsonld @@ -62,6 +62,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The knora-api ontology in the complex schema" }, { "@id" : "http://api.knora.org/ontology/salsah-gui/v2", @@ -69,13 +70,24 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The salsah-gui ontology" + }, { + "@id" : "http://api.knora.org/ontology/shared/example-box/v2", + "@type" : "owl:Ontology", + "knora-api:attachedToProject" : { + "@id" : "http://www.knora.org/ontology/knora-base#DefaultSharedOntologiesProject" + }, + "knora-api:isShared" : true, + "knora-api:lastModificationDate" : "2018-09-10T14:53:00Z", + "rdfs:label" : "An example of a shared ontology" }, { "@id" : "http://api.knora.org/ontology/standoff/v2", "@type" : "owl:Ontology", "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The standoff ontology" } ], "@context" : { diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.rdf index ec1a4176ab..3c17558df9 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.rdf @@ -39,14 +39,23 @@ + true The knora-api ontology in the complex schema + true The salsah-gui ontology + + + true + 2018-09-10T14:53:00Z + An example of a shared ontology + + true The standoff ontology diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.ttl index ac534471fc..a9f48155b1 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/allOntologyMetadata.ttl @@ -37,12 +37,21 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The knora-api ontology in the complex schema" . a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The salsah-gui ontology" . + a owl:Ontology; + knora-api:attachedToProject ; + knora-api:isShared true; + knora-api:lastModificationDate "2018-09-10T14:53:00Z"; + rdfs:label "An example of a shared ontology" . + a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The standoff ontology" . diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.jsonld new file mode 100644 index 0000000000..b1d132a68c --- /dev/null +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.jsonld @@ -0,0 +1,139 @@ +{ + "@graph" : [ { + "@id" : "example-box:Box", + "@type" : "owl:Class", + "knora-api:canBeInstantiated" : true, + "knora-api:isResourceClass" : true, + "knora-api:resourceIcon" : "thing.png", + "rdfs:comment" : "A shared thing.", + "rdfs:label" : "shared thing", + "rdfs:subClassOf" : [ { + "@id" : "knora-api:Resource" + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:attachedToProject" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:attachedToUser" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:creationDate" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:maxCardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:deleteComment" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:maxCardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:deleteDate" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:minCardinality" : 0, + "owl:onProperty" : { + "@id" : "knora-api:hasIncomingLink" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:hasPermissions" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:minCardinality" : 0, + "owl:onProperty" : { + "@id" : "knora-api:hasStandoffLinkTo" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:minCardinality" : 0, + "owl:onProperty" : { + "@id" : "knora-api:hasStandoffLinkToValue" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:isDeleted" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:maxCardinality" : 1, + "owl:onProperty" : { + "@id" : "knora-api:lastModificationDate" + } + }, { + "@type" : "owl:Restriction", + "knora-api:isInherited" : true, + "owl:cardinality" : 1, + "owl:onProperty" : { + "@id" : "rdfs:label" + } + }, { + "@type" : "owl:Restriction", + "salsah-gui:guiOrder" : 0, + "owl:maxCardinality" : 1, + "owl:onProperty" : { + "@id" : "example-box:hasName" + } + } ] + }, { + "@id" : "example-box:hasName", + "@type" : "owl:ObjectProperty", + "knora-api:isEditable" : true, + "knora-api:isResourceProperty" : true, + "knora-api:objectType" : { + "@id" : "knora-api:TextValue" + }, + "salsah-gui:guiAttribute" : [ "maxlength=255", "size=80" ], + "salsah-gui:guiElement" : { + "@id" : "salsah-gui:SimpleText" + }, + "rdfs:comment" : "Has name.", + "rdfs:label" : "has name", + "rdfs:subPropertyOf" : { + "@id" : "knora-api:hasValue" + } + } ], + "@id" : "http://api.knora.org/ontology/shared/example-box/v2", + "@type" : "owl:Ontology", + "knora-api:attachedToProject" : { + "@id" : "http://www.knora.org/ontology/knora-base#DefaultSharedOntologiesProject" + }, + "knora-api:isShared" : true, + "knora-api:lastModificationDate" : "2018-09-10T14:53:00Z", + "rdfs:label" : "An example of a shared ontology", + "@context" : { + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + "owl" : "http://www.w3.org/2002/07/owl#", + "salsah-gui" : "http://api.knora.org/ontology/salsah-gui/v2#", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "example-box" : "http://api.knora.org/ontology/shared/example-box/v2#" + } +} \ No newline at end of file diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.rdf new file mode 100644 index 0000000000..51f9bae669 --- /dev/null +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.rdf @@ -0,0 +1,115 @@ + + + + + true + 2018-09-10T14:53:00Z + An example of a shared ontology + + + true + true + thing.png + A shared thing. + shared thing + + + + + + + + + + + + + + + + + true + 1 + + + + true + 1 + + + + true + 1 + + + + true + 1 + + + + true + 1 + + + + true + 0 + + + + true + 1 + + + + true + 0 + + + + true + 0 + + + + true + 1 + + + + true + 1 + + + + true + 1 + + + + 0 + 1 + + + true + true + + maxlength=255 + size=80 + + Has name. + has name + + + + + + \ No newline at end of file diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.ttl new file mode 100644 index 0000000000..228bacb3d0 --- /dev/null +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/boxOntologyWithValueObjects.ttl @@ -0,0 +1,83 @@ +@prefix example-box: . +@prefix knora-api: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix salsah-gui: . +@prefix xsd: . + + a owl:Ontology; + knora-api:attachedToProject ; + knora-api:isShared true; + knora-api:lastModificationDate "2018-09-10T14:53:00Z"; + rdfs:label "An example of a shared ontology" . + +example-box:Box a owl:Class; + knora-api:canBeInstantiated true; + knora-api:isResourceClass true; + knora-api:resourceIcon "thing.png"; + rdfs:comment "A shared thing."; + rdfs:label "shared thing"; + rdfs:subClassOf knora-api:Resource, [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty knora-api:attachedToProject + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty knora-api:attachedToUser + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:maxCardinality 1; + owl:onProperty knora-api:lastModificationDate + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty rdfs:label + ], [ a owl:Restriction; + salsah-gui:guiOrder 0; + owl:maxCardinality 1; + owl:onProperty example-box:hasName + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty knora-api:creationDate + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:maxCardinality 1; + owl:onProperty knora-api:deleteComment + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:maxCardinality 1; + owl:onProperty knora-api:deleteDate + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:minCardinality 0; + owl:onProperty knora-api:hasIncomingLink + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty knora-api:hasPermissions + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:minCardinality 0; + owl:onProperty knora-api:hasStandoffLinkTo + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:minCardinality 0; + owl:onProperty knora-api:hasStandoffLinkToValue + ], [ a owl:Restriction; + knora-api:isInherited true; + owl:cardinality 1; + owl:onProperty knora-api:isDeleted + ] . + +example-box:hasName a owl:ObjectProperty; + knora-api:isEditable true; + knora-api:isResourceProperty true; + knora-api:objectType knora-api:TextValue; + salsah-gui:guiAttribute "maxlength=255", "size=80"; + salsah-gui:guiElement salsah-gui:SimpleText; + rdfs:comment "Has name."; + rdfs:label "has name"; + rdfs:subPropertyOf knora-api:hasValue . diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologySimple.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologySimple.rdf index d6961b1ffb..a93c95ed3e 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologySimple.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologySimple.rdf @@ -13,41 +13,41 @@ Randleistentyp Randleiste - - - - - - - - - + + + + + + + + + - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 - + 1 @@ -59,7 +59,7 @@ - + 1 @@ -72,7 +72,7 @@ - + 0 @@ -89,45 +89,45 @@ Diese Resource-Klasse beschreibt ein Buch Book - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 @@ -140,11 +140,11 @@ - + 1 - + 0 @@ -157,7 +157,7 @@ - + 0 @@ -170,7 +170,7 @@ - + 1 @@ -182,7 +182,7 @@ - + 0 @@ -194,7 +194,7 @@ - + 1 @@ -207,7 +207,7 @@ - + 1 @@ -219,7 +219,7 @@ - + 1 @@ -231,7 +231,7 @@ - + 1 @@ -243,7 +243,7 @@ - + 0 @@ -255,7 +255,7 @@ - + 0 @@ -279,22 +279,22 @@ A page is a part of a book Page - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -307,36 +307,36 @@ A fake resource class that only has optional properties Sonstiges - - - - - - - - + + + + + + + + - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 @@ -347,7 +347,7 @@ - + 1 @@ -358,7 +358,7 @@ - + 1 @@ -376,31 +376,31 @@ Original filename - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 - + 1 @@ -412,11 +412,11 @@ - + 1 - + 1 @@ -428,7 +428,7 @@ - + 1 @@ -440,11 +440,11 @@ - + 0 - + 0 @@ -456,19 +456,19 @@ - + 1 - + 1 - + 1 - + 0 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologyWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologyWithValueObjects.rdf index 709781bd39..00c7af9df0 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologyWithValueObjects.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaOntologyWithValueObjects.rdf @@ -17,89 +17,89 @@ Randleistentyp Randleiste - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + 0 1 @@ -117,7 +117,7 @@ - + 1 1 @@ -134,7 +134,7 @@ - + 2 0 @@ -157,92 +157,92 @@ Diese Resource-Klasse beschreibt ein Buch Book - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + 1 1 @@ -261,12 +261,12 @@ - + 2 1 - + 2 0 @@ -285,7 +285,7 @@ - + 3 0 @@ -304,7 +304,7 @@ - + 4 1 @@ -322,7 +322,7 @@ - + 5 0 @@ -341,7 +341,7 @@ - + 5 1 @@ -358,7 +358,7 @@ - + 6 1 @@ -377,7 +377,7 @@ - + 7 1 @@ -395,7 +395,7 @@ - + 9 1 @@ -414,7 +414,7 @@ - + 10 0 @@ -433,7 +433,7 @@ - + 12 0 @@ -471,32 +471,32 @@ A page is a part of a book Page - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + true @@ -538,84 +538,84 @@ A fake resource class that only has optional properties Sonstiges - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + 0 1 @@ -630,7 +630,7 @@ - + 1 1 @@ -645,7 +645,7 @@ - + 2 1 @@ -661,7 +661,7 @@ - + 2 1 @@ -689,72 +689,72 @@ Original filename - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 1 @@ -772,12 +772,12 @@ - + 2 1 - + 2 1 @@ -794,7 +794,7 @@ - + 2 1 @@ -811,7 +811,7 @@ - + 3 1 @@ -829,12 +829,12 @@ - + 5 0 - + 6 0 @@ -853,32 +853,32 @@ - + 7 1 - + 10 1 - + 10 1 - + 11 1 - + 11 1 - + 12 0 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaPageAndBookWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaPageAndBookWithValueObjects.rdf index c4dd4104e3..f7871c9510 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaPageAndBookWithValueObjects.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/incunabulaPageAndBookWithValueObjects.rdf @@ -18,147 +18,147 @@ Diese Resource-Klasse beschreibt ein Buch Book - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + 1 1 - + 2 1 - + 2 0 - + 3 0 - + 4 1 - + 5 0 - + 5 1 - + 6 1 - + 7 1 - + 9 1 - + 10 0 - + 12 0 @@ -170,159 +170,159 @@ A page is a part of a book Page - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 1 - + 2 1 - + 2 1 - + 2 1 - + 3 1 - + 5 0 - + 6 0 - + 7 1 - + 10 1 - + 10 1 - + 11 1 - + 11 1 - + 12 0 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDate.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDate.rdf index 35cdfc190c..1648b61f3d 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDate.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDate.rdf @@ -12,10 +12,10 @@ Represents a date as a period with different possible precisions. Date literal - + - + (GREGORIAN|JULIAN):\d{1,4}(-\d{1,2}(-\d{1,2})?)?( BC| AD| BCE| CE)?(:\d{1,4}(-\d{1,2}(-\d{1,2})?)?( BC| AD| BCE| CE)?)? diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.jsonld index 03bfb80b97..f4a2eaf86d 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.jsonld @@ -134,6 +134,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The knora-api ontology in the complex schema", "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.rdf index e53b6c0ec5..7cd41a6156 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.rdf @@ -8,6 +8,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#"> + true The knora-api ontology in the complex schema @@ -15,105 +16,105 @@ Represents a Knora date value - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.ttl index 716c4a2da8..36ef66f968 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiDateValue.ttl @@ -6,6 +6,7 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The knora-api ontology in the complex schema" . knora-api:DateValue a owl:Class; diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf index 8a2148f044..4fafca5353 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf @@ -12,24 +12,24 @@ A generic class for representing annotations Annotation - - - - - - - + + + + + + + Represents something in the world, or an abstract thing Resource - - - - - + + + + + - + 1 @@ -39,7 +39,7 @@ - + 1 @@ -51,11 +51,11 @@ - + 0 - + 0 @@ -67,7 +67,7 @@ - + 1 @@ -78,7 +78,7 @@ - + 1 @@ -86,7 +86,7 @@ - + 1 @@ -94,29 +94,29 @@ Represents a file containing audio data Representation (Audio) - - - - - - + + + + + + A resource that can store one or more files Representation - - - - - - + + + + + + - + 1 - + 1 @@ -128,19 +128,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -148,10 +148,10 @@ Represents a color. Color literal - + - + #([0-9a-fA-F]{3}){1,2} @@ -162,18 +162,18 @@ Represents a file containg 3D data Representation (3D) - - - - - - + + + + + + - + 1 - + 1 @@ -185,19 +185,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -205,10 +205,10 @@ Represents a date as a period with different possible precisions. Date literal - + - + (GREGORIAN|JULIAN):\d{1,4}(-\d{1,2}(-\d{1,2})?)?( BC| AD| BCE| CE)?(:\d{1,4}(-\d{1,2}(-\d{1,2})?)?( BC| AD| BCE| CE)?)? @@ -218,18 +218,18 @@ Representation (Document) - - - - - - + + + + + + - + 1 - + 1 @@ -241,19 +241,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -266,34 +266,34 @@ A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see. A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see. - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 0 - + 1 - + 1 @@ -306,10 +306,10 @@ Represents a Geoname code. Geoname code - + - + \d{1,8} @@ -320,10 +320,10 @@ Represents an interval. Interprivate val literal - + - + \d+(\.\d+)?,\d+(\.\d+)? @@ -335,27 +335,27 @@ Represents a generic link object Link Object - - - - - - - + + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -367,15 +367,15 @@ - + 0 - + 1 - + 1 @@ -383,22 +383,22 @@ A resource containing moving image data Representation (Movie) - - - - - - + + + + + + - + 1 - + 0 - + 1 @@ -410,15 +410,15 @@ - + 0 - + 1 - + 1 @@ -427,21 +427,21 @@ Represents a geometric region of a resource. The geometry is represented currently as JSON string. Region - - - - - - - - - + + + + + + + + + - + 1 - + 1 @@ -453,11 +453,11 @@ - + 1 - + 1 @@ -469,15 +469,15 @@ - + 0 - + 0 - + 1 @@ -489,19 +489,19 @@ - + 1 - + 1 - + 1 - + 1 @@ -513,39 +513,39 @@ - + 0 - + 0 - + 1 - + 1 - + 1 - + 0 - + 0 - + 1 - + 1 @@ -553,26 +553,26 @@ A resource that can contain two-dimensional still image files Representation (Image) - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -584,11 +584,11 @@ - + 1 - + 1 @@ -596,26 +596,26 @@ A resource containing text files Representation (Text) - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -627,11 +627,11 @@ - + 1 - + 1 @@ -639,34 +639,34 @@ a TextRepresentation representing an XSL transformation that can be applied to an XML created from standoff. The transformation's result is ecptected to be HTML. a TextRepresentation representing an XSL transformation that can be applied to an XML created from standoff. The transformation's result is ecptected to be HTML. - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld index 2cbb1ed9fb..f8512e0468 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld @@ -4647,6 +4647,14 @@ "rdfs:subPropertyOf" : { "@id" : "knora-api:hasLinkToValue" } + }, { + "@id" : "knora-api:isBuiltIn", + "@type" : "owl:DatatypeProperty", + "knora-api:objectType" : { + "@id" : "xsd:boolean" + }, + "rdfs:comment" : "Indicates whether an ontology is built into Knora", + "rdfs:label" : "is shared" }, { "@id" : "knora-api:isDeleted", "@type" : "owl:DatatypeProperty", @@ -4783,6 +4791,14 @@ }, "rdfs:comment" : "Indicates whether class is a subclass of Resource.", "rdfs:label" : "is resource class" + }, { + "@id" : "knora-api:isShared", + "@type" : "owl:DatatypeProperty", + "knora-api:objectType" : { + "@id" : "xsd:boolean" + }, + "rdfs:comment" : "Indicates whether an ontology can be shared by multiple projects", + "rdfs:label" : "is shared" }, { "@id" : "knora-api:isStandoffClass", "@type" : "owl:AnnotationProperty", @@ -5268,6 +5284,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The knora-api ontology in the complex schema", "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf index 4b524bb947..bccc3083f4 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf @@ -8,6 +8,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#"> + true The knora-api ontology in the complex schema @@ -16,40 +17,40 @@ A generic class for representing annotations Annotation - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + true Represents something in the world, or an abstract thing Resource - - - - - - - - - - - - + + + + + + + + + + + + - + true 1 @@ -60,7 +61,7 @@ - + true 1 @@ -71,7 +72,7 @@ - + true 1 @@ -82,7 +83,7 @@ - + true 1 @@ -92,7 +93,7 @@ - + true 1 @@ -102,7 +103,7 @@ - + 1 @@ -117,7 +118,7 @@ - + true 0 @@ -132,7 +133,7 @@ - + true 1 @@ -141,7 +142,7 @@ - + true 0 @@ -156,7 +157,7 @@ - + true 0 @@ -171,7 +172,7 @@ - + 1 @@ -185,7 +186,7 @@ - + 1 @@ -198,7 +199,7 @@ - + true 1 @@ -208,7 +209,7 @@ - + true 1 @@ -217,7 +218,7 @@ - + true 1 @@ -226,40 +227,40 @@ true Represents an audio file - - - - - - - - - - - - + + + + + + + + + + + + true - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + 1 @@ -271,17 +272,17 @@ - + true 1 - + true 1 - + true 1 @@ -294,7 +295,7 @@ - + true 1 @@ -307,7 +308,7 @@ - + true 1 @@ -320,17 +321,17 @@ - + true 1 - + true 1 - + true 1 @@ -341,7 +342,7 @@ - + true 1 @@ -352,7 +353,7 @@ - + true 1 @@ -369,65 +370,65 @@ Represents a file containing audio data Representation (Audio) - - - - - - - - - - - - - + + + + + + + + + + + + + true A resource that can store one or more files Representation - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -442,47 +443,47 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + - + 1 @@ -499,70 +500,70 @@ Represents a boolean value - - - - - - - - - + + + + + + + + + true The base class of classes representing Knora values - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -570,7 +571,7 @@ - + 1 @@ -589,57 +590,57 @@ Represents a color in HTML format, e.g. "#33eeff" - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -648,69 +649,69 @@ true This represents some 3D-object with mesh data, point cloud, etc. - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -720,46 +721,46 @@ Represents a file containg 3D data Representation (3D) - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -774,54 +775,54 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - - - - - - - - - + + + + + + + + + - + 1 @@ -833,7 +834,7 @@ - + 1 @@ -845,7 +846,7 @@ - + 1 @@ -857,7 +858,7 @@ - + 1 @@ -869,7 +870,7 @@ - + 1 @@ -881,7 +882,7 @@ - + 1 @@ -893,7 +894,7 @@ - + 1 @@ -905,7 +906,7 @@ - + 1 @@ -917,7 +918,7 @@ - + 1 @@ -934,105 +935,105 @@ Represents a Knora date value - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1040,7 +1041,7 @@ - + 1 @@ -1059,57 +1060,57 @@ Represents an arbitrary-precision decimal value - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1117,69 +1118,69 @@ true - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1188,46 +1189,46 @@ true Representation (Document) - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1242,89 +1243,89 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1334,80 +1335,80 @@ A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see. A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see. - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 0 - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 @@ -1416,32 +1417,32 @@ true Represents a geometrical objects as JSON string - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + 1 @@ -1453,27 +1454,27 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1481,32 +1482,32 @@ true - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + 1 @@ -1518,27 +1519,27 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1546,7 +1547,7 @@ - + 1 @@ -1565,71 +1566,71 @@ Represents an integer value - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - - + + - + 1 - + 1 @@ -1638,63 +1639,63 @@ Represents a time interval, e.g. in an audio recording - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1706,57 +1707,57 @@ Represents a generic link object Link Object - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 0 - + true 0 - + 1 @@ -1771,7 +1772,7 @@ - + 1 @@ -1786,32 +1787,32 @@ - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 @@ -1821,123 +1822,123 @@ A reification node that describes direct links between resources - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 Represents a flat or hierarchical list - - + + - + 1 - + 1 true - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1949,7 +1950,7 @@ - + 1 @@ -1961,17 +1962,17 @@ - + true 1 - + true 1 - + true 1 @@ -1980,64 +1981,64 @@ true Represents a moving image file - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2049,7 +2050,7 @@ - + 1 @@ -2061,7 +2062,7 @@ - + 1 @@ -2073,7 +2074,7 @@ - + 1 @@ -2085,7 +2086,7 @@ - + 1 @@ -2097,17 +2098,17 @@ - + true 1 - + true 1 - + true 1 @@ -2117,51 +2118,51 @@ A resource containing moving image data Representation (Movie) - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + 1 @@ -2176,32 +2177,32 @@ - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 @@ -2213,50 +2214,50 @@ Represents a geometric region of a resource. The geometry is represented currently as JSON string. Region - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2272,11 +2273,11 @@ - + 1 - + 1 @@ -2291,32 +2292,32 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + 1 @@ -2331,7 +2332,7 @@ - + 1 @@ -2346,42 +2347,42 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2395,86 +2396,86 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 0 - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 @@ -2483,35 +2484,35 @@ Represents a boolean in a TextValue - - - - - - - - - + + + + + + + + + true Represents a knora-base value type in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 @@ -2521,7 +2522,7 @@ - + true 1 @@ -2531,7 +2532,7 @@ - + true 1 @@ -2541,7 +2542,7 @@ - + true 1 @@ -2551,7 +2552,7 @@ - + true 1 @@ -2561,7 +2562,7 @@ - + true 1 @@ -2571,7 +2572,7 @@ - + true 1 @@ -2582,7 +2583,7 @@ - + true 1 @@ -2597,57 +2598,57 @@ Represents a color in a TextValue - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2655,51 +2656,51 @@ true Represents a standoff markup tag - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2709,105 +2710,105 @@ Represents a date in a TextValue - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2817,57 +2818,57 @@ Represents a decimal (floating point) value in a TextValue - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2877,57 +2878,57 @@ Represents an integer value in a TextValue - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2937,32 +2938,32 @@ Represents an internal reference in a TextValue - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + 1 @@ -2971,27 +2972,27 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3001,63 +3002,63 @@ Represents an interval in a TextValue - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3066,32 +3067,32 @@ true Represents a reference to a Knora resource in a TextValue - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + 1 @@ -3100,60 +3101,60 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 @@ -3162,61 +3163,61 @@ Represents an arbitrary URI in a TextValue - - - - - - - - - + + + + + + + + + - + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3233,62 +3234,62 @@ true A file containing a two-dimensional still image - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -3300,7 +3301,7 @@ - + 1 @@ -3312,7 +3313,7 @@ - + 1 @@ -3324,17 +3325,17 @@ - + true 1 - + true 1 - + true 1 @@ -3344,66 +3345,66 @@ A resource that can contain two-dimensional still image files Representation (Image) - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 @@ -3418,17 +3419,17 @@ - + true 1 - + true 1 - + true 1 @@ -3437,69 +3438,69 @@ true A text file such as plain Unicode text, LaTeX, TEI/XML, etc. - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3509,66 +3510,66 @@ A resource containing text files Representation (Text) - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 @@ -3583,17 +3584,17 @@ - + true 1 - + true 1 - + true 1 @@ -3601,45 +3602,45 @@ true - - - - - - - - - - - - + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -3651,7 +3652,7 @@ - + 1 @@ -3663,7 +3664,7 @@ - + 1 @@ -3675,7 +3676,7 @@ - + 0 @@ -3687,22 +3688,22 @@ - + true 1 - + true 1 - + true 1 - + 1 @@ -3711,57 +3712,57 @@ Represents a URI - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3770,35 +3771,35 @@ Represents a Knora user - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 @@ -3807,80 +3808,80 @@ a TextRepresentation representing an XSL transformation that can be applied to an XML created from standoff. The transformation's result is ecptected to be HTML. a TextRepresentation representing an XSL transformation that can be applied to an XML created from standoff. The transformation's result is ecptected to be HTML. - - - - - - - - - - - - - + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 - + true 1 - + true 1 - + true 1 @@ -3928,6 +3929,11 @@ + + + Indicates whether an ontology is built into Knora + is shared + @@ -3979,6 +3985,11 @@ Indicates whether class is a subclass of Resource. is resource class + + + Indicates whether an ontology can be shared by multiple projects + is shared + diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl index bf6a037dfd..a3f19588f7 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl @@ -7,6 +7,7 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The knora-api ontology in the complex schema" . knora-api:Annotation a owl:Class; @@ -2803,6 +2804,11 @@ knora-api:hasRepresentationValue a owl:ObjectProperty; knora-api:subjectType knora-api:Resource; rdfs:subPropertyOf knora-api:hasLinkToValue . +knora-api:isBuiltIn a owl:DatatypeProperty; + knora-api:objectType xsd:boolean; + rdfs:comment "Indicates whether an ontology is built into Knora"; + rdfs:label "is shared" . + knora-api:isEditable a owl:AnnotationProperty; knora-api:objectType xsd:boolean; knora-api:subjectType rdf:Property; @@ -2854,6 +2860,11 @@ knora-api:isResourceClass a owl:AnnotationProperty; rdfs:comment "Indicates whether class is a subclass of Resource."; rdfs:label "is resource class" . +knora-api:isShared a owl:DatatypeProperty; + knora-api:objectType xsd:boolean; + rdfs:comment "Indicates whether an ontology can be shared by multiple projects"; + rdfs:label "is shared" . + knora-api:isStandoffClass a owl:AnnotationProperty; knora-api:objectType xsd:boolean; knora-api:subjectType owl:Class; diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.jsonld index 59b55d32c6..8efee7ffe1 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.jsonld @@ -25,6 +25,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The knora-api ontology in the complex schema", "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.rdf index c297402ee3..bd98df09d1 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.rdf @@ -8,6 +8,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#"> + true The knora-api ontology in the complex schema diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.ttl index ceedb271eb..cd7bebea5e 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiWithValueObjectsHasColor.ttl @@ -7,6 +7,7 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The knora-api ontology in the complex schema" . knora-api:hasColor a owl:ObjectProperty; diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.jsonld index f4d3c67a90..853f8433f7 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.jsonld @@ -156,6 +156,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The salsah-gui ontology", "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.rdf index 9f9b6699af..8572c6af3a 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.rdf @@ -8,6 +8,7 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#"> + true The salsah-gui ontology diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.ttl index 27c7fa0f77..144e719c99 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/salsahGuiOntology.ttl @@ -7,6 +7,7 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The salsah-gui ontology" . salsah-gui:Checkbox a salsah-gui:Guielement, owl:NamedIndividual . diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.jsonld index ecbcf6ff79..baf05ee532 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.jsonld @@ -1964,6 +1964,7 @@ "knora-api:attachedToProject" : { "@id" : "http://www.knora.org/ontology/knora-base#SystemProject" }, + "knora-api:isBuiltIn" : true, "rdfs:label" : "The standoff ontology", "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.rdf index de705235ea..4c798e3203 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.rdf @@ -9,70 +9,71 @@ xmlns:xsd="http://www.w3.org/2001/XMLSchema#"> + true The standoff ontology true Represents a section that is quoted from another source in a text - - - - - - - - + + + + + + + + true Represents structural markup information in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -81,64 +82,64 @@ true Represents bold text in a TextValue - - - - - - - - + + + + + + + + true Represents visual markup information in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -147,51 +148,51 @@ true Represents a linebreak - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -200,51 +201,51 @@ true Represents the title of a work in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -253,51 +254,51 @@ true Represents a section of computer source code in a text - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -306,51 +307,51 @@ true Represents a header of level 1 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -359,51 +360,51 @@ true Represents a header of level 2 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -412,51 +413,51 @@ true Represents a header of level 3 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -465,51 +466,51 @@ true Represents a header of level 4 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -518,51 +519,51 @@ true Represents a header of level 5 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -571,51 +572,51 @@ true Represents a header of level 6 in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -624,63 +625,63 @@ true Represents a hyperlink in a text - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -694,51 +695,51 @@ true Represents italics in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -747,51 +748,51 @@ true Represents a line to seperate content in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -800,51 +801,51 @@ true Represents a list element in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -853,51 +854,51 @@ true Represents an ordered list in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -906,51 +907,51 @@ true Represents a paragraph in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -959,51 +960,51 @@ true Represents a preformatted content in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1012,57 +1013,57 @@ true Represents the root node if the TextValue has been created from XML - - - - - - - - - + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1076,91 +1077,91 @@ true Represents struck text in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1169,51 +1170,51 @@ true Represents subscript in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1222,51 +1223,51 @@ true Represents superscript in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1275,51 +1276,51 @@ true Represents a table body in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1328,51 +1329,51 @@ true Represents a cell in a table - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1381,51 +1382,51 @@ true Represents a row in a table - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1434,51 +1435,51 @@ true Represents a table in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1487,51 +1488,51 @@ true Represents underlined text in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1540,91 +1541,91 @@ true Represents an unordered list in a TextValue - - - - - - - - + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.ttl index 6ab8a1bc66..d1ccc9ff97 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/standoffOntologyWithValueObjects.ttl @@ -7,6 +7,7 @@ a owl:Ontology; knora-api:attachedToProject ; + knora-api:isBuiltIn true; rdfs:label "The standoff ontology" . standoff:StandoffBlockquoteTag a owl:Class; diff --git a/webapi/src/test/scala/org/knora/webapi/SharedTestDataADM.scala b/webapi/src/test/scala/org/knora/webapi/SharedTestDataADM.scala index 8ddfaf5c71..d8c04da1e8 100644 --- a/webapi/src/test/scala/org/knora/webapi/SharedTestDataADM.scala +++ b/webapi/src/test/scala/org/knora/webapi/SharedTestDataADM.scala @@ -161,6 +161,22 @@ object SharedTestDataADM { selfjoin = false ) + val DefaultSharedOntologiesProjectIri: IRI = OntologyConstants.KnoraBase.DefaultSharedOntologiesProject // built-in project + + /* represents the full project info of the default shared ontologies project */ + def defaultSharedOntologiesProject = ProjectADM( + id = OntologyConstants.KnoraBase.DefaultSharedOntologiesProject, + shortname = "DefaultSharedOntologiesProject", + shortcode = "0000", + longname = Some("Default Knora Shared Ontologies Project"), + description = Seq(StringLiteralV2(value = "Default Knora Shared Ontologies Project", language = Some("en"))), + keywords = Seq.empty[String], + logo = None, + ontologies = Seq.empty[IRI], + status = true, + selfjoin = false + ) + /** ***********************************/ /** Images Demo Project Admin Data **/ diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2ESpec.scala index 236af8a864..1dea7e9a41 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/admin/ProjectsADME2ESpec.scala @@ -76,7 +76,7 @@ class ProjectsADME2ESpec extends E2ESpec(ProjectsADME2ESpec.config) with Session // log.debug("projects as objects: {}", AkkaHttpUtils.httpResponseToJson(response).fields("projects").convertTo[Seq[ProjectInfoV1]]) val projects: Seq[ProjectADM] = AkkaHttpUtils.httpResponseToJson(response).fields("projects").convertTo[Seq[ProjectADM]] - projects.size should be (7) + projects.size should be (8) } diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v1/ProjectsV1E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v1/ProjectsV1E2ESpec.scala index 38b154679b..ec315361ef 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v1/ProjectsV1E2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v1/ProjectsV1E2ESpec.scala @@ -74,7 +74,8 @@ class ProjectsV1E2ESpec extends E2ESpec(ProjectsV1E2ESpec.config) with SessionJs // log.debug("projects as objects: {}", AkkaHttpUtils.httpResponseToJson(response).fields("projects").convertTo[Seq[ProjectInfoV1]]) val projects: Seq[ProjectInfoV1] = AkkaHttpUtils.httpResponseToJson(response).fields("projects").convertTo[Seq[ProjectInfoV1]] - projects.size should be (7) + + projects.size should be (8) } diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v1/ResourcesV1R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v1/ResourcesV1R2RSpec.scala index 5deb312947..1b2cb3fd20 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v1/ResourcesV1R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v1/ResourcesV1R2RSpec.scala @@ -61,6 +61,9 @@ class ResourcesV1R2RSpec extends R2RSpec { private val resourcesPath = ResourcesRouteV1.knoraApiPath(system, settings, log) private val valuesPath = ValuesRouteV1.knoraApiPath(system, settings, log) + private val superUser = SharedTestDataADM.superUser + private val superUserEmail = superUser.email + private val imagesUser = SharedTestDataADM.imagesUser01 private val imagesUserEmail = imagesUser.email @@ -86,6 +89,7 @@ class ResourcesV1R2RSpec extends R2RSpec { implicit val ec: ExecutionContextExecutor = system.dispatcher override lazy val rdfDataObjects = List( + RdfDataObject(path = "_test_data/ontologies/example-box.ttl", name = "http://www.knora.org/ontology/shared/example-box"), RdfDataObject(path = "_test_data/ontologies/empty-thing-onto.ttl", name = "http://www.knora.org/ontology/0001/empty-thing"), RdfDataObject(path = "_test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/0001/anything"), RdfDataObject(path = "_test_data/demo_data/images-demo-data.ttl", name = "http://www.knora.org/data/00FF/images"), @@ -1438,6 +1442,75 @@ class ResourcesV1R2RSpec extends R2RSpec { } } + "not create an anything:Thing in the incunabula project in a bulk import" in { + val xmlImport = + s""" + | + | + | These are a few of my favorite things + | This is a test. + | + |""".stripMargin + + val projectIri = URLEncoder.encode("http://rdfh.ch/projects/0803", "UTF-8") + + Post(s"/v1/resources/xmlimport/$projectIri", HttpEntity(ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`), xmlImport)) ~> addCredentials(BasicHttpCredentials(incunabulaUserEmail, password)) ~> resourcesPath ~> check { + assert(status == StatusCodes.BadRequest, response.toString) + val responseStr = responseAs[String] + responseStr should include("not shared") + } + } + + "not create a resource in a shared ontologies project in a bulk import" in { + val xmlImport = + s""" + | + | + | test box + | This is a test. + | + |""".stripMargin + + val projectIri = URLEncoder.encode("http://www.knora.org/ontology/knora-base#DefaultSharedOntologiesProject", "UTF-8") + + Post(s"/v1/resources/xmlimport/$projectIri", HttpEntity(ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`), xmlImport)) ~> addCredentials(BasicHttpCredentials(superUserEmail, password)) ~> resourcesPath ~> check { + assert(status == StatusCodes.BadRequest, response.toString) + val responseStr = responseAs[String] + responseStr should include("Resources cannot be created in project") + } + } + + "create a resource in the incunabula project using a class from the default shared ontologies project" in { + val xmlImport = + s""" + | + | + | test box + | This is a test. + | + |""".stripMargin + + val projectIri = URLEncoder.encode("http://rdfh.ch/projects/0803", "UTF-8") + + Post(s"/v1/resources/xmlimport/$projectIri", HttpEntity(ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`), xmlImport)) ~> addCredentials(BasicHttpCredentials(incunabulaUserEmail, password)) ~> resourcesPath ~> check { + val responseStr = responseAs[String] + assert(status == StatusCodes.OK, responseStr) + responseStr should include("createdResources") + } + } + "use a knora-base property directly in a bulk import" in { val xmlImport = s""" diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala index 2e0acaf25c..a9e9ed678f 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/OntologyV2R2RSpec.scala @@ -9,6 +9,7 @@ import akka.http.scaladsl.model._ import akka.http.scaladsl.model.headers.{Accept, BasicHttpCredentials} import akka.http.scaladsl.testkit.RouteTestTimeout import org.knora.webapi._ +import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.messages.v2.responder.ontologymessages.InputOntologyV2 import org.knora.webapi.routing.v2.OntologiesRouteV2 import org.knora.webapi.util.IriConversions._ @@ -26,6 +27,9 @@ object OntologyV2R2RSpec { private val anythingUserProfile = SharedTestDataADM.anythingAdminUser private val anythingUsername = anythingUserProfile.email + private val superUserProfile = SharedTestDataADM.superUser + private val superUsername = superUserProfile.email + private val password = "test" } @@ -54,6 +58,10 @@ class OntologyV2R2RSpec extends R2RSpec { // If false, the responses from the server are compared to the contents fo the expected response files. private val writeGetTestResponses = false + override lazy val rdfDataObjects = List( + RdfDataObject(path = "_test_data/ontologies/example-box.ttl", name = "http://www.knora.org/ontology/shared/example-box") + ) + /** * Represents an HTTP GET test that requests ontology information. * @@ -105,6 +113,7 @@ class OntologyV2R2RSpec extends R2RSpec { private val incunabulaWithValueObjectsPubDateSegment = URLEncoder.encode("http://0.0.0.0:3333/ontology/0803/incunabula/v2#pubdate", "UTF-8") private val incunabulaWithValueObjectsPageSegment = URLEncoder.encode("http://0.0.0.0:3333/ontology/0803/incunabula/v2#page", "UTF-8") private val incunabulaWithValueObjectsBookSegment = URLEncoder.encode("http://0.0.0.0:3333/ontology/0803/incunabula/v2#book", "UTF-8") + private val boxOntologyWithValueObjectsSegment = URLEncoder.encode("http://api.knora.org/ontology/shared/example-box/v2", "UTF-8") // The URLs and expected response files for each HTTP GET test. private val httpGetTests = Seq( @@ -124,7 +133,8 @@ class OntologyV2R2RSpec extends R2RSpec { HttpGetTest(s"/v2/ontologies/properties/$knoraApiWithValueObjectsHasColorSegment", "knoraApiWithValueObjectsHasColor"), HttpGetTest(s"/v2/ontologies/properties/$incunabulaSimplePubdateSegment", "incunabulaSimplePubDate"), HttpGetTest(s"/v2/ontologies/properties/$incunabulaWithValueObjectsPubDateSegment", "incunabulaWithValueObjectsPubDate"), - HttpGetTest(s"/v2/ontologies/classes/$incunabulaWithValueObjectsPageSegment/$incunabulaWithValueObjectsBookSegment", "incunabulaPageAndBookWithValueObjects") + HttpGetTest(s"/v2/ontologies/classes/$incunabulaWithValueObjectsPageSegment/$incunabulaWithValueObjectsBookSegment", "incunabulaPageAndBookWithValueObjects"), + HttpGetTest(s"/v2/ontologies/allentities/$boxOntologyWithValueObjectsSegment", "boxOntologyWithValueObjects") ) // The media types that will be used in HTTP Accept headers in HTTP GET tests. @@ -139,7 +149,9 @@ class OntologyV2R2RSpec extends R2RSpec { private val AnythingOntologyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2".toSmartIri private var anythingLastModDate: Instant = Instant.parse("2017-12-19T15:23:42.166Z") - private var secondAnythingLastModDate: Instant = Instant.now + + private val uselessIri = new MutableTestIri + private var uselessLastModDate: Instant = Instant.now private def getPropertyIrisFromResourceClassResponse(responseJsonDoc: JsonLDDocument): Set[SmartIri] = { val classDef = responseJsonDoc.body.requireArray("@graph").value.head.asInstanceOf[JsonLDObject] @@ -362,7 +374,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.properties.head._2.predicates(OntologyConstants.Rdfs.Label.toSmartIri).objects should ===(paramsAsInput.properties.head._2.predicates.head._2.objects) @@ -412,7 +424,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.properties.head._2.predicates(OntologyConstants.Rdfs.Comment.toSmartIri).objects should ===(paramsAsInput.properties.head._2.predicates.head._2.objects) @@ -626,7 +638,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.classes.head._2.predicates(OntologyConstants.Rdfs.Label.toSmartIri).objects should ===(paramsAsInput.classes.head._2.predicates.head._2.objects) @@ -673,7 +685,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.classes.head._2.predicates(OntologyConstants.Rdfs.Comment.toSmartIri).objects should ===(paramsAsInput.classes.head._2.predicates.head._2.objects) @@ -730,7 +742,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.properties should ===(paramsAsInput.properties) @@ -934,7 +946,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.properties should ===(paramsAsInput.properties) @@ -1056,7 +1068,7 @@ class OntologyV2R2RSpec extends R2RSpec { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - // Comvert the response to an InputOntologyV2 and compare the relevant part of it to the request. + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape responseAsInput.properties should ===(paramsAsInput.properties) @@ -1228,16 +1240,18 @@ class OntologyV2R2RSpec extends R2RSpec { } } + "create a shared ontology and put a property in it" in { + val label = "The useless ontology" - "create a second ontology called 'anything' in another project, and get a response containing prefix labels for both 'anything' ontologies" in { - val createOntologyRequest = + val createOntologyJson = s""" |{ - | "knora-api:ontologyName": "anything", + | "knora-api:ontologyName": "useless", | "knora-api:attachedToProject": { - | "@id": "$imagesProjectIri" + | "@id": "${OntologyConstants.KnoraBase.DefaultSharedOntologiesProject}" | }, - | "rdfs:label": "The second anything ontology", + | "knora-api:isShared": true, + | "rdfs:label": "$label", | "@context": { | "rdfs": "${OntologyConstants.Rdfs.RdfsPrefixExpansion}", | "knora-api": "${OntologyConstants.KnoraApiV2WithValueObjects.KnoraApiV2PrefixExpansion}" @@ -1245,32 +1259,40 @@ class OntologyV2R2RSpec extends R2RSpec { |} """.stripMargin - Post("/v2/ontologies", HttpEntity(RdfMediaTypes.`application/ld+json`, createOntologyRequest)) ~> addCredentials(BasicHttpCredentials(imagesUsername, password)) ~> ontologiesPath ~> check { + Post("/v2/ontologies", HttpEntity(RdfMediaTypes.`application/ld+json`, createOntologyJson)) ~> addCredentials(BasicHttpCredentials(superUsername, password)) ~> ontologiesPath ~> check { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) val metadata = responseJsonDoc.body - secondAnythingLastModDate = Instant.parse(metadata.value(OntologyConstants.KnoraApiV2WithValueObjects.LastModificationDate).asInstanceOf[JsonLDString].value) + val ontologyIri = metadata.value("@id").asInstanceOf[JsonLDString].value + assert(ontologyIri == "http://api.knora.org/ontology/shared/useless/v2") + uselessIri.set(ontologyIri) + assert(metadata.value(OntologyConstants.Rdfs.Label) == JsonLDString(label)) + val lastModDate = Instant.parse(metadata.value(OntologyConstants.KnoraApiV2WithValueObjects.LastModificationDate).asInstanceOf[JsonLDString].value) + uselessLastModDate = lastModDate } - val createClassRequest = + val createPropertyJson = s""" |{ - | "@id" : "http://0.0.0.0:3333/ontology/00FF/anything/v2", + | "@id" : "${uselessIri.get}", | "@type" : "owl:Ontology", - | "knora-api:lastModificationDate" : "$secondAnythingLastModDate", + | "knora-api:lastModificationDate" : "$uselessLastModDate", | "@graph" : [ { - | "@id" : "p00FF-anything:DerivedThing", - | "@type" : "owl:Class", - | "rdfs:label" : { - | "@language" : "en", - | "@value" : "derived thing" + | "@id" : "useless:hasSharedName", + | "@type" : "owl:ObjectProperty", + | "knora-api:objectType" : { + | "@id" : "knora-api:TextValue" | }, | "rdfs:comment" : { | "@language" : "en", - | "@value" : "Represents a derived thing" + | "@value" : "Represents a name" | }, - | "rdfs:subClassOf" : { - | "@id" : "p0001-anything:Thing" + | "rdfs:label" : { + | "@language" : "en", + | "@value" : "has shared name" + | }, + | "rdfs:subPropertyOf" : { + | "@id" : "knora-api:hasValue" | } | } ], | "@context" : { @@ -1279,28 +1301,26 @@ class OntologyV2R2RSpec extends R2RSpec { | "owl" : "http://www.w3.org/2002/07/owl#", | "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", | "xsd" : "http://www.w3.org/2001/XMLSchema#", - | "p0001-anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#", - | "p00FF-anything" : "http://0.0.0.0:3333/ontology/00FF/anything/v2#" + | "useless" : "${uselessIri.get}#" | } |} """.stripMargin - Post("/v2/ontologies/classes", HttpEntity(RdfMediaTypes.`application/ld+json`, createClassRequest)) ~> addCredentials(BasicHttpCredentials(imagesUsername, password)) ~> ontologiesPath ~> check { + // Convert the submitted JSON-LD to an InputOntologyV2, without SPARQL-escaping, so we can compare it to the response. + val paramsAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(JsonLDUtil.parseJsonLD(createPropertyJson)).unescape + + Post("/v2/ontologies/properties", HttpEntity(RdfMediaTypes.`application/ld+json`, createPropertyJson)) ~> addCredentials(BasicHttpCredentials(superUsername, password)) ~> ontologiesPath ~> check { assert(status == StatusCodes.OK, response.toString) val responseJsonDoc = responseToJsonLDDocument(response) - val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape - val newAnythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get - assert(newAnythingLastModDate.isAfter(anythingLastModDate)) - secondAnythingLastModDate = newAnythingLastModDate - } - Get(s"/v2/ontologies/allentities/${URLEncoder.encode("http://0.0.0.0:3333/ontology/00FF/anything/v2", "UTF-8")}") ~> ontologiesPath ~> check { - assert(status == StatusCodes.OK, response.toString) + // Convert the response to an InputOntologyV2 and compare the relevant part of it to the request. + val responseAsInput: InputOntologyV2 = InputOntologyV2.fromJsonLD(responseJsonDoc, ignoreExtraData = true).unescape + responseAsInput.properties should ===(paramsAsInput.properties) - val jsonResponse = JsonParser(responseAs[String]) - val context = jsonResponse.asJsObject.fields("@context").asJsObject.fields - assert(context("p0001-anything").asInstanceOf[JsString] == JsString("http://0.0.0.0:3333/ontology/0001/anything/v2#")) - assert(context("p00FF-anything").asInstanceOf[JsString] == JsString("http://0.0.0.0:3333/ontology/00FF/anything/v2#")) + // Check that the ontology's last modification date was updated. + val newAnythingLastModDate = responseAsInput.ontologyMetadata.lastModificationDate.get + assert(newAnythingLastModDate.isAfter(uselessLastModDate)) + uselessLastModDate = newAnythingLastModDate } } } diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2R2RSpec.scala index 6e7323fc9a..d0752e99cc 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ValuesRouteV2R2RSpec.scala @@ -3,25 +3,20 @@ package org.knora.webapi.e2e.v2 import java.net.URLEncoder import java.time.Instant -import akka.actor.{ActorSystem, Props} +import akka.actor.ActorSystem import akka.http.scaladsl.model.headers.BasicHttpCredentials import akka.http.scaladsl.model.{HttpEntity, StatusCodes} import akka.http.scaladsl.testkit.RouteTestTimeout -import akka.pattern._ -import akka.util.Timeout import org.knora.webapi._ -import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, ResetTriplestoreContent} -import org.knora.webapi.messages.v2.responder.ontologymessages.LoadOntologiesRequestV2 -import org.knora.webapi.responders.{RESPONDER_MANAGER_ACTOR_NAME, ResponderManager} +import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject import org.knora.webapi.routing.v2.{ResourcesRouteV2, SearchRouteV2, ValuesRouteV2} -import org.knora.webapi.store.{STORE_MANAGER_ACTOR_NAME, StoreManager} import org.knora.webapi.util.IriConversions._ import org.knora.webapi.util._ import org.knora.webapi.util.jsonld._ import org.knora.webapi.util.search.SparqlQueryConstants +import scala.concurrent.ExecutionContextExecutor import scala.concurrent.duration.DurationInt -import scala.concurrent.{Await, ExecutionContextExecutor} class ValuesRouteV2R2RSpec extends R2RSpec { @@ -162,11 +157,6 @@ class ValuesRouteV2R2RSpec extends R2RSpec { ) } - "Load test data" in { - Await.result(storeManager ? ResetTriplestoreContent(rdfDataObjects), 360.seconds) - Await.result(responderManager ? LoadOntologiesRequestV2(KnoraSystemInstances.Users.SystemUser), 30.seconds) - } - "The values v2 endpoint" should { "create an integer value" in { diff --git a/webapi/src/test/scala/org/knora/webapi/other/v1/DrawingsGodsV1E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/other/v1/DrawingsGodsV1E2ESpec.scala index be9eeb5c69..9f767fc7d6 100644 --- a/webapi/src/test/scala/org/knora/webapi/other/v1/DrawingsGodsV1E2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/other/v1/DrawingsGodsV1E2ESpec.scala @@ -49,7 +49,8 @@ class DrawingsGodsV1E2ESpec extends E2ESpec(DrawingsGodsV1E2ESpec.config) with T RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/drawings-gods_admin-data.ttl", name = "http://www.knora.org/data/admin"), RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/drawings-gods_permissions-data.ttl", name = "http://www.knora.org/data/permissions"), RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/drawings-gods_ontology.ttl", name = "http://www.knora.org/ontology/0105/drawings-gods"), - RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/drawings-gods_data.ttl", name = "http://www.knora.org/data/0105/drawings-gods") + RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/drawings-gods_data.ttl", name = "http://www.knora.org/data/0105/drawings-gods"), + RdfDataObject(path = "_test_data/other.v1.DrawingsGodsV1Spec/parole-religieuse-dummy-onto.ttl", name = "http://www.knora.org/ontology/0106/parole-religieuse") ) /** @@ -72,7 +73,7 @@ class DrawingsGodsV1E2ESpec extends E2ESpec(DrawingsGodsV1E2ESpec.config) with T val params = s""" |{ - | "restype_id": "http://www.knora.org/ontology/0001/anything#Thing", + | "restype_id": "http://www.knora.org/ontology/0106/parole-religieuse#Thing", | "label": "A thing", | "project_id": "http://rdfh.ch/projects/0106", | "properties": {} @@ -95,7 +96,7 @@ class DrawingsGodsV1E2ESpec extends E2ESpec(DrawingsGodsV1E2ESpec.config) with T s""" |{ | "res_id": "${thingIri.get}", - | "prop": "http://www.knora.org/ontology/0001/anything#hasInteger", + | "prop": "http://www.knora.org/ontology/0106/parole-religieuse#hasInteger", | "int_value": 1234 |} """.stripMargin @@ -115,7 +116,7 @@ class DrawingsGodsV1E2ESpec extends E2ESpec(DrawingsGodsV1E2ESpec.config) with T s""" |{ | "res_id": "${thingIri.get}", - | "prop": "http://www.knora.org/ontology/0001/anything#hasInteger", + | "prop": "http://www.knora.org/ontology/0106/parole-religieuse#hasInteger", | "int_value": 1111 |} """.stripMargin @@ -136,7 +137,7 @@ class DrawingsGodsV1E2ESpec extends E2ESpec(DrawingsGodsV1E2ESpec.config) with T s""" |{ | "res_id": "${thingIri.get}", - | "prop": "http://www.knora.org/ontology/0001/anything#hasInteger", + | "prop": "http://www.knora.org/ontology/0106/parole-religieuse#hasInteger", | "int_value": 2222 |} """.stripMargin diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v1/ResourcesResponderV1Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v1/ResourcesResponderV1Spec.scala index 9bf86b1235..3b62373ce4 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v1/ResourcesResponderV1Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v1/ResourcesResponderV1Spec.scala @@ -1447,6 +1447,38 @@ class ResourcesResponderV1Spec extends CoreSpec(ResourcesResponderV1Spec.config) } } + "not create an instance of anything:Thing in the incunabula project" in { + actorUnderTest ! ResourceCreateRequestV1( + resourceTypeIri = "http://www.knora.org/ontology/0001/anything#Thing", + label = "Test Resource", + projectIri = "http://rdfh.ch/projects/0803", + values = Map.empty[IRI, Seq[CreateValueV1WithComment]], + file = None, + userProfile = SharedTestDataADM.incunabulaProjectAdminUser, + apiRequestID = UUID.randomUUID + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + } + + "not create a resource in the default shared ontologies project" in { + actorUnderTest ! ResourceCreateRequestV1( + resourceTypeIri = "http://www.knora.org/ontology/shared/example-box#Box", + label = "Test Resource", + projectIri = OntologyConstants.KnoraBase.DefaultSharedOntologiesProject, + values = Map.empty[IRI, Seq[CreateValueV1WithComment]], + file = None, + userProfile = SharedTestDataADM.superUser, + apiRequestID = UUID.randomUUID + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + } + "change a resource's label" in { val myNewLabel = "my new beautiful label" diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala index c00d8a6224..c46556f6af 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/OntologyResponderV2Spec.scala @@ -49,6 +49,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { private val actorUnderTest = TestActorRef[OntologyResponderV2] + private val exampleSharedOntology = RdfDataObject(path = "_test_data/ontologies/example-box.ttl", name = "http://www.knora.org/ontology/shared/example-box") private val anythingData = RdfDataObject(path = "_test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/0001/anything") // The default timeout for receiving reply messages from actors. @@ -57,12 +58,17 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { private val fooIri = new MutableTestIri private var fooLastModDate: Instant = Instant.now + private val chairIri = new MutableTestIri + private var chairLastModDate: Instant = Instant.now + + private val ExampleSharedOntologyIri = "http://api.knora.org/ontology/shared/example-box/v2".toSmartIri + private val IncunabulaOntologyIri = "http://0.0.0.0:3333/ontology/0803/incunabula/v2".toSmartIri private val AnythingOntologyIri = "http://0.0.0.0:3333/ontology/0001/anything/v2".toSmartIri private var anythingLastModDate: Instant = Instant.now private val printErrorMessages = false - override lazy val rdfDataObjects: Seq[RdfDataObject] = List(anythingData) + override lazy val rdfDataObjects: Seq[RdfDataObject] = List(exampleSharedOntology, anythingData) private def customLoadTestData(rdfDataObjs: List[RdfDataObject], expectOK: Boolean = false): Unit = { storeManager ! ResetTriplestoreContent(rdfDataObjs) @@ -188,7 +194,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { // Request the metadata of all ontologies to check that 'foo' isn't listed. - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( requestingUser = imagesUser ) @@ -200,7 +206,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! LoadOntologiesRequestV2(KnoraSystemInstances.Users.SystemUser) expectMsgType[SuccessResponseV2](10.seconds) - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( requestingUser = imagesUser ) @@ -209,7 +215,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } "not delete the 'anything' ontology, because it is used in data and in the 'something' ontology" in { - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( projectIris = Set(anythingProjectIri), requestingUser = anythingAdminUser ) @@ -363,9 +369,77 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } + "not create an ontology called 'shared'" in { + actorUnderTest ! CreateOntologyRequestV2( + ontologyName = "shared", + projectIri = imagesProjectIri, + label = "The invalid shared ontology", + apiRequestID = UUID.randomUUID, + requestingUser = imagesUser + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + + } + + "not create a shared ontology in the wrong project" in { + actorUnderTest ! CreateOntologyRequestV2( + ontologyName = "misplaced", + projectIri = imagesProjectIri, + isShared = true, + label = "The invalid shared ontology", + apiRequestID = UUID.randomUUID, + requestingUser = imagesUser + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + } + + "not create a non-shared ontology in the shared ontologies project" in { + actorUnderTest ! CreateOntologyRequestV2( + ontologyName = "misplaced", + projectIri = OntologyConstants.KnoraBase.DefaultSharedOntologiesProject.toSmartIri, + label = "The invalid non-shared ontology", + apiRequestID = UUID.randomUUID, + requestingUser = SharedTestDataADM.superUser + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + } + + "create a shared ontology" in { + actorUnderTest ! CreateOntologyRequestV2( + ontologyName = "chair", + projectIri = OntologyConstants.KnoraBase.DefaultSharedOntologiesProject.toSmartIri, + isShared = true, + label = "a chaired ontology", + apiRequestID = UUID.randomUUID, + requestingUser = SharedTestDataADM.superUser + ) + + val response = expectMsgType[ReadOntologyMetadataV2](timeout) + assert(response.ontologies.size == 1) + val metadata = response.ontologies.head + assert(metadata.ontologyIri.toString == "http://www.knora.org/ontology/shared/chair") + chairIri.set(metadata.ontologyIri.toOntologySchema(ApiV2WithValueObjects).toString) + chairLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + } + "not allow a user to create a property if they are not a sysadmin or an admin in the ontology's project" in { - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( projectIris = Set(anythingProjectIri), requestingUser = anythingNonAdminUser ) @@ -426,7 +500,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { "create a property anything:hasName as a subproperty of knora-api:hasValue and schema:name" in { - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( projectIris = Set(anythingProjectIri), requestingUser = anythingAdminUser ) @@ -511,7 +585,7 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { "create a link property in the 'anything' ontology, and automatically create the corresponding link value property" in { - actorUnderTest ! OntologyMetadataGetRequestV2( + actorUnderTest ! OntologyMetadataGetByProjectRequestV2( projectIris = Set(anythingProjectIri), requestingUser = anythingAdminUser ) @@ -3397,107 +3471,661 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { } } - "not load an ontology that has no knora-base:attachedToProject" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/onto-without-project.ttl", name = "http://www.knora.org/ontology/invalid" - )) + "not create a class whose base class is in a non-shared ontology in another project" in { + val classIri = AnythingOntologyIri.makeEntityIri("InvalidClass") - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("invalid class", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents an invalid class", Some("en"))) + ) + ), + subClassOf = Set(IncunabulaOntologyIri.makeEntityIri("book")), + ontologySchema = ApiV2WithValueObjects + ) - "not load an ontology containing a class that's missing a cardinality for a link value property" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/missing-link-value-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + actorUnderTest ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } } + "not create a class with a cardinality on a property defined in a non-shared ontology in another project" in { + val classIri = AnythingOntologyIri.makeEntityIri("InvalidClass") - "not load an ontology containing a class that's missing a cardinality for a link property" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/missing-link-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) - - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("invalid class", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents an invalid class", Some("en"))) + ) + ), + subClassOf = Set(OntologyConstants.KnoraApiV2WithValueObjects.Resource.toSmartIri), + directCardinalities = Map(IncunabulaOntologyIri.makeEntityIri("description") -> KnoraCardinalityInfo(Cardinality.MayHaveOne)), + ontologySchema = ApiV2WithValueObjects + ) - "not load an ontology containing a class with a cardinality whose subject class constraint is incompatible with the class" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-incompatible-with-scc-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + actorUnderTest ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } } - "not load an ontology containing a resource class without an rdfs:label" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-without-label-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) - - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + "not create a subproperty of a property defined in a non-shared ontology in another project" in { - "not load an ontology containing a resource property without an rdfs:label" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/property-without-label-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + val propertyIri = AnythingOntologyIri.makeEntityIri("invalidProperty") - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.KnoraApiV2WithValueObjects.TextValue.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq( + StringLiteralV2("invalid property", Some("en")) + ) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq( + StringLiteralV2("An invalid property definition", Some("en")) + ) + ) + ), + subPropertyOf = Set(IncunabulaOntologyIri.makeEntityIri("description")), + ontologySchema = ApiV2WithValueObjects + ) - "not load an ontology containing a resource class that is also a standoff class" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/resource-class-is-standoff-class-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } } - "not load an ontology containing a resource class with a cardinality on an undefined property" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-missing-property-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) - - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + "not create property with a subject type defined in a non-shared ontology in another project" in { - "not load an ontology containing a resource class with a directly defined cardinality on a non-resource property" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-resource-prop-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + val propertyIri = AnythingOntologyIri.makeEntityIri("invalidProperty") - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.SubjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.SubjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(IncunabulaOntologyIri.makeEntityIri("book"))) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.KnoraApiV2WithValueObjects.TextValue.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq( + StringLiteralV2("invalid property", Some("en")) + ) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq( + StringLiteralV2("An invalid property definition", Some("en")) + ) + ) + ), + subPropertyOf = Set(OntologyConstants.KnoraApiV2WithValueObjects.HasValue.toSmartIri), + ontologySchema = ApiV2WithValueObjects + ) - "not load an ontology containing a resource class with a cardinality on knora-base:resourceProperty" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-cardinality-on-kbresprop-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } } - "not load an ontology containing a resource class with a cardinality on knora-base:hasValue" in { - val invalidOnto = List(RdfDataObject( - path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-cardinality-on-kbhasvalue-onto.ttl", name = "http://www.knora.org/ontology/invalid" - )) + "not create property with an object type defined in a non-shared ontology in another project" in { - customLoadTestData(invalidOnto) - expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) - } + val propertyIri = AnythingOntologyIri.makeEntityIri("invalidProperty") - "not load an ontology containing a resource class with a base class that has a Knora IRI but isn't a resource class" in { + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(IncunabulaOntologyIri.makeEntityIri("book"))) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq( + StringLiteralV2("invalid property", Some("en")) + ) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq( + StringLiteralV2("An invalid property definition", Some("en")) + ) + ) + ), + subPropertyOf = Set(OntologyConstants.KnoraApiV2WithValueObjects.HasLinkTo.toSmartIri), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: akka.actor.Status.Failure => + if (printErrorMessages) println(msg.cause.getMessage) + msg.cause.isInstanceOf[BadRequestException] should ===(true) + } + } + + "create a class anything:AnyBox1 as a subclass of example-box:Box" in { + val classIri = AnythingOntologyIri.makeEntityIri("AnyBox1") + + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("any box", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents any box", Some("en"))) + ) + ), + subClassOf = Set(ExampleSharedOntologyIri.makeEntityIri("Box")), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2WithValueObjects) + assert(externalOntology.classes.size == 1) + val readClassInfo = externalOntology.classes(classIri) + readClassInfo.entityInfoContent should ===(classInfoContent) + + val metadata = externalOntology.ontologyMetadata + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "delete the class anything:AnyBox1" in { + val classIri = AnythingOntologyIri.makeEntityIri("AnyBox1") + + actorUnderTest ! DeleteClassRequestV2( + classIri = classIri, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyMetadataV2 => + assert(msg.ontologies.size == 1) + val metadata = msg.ontologies.head + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "create a class anything:AnyBox2 with a cardinality on example-box:hasName" in { + val classIri = AnythingOntologyIri.makeEntityIri("AnyBox2") + + val classInfoContent = ClassInfoContentV2( + classIri = classIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.Class.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("any box", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents any box", Some("en"))) + ) + ), + subClassOf = Set(ExampleSharedOntologyIri.makeEntityIri("Box")), + directCardinalities = Map(ExampleSharedOntologyIri.makeEntityIri("hasName") -> KnoraCardinalityInfo(Cardinality.MayHaveOne)), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreateClassRequestV2( + classInfoContent = classInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2WithValueObjects) + assert(externalOntology.classes.size == 1) + val readClassInfo = externalOntology.classes(classIri) + readClassInfo.entityInfoContent should ===(classInfoContent) + + val metadata = externalOntology.ontologyMetadata + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "delete the class anything:AnyBox2" in { + val classIri = AnythingOntologyIri.makeEntityIri("AnyBox2") + + actorUnderTest ! DeleteClassRequestV2( + classIri = classIri, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyMetadataV2 => + assert(msg.ontologies.size == 1) + val metadata = msg.ontologies.head + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "create a property anything:hasAnyName with base property example-box:hasName" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("hasAnyName") + + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.KnoraApiV2WithValueObjects.TextValue.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("has any shared name", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents a shared name", Some("en"))) + ) + ), + subPropertyOf = Set(ExampleSharedOntologyIri.makeEntityIri("hasName")), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2WithValueObjects) + assert(externalOntology.properties.size == 1) + val property = externalOntology.properties(propertyIri) + + property.entityInfoContent should ===(propertyInfoContent) + val metadata = externalOntology.ontologyMetadata + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "delete the property anything:hasAnyName" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("hasAnyName") + + actorUnderTest ! DeletePropertyRequestV2( + propertyIri = propertyIri, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyMetadataV2 => + assert(msg.ontologies.size == 1) + val metadata = msg.ontologies.head + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "create a property anything:BoxHasBoolean with subject type example-box:Box" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("BoxHasBoolean") + + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.SubjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.SubjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(ExampleSharedOntologyIri.makeEntityIri("Box"))) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.KnoraApiV2WithValueObjects.BooleanValue.toSmartIri)) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("has boolean", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Represents a boolean", Some("en"))) + ) + ), + subPropertyOf = Set(OntologyConstants.KnoraApiV2WithValueObjects.HasValue.toSmartIri), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2WithValueObjects) + assert(externalOntology.properties.size == 1) + val property = externalOntology.properties(propertyIri) + + property.entityInfoContent should ===(propertyInfoContent) + val metadata = externalOntology.ontologyMetadata + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "delete the property anything:BoxHasBoolean" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("BoxHasBoolean") + + actorUnderTest ! DeletePropertyRequestV2( + propertyIri = propertyIri, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyMetadataV2 => + assert(msg.ontologies.size == 1) + val metadata = msg.ontologies.head + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "create a property anything:hasBox with object type example-box:Box" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("hasBox") + + val propertyInfoContent = PropertyInfoContentV2( + propertyIri = propertyIri, + predicates = Map( + OntologyConstants.Rdf.Type.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdf.Type.toSmartIri, + objects = Seq(SmartIriLiteralV2(OntologyConstants.Owl.ObjectProperty.toSmartIri)) + ), + OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.KnoraApiV2WithValueObjects.ObjectType.toSmartIri, + objects = Seq(SmartIriLiteralV2(ExampleSharedOntologyIri.makeEntityIri("Box"))) + ), + OntologyConstants.Rdfs.Label.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Label.toSmartIri, + objects = Seq(StringLiteralV2("has box", Some("en"))) + ), + OntologyConstants.Rdfs.Comment.toSmartIri -> PredicateInfoV2( + predicateIri = OntologyConstants.Rdfs.Comment.toSmartIri, + objects = Seq(StringLiteralV2("Has a box", Some("en"))) + ) + ), + subPropertyOf = Set(OntologyConstants.KnoraApiV2WithValueObjects.HasLinkTo.toSmartIri), + ontologySchema = ApiV2WithValueObjects + ) + + actorUnderTest ! CreatePropertyRequestV2( + propertyInfoContent = propertyInfoContent, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyV2 => + val externalOntology = msg.toOntologySchema(ApiV2WithValueObjects) + assert(externalOntology.properties.size == 1) + val property = externalOntology.properties(propertyIri) + + property.entityInfoContent should ===(propertyInfoContent) + val metadata = externalOntology.ontologyMetadata + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "delete the property anything:hasBox" in { + val propertyIri = AnythingOntologyIri.makeEntityIri("hasBox") + + actorUnderTest ! DeletePropertyRequestV2( + propertyIri = propertyIri, + lastModificationDate = anythingLastModDate, + apiRequestID = UUID.randomUUID, + requestingUser = anythingAdminUser + ) + + expectMsgPF(timeout) { + case msg: ReadOntologyMetadataV2 => + assert(msg.ontologies.size == 1) + val metadata = msg.ontologies.head + val newAnythingLastModDate = metadata.lastModificationDate.getOrElse(throw AssertionException(s"${metadata.ontologyIri} has no last modification date")) + assert(newAnythingLastModDate.isAfter(anythingLastModDate)) + anythingLastModDate = newAnythingLastModDate + } + } + + "not load an ontology that has no knora-base:attachedToProject" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/onto-without-project.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a class that's missing a cardinality for a link value property" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/missing-link-value-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + + "not load an ontology containing a class that's missing a cardinality for a link property" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/missing-link-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a class with a cardinality whose subject class constraint is incompatible with the class" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-incompatible-with-scc-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class without an rdfs:label" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-without-label-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource property without an rdfs:label" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/property-without-label-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class that is also a standoff class" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/resource-class-is-standoff-class-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class with a cardinality on an undefined property" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-missing-property-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class with a directly defined cardinality on a non-resource property" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-resource-prop-cardinality-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class with a cardinality on knora-base:resourceProperty" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-cardinality-on-kbresprop-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class with a cardinality on knora-base:hasValue" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-cardinality-on-kbhasvalue-onto.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load an ontology containing a resource class with a base class that has a Knora IRI but isn't a resource class" in { val invalidOnto = List(RdfDataObject( path = "_test_data/responders.v2.OntologyResponderV2Spec/resource-class-with-invalid-base-class-onto.ttl", name = "http://www.knora.org/ontology/invalid" )) @@ -3694,5 +4322,51 @@ class OntologyResponderV2Spec extends CoreSpec() with ImplicitSender { customLoadTestData(invalidOnto) expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) } + + "not load a project-specific ontology containing a class with a cardinality on a property from a non-shared ontology in another project" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-cardinality.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load a project-specific ontology containing a class with a base class defined in a non-shared ontology in another project" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/class-with-non-shared-base-class.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load a project-specific ontology containing a property with a base property defined in a non-shared ontology in another project" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-base-prop.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + + "not load a project-specific ontology containing a property whose subject class constraint is defined in a non-shared ontology in another project" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-scc.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } + + "not load a project-specific ontology containing a property whose object class constraint is defined in a non-shared ontology in another project" in { + val invalidOnto = List(RdfDataObject( + path = "_test_data/responders.v2.OntologyResponderV2Spec/prop-with-non-shared-occ.ttl", name = "http://www.knora.org/ontology/invalid" + )) + + customLoadTestData(invalidOnto) + expectMsgType[akka.actor.Status.Failure](timeout).cause.isInstanceOf[InconsistentTriplestoreDataException] should ===(true) + } } } diff --git a/webapi/src/test/scala/org/knora/webapi/util/StringFormatterSpec.scala b/webapi/src/test/scala/org/knora/webapi/util/StringFormatterSpec.scala index 8114bf0b98..78c4e4e8b0 100644 --- a/webapi/src/test/scala/org/knora/webapi/util/StringFormatterSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/util/StringFormatterSpec.scala @@ -141,6 +141,9 @@ class StringFormatterSpec extends CoreSpec() { validIri should be(testUrl) } + ///////////////////////////////////// + // Built-in ontologies + "convert http://www.knora.org/ontology/knora-base to http://api.knora.org/ontology/knora-api/simple/v2" in { val internalOntologyIri = "http://www.knora.org/ontology/knora-base".toSmartIri assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && @@ -261,6 +264,9 @@ class StringFormatterSpec extends CoreSpec() { internalEntityIri.getProjectCode.isEmpty) } + ////////////////////////////////////////// + // Non-shared, project-specific ontologies + "convert http://www.knora.org/ontology/00FF/images to http://0.0.0.0:3333/ontology/00FF/images/simple/v2" in { val internalOntologyIri = "http://www.knora.org/ontology/00FF/images".toSmartIri assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && @@ -392,6 +398,274 @@ class StringFormatterSpec extends CoreSpec() { assert(externalEntityIri.toString == "http://www.w3.org/2001/XMLSchema#string" && !externalEntityIri.isKnoraIri) } + ///////////////////////////////////////////////////////////// + // Shared ontologies in the default shared ontologies project + + "convert http://www.knora.org/ontology/shared/example to http://api.knora.org/ontology/shared/example/simple/v2" in { + val internalOntologyIri = "http://www.knora.org/ontology/shared/example".toSmartIri + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0000")) + + val externalOntologyIri = internalOntologyIri.toOntologySchema(ApiV2Simple) + externalOntologyIri.toString should ===("http://api.knora.org/ontology/shared/example/simple/v2") + assert(externalOntologyIri.getOntologySchema.contains(ApiV2Simple) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0000")) + } + + "convert http://www.knora.org/ontology/shared/example#Person to http://api.knora.org/ontology/shared/example/simple/v2#Person" in { + val internalEntityIri = "http://www.knora.org/ontology/shared/example#Person".toSmartIri + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0000")) + + val externalEntityIri = internalEntityIri.toOntologySchema(ApiV2Simple) + externalEntityIri.toString should ===("http://api.knora.org/ontology/shared/example/simple/v2#Person") + assert(externalEntityIri.getOntologySchema.contains(ApiV2Simple) && + externalEntityIri.isKnoraApiV2EntityIri && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0000")) + } + + "convert http://www.knora.org/ontology/shared/example to http://api.knora.org/ontology/shared/example/v2" in { + val internalOntologyIri = "http://www.knora.org/ontology/shared/example".toSmartIri + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0000")) + + val externalOntologyIri = internalOntologyIri.toOntologySchema(ApiV2WithValueObjects) + externalOntologyIri.toString should ===("http://api.knora.org/ontology/shared/example/v2") + assert(externalOntologyIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0000")) + } + + "convert http://www.knora.org/ontology/shared/example#Person to http://api.knora.org/ontology/shared/example/v2#Person" in { + val internalEntityIri = "http://www.knora.org/ontology/shared/example#Person".toSmartIri + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0000")) + + val externalEntityIri = internalEntityIri.toOntologySchema(ApiV2WithValueObjects) + externalEntityIri.toString should ===("http://api.knora.org/ontology/shared/example/v2#Person") + assert(externalEntityIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalEntityIri.isKnoraApiV2EntityIri && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0000")) + } + + "convert http://api.knora.org/ontology/shared/example/simple/v2 to http://www.knora.org/ontology/shared/example" in { + val externalOntologyIri = "http://api.knora.org/ontology/shared/example/simple/v2".toSmartIri + assert(externalOntologyIri.getOntologySchema.contains(ApiV2Simple) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0000")) + + val internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema) + internalOntologyIri.toString should ===("http://www.knora.org/ontology/shared/example") + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0000")) + } + + "convert http://api.knora.org/ontology/shared/example/simple/v2#Person to http://www.knora.org/ontology/shared/example#Person" in { + val externalEntityIri = "http://api.knora.org/ontology/shared/example/simple/v2#Person".toSmartIri + assert(externalEntityIri.getOntologySchema.contains(ApiV2Simple) && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0000")) + + val internalEntityIri = externalEntityIri.toOntologySchema(InternalSchema) + internalEntityIri.toString should ===("http://www.knora.org/ontology/shared/example#Person") + assert(internalEntityIri.getOntologySchema.contains(InternalSchema) && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0000")) + } + + "convert http://api.knora.org/ontology/shared/example/v2 to http://www.knora.org/ontology/shared/example" in { + val externalOntologyIri = "http://api.knora.org/ontology/shared/example/v2".toSmartIri + assert(externalOntologyIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0000")) + + val internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema) + internalOntologyIri.toString should ===("http://www.knora.org/ontology/shared/example") + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0000")) + } + + "convert http://api.knora.org/ontology/shared/example/v2#Person to http://www.knora.org/ontology/shared/example#Person" in { + val externalEntityIri = "http://api.knora.org/ontology/shared/example/v2#Person".toSmartIri + assert(externalEntityIri.getOntologySchema.contains(ApiV2WithValueObjects) && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0000")) + + val internalEntityIri = externalEntityIri.toOntologySchema(InternalSchema) + internalEntityIri.toString should ===("http://www.knora.org/ontology/shared/example#Person") + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0000")) + } + + /////////////////////////////////////////////////////////////// + // Shared ontologies in a non-default shared ontologies project + + "convert http://www.knora.org/ontology/shared/0111/example to http://api.knora.org/ontology/shared/0111/example/simple/v2" in { + val internalOntologyIri = "http://www.knora.org/ontology/shared/0111/example".toSmartIri + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0111")) + + val externalOntologyIri = internalOntologyIri.toOntologySchema(ApiV2Simple) + externalOntologyIri.toString should ===("http://api.knora.org/ontology/shared/0111/example/simple/v2") + assert(externalOntologyIri.getOntologySchema.contains(ApiV2Simple) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0111")) + } + + "convert http://www.knora.org/ontology/shared/0111/example#Person to http://api.knora.org/ontology/shared/0111/example/simple/v2#Person" in { + val internalEntityIri = "http://www.knora.org/ontology/shared/0111/example#Person".toSmartIri + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0111")) + + val externalEntityIri = internalEntityIri.toOntologySchema(ApiV2Simple) + externalEntityIri.toString should ===("http://api.knora.org/ontology/shared/0111/example/simple/v2#Person") + assert(externalEntityIri.getOntologySchema.contains(ApiV2Simple) && + externalEntityIri.isKnoraApiV2EntityIri && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0111")) + } + + "convert http://www.knora.org/ontology/shared/0111/example to http://api.knora.org/ontology/shared/0111/example/v2" in { + val internalOntologyIri = "http://www.knora.org/ontology/shared/0111/example".toSmartIri + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0111")) + + val externalOntologyIri = internalOntologyIri.toOntologySchema(ApiV2WithValueObjects) + externalOntologyIri.toString should ===("http://api.knora.org/ontology/shared/0111/example/v2") + assert(externalOntologyIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0111")) + } + + "convert http://www.knora.org/ontology/shared/0111/example#Person to http://api.knora.org/ontology/shared/0111/example/v2#Person" in { + val internalEntityIri = "http://www.knora.org/ontology/shared/0111/example#Person".toSmartIri + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0111")) + + val externalEntityIri = internalEntityIri.toOntologySchema(ApiV2WithValueObjects) + externalEntityIri.toString should ===("http://api.knora.org/ontology/shared/0111/example/v2#Person") + assert(externalEntityIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalEntityIri.isKnoraApiV2EntityIri && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0111")) + } + + "convert http://api.knora.org/ontology/shared/0111/example/simple/v2 to http://www.knora.org/ontology/shared/0111/example" in { + val externalOntologyIri = "http://api.knora.org/ontology/shared/0111/example/simple/v2".toSmartIri + assert(externalOntologyIri.getOntologySchema.contains(ApiV2Simple) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0111")) + + val internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema) + internalOntologyIri.toString should ===("http://www.knora.org/ontology/shared/0111/example") + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0111")) + } + + "convert http://api.knora.org/ontology/shared/0111/example/simple/v2#Person to http://www.knora.org/ontology/shared/0111/example#Person" in { + val externalEntityIri = "http://api.knora.org/ontology/shared/0111/example/simple/v2#Person".toSmartIri + assert(externalEntityIri.getOntologySchema.contains(ApiV2Simple) && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0111")) + + val internalEntityIri = externalEntityIri.toOntologySchema(InternalSchema) + internalEntityIri.toString should ===("http://www.knora.org/ontology/shared/0111/example#Person") + assert(internalEntityIri.getOntologySchema.contains(InternalSchema) && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0111")) + } + + "convert http://api.knora.org/ontology/shared/0111/example/v2 to http://www.knora.org/ontology/shared/0111/example" in { + val externalOntologyIri = "http://api.knora.org/ontology/shared/0111/example/v2".toSmartIri + assert(externalOntologyIri.getOntologySchema.contains(ApiV2WithValueObjects) && + externalOntologyIri.isKnoraOntologyIri && + !externalOntologyIri.isKnoraBuiltInDefinitionIri && + externalOntologyIri.isKnoraSharedDefinitionIri && + externalOntologyIri.getProjectCode.contains("0111")) + + val internalOntologyIri = externalOntologyIri.toOntologySchema(InternalSchema) + internalOntologyIri.toString should ===("http://www.knora.org/ontology/shared/0111/example") + assert(internalOntologyIri.getOntologySchema.contains(InternalSchema) && + internalOntologyIri.isKnoraOntologyIri && + !internalOntologyIri.isKnoraBuiltInDefinitionIri && + internalOntologyIri.isKnoraSharedDefinitionIri && + internalOntologyIri.getProjectCode.contains("0111")) + } + + "convert http://api.knora.org/ontology/shared/0111/example/v2#Person to http://www.knora.org/ontology/shared/0111/example#Person" in { + val externalEntityIri = "http://api.knora.org/ontology/shared/0111/example/v2#Person".toSmartIri + assert(externalEntityIri.getOntologySchema.contains(ApiV2WithValueObjects) && + !externalEntityIri.isKnoraBuiltInDefinitionIri && + externalEntityIri.isKnoraSharedDefinitionIri && + externalEntityIri.getProjectCode.contains("0111")) + + val internalEntityIri = externalEntityIri.toOntologySchema(InternalSchema) + internalEntityIri.toString should ===("http://www.knora.org/ontology/shared/0111/example#Person") + assert(internalEntityIri.isKnoraInternalEntityIri && + !internalEntityIri.isKnoraBuiltInDefinitionIri && + internalEntityIri.isKnoraSharedDefinitionIri && + internalEntityIri.getProjectCode.contains("0111")) + } + + ///////////////////// + "not change http://www.w3.org/2001/XMLSchema#string when converting to InternalSchema" in { val externalEntityIri = "http://www.w3.org/2001/XMLSchema#string".toSmartIri assert(!externalEntityIri.isKnoraIri) @@ -591,6 +865,12 @@ class StringFormatterSpec extends CoreSpec() { } } + "reject http://0.0.0.0:3333/ontology/shared/example/v2 (shared project code with local hostname in ontology IRI)" in { + assertThrows[AssertionException] { + "http://0.0.0.0:3333/ontology/shared/example/v2".toSmartIriWithErr(throw AssertionException(s"Invalid IRI")) + } + } + "enable pattern matching with SmartIri" in { val input: SmartIri = "http://www.knora.org/ontology/knora-base#Resource".toSmartIri