diff --git a/docs/src/paradox/03-apis/api-v2/query-language.md b/docs/src/paradox/03-apis/api-v2/query-language.md index e5b89845a2..6fbaf974b2 100644 --- a/docs/src/paradox/03-apis/api-v2/query-language.md +++ b/docs/src/paradox/03-apis/api-v2/query-language.md @@ -186,9 +186,21 @@ that refer to events that took place within a certain date range. Each matching resource is returned with the values that the user has permission to see. If the user does not have permission to see a matching -main resource, it is replaced by a proxy resource called -`knora-api:ForbiddenResource`. If a user does not have permission to see -a matching dependent resource, only its IRI is returned. +main resource, it is hidden in the results. If a user does not have +permission to see a matching dependent resource, the link value is hidden. + +## Paging + +Gravsearch results are returned in pages. The maximum number of main +resources per page is determined by Knora (and can be configured +in `application.conf` via the setting `app/v2/resources-sequence/results-per-page`). +If some resources have been filtered out because the user does not have +permission to see them, a page could contain fewer results, or no results. +If it is possible that more results are available in subsequent pages, +the Gravsearch response will contain the predicate `knora-api:mayHaveMoreResults` +with the boolean value `true`, otherwise it will not contain this predicate. +Therefore, to retrieve all available results, the client must request each page +one at a time, until the response does not contain `knora-api:mayHaveMoreResults`. ## Inference @@ -227,8 +239,7 @@ clauses use the following patterns, with the specified restrictions: - `OFFSET`: the `OFFSET` is needed for paging. It does not actually refer to the number of triples to be returned, but to the requested page of results. The default value is 0, which refers - to the first page of results. The number of results per page is - defined in `app/v2` in `application.conf`. + to the first page of results. - `ORDER BY`: In SPARQL, the result of a `CONSTRUCT` query is an unordered set of triples. However, a Gravsearch query returns an ordered list of resources, which can be ordered by the values of diff --git a/docs/src/paradox/05-internals/design/api-v2/gravsearch.md b/docs/src/paradox/05-internals/design/api-v2/gravsearch.md index 702372628d..610e1af8c5 100644 --- a/docs/src/paradox/05-internals/design/api-v2/gravsearch.md +++ b/docs/src/paradox/05-internals/design/api-v2/gravsearch.md @@ -228,32 +228,39 @@ the main query can specifically ask for more detailed information on these resou #### Generating the Main Query -The classes involved in generating prequeries can be found in `org.knora.webapi.responders.v2.search.gravsearch.mainquery`. - -The main query is a SPARQL CONSTRUCT query. Its generation is handled by the method `GravsearchMainQueryGenerator.createMainQuery`. -It takes three arguments: `mainResourceIris: Set[IriRef], dependentResourceIris: Set[IriRef], valueObjectIris: Set[IRI]`. -From the given IRIs, statements are generated that ask for complete information on *exactly* these resources and values. -For any given resource IRI, only the values present in `valueObjectIris` are to be queried. -This is achieved by using SPARQL's `VALUES` expression for the main resource and dependent resources as well as for values. +The classes involved in generating the main query can be found in +`org.knora.webapi.responders.v2.search.gravsearch.mainquery`. + +The main query is a SPARQL CONSTRUCT query. Its generation is handled by the +method `GravsearchMainQueryGenerator.createMainQuery`. +It takes three arguments: `mainResourceIris: Set[IriRef], dependentResourceIris: +Set[IriRef], valueObjectIris: Set[IRI]`. From the given Iris, statements are +generated that ask for complete information on *exactly* these resources and +values. For any given resource Iri, only the values present in +`valueObjectIris` are to be queried. This is achieved by using SPARQL's +`VALUES` expression for the main resource and dependent resources as well as +for values. #### Processing the Main Query's results -When processing the main query's results, permissions are checked and resources and values that the user did not explicitly ask for in the input query are filtered out. This is implemented in `MainQueryResultProcessor`. - -The method `getMainQueryResultsWithFullGraphPattern` takes the main query's results as an input and makes sure that the client has sufficient permissions on the results. -A main resource and its dependent resources and values are only returned if the user has view permissions on all the resources and value objects present in the main query. -Otherwise the method suppresses the main resource. -To do the permission checking, the results of the main query are passed to `ConstructResponseUtilV2` which transforms a `SparqlConstructResponse` (a set of RDF triples) -into a structure organized by main resource IRIs. In this structure, dependent resources and values are nested can be accessed via their main resource. -`SparqlConstructResponse` suppresses all resources and values the user has insufficient permissions on. -For each main resource, a check is performed for the presence of all resources and values after permission checking. - -The method `getRequestedValuesFromResultsWithFullGraphPattern` filters out those resources and values that the user does not want to be returned by the query. -All the resources and values not present in the input query's CONSTRUCT clause are filtered out. This only happens after permission checking. - -The main resources that have been filtered out due to insufficient permissions are represented by the placeholder `ForbiddenResource`. -This placeholder stands for a main resource that cannot be returned, nevertheless it informs the client that such a resource exists. -This is necessary for a consistent behaviour when doing paging. +To do the permission checking, the results of the main query are passed to +`ConstructResponseUtilV2.splitMainResourcesAndValueRdfData`, +which transforms a `SparqlConstructResponse` (a set of RDF triples) +into a structure organized by main resource Iris. In this structure, dependent +resources and values are nested and can be accessed via their main resource, +and resources and values that the user does not have permission to see are +filtered out. As a result, a page of results may contain fewer than the maximum +allowed number of results per page, even if more pages of results are available. + +`MainQueryResultProcessor.getRequestedValuesFromResultsWithFullGraphPattern` +then filters out values that the user did not explicitly ask for in the input +query. + +Finally, `ConstructResponseUtilV2.createApiResponse` transforms the query +results into an API response (a `ReadResourcesSequenceV2`). If the number +of main resources found (even if filtered out because of permissions) is equal +to the maximum allowed page size, the predicate +`knora-api:mayHaveMoreResults: true` is included in the response. ## Inference @@ -279,5 +286,6 @@ as an optimisation if the triplestore provides it. For example, the virtual prop `knora-api:standoffTagHasStartAncestor` is equivalent to `knora-base:standoffTagHasStartParent*`, but with GraphDB it is implemented using a custom inference rule (in `KnoraRules.pie`) and is therefore more efficient. If Knora is not using the triplestore's inference, + `SparqlTransformer.transformStatementInWhereForNoInference` replaces `knora-api:standoffTagHasStartAncestor` with `knora-base:standoffTagHasStartParent*`. diff --git a/knora-ontologies/knora-base.ttl b/knora-ontologies/knora-base.ttl index 701c8ce57a..94246c57e5 100644 --- a/knora-ontologies/knora-base.ttl +++ b/knora-ontologies/knora-base.ttl @@ -33,7 +33,7 @@ :attachedToProject knora-admin:SystemProject ; - :ontologyVersion "knora-base v7" . + :ontologyVersion "knora-base v8" . @@ -2558,19 +2558,6 @@ rdfs:comment "A resource containing a text file"@en . -### http://www.knora.org/ontology/knora-base#ForbiddenResource - -:ForbiddenResource rdf:type owl:Class ; - - rdfs:subClassOf :Resource , - [ rdf:type owl:Restriction ; - owl:onProperty :hasComment ; - owl:minCardinality "0"^^xsd:nonNegativeInteger - ]; - - rdfs:label """A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."""@en ; - - rdfs:comment """A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."""@en . ### http://www.knora.org/ontology/knora-base#XSLTransformation diff --git a/upgrade/src/main/scala/org.knora.upgrade/Main.scala b/upgrade/src/main/scala/org.knora.upgrade/Main.scala index b4293ae619..5dc3cdcebe 100644 --- a/upgrade/src/main/scala/org.knora.upgrade/Main.scala +++ b/upgrade/src/main/scala/org.knora.upgrade/Main.scala @@ -51,7 +51,8 @@ object Main extends App { PluginForKnoraBaseVersion(versionNumber = 4, plugin = new UpgradePluginPR1372, prBasedVersionString = Some("PR 1372")), PluginForKnoraBaseVersion(versionNumber = 5, plugin = new NoopPlugin, prBasedVersionString = Some("PR 1440")), PluginForKnoraBaseVersion(versionNumber = 6, plugin = new NoopPlugin), // PR 1206 - PluginForKnoraBaseVersion(versionNumber = 7, plugin = new NoopPlugin) // PR 1403 + PluginForKnoraBaseVersion(versionNumber = 7, plugin = new NoopPlugin), // PR 1403 + PluginForKnoraBaseVersion(versionNumber = 8, plugin = new NoopPlugin) // PR 1615 ) /** diff --git a/webapi/_test_data/all_data/anything-data.ttl b/webapi/_test_data/all_data/anything-data.ttl index 687426ae70..827d93fc24 100644 --- a/webapi/_test_data/all_data/anything-data.ttl +++ b/webapi/_test_data/all_data/anything-data.ttl @@ -1618,6 +1618,117 @@ knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:UnknownUser"; knora-base:attachedToUser . + a anything:Thing; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:creationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + anything:hasOtherThingValue ; + anything:hasOtherThing ; + anything:hasInteger ; + rdfs:label "thing with one hidden thing"; + knora-base:isDeleted false . + + a knora-base:LinkValue; + knora-base:valueHasUUID "UgSp5mXTTSKdI02ZU1KIAA"^^xsd:string; + rdf:subject ; + rdf:predicate anything:hasOtherThing; + rdf:object ; + knora-base:isDeleted false; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasString "http://rdfh.ch/0001/XTxSMt0ySraVmwXD-bD2wQ"; + knora-base:valueHasComment "link value pointing to hidden resource"; + knora-base:valueHasRefCount "1"^^xsd:int . + + a knora-base:IntValue; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:valueHasUUID "U1PwfNaVRQebbOSFWNdMqQ"^^xsd:string; + knora-base:isDeleted false; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasInteger 123454321; + knora-base:valueHasOrder 0; + knora-base:valueHasComment "visible int value in main resource"; + knora-base:valueHasString "123454321" . + + a anything:Thing; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:Creator"; + knora-base:creationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + anything:hasInteger ; + rdfs:label "hidden thing"; + knora-base:isDeleted false . + + a knora-base:IntValue; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:Creator"; + knora-base:valueHasUUID "PVPAa37xR--K_wxQwlvSsg"^^xsd:string; + knora-base:isDeleted false; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasInteger 123454321; + knora-base:valueHasOrder 0; + knora-base:valueHasComment "hidden int value in hidden resource"; + knora-base:valueHasString "123454321" . + + a anything:Thing; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:creationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + anything:hasOtherThingValue ; + anything:hasOtherThing ; + anything:hasInteger ; + rdfs:label "thing with one deleted thing"; + knora-base:isDeleted false . + + a knora-base:LinkValue; + knora-base:valueHasUUID "Nlcc7XWXQtmEITsIRQ5z4w"^^xsd:string; + rdf:subject ; + rdf:predicate anything:hasOtherThing; + rdf:object ; + knora-base:isDeleted false; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasString "http://rdfh.ch/0001/XTxSMt0ySraVmwXD-bD2wQ"; + knora-base:valueHasComment "link value pointing to deleted resource"; + knora-base:valueHasRefCount "1"^^xsd:int . + + a knora-base:IntValue; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:valueHasUUID "a-40v6WiT4GHa79Kqwojjw"^^xsd:string; + knora-base:isDeleted false; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasInteger 123454321; + knora-base:valueHasOrder 0; + knora-base:valueHasComment "int value in main resource"; + knora-base:valueHasString "123454321" . + + a anything:Thing; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:creationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + anything:hasInteger ; + rdfs:label "deleted thing"; + knora-base:isDeleted true; + knora-base:deleteDate "2020-04-07T14:59:28.960124Z"^^xsd:dateTime . + + a knora-base:IntValue; + knora-base:attachedToUser ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; + knora-base:valueHasUUID "7BIm9QAiQqKixcgXDWf12Q"^^xsd:string; + knora-base:isDeleted false; + knora-base:valueCreationDate "2020-04-07T09:12:56.710717Z"^^xsd:dateTime; + knora-base:valueHasInteger 123454321; + knora-base:valueHasOrder 0; + knora-base:valueHasComment "int value in deleted resource"; + knora-base:valueHasString "123454321" . + a knora-base:ListNode; knora-base:isRootNode true; rdfs:label "Tree list root"@en; diff --git a/webapi/_test_data/all_data/system-data.ttl b/webapi/_test_data/all_data/system-data.ttl index a187f0fe21..61f260bbda 100644 --- a/webapi/_test_data/all_data/system-data.ttl +++ b/webapi/_test_data/all_data/system-data.ttl @@ -39,26 +39,3 @@ knora-base:listNodePosition 2 ; rdfs:label "Maybe"@en ; rdfs:label "Vielleicht"@de . - - -########################################################## -# -# Sweet and tasty but forbidden resource -# -# but of the tree of the knowledge of good and evil, thou shalt not eat of it -# -########################################################## - - a knora-base:ForbiddenResource ; - - rdfs:label "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" ; - - knora-base:isDeleted false ; - - knora-base:attachedToUser ; - - knora-base:attachedToProject knora-admin:SystemProject ; - - knora-base:creationDate "2017-10-06T11:05:37Z"^^xsd:dateTime ; - - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser" . diff --git a/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala b/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala index 882c50fd43..98c5172131 100644 --- a/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala +++ b/webapi/src/main/scala/org/knora/webapi/OntologyConstants.scala @@ -22,8 +22,8 @@ package org.knora.webapi import org.knora.webapi.util.SmartIri /** - * Contains string constants for IRIs from ontologies used by the application. - */ + * Contains string constants for IRIs from ontologies used by the application. + */ object OntologyConstants { object Rdf { @@ -71,9 +71,9 @@ object OntologyConstants { val OnDatatype: IRI = OwlPrefixExpansion + "onDatatype" /** - * Cardinality IRIs expressed as OWL restrictions, which specify the properties that resources of - * a particular type can have. - */ + * Cardinality IRIs expressed as OWL restrictions, which specify the properties that resources of + * a particular type can have. + */ val cardinalityOWLRestrictions: Set[IRI] = Set( Cardinality, MinCardinality, @@ -83,8 +83,8 @@ object OntologyConstants { val NamedIndividual: IRI = OwlPrefixExpansion + "NamedIndividual" /** - * Classes defined by OWL that can be used as knora-base:subjectClassConstraint or knora-base:objectClassConstraint. - */ + * Classes defined by OWL that can be used as knora-base:subjectClassConstraint or knora-base:objectClassConstraint. + */ val ClassesThatCanBeKnoraClassConstraints: Set[IRI] = Set( Ontology, Class, @@ -120,8 +120,8 @@ object OntologyConstants { } /** - * http://schema.org - */ + * http://schema.org + */ object SchemaOrg { val SchemaOrgPrefixExpansion: IRI = "http://schema.org/" val Name: IRI = SchemaOrgPrefixExpansion + "name" @@ -134,8 +134,8 @@ object OntologyConstants { } /** - * Ontology labels that are reserved for built-in ontologies. - */ + * Ontology labels that are reserved for built-in ontologies. + */ val BuiltInOntologyLabels: Set[String] = Set( KnoraBase.KnoraBaseOntologyLabel, KnoraAdmin.KnoraAdminOntologyLabel, @@ -531,14 +531,14 @@ object OntologyConstants { val DefaultSharedOntologiesProject: IRI = KnoraAdminPrefixExpansion + "DefaultSharedOntologiesProject" /** - * The system user is the owner of objects that are created by the system, rather than directly by the user, - * such as link values for standoff resource references. - */ + * The system user is the owner of objects that are created by the system, rather than directly by the user, + * such as link values for standoff resource references. + */ val SystemUser: IRI = KnoraAdminPrefixExpansion + "SystemUser" /** - * Every user not logged-in is per default an anonymous user. - */ + * Every user not logged-in is per default an anonymous user. + */ val AnonymousUser: IRI = KnoraAdminPrefixExpansion + "AnonymousUser" } @@ -652,17 +652,17 @@ object OntologyConstants { val KnoraApiPrefix: String = KnoraApiOntologyLabel + ":" /** - * Returns `true` if the specified IRI is `knora-api:Resource` in Knora API v2, in the simple - * or complex schema. - */ + * Returns `true` if the specified IRI is `knora-api:Resource` in Knora API v2, in the simple + * or complex schema. + */ def isKnoraApiV2Resource(iri: SmartIri): Boolean = { val iriStr = iri.toString iriStr == OntologyConstants.KnoraApiV2Simple.Resource || iriStr == OntologyConstants.KnoraApiV2Complex.Resource } /** - * Returns the IRI of `knora-api:subjectType` in the specified schema. - */ + * Returns the IRI of `knora-api:subjectType` in the specified schema. + */ def getSubjectTypePredicate(apiV2Schema: ApiV2Schema): IRI = { apiV2Schema match { case ApiV2Simple => KnoraApiV2Simple.SubjectType @@ -671,8 +671,8 @@ object OntologyConstants { } /** - * Returns the IRI of `knora-api:objectType` in the specified schema. - */ + * Returns the IRI of `knora-api:objectType` in the specified schema. + */ def getObjectTypePredicate(apiV2Schema: ApiV2Schema): IRI = { apiV2Schema match { case ApiV2Simple => KnoraApiV2Simple.ObjectType @@ -690,6 +690,7 @@ object OntologyConstants { val Result: IRI = KnoraApiV2PrefixExpansion + "result" val Error: IRI = KnoraApiV2PrefixExpansion + "error" + val MayHaveMoreResults: IRI = KnoraApiV2PrefixExpansion + "mayHaveMoreResults" val IsShared: IRI = KnoraApiV2PrefixExpansion + "isShared" val IsBuiltIn: IRI = KnoraApiV2PrefixExpansion + "isBuiltIn" @@ -734,7 +735,6 @@ object OntologyConstants { val Author: IRI = KnoraApiV2PrefixExpansion + "author" val Resource: IRI = KnoraApiV2PrefixExpansion + "Resource" - val ForbiddenResource: IRI = KnoraApiV2PrefixExpansion + "ForbiddenResource" val Region: IRI = KnoraApiV2PrefixExpansion + "Region" val Representation: IRI = KnoraApiV2PrefixExpansion + "Representation" val StillImageRepresentation: IRI = KnoraApiV2PrefixExpansion + "StillImageRepresentation" @@ -951,6 +951,7 @@ object OntologyConstants { val Result: IRI = KnoraApiV2PrefixExpansion + "result" val Error: IRI = KnoraApiV2PrefixExpansion + "error" + val MayHaveMoreResults: IRI = KnoraApiV2PrefixExpansion + "mayHaveMoreResults" val SubjectType: IRI = KnoraApiV2PrefixExpansion + "subjectType" @@ -981,7 +982,6 @@ object OntologyConstants { val ListNode: IRI = KnoraApiV2PrefixExpansion + "ListNode" val Resource: IRI = KnoraApiV2PrefixExpansion + "Resource" - val ForbiddenResource: IRI = KnoraApiV2PrefixExpansion + "ForbiddenResource" val ResourceIcon: IRI = KnoraApiV2PrefixExpansion + "resourceIcon" @@ -1028,9 +1028,9 @@ object OntologyConstants { } /** - * A map of IRIs in each possible source schema to the corresponding IRIs in each possible target schema, for the - * cases where this can't be done formally by [[org.knora.webapi.util.SmartIri]]. - */ + * A map of IRIs in each possible source schema to the corresponding IRIs in each possible target schema, for the + * cases where this can't be done formally by [[org.knora.webapi.util.SmartIri]]. + */ val CorrespondingIris: Map[(OntologySchema, OntologySchema), Map[IRI, IRI]] = Map( (InternalSchema, ApiV2Simple) -> Map( // All the values of this map must be either properties or datatypes. PropertyInfoContentV2.toOntologySchema diff --git a/webapi/src/main/scala/org/knora/webapi/Settings.scala b/webapi/src/main/scala/org/knora/webapi/Settings.scala index 878caa181f..f57626a712 100644 --- a/webapi/src/main/scala/org/knora/webapi/Settings.scala +++ b/webapi/src/main/scala/org/knora/webapi/Settings.scala @@ -220,7 +220,7 @@ class SettingsImpl(config: Config) extends Extension { case _ ⇒ throw new ConfigurationException(s"Config setting '$path' must be a finite duration") } - val prometheusEndpoint = config.getBoolean("app.monitoring.prometheus-endpoint") + val prometheusEndpoint: Boolean = config.getBoolean("app.monitoring.prometheus-endpoint") } diff --git a/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala b/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala index 73e7b0a16a..d8fb4f90a5 100644 --- a/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/SharedTestDataADM.scala @@ -1610,6 +1610,16 @@ object SharedTestDataADM { | ?thing anything:hasOtherThing . |}""".stripMargin + val gravsearchThingsWithPaging: String = + """PREFIX anything: + |PREFIX knora-api: + | + |CONSTRUCT { + | ?thing knora-api:isMainResource true . + |} WHERE { + | ?thing a anything:Thing . + |}""".stripMargin + val createResourceWithValues: String = """{ | "@type" : "anything:Thing", diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2ComplexTransformationRules.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2ComplexTransformationRules.scala index 5b931ce0a9..4195c8ae69 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2ComplexTransformationRules.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2ComplexTransformationRules.scala @@ -86,6 +86,26 @@ object KnoraBaseToApiV2ComplexTransformationRules extends OntologyTransformation objectType = Some(OntologyConstants.Xsd.String) ) + private val MayHaveMoreResults: ReadPropertyInfoV2 = makeProperty( + propertyIri = OntologyConstants.KnoraApiV2Complex.MayHaveMoreResults, + propertyType = OntologyConstants.Owl.DatatypeProperty, + predicates = Seq( + makePredicate( + predicateIri = OntologyConstants.Rdfs.Label, + objectsWithLang = Map( + LanguageCodes.EN -> "May have more results" + ) + ), + makePredicate( + predicateIri = OntologyConstants.Rdfs.Comment, + objectsWithLang = Map( + LanguageCodes.EN -> "Indicates whether more results may be available for a search query" + ) + ) + ), + objectType = Some(OntologyConstants.Xsd.Boolean) + ) + private val UserHasPermission: ReadPropertyInfoV2 = makeProperty( propertyIri = OntologyConstants.KnoraApiV2Complex.UserHasPermission, propertyType = OntologyConstants.Owl.DatatypeProperty, @@ -1764,6 +1784,7 @@ object KnoraBaseToApiV2ComplexTransformationRules extends OntologyTransformation override val externalPropertiesToAdd: Map[SmartIri, ReadPropertyInfoV2] = Set( Label, Result, + MayHaveMoreResults, Error, UserHasPermission, VersionDate, diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2SimpleTransformationRules.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2SimpleTransformationRules.scala index 911a3fbebb..32ca2b7c07 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2SimpleTransformationRules.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/ontologymessages/KnoraBaseToApiV2SimpleTransformationRules.scala @@ -67,6 +67,26 @@ object KnoraBaseToApiV2SimpleTransformationRules extends OntologyTransformationR objectType = Some(OntologyConstants.Xsd.String) ) + private val MayHaveMoreResults: ReadPropertyInfoV2 = makeProperty( + propertyIri = OntologyConstants.KnoraApiV2Simple.MayHaveMoreResults, + propertyType = OntologyConstants.Owl.DatatypeProperty, + predicates = Seq( + makePredicate( + predicateIri = OntologyConstants.Rdfs.Label, + objectsWithLang = Map( + LanguageCodes.EN -> "May have more results" + ) + ), + makePredicate( + predicateIri = OntologyConstants.Rdfs.Comment, + objectsWithLang = Map( + LanguageCodes.EN -> "Indicates whether more results may be available for a search query" + ) + ) + ), + objectType = Some(OntologyConstants.Xsd.Boolean) + ) + private val Error: ReadPropertyInfoV2 = makeProperty( propertyIri = OntologyConstants.KnoraApiV2Simple.Error, propertyType = OntologyConstants.Owl.DatatypeProperty, @@ -559,6 +579,7 @@ object KnoraBaseToApiV2SimpleTransformationRules extends OntologyTransformationR override val externalPropertiesToAdd: Map[SmartIri, ReadPropertyInfoV2] = Set( Label, Result, + MayHaveMoreResults, Error, ArkUrl, VersionArkUrl, diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala index a2f99867ac..af5618a8f6 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/resourcemessages/ResourceMessagesV2.scala @@ -35,7 +35,6 @@ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.v2.responder._ import org.knora.webapi.messages.v2.responder.standoffmessages.MappingXMLtoStandoff import org.knora.webapi.messages.v2.responder.valuemessages._ -import org.knora.webapi.responders.v2.SearchResponderV2Constants import org.knora.webapi.util.IriConversions._ import org.knora.webapi.util.PermissionUtilADM.EntityPermission import org.knora.webapi.util._ @@ -194,7 +193,7 @@ case class TEIHeader(headerInfo: ReadResourceV2, headerXSLT: Option[String], set if (headerXSLT.nonEmpty) { - val headerJSONLD = ReadResourcesSequenceV2(1, Vector(headerInfo)).toJsonLDDocument(ApiV2Complex, settings) + val headerJSONLD = ReadResourcesSequenceV2(Vector(headerInfo)).toJsonLDDocument(ApiV2Complex, settings) val rdfParser: RDFParser = Rio.createParser(RDFFormat.JSONLD) val stringReader = new StringReader(headerJSONLD.toCompactString) @@ -811,10 +810,13 @@ object DeleteOrEraseResourceRequestV2 extends KnoraJsonLDRequestReaderV2[DeleteO /** * Represents a sequence of resources read back from Knora. * - * @param numberOfResources the amount of resources returned. - * @param resources a sequence of resources. + * @param resources a sequence of resources that the user has permission to see. + * @param hiddenResourceIris the IRIs of resources that were requested but that the user did not have permission to see. + * @param mayHaveMoreResults `true` if more resources matching the request may be available. */ -case class ReadResourcesSequenceV2(numberOfResources: Int, resources: Seq[ReadResourceV2]) extends KnoraResponseV2 with KnoraReadV2[ReadResourcesSequenceV2] with UpdateResultInProject { +case class ReadResourcesSequenceV2(resources: Seq[ReadResourceV2], + hiddenResourceIris: Set[IRI] = Set.empty, + mayHaveMoreResults: Boolean = false) extends KnoraResponseV2 with KnoraReadV2[ReadResourcesSequenceV2] with UpdateResultInProject { override def toOntologySchema(targetSchema: ApiV2Schema): ReadResourcesSequenceV2 = { copy( @@ -853,14 +855,14 @@ case class ReadResourcesSequenceV2(numberOfResources: Int, resources: Seq[ReadRe // Make the knora-api prefix for the target schema. - val knoraApiPrefixExpansion = targetSchema match { + val knoraApiPrefixExpansion: IRI = targetSchema match { case ApiV2Simple => OntologyConstants.KnoraApiV2Simple.KnoraApiV2PrefixExpansion case ApiV2Complex => OntologyConstants.KnoraApiV2Complex.KnoraApiV2PrefixExpansion } // Make the JSON-LD document. - val context = JsonLDUtil.makeContext( + val context: JsonLDObject = JsonLDUtil.makeContext( fixedPrefixes = Map( "rdf" -> OntologyConstants.Rdf.RdfPrefixExpansion, "rdfs" -> OntologyConstants.Rdfs.RdfsPrefixExpansion, @@ -870,9 +872,22 @@ case class ReadResourcesSequenceV2(numberOfResources: Int, resources: Seq[ReadRe knoraOntologiesNeedingPrefixes = projectSpecificOntologiesUsed ) - val body = JsonLDObject(Map( - JsonLDConstants.GRAPH -> JsonLDArray(resourcesJsonObjects) - )) + val mayHaveMoreResultsStatement: Option[(IRI, JsonLDBoolean)] = if (mayHaveMoreResults) { + val mayHaveMoreResultsProp: IRI = targetSchema match { + case ApiV2Simple => OntologyConstants.KnoraApiV2Simple.MayHaveMoreResults + case ApiV2Complex => OntologyConstants.KnoraApiV2Complex.MayHaveMoreResults + } + + Some(mayHaveMoreResultsProp -> JsonLDBoolean(mayHaveMoreResults)) + } else { + None + } + + val body = JsonLDObject( + Map( + JsonLDConstants.GRAPH -> JsonLDArray(resourcesJsonObjects) + ) ++ mayHaveMoreResultsStatement + ) JsonLDDocument(body = body, context = context) @@ -890,28 +905,55 @@ case class ReadResourcesSequenceV2(numberOfResources: Int, resources: Seq[ReadRe // #toJsonLDDocument /** - * Checks that a [[ReadResourcesSequenceV2]] contains exactly one resource, and returns that resource. If the resource - * is not present, or if it's `ForbiddenResource`, throws an exception. + * Checks that a [[ReadResourcesSequenceV2]] contains exactly one resource, and returns that resource. * * @param requestedResourceIri the IRI of the expected resource. * @return the resource. + * @throws NotFoundException if the resource is not found. + * @throws ForbiddenException if the user does not have permission to see the requested resource. + * @throws BadRequestException if more than one resource was returned. */ - def toResource(requestedResourceIri: IRI): ReadResourceV2 = { - if (numberOfResources == 0) { - throw AssertionException(s"Expected one resource, <$requestedResourceIri>, but no resources were returned") + def toResource(requestedResourceIri: IRI)(implicit stringFormatter: StringFormatter): ReadResourceV2 = { + if (hiddenResourceIris.contains(requestedResourceIri)) { + throw ForbiddenException(s"You do not have permission to see resource <$requestedResourceIri>") + } + + if (resources.isEmpty) { + throw NotFoundException(s"Expected <$requestedResourceIri>, but no resources were returned") + } + + if (resources.size > 1) { + throw BadRequestException(s"Expected one resource, <$requestedResourceIri>, but more than one was returned") } - if (numberOfResources > 1) { - throw AssertionException(s"More than one resource returned with IRI <$requestedResourceIri>") + if (resources.head.resourceIri != requestedResourceIri) { + throw NotFoundException(s"Expected resource <$requestedResourceIri>, but <${resources.head.resourceIri}> was returned") } - val resourceInfo = resources.head + resources.head + } + - if (resourceInfo.resourceIri == SearchResponderV2Constants.forbiddenResourceIri) { // TODO: #953 - throw NotFoundException(s"Resource <$requestedResourceIri> does not exist, has been deleted, or you do not have permission to view it and/or the values of the specified property") + /** + * Checks that requested resources were found and that the user has permission to see them. If not, throws an exception. + * + * @param targetResourceIris the IRIs to be checked. + * @param resourcesSequence the result of requesting those IRIs. + * @throws NotFoundException if the requested resources are not found. + * @throws ForbiddenException if the user does not have permission to see the requested resources. + */ + def checkResourceIris(targetResourceIris: Set[IRI], resourcesSequence: ReadResourcesSequenceV2): Unit = { + val hiddenTargetResourceIris: Set[IRI] = targetResourceIris.intersect(resourcesSequence.hiddenResourceIris) + + if (hiddenTargetResourceIris.nonEmpty) { + throw ForbiddenException(s"You do not have permission to see one or more resources: ${hiddenTargetResourceIris.map(iri => s"<$iri>").mkString(", ")}") } - resourceInfo + val missingResourceIris: Set[IRI] = targetResourceIris -- resourcesSequence.resources.map(_.resourceIri).toSet + + if (missingResourceIris.nonEmpty) { + throw NotFoundException(s"One or more resources were not found: ${missingResourceIris.map(iri => s"<$iri>").mkString(", ")}") + } } /** diff --git a/webapi/src/main/scala/org/knora/webapi/package.scala b/webapi/src/main/scala/org/knora/webapi/package.scala index 4ad30fa950..f2f6138055 100644 --- a/webapi/src/main/scala/org/knora/webapi/package.scala +++ b/webapi/src/main/scala/org/knora/webapi/package.scala @@ -27,7 +27,7 @@ package object webapi { * The version of `knora-base` and of the other built-in ontologies that this version of Knora requires. * Must be the same as the object of `knora-base:ontologyVersion` in the `knora-base` ontology being used. */ - val KnoraBaseVersion: String = "knora-base v7" + val KnoraBaseVersion: String = "knora-base v8" /** * `IRI` is a synonym for `String`, used to improve code readability. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/Responder.scala b/webapi/src/main/scala/org/knora/webapi/responders/Responder.scala index bff24321de..9f0a03adbd 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/Responder.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/Responder.scala @@ -24,9 +24,9 @@ import akka.http.scaladsl.util.FastFuture import akka.pattern._ import akka.util.Timeout import com.typesafe.scalalogging.{LazyLogging, Logger} +import org.knora.webapi._ import org.knora.webapi.messages.store.triplestoremessages.{SparqlSelectRequest, SparqlSelectResponse} import org.knora.webapi.util.{SmartIri, StringFormatter} -import org.knora.webapi.{KnoraDispatchers, Settings, SettingsImpl, UnexpectedMessageException} import scala.concurrent.{ExecutionContext, Future} import scala.language.postfixOps @@ -107,7 +107,7 @@ abstract class Responder(responderData: ResponderData) extends LazyLogging { /** * Provides logging */ - val log: Logger = logger + protected val log: Logger = logger /** * Checks whether an entity is used in the triplestore. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala index c7cd569643..aba1e6ef1b 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResourcesResponderV2.scala @@ -42,7 +42,7 @@ import org.knora.webapi.responders.v2.search.ConstructQuery import org.knora.webapi.responders.v2.search.gravsearch.GravsearchParser import org.knora.webapi.responders.{IriLocker, ResponderData} import org.knora.webapi.twirl.SparqlTemplateResourceToCreate -import org.knora.webapi.util.ConstructResponseUtilV2.{MappingAndXSLTransformation, RdfResources} +import org.knora.webapi.util.ConstructResponseUtilV2.MappingAndXSLTransformation import org.knora.webapi.util.IriConversions._ import org.knora.webapi.util.PermissionUtilADM.{AGreaterThanB, DeletePermission, ModifyPermission, PermissionComparisonResult} import org.knora.webapi.util._ @@ -95,7 +95,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt // Convert the resource to the internal ontology schema. internalCreateResource: CreateResourceV2 <- Future(createResourceRequestV2.createResource.toOntologySchema(InternalSchema)) - // Check standoff link targets and list nodes that should exist. + // Check link targets and list nodes that should exist. _ <- checkStandoffLinkTargets(internalCreateResource.flatValues, createResourceRequestV2.requestingUser) _ <- checkListNodes(internalCreateResource.flatValues, createResourceRequestV2.requestingUser) @@ -314,8 +314,8 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt requestingUser = updateResourceMetadataRequestV2.requestingUser ) - _ = if (updatedResourcesSeq.numberOfResources != 1) { - throw AssertionException(s"Expected one resource, got ${resourcesSeq.numberOfResources}") + _ = if (updatedResourcesSeq.resources.size != 1) { + throw AssertionException(s"Expected one resource, got ${resourcesSeq.resources.size}") } updatedResource: ReadResourceV2 = updatedResourcesSeq.resources.head @@ -699,7 +699,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt */ private def getLinkTargetClasses(internalCreateResources: Seq[CreateResourceV2], requestingUser: UserADM): Future[Map[IRI, SmartIri]] = { // Get the IRIs of the new and existing resources that are targets of links. - val (existingTargets: Set[IRI], newTargets: Set[IRI]) = internalCreateResources.flatMap(_.flatValues).foldLeft((Set.empty[IRI], Set.empty[IRI])) { + val (existingTargetIris: Set[IRI], newTargets: Set[IRI]) = internalCreateResources.flatMap(_.flatValues).foldLeft((Set.empty[IRI], Set.empty[IRI])) { case ((accExisting: Set[IRI], accNew: Set[IRI]), valueToCreate: CreateValueInNewResourceV2) => valueToCreate.valueContent match { case linkValueContentV2: LinkValueContentV2 => @@ -721,7 +721,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt for { // Get information about the existing resources that are targets of links. existingTargets: ReadResourcesSequenceV2 <- getResourcePreviewV2( - resourceIris = existingTargets.toSeq, + resourceIris = existingTargetIris.toSeq, targetSchema = ApiV2Complex, requestingUser = requestingUser ) @@ -855,7 +855,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt } } - checkResourceIris(standoffLinkTargetsThatShouldExist, requestingUser) + getResourcePreviewV2(standoffLinkTargetsThatShouldExist.toSeq, targetSchema = ApiV2Complex, requestingUser).map(_ => ()) } /** @@ -1010,7 +1010,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt requestingUser: UserADM): Future[ReadResourcesSequenceV2] = { val resourceIri = resourceReadyToCreate.sparqlTemplateResourceToCreate.resourceIri - for { + val resourceFuture: Future[ReadResourcesSequenceV2] = for { resourcesResponse: ReadResourcesSequenceV2 <- getResourcesV2( resourceIris = Seq(resourceIri), requestingUser = requestingUser, @@ -1018,11 +1018,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt schemaOptions = SchemaOptions.ForStandoffWithTextValues ) - resource: ReadResourceV2 = try { - resourcesResponse.toResource(requestedResourceIri = resourceIri) - } catch { - case _: NotFoundException => throw UpdateNotPerformedException(s"Resource <$resourceIri> was not created. Please report this as a possible bug.") - } + resource: ReadResourceV2 = resourcesResponse.toResource(requestedResourceIri = resourceIri) _ = if (resource.resourceClassIri.toString != resourceReadyToCreate.sparqlTemplateResourceToCreate.resourceClassIri) { throw AssertionException(s"Resource <$resourceIri> was saved, but it has the wrong resource class") @@ -1070,9 +1066,12 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt } } } yield ReadResourcesSequenceV2( - numberOfResources = 1, resources = Seq(resource.copy(values = Map.empty)) ) + + resourceFuture.recover { + case _: NotFoundException => throw UpdateNotPerformedException(s"Resource <$resourceIri> was not created. Please report this as a possible bug.") + } } /** @@ -1125,8 +1124,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt valueUuid: Option[UUID], versionDate: Option[Instant], queryStandoff: Boolean, - requestingUser: UserADM): Future[RdfResources] = { - + requestingUser: UserADM): Future[ConstructResponseUtilV2.MainResourcesAndValueRdfData] = { // eliminate duplicate Iris val resourceIrisDistinct: Seq[IRI] = resourceIris.distinct @@ -1156,15 +1154,11 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt resourceRequestResponse: SparqlExtendedConstructResponse <- (storeManager ? SparqlExtendedConstructRequest(resourceRequestSparql)).mapTo[SparqlExtendedConstructResponse] // separate resources and values - queryResultsSeparated: RdfResources = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = requestingUser) - - // check if all the requested resources were returned - requestedButMissing: Set[IRI] = resourceIrisDistinct.toSet -- queryResultsSeparated.keySet - - _ = if (requestedButMissing.nonEmpty) { - throw NotFoundException(s"One or more requested resources were not found (maybe you do not have permission to see them, or they are marked as deleted): ${requestedButMissing.map(resourceIri => s"<$resourceIri>").mkString(", ")}") - } - } yield queryResultsSeparated + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData( + constructQueryResults = resourceRequestResponse, + requestingUser = requestingUser + ) + } yield mainResourcesAndValueRdfData } @@ -1187,7 +1181,6 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt targetSchema: ApiV2Schema, schemaOptions: Set[SchemaOption], requestingUser: UserADM): Future[ReadResourcesSequenceV2] = { - // eliminate duplicate Iris val resourceIrisDistinct: Seq[IRI] = resourceIris.distinct @@ -1197,7 +1190,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt for { - queryResultsSeparated: RdfResources <- getResourcesFromTriplestore( + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData <- getResourcesFromTriplestore( resourceIris = resourceIris, preview = false, propertyIri = propertyIri, @@ -1209,38 +1202,39 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt // If we're querying standoff, get XML-to standoff mappings. mappingsAsMap: Map[IRI, MappingAndXSLTransformation] <- if (queryStandoff) { - getMappingsFromQueryResultsSeparated(queryResultsSeparated, requestingUser) + getMappingsFromQueryResultsSeparated(mainResourcesAndValueRdfData.resources, requestingUser) } else { FastFuture.successful(Map.empty[IRI, MappingAndXSLTransformation]) } - resourcesResponseFutures: Vector[Future[ReadResourceV2]] = resourceIrisDistinct.map { - resIri: IRI => - ConstructResponseUtilV2.createFullResourceResponse( - resourceIri = resIri, - resourceRdfData = queryResultsSeparated(resIri), - mappings = mappingsAsMap, - queryStandoff = queryStandoff, - versionDate = versionDate, - responderManager = responderManager, - targetSchema = targetSchema, - settings = settings, - requestingUser = requestingUser - ) - }.toVector + apiResponse: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, + orderByResourceIri = resourceIrisDistinct, + mappings = mappingsAsMap, + queryStandoff = queryStandoff, + versionDate = versionDate, + calculateMayHaveMoreResults = false, + responderManager = responderManager, + targetSchema = targetSchema, + settings = settings, + requestingUser = requestingUser + ) - resourcesResponse: Vector[ReadResourceV2] <- Future.sequence(resourcesResponseFutures) + _ = apiResponse.checkResourceIris( + targetResourceIris = resourceIris.toSet, + resourcesSequence = apiResponse + ) _ = valueUuid match { case Some(definedValueUuid) => - if (!resourcesResponse.exists(_.values.values.exists(_.exists(_.valueHasUUID == definedValueUuid)))) { + if (!apiResponse.resources.exists(_.values.values.exists(_.exists(_.valueHasUUID == definedValueUuid)))) { throw NotFoundException(s"Value with UUID ${stringFormatter.base64EncodeUuid(definedValueUuid)} not found (maybe you do not have permission to see it, or it is marked as deleted)") } case None => () } - } yield ReadResourcesSequenceV2(numberOfResources = resourceIrisDistinct.size, resources = resourcesResponse) + } yield apiResponse } @@ -1257,7 +1251,7 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt val resourceIrisDistinct: Seq[IRI] = resourceIris.distinct for { - queryResultsSeparated: RdfResources <- getResourcesFromTriplestore( + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData <- getResourcesFromTriplestore( resourceIris = resourceIris, preview = true, propertyIri = None, @@ -1267,25 +1261,24 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt requestingUser = requestingUser ) - resourcesResponseFutures: Vector[Future[ReadResourceV2]] = resourceIrisDistinct.map { - resIri: IRI => - ConstructResponseUtilV2.createFullResourceResponse( - resourceIri = resIri, - resourceRdfData = queryResultsSeparated(resIri), - mappings = Map.empty[IRI, MappingAndXSLTransformation], - queryStandoff = false, - versionDate = None, - responderManager = responderManager, - targetSchema = targetSchema, - settings = settings, - requestingUser = requestingUser - ) - }.toVector - - resourcesResponse: Vector[ReadResourceV2] <- Future.sequence(resourcesResponseFutures) - - } yield ReadResourcesSequenceV2(numberOfResources = resourceIrisDistinct.size, resources = resourcesResponse) + apiResponse: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, + orderByResourceIri = resourceIrisDistinct, + mappings = Map.empty[IRI, MappingAndXSLTransformation], + queryStandoff = false, + versionDate = None, + calculateMayHaveMoreResults = false, + responderManager = responderManager, + targetSchema = targetSchema, + settings = settings, + requestingUser = requestingUser + ) + _ = apiResponse.checkResourceIris( + targetResourceIris = resourceIris.toSet, + resourcesSequence = apiResponse + ) + } yield apiResponse } /** @@ -1552,21 +1545,6 @@ class ResourcesResponderV2(responderData: ResponderData) extends ResponderWithSt } yield tei } - /** - * Given a set of resource IRIs, checks that they point to Knora resources. - * If not, throws an exception. - * - * @param targetResourceIris the IRIs to be checked. - * @param requestingUser the user making the request. - */ - private def checkResourceIris(targetResourceIris: Set[IRI], requestingUser: UserADM): Future[Unit] = { - if (targetResourceIris.isEmpty) { - FastFuture.successful(()) - } else { - getResourcePreviewV2(targetResourceIris.toSeq, targetSchema = ApiV2Complex, requestingUser).map(_ => ()) - } - } - /** * Gets a graph of resources that are reachable via links to or from a given resource. * diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResponderWithStandoffV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResponderWithStandoffV2.scala index 3d2576fcbf..d0f9d15648 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ResponderWithStandoffV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ResponderWithStandoffV2.scala @@ -31,17 +31,17 @@ import org.knora.webapi.util.ConstructResponseUtilV2.{MappingAndXSLTransformatio import scala.concurrent.Future /** - * An abstract class with standoff utility methods for v2 responders. - */ + * An abstract class with standoff utility methods for v2 responders. + */ abstract class ResponderWithStandoffV2(responderData: ResponderData) extends Responder(responderData) { /** - * Gets mappings referred to in query results [[Map[IRI, ResourceWithValueRdfData]]]. - * - * @param queryResultsSeparated query results referring to mappings. - * @param requestingUser the user making the request. - * @return the referred mappings. - */ + * Gets mappings referred to in query results [[Map[IRI, ResourceWithValueRdfData]]]. + * + * @param queryResultsSeparated query results referring to mappings. + * @param requestingUser the user making the request. + * @return the referred mappings. + */ protected def getMappingsFromQueryResultsSeparated(queryResultsSeparated: Map[IRI, ResourceWithValueRdfData], requestingUser: UserADM): Future[Map[IRI, MappingAndXSLTransformation]] = { // collect the Iris of the mappings referred to in the resources' text values diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/SearchResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/SearchResponderV2.scala index c12c90e775..6c5e2ee994 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/SearchResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/SearchResponderV2.scala @@ -23,7 +23,8 @@ import akka.http.scaladsl.util.FastFuture import akka.pattern._ import org.knora.webapi._ import org.knora.webapi.messages.admin.responder.usersmessages.UserADM -import org.knora.webapi.messages.store.triplestoremessages.{SubjectV2, _} +import org.knora.webapi.messages.store.triplestoremessages._ +import org.knora.webapi.messages.v2.responder.KnoraResponseV2 import org.knora.webapi.messages.v2.responder.ontologymessages.{EntityInfoGetRequestV2, EntityInfoGetResponseV2, ReadClassInfoV2, ReadPropertyInfoV2} import org.knora.webapi.messages.v2.responder.resourcemessages._ import org.knora.webapi.messages.v2.responder.searchmessages._ @@ -34,21 +35,13 @@ import org.knora.webapi.responders.v2.search.gravsearch._ import org.knora.webapi.responders.v2.search.gravsearch.prequery._ import org.knora.webapi.responders.v2.search.gravsearch.types._ import org.knora.webapi.util.ApacheLuceneSupport._ -import org.knora.webapi.util.ConstructResponseUtilV2.{MappingAndXSLTransformation, RdfResources} +import org.knora.webapi.util.ConstructResponseUtilV2.MappingAndXSLTransformation import org.knora.webapi.util.IriConversions._ import org.knora.webapi.util._ import org.knora.webapi.util.standoff.StandoffTagUtilV2 import scala.concurrent.Future -/** - * Constants used in [[SearchResponderV2]]. - */ -object SearchResponderV2Constants { - - val forbiddenResourceIri: IRI = s"http://${StringFormatter.IriDomain}/0000/forbiddenResource" -} - class SearchResponderV2(responderData: ResponderData) extends ResponderWithStandoffV2(responderData) { // A Gravsearch type inspection runner. @@ -57,7 +50,7 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand /** * Receives a message of type [[SearchResponderRequestV2]], and returns an appropriate response message. */ - def receive(msg: SearchResponderRequestV2) = msg match { + def receive(msg: SearchResponderRequestV2): Future[KnoraResponseV2] = msg match { case FullTextSearchCountRequestV2(searchValue, limitToProject, limitToResourceClass, limitToStandoffClass, requestingUser) => fulltextSearchCountV2(searchValue, limitToProject, limitToResourceClass, limitToStandoffClass, requestingUser) case FulltextSearchRequestV2(searchValue, offset, limitToProject, limitToResourceClass, limitToStandoffClass, targetSchema, schemaOptions, requestingUser) => fulltextSearchV2(searchValue, offset, limitToProject, limitToResourceClass, limitToStandoffClass, targetSchema, schemaOptions, requestingUser) case GravsearchCountRequestV2(query, requestingUser) => gravsearchCountV2(inputQuery = query, requestingUser = requestingUser) @@ -68,24 +61,6 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand case other => handleUnexpectedMessage(other, log, this.getClass.getName) } - /** - * Gets the forbidden resource. - * - * @param requestingUser the user making the request. - * @return the forbidden resource. - */ - private def getForbiddenResource(requestingUser: UserADM): Future[Some[ReadResourceV2]] = { - import SearchResponderV2Constants.forbiddenResourceIri - - for { - forbiddenResSeq: ReadResourcesSequenceV2 <- (responderManager ? ResourcesGetRequestV2( - resourceIris = Seq(forbiddenResourceIri), - targetSchema = ApiV2Complex, // This has no effect, because ForbiddenResource has no values. - requestingUser = requestingUser)).mapTo[ReadResourcesSequenceV2] - forbiddenRes = forbiddenResSeq.resources.headOption.getOrElse(throw InconsistentTriplestoreDataException(s"$forbiddenResourceIri was not returned")) - } yield Some(forbiddenRes) - } - /** * Performs a fulltext search and returns the resources count (how many resources match the search criteria), * without taking into consideration permission checking. @@ -184,8 +159,8 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand resultRow: VariableResultsRow => resultRow.rowMap(FullTextSearchConstants.resourceVar.variableName) } - // make sure that the prequery returned some results - queryResultsSeparatedWithFullGraphPattern: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] <- if (resourceIris.nonEmpty) { + // If the prequery returned some results, prepare a main query. + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData <- if (resourceIris.nonEmpty) { // for each resource, create a Set of value object IRIs val valueObjectIrisPerResource: Map[IRI, Set[IRI]] = prequeryResponse.results.bindings.foldLeft(Map.empty[IRI, Set[IRI]]) { @@ -209,7 +184,7 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand // collect all value object IRIs val allValueObjectIris = valueObjectIrisPerResource.values.flatten.toSet - // create CONSTRUCT queries to query resources and their values + // create a CONSTRUCT query to query resources and their values val mainQuery = FullTextMainQueryGenerator.createMainQuery( resourceIris = resourceIris.toSet, valueObjectIris = allValueObjectIris, @@ -239,90 +214,43 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand searchResponse: SparqlExtendedConstructResponse <- (storeManager ? SparqlExtendedConstructRequest(triplestoreSpecificQuery.toSparql)).mapTo[SparqlExtendedConstructResponse] // separate resources and value objects - queryResultsSep = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = searchResponse, requestingUser = requestingUser) - - // for each main resource check if all dependent resources and value objects are still present after permission checking - // this ensures that the user has sufficient permissions on the whole graph pattern - queryResWithFullGraphPattern = queryResultsSep.foldLeft(Map.empty[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData]) { - case (acc: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData], (mainResIri: IRI, values: ConstructResponseUtilV2.ResourceWithValueRdfData)) => - - valueObjectIrisPerResource.get(mainResIri) match { - - case Some(valObjIris) => - - // check for presence of value objects: valueObjectIrisPerResource - val expectedValueObjects: Set[IRI] = valueObjectIrisPerResource(mainResIri) - - // value property assertions for the current resource - val valuePropAssertions: Map[SmartIri, Seq[ConstructResponseUtilV2.ValueRdfData]] = values.valuePropertyAssertions - - // all value objects contained in `valuePropAssertions` - val resAndValueObjIris: MainQueryResultProcessor.ResourceIrisAndValueObjectIris = MainQueryResultProcessor.collectResourceIrisAndValueObjectIrisFromMainQueryResult(valuePropAssertions) - - // check if the client has sufficient permissions on all value objects IRIs present in the graph pattern - val allValueObjects: Boolean = resAndValueObjIris.valueObjectIris.intersect(expectedValueObjects) == expectedValueObjects - - if (allValueObjects) { - // sufficient permissions, include the main resource and its values - acc + (mainResIri -> values) - } else { - // insufficient permissions, skip the resource - acc - } - - case None => - // no properties -> rfs:label matched - acc + (mainResIri -> values) - } - } - - } yield queryResWithFullGraphPattern + queryResultsSep: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = searchResponse, requestingUser = requestingUser) + } yield queryResultsSep } else { // the prequery returned no results, no further query is necessary - Future(Map.empty[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData]) - } - - // check if there are resources the user does not have sufficient permissions to see - forbiddenResourceOption: Option[ReadResourceV2] <- if (resourceIris.size > queryResultsSeparatedWithFullGraphPattern.size) { - // some of the main resources have been suppressed, represent them using the forbidden resource - - getForbiddenResource(requestingUser) - } else { - // all resources visible, no need for the forbidden resource - Future(None) + Future( + ConstructResponseUtilV2.MainResourcesAndValueRdfData(resources = Map.empty) + ) } // Find out whether to query standoff along with text values. This boolean value will be passed to // ConstructResponseUtilV2.makeTextValueContentV2. - queryStandoff = SchemaOptions.queryStandoffWithTextValues(targetSchema = targetSchema, schemaOptions = schemaOptions) + queryStandoff: Boolean = SchemaOptions.queryStandoffWithTextValues(targetSchema = targetSchema, schemaOptions = schemaOptions) // If we're querying standoff, get XML-to standoff mappings. mappingsAsMap: Map[IRI, MappingAndXSLTransformation] <- if (queryStandoff) { - getMappingsFromQueryResultsSeparated(queryResultsSeparatedWithFullGraphPattern, requestingUser) + getMappingsFromQueryResultsSeparated(mainResourcesAndValueRdfData.resources, requestingUser) } else { FastFuture.successful(Map.empty[IRI, MappingAndXSLTransformation]) } // _ = println(mappingsAsMap) - resources: Vector[ReadResourceV2] <- ConstructResponseUtilV2.createSearchResponse( - searchResults = queryResultsSeparatedWithFullGraphPattern, + apiResponse: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, orderByResourceIri = resourceIris, mappings = mappingsAsMap, queryStandoff = queryStandoff, - forbiddenResource = forbiddenResourceOption, + calculateMayHaveMoreResults = true, + versionDate = None, responderManager = responderManager, settings = settings, targetSchema = targetSchema, requestingUser = requestingUser ) - } yield ReadResourcesSequenceV2( - numberOfResources = resourceIris.size, - resources = resources - ) - + } yield apiResponse } @@ -476,7 +404,7 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand resultRow.rowMap(mainResourceVar.variableName) } - queryResultsSeparatedWithFullGraphPattern: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] <- if (mainResourceIris.nonEmpty) { + queryResultsSeparatedWithFullGraphPattern: ConstructResponseUtilV2.MainResourcesAndValueRdfData <- if (mainResourceIris.nonEmpty) { // at least one resource matched the prequery // get all the IRIs for variables representing dependent resources per main resource @@ -542,14 +470,14 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand mainQueryResponse: SparqlExtendedConstructResponse <- (storeManager ? SparqlExtendedConstructRequest(triplestoreSpecificSparql)).mapTo[SparqlExtendedConstructResponse] // Filter out values that the user doesn't have permission to see. - queryResultsFilteredForPermissions: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData( + queryResultsFilteredForPermissions: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData( constructQueryResults = mainQueryResponse, requestingUser = requestingUser ) // filter out those value objects that the user does not want to be returned by the query (not present in the input query's CONSTRUCT clause) queryResWithFullGraphPatternOnlyRequestedValues: Map[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData] = MainQueryResultProcessor.getRequestedValuesFromResultsWithFullGraphPattern( - queryResultsFilteredForPermissions, + queryResultsFilteredForPermissions.resources, valueObjectVarsAndIrisPerMainResource, allResourceVariablesFromTypeInspection, dependentResourceIrisFromTypeInspection, @@ -558,50 +486,40 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand inputQuery ) - } yield queryResWithFullGraphPatternOnlyRequestedValues + } yield queryResultsFilteredForPermissions.copy( + resources = queryResWithFullGraphPatternOnlyRequestedValues + ) } else { // the prequery returned no results, no further query is necessary - Future(Map.empty[IRI, ConstructResponseUtilV2.ResourceWithValueRdfData]) - } - - // check if there are resources the user does not have sufficient permissions to see - forbiddenResourceOption: Option[ReadResourceV2] <- if (mainResourceIris.size > queryResultsSeparatedWithFullGraphPattern.size) { - // some of the main resources have been suppressed, represent them using the forbidden resource - - getForbiddenResource(requestingUser) - } else { - // all resources visible, no need for the forbidden resource - Future(None) + Future(ConstructResponseUtilV2.MainResourcesAndValueRdfData(resources = Map.empty)) } // Find out whether to query standoff along with text values. This boolean value will be passed to // ConstructResponseUtilV2.makeTextValueContentV2. - queryStandoff = SchemaOptions.queryStandoffWithTextValues(targetSchema = targetSchema, schemaOptions = schemaOptions) + queryStandoff: Boolean = SchemaOptions.queryStandoffWithTextValues(targetSchema = targetSchema, schemaOptions = schemaOptions) // If we're querying standoff, get XML-to standoff mappings. mappingsAsMap: Map[IRI, MappingAndXSLTransformation] <- if (queryStandoff) { - getMappingsFromQueryResultsSeparated(queryResultsSeparatedWithFullGraphPattern, requestingUser) + getMappingsFromQueryResultsSeparated(queryResultsSeparatedWithFullGraphPattern.resources, requestingUser) } else { FastFuture.successful(Map.empty[IRI, MappingAndXSLTransformation]) } - resources <- ConstructResponseUtilV2.createSearchResponse( - searchResults = queryResultsSeparatedWithFullGraphPattern, + apiResponse: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = queryResultsSeparatedWithFullGraphPattern, orderByResourceIri = mainResourceIris, mappings = mappingsAsMap, queryStandoff = queryStandoff, - forbiddenResource = forbiddenResourceOption, + versionDate = None, + calculateMayHaveMoreResults = true, responderManager = responderManager, settings = settings, targetSchema = targetSchema, requestingUser = requestingUser ) - } yield ReadResourcesSequenceV2( - numberOfResources = mainResourceIris.size, - resources = resources - ) + } yield apiResponse } @@ -690,7 +608,7 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand ) // Are there any matching resources? - resources: Vector[ReadResourceV2] <- if (mainResourceIris.nonEmpty) { + apiResponse: ReadResourcesSequenceV2 <- if (mainResourceIris.nonEmpty) { for { // Yes. Do a CONSTRUCT query to get the contents of those resources. If we're querying standoff, get // at most one page of standoff per text value. @@ -711,45 +629,33 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand resourceRequestResponse: SparqlExtendedConstructResponse <- (storeManager ? SparqlExtendedConstructRequest(resourceRequestSparql)).mapTo[SparqlExtendedConstructResponse] // separate resources and values - queryResultsSeparated: RdfResources = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = resourcesInProjectGetRequestV2.requestingUser) - - // check if there are resources the user does not have sufficient permissions to see - forbiddenResourceOption: Option[ReadResourceV2] <- if (mainResourceIris.size > queryResultsSeparated.size) { - // some of the main resources have been suppressed, represent them using the forbidden resource - getForbiddenResource(resourcesInProjectGetRequestV2.requestingUser) - } else { - // all resources visible, no need for the forbidden resource - Future(None) - } + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = resourcesInProjectGetRequestV2.requestingUser) // If we're querying standoff, get XML-to standoff mappings. mappings: Map[IRI, MappingAndXSLTransformation] <- if (queryStandoff) { - getMappingsFromQueryResultsSeparated(queryResultsSeparated, resourcesInProjectGetRequestV2.requestingUser) + getMappingsFromQueryResultsSeparated(mainResourcesAndValueRdfData.resources, resourcesInProjectGetRequestV2.requestingUser) } else { FastFuture.successful(Map.empty[IRI, MappingAndXSLTransformation]) } // Construct a ReadResourceV2 for each resource that the user has permission to see. - searchResponse <- ConstructResponseUtilV2.createSearchResponse( - searchResults = queryResultsSeparated, + readResourcesSequence: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, orderByResourceIri = mainResourceIris, mappings = mappings, queryStandoff = maybeStandoffMinStartIndex.nonEmpty, - forbiddenResource = forbiddenResourceOption, + versionDate = None, + calculateMayHaveMoreResults = true, responderManager = responderManager, targetSchema = resourcesInProjectGetRequestV2.targetSchema, settings = settings, requestingUser = resourcesInProjectGetRequestV2.requestingUser ) - } yield searchResponse + } yield readResourcesSequence } else { - FastFuture.successful(Vector.empty[ReadResourceV2]) + FastFuture.successful(ReadResourcesSequenceV2(Vector.empty[ReadResourceV2])) } - - } yield ReadResourcesSequenceV2( - numberOfResources = resources.size, - resources = resources - ) + } yield apiResponse } /** @@ -761,8 +667,7 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand * @param requestingUser the the client making the request. * @return a [[ReadResourcesSequenceV2]] representing the resources that have been found. */ - private def searchResourcesByLabelCountV2(searchValue: String, limitToProject: Option[IRI], limitToResourceClass: Option[SmartIri], requestingUser: UserADM) = { - + private def searchResourcesByLabelCountV2(searchValue: String, limitToProject: Option[IRI], limitToResourceClass: Option[SmartIri], requestingUser: UserADM): Future[ResourceCountV2] = { val searchPhrase: MatchStringWhileTyping = MatchStringWhileTyping(searchValue) for { @@ -787,9 +692,8 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand count = countResponse.results.bindings.head.rowMap("count") - } yield ReadResourcesSequenceV2( - numberOfResources = count.toInt, - resources = Seq.empty[ReadResourceV2] // no results for a count query + } yield ResourceCountV2( + numberOfResources = count.toInt ) } @@ -854,35 +758,23 @@ class SearchResponderV2(responderData: ResponderData) extends ResponderWithStand // _ = println(mainResourceIris.size) // separate resources and value objects - queryResultsSeparated = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = searchResourceByLabelResponse, requestingUser = requestingUser) - - // check if there are resources the user does not have sufficient permissions to see - forbiddenResourceOption: Option[ReadResourceV2] <- if (mainResourceIris.size > queryResultsSeparated.size) { - // some of the main resources have been suppressed, represent them using the forbidden resource - getForbiddenResource(requestingUser) - } else { - // all resources visible, no need for the forbidden resource - Future(None) - } + mainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = searchResourceByLabelResponse, requestingUser = requestingUser) //_ = println(queryResultsSeparated) - resources <- ConstructResponseUtilV2.createSearchResponse( - searchResults = queryResultsSeparated, + apiResponse: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, orderByResourceIri = mainResourceIris.toSeq.sorted, queryStandoff = false, - forbiddenResource = forbiddenResourceOption, + versionDate = None, + calculateMayHaveMoreResults = true, responderManager = responderManager, targetSchema = targetSchema, settings = settings, requestingUser = requestingUser ) - } yield ReadResourcesSequenceV2( - numberOfResources = queryResultsSeparated.size, - resources = resources - ) - + } yield apiResponse } /** diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala index 6b623f8022..83286d6c33 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/StandoffResponderV2.scala @@ -41,7 +41,6 @@ import org.knora.webapi.messages.v2.responder.valuemessages._ import org.knora.webapi.responders.Responder.handleUnexpectedMessage import org.knora.webapi.responders.{IriLocker, Responder, ResponderData} import org.knora.webapi.twirl.{MappingElement, MappingStandoffDatatypeClass, MappingXMLAttribute} -import org.knora.webapi.util.ConstructResponseUtilV2.ResourceWithValueRdfData import org.knora.webapi.util.IriConversions._ import org.knora.webapi.util._ import org.knora.webapi.util.standoff.StandoffTagUtilV2 @@ -102,17 +101,14 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon // _ = println(s"Got a page of standoff in ${standoffPageEndTime - standoffPageStartTime} ms") // separate resources and values - queryResultsSeparated: Map[IRI, ResourceWithValueRdfData] = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = getStandoffRequestV2.requestingUser) + mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = getStandoffRequestV2.requestingUser) - _ = if (queryResultsSeparated.keySet != Set(getStandoffRequestV2.resourceIri)) { - throw NotFoundException(s"Resource <${getStandoffRequestV2.resourceIri}> was not found (maybe you do not have permission to see it, or it is marked as deleted)") - } - - readResourceV2: ReadResourceV2 <- ConstructResponseUtilV2.createFullResourceResponse( - resourceIri = getStandoffRequestV2.resourceIri, - resourceRdfData = queryResultsSeparated(getStandoffRequestV2.resourceIri), + readResourcesSequenceV2: ReadResourcesSequenceV2 <- ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, + orderByResourceIri = Seq(getStandoffRequestV2.resourceIri), mappings = Map.empty, queryStandoff = false, + calculateMayHaveMoreResults = false, versionDate = None, responderManager = responderManager, targetSchema = getStandoffRequestV2.targetSchema, @@ -120,6 +116,8 @@ class StandoffResponderV2(responderData: ResponderData) extends Responder(respon requestingUser = getStandoffRequestV2.requestingUser ) + readResourceV2 = readResourcesSequenceV2.toResource(getStandoffRequestV2.resourceIri) + valueObj: ReadValueV2 = readResourceV2.values.values.flatten.find(_.valueIri == getStandoffRequestV2.valueIri).getOrElse(throw NotFoundException(s"Value <${getStandoffRequestV2.valueIri}> not found in resource <${getStandoffRequestV2.resourceIri}> (maybe you do not have permission to see it, or it is marked as deleted)")) textValueObj: ReadTextValueV2 = valueObj match { diff --git a/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala b/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala index 407f732776..2c0fb9050d 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/v2/ValuesResponderV2.scala @@ -1717,7 +1717,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde propertyIri: SmartIri, unverifiedValue: UnverifiedValueV2, requestingUser: UserADM): Future[VerifiedValueV2] = { - for { + val verifiedValueFuture: Future[VerifiedValueV2] = for { resourcesRequest <- Future { ResourcesGetRequestV2( resourceIris = Seq(resourceIri), @@ -1730,12 +1730,7 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde } resourcesResponse <- (responderManager ? resourcesRequest).mapTo[ReadResourcesSequenceV2] - - resource = try { - resourcesResponse.toResource(resourceIri) - } catch { - case _: NotFoundException => throw UpdateNotPerformedException(s"Resource <$resourceIri> was not created. Please report this as a possible bug.") - } + resource = resourcesResponse.toResource(resourceIri) propertyValues = resource.values.getOrElse(propertyIri, throw UpdateNotPerformedException()) valueInTriplestore: ReadValueV2 = propertyValues.find(_.valueIri == unverifiedValue.newValueIri).getOrElse(throw UpdateNotPerformedException()) @@ -1760,6 +1755,10 @@ class ValuesResponderV2(responderData: ResponderData) extends Responder(responde value = unverifiedValue.valueContent, permissions = unverifiedValue.permissions ) + + verifiedValueFuture.recover { + case _: NotFoundException => throw UpdateNotPerformedException(s"Resource <$resourceIri> was not found. Please report this as a possible bug.") + } } /** diff --git a/webapi/src/main/scala/org/knora/webapi/routing/v2/SearchRouteV2.scala b/webapi/src/main/scala/org/knora/webapi/routing/v2/SearchRouteV2.scala index 6090def81a..30ac8295a8 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/v2/SearchRouteV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/v2/SearchRouteV2.scala @@ -346,7 +346,8 @@ class SearchRouteV2(routeData: KnoraRouteData) extends KnoraRoute(routeData) wit // Search results to return in test data. private val testSearches: Map[String, IRI] = Map( "things" -> SharedTestDataADM.gravsearchComplexThingSmallerThanDecimal, - "thing-links" -> SharedTestDataADM.gravsearchThingLinks + "thing-links" -> SharedTestDataADM.gravsearchThingLinks, + "things-with-paging" -> SharedTestDataADM.gravsearchThingsWithPaging ) private def getSearchTestResponses: Future[Set[TestDataFileContent]] = { diff --git a/webapi/src/main/scala/org/knora/webapi/util/ConstructResponseUtilV2.scala b/webapi/src/main/scala/org/knora/webapi/util/ConstructResponseUtilV2.scala index 05dc6c5097..d0ed7674e9 100644 --- a/webapi/src/main/scala/org/knora/webapi/util/ConstructResponseUtilV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/util/ConstructResponseUtilV2.scala @@ -52,66 +52,66 @@ object ConstructResponseUtilV2 { ) /** - * A map of resource IRIs to resource RDF data. - */ + * A map of resource IRIs to resource RDF data. + */ type RdfResources = Map[IRI, ResourceWithValueRdfData] /** - * Makes an empty instance of [[RdfResources]]. - */ + * Makes an empty instance of [[RdfResources]]. + */ def emptyRdfResources: RdfResources = Map.empty /** - * A map of property IRIs to value RDF data. - */ + * A map of property IRIs to value RDF data. + */ type RdfPropertyValues = Map[SmartIri, Seq[ValueRdfData]] /** - * Makes an empty instance of [[RdfPropertyValues]]. - */ + * Makes an empty instance of [[RdfPropertyValues]]. + */ def emptyRdfPropertyValues: RdfPropertyValues = Map.empty /** - * A map of subject IRIs to [[ConstructPredicateObjects]] instances. - */ + * A map of subject IRIs to [[ConstructPredicateObjects]] instances. + */ type Statements = Map[IRI, ConstructPredicateObjects] /** - * A flattened map of predicates to objects. This assumes that each predicate has - * * only one object. - */ + * A flattened map of predicates to objects. This assumes that each predicate has + * * only one object. + */ type FlatPredicateObjects = Map[SmartIri, LiteralV2] /** - * A map of subject IRIs to flattened maps of predicates to objects. - */ + * A map of subject IRIs to flattened maps of predicates to objects. + */ type FlatStatements = Map[IRI, Map[SmartIri, LiteralV2]] /** - * Makes an empty instance of [[FlatStatements]]. - */ + * Makes an empty instance of [[FlatStatements]]. + */ def emptyFlatStatements: FlatStatements = Map.empty /** - * Represents assertions about an RDF subject. - */ + * Represents assertions about an RDF subject. + */ sealed trait RdfData { /** - * The IRI of the subject. - */ + * The IRI of the subject. + */ val subjectIri: IRI /** - * Assertions about the subject. - */ + * Assertions about the subject. + */ val assertions: FlatPredicateObjects /** - * Returns the optional string object of the specified predicate. Throws an exception if the object is not a string. - * - * @param predicateIri the predicate. - * @return the string object of the predicate. - */ + * Returns the optional string object of the specified predicate. Throws an exception if the object is not a string. + * + * @param predicateIri the predicate. + * @return the string object of the predicate. + */ def maybeStringObject(predicateIri: SmartIri): Option[String] = { assertions.get(predicateIri).map { literal => literal.asStringLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -119,22 +119,22 @@ object ConstructResponseUtilV2 { } /** - * Returns the required string object of the specified predicate. Throws an exception if the object is not found or - * is not a string. - * - * @param predicateIri the predicate. - * @return the string object of the predicate. - */ + * Returns the required string object of the specified predicate. Throws an exception if the object is not found or + * is not a string. + * + * @param predicateIri the predicate. + * @return the string object of the predicate. + */ def requireStringObject(predicateIri: SmartIri): String = { maybeStringObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } /** - * Returns the optional IRI object of the specified predicate. Throws an exception if the object is not an IRI. - * - * @param predicateIri the predicate. - * @return the IRI object of the predicate. - */ + * Returns the optional IRI object of the specified predicate. Throws an exception if the object is not an IRI. + * + * @param predicateIri the predicate. + * @return the IRI object of the predicate. + */ def maybeIriObject(predicateIri: SmartIri): Option[IRI] = { assertions.get(predicateIri).map { literal => literal.asIriLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -142,22 +142,22 @@ object ConstructResponseUtilV2 { } /** - * Returns the required IRI object of the specified predicate. Throws an exception if the object is not found or - * is not an IRI. - * - * @param predicateIri the predicate. - * @return the IRI object of the predicate. - */ + * Returns the required IRI object of the specified predicate. Throws an exception if the object is not found or + * is not an IRI. + * + * @param predicateIri the predicate. + * @return the IRI object of the predicate. + */ def requireIriObject(predicateIri: SmartIri): IRI = { maybeIriObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } /** - * Returns the optional integer object of the specified predicate. Throws an exception if the object is not an integer. - * - * @param predicateIri the predicate. - * @return the integer object of the predicate. - */ + * Returns the optional integer object of the specified predicate. Throws an exception if the object is not an integer. + * + * @param predicateIri the predicate. + * @return the integer object of the predicate. + */ def maybeIntObject(predicateIri: SmartIri): Option[Int] = { assertions.get(predicateIri).map { literal => literal.asIntLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -165,22 +165,22 @@ object ConstructResponseUtilV2 { } /** - * Returns the required integer object of the specified predicate. Throws an exception if the object is not found or - * is not an integer. - * - * @param predicateIri the predicate. - * @return the integer object of the predicate. - */ + * Returns the required integer object of the specified predicate. Throws an exception if the object is not found or + * is not an integer. + * + * @param predicateIri the predicate. + * @return the integer object of the predicate. + */ def requireIntObject(predicateIri: SmartIri): Int = { maybeIntObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } /** - * Returns the optional boolean object of the specified predicate. Throws an exception if the object is not a boolean. - * - * @param predicateIri the predicate. - * @return the boolean object of the predicate. - */ + * Returns the optional boolean object of the specified predicate. Throws an exception if the object is not a boolean. + * + * @param predicateIri the predicate. + * @return the boolean object of the predicate. + */ def maybeBooleanObject(predicateIri: SmartIri): Option[Boolean] = { assertions.get(predicateIri).map { literal => literal.asBooleanLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -188,22 +188,22 @@ object ConstructResponseUtilV2 { } /** - * Returns the required boolean object of the specified predicate. Throws an exception if the object is not found or - * is not an boolean value. - * - * @param predicateIri the predicate. - * @return the boolean object of the predicate. - */ + * Returns the required boolean object of the specified predicate. Throws an exception if the object is not found or + * is not an boolean value. + * + * @param predicateIri the predicate. + * @return the boolean object of the predicate. + */ def requireBooleanObject(predicateIri: SmartIri): Boolean = { maybeBooleanObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } /** - * Returns the optional decimal object of the specified predicate. Throws an exception if the object is not a decimal. - * - * @param predicateIri the predicate. - * @return the decimal object of the predicate. - */ + * Returns the optional decimal object of the specified predicate. Throws an exception if the object is not a decimal. + * + * @param predicateIri the predicate. + * @return the decimal object of the predicate. + */ def maybeDecimalObject(predicateIri: SmartIri): Option[BigDecimal] = { assertions.get(predicateIri).map { literal => literal.asDecimalLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -211,23 +211,23 @@ object ConstructResponseUtilV2 { } /** - * Returns the required decimal object of the specified predicate. Throws an exception if the object is not found or - * is not an decimal value. - * - * @param predicateIri the predicate. - * @return the decimal object of the predicate. - */ + * Returns the required decimal object of the specified predicate. Throws an exception if the object is not found or + * is not an decimal value. + * + * @param predicateIri the predicate. + * @return the decimal object of the predicate. + */ def requireDecimalObject(predicateIri: SmartIri): BigDecimal = { maybeDecimalObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } /** - * Returns the optional timestamp object of the specified predicate. Throws an exception if the object is not a timestamp. - * - * @param predicateIri the predicate. - * @return the timestamp object of the predicate. - */ + * Returns the optional timestamp object of the specified predicate. Throws an exception if the object is not a timestamp. + * + * @param predicateIri the predicate. + * @return the timestamp object of the predicate. + */ def maybeDateTimeObject(predicateIri: SmartIri): Option[Instant] = { assertions.get(predicateIri).map { literal => literal.asDateTimeLiteral(throw InconsistentTriplestoreDataException(s"Unexpected object of $subjectIri $predicateIri: $literal")).value @@ -235,28 +235,28 @@ object ConstructResponseUtilV2 { } /** - * Returns the required timestamp object of the specified predicate. Throws an exception if the object is not found or - * is not an timestamp value. - * - * @param predicateIri the predicate. - * @return the timestamp object of the predicate. - */ + * Returns the required timestamp object of the specified predicate. Throws an exception if the object is not found or + * is not an timestamp value. + * + * @param predicateIri the predicate. + * @return the timestamp object of the predicate. + */ def requireDateTimeObject(predicateIri: SmartIri): Instant = { maybeDateTimeObject(predicateIri).getOrElse(throw InconsistentTriplestoreDataException(s"Subject $subjectIri does not have predicate $predicateIri")) } } /** - * Represents the RDF data about a value, possibly including standoff. - * - * @param subjectIri the value object's IRI. - * @param valueObjectClass the type (class) of the value object. - * @param nestedResource the nested resource in case of a link value (either the source or the target of a link value, depending on [[isIncomingLink]]). - * @param isIncomingLink indicates if it is an incoming or outgoing link in case of a link value. - * @param userPermission the permission that the requesting user has on the value. - * @param assertions the value objects assertions. - * @param standoff standoff assertions, if any. - */ + * Represents the RDF data about a value, possibly including standoff. + * + * @param subjectIri the value object's IRI. + * @param valueObjectClass the type (class) of the value object. + * @param nestedResource the nested resource in case of a link value (either the source or the target of a link value, depending on [[isIncomingLink]]). + * @param isIncomingLink indicates if it is an incoming or outgoing link in case of a link value. + * @param userPermission the permission that the requesting user has on the value. + * @param assertions the value objects assertions. + * @param standoff standoff assertions, if any. + */ case class ValueRdfData(subjectIri: IRI, valueObjectClass: SmartIri, nestedResource: Option[ResourceWithValueRdfData] = None, @@ -266,39 +266,52 @@ object ConstructResponseUtilV2 { standoff: FlatStatements) extends RdfData /** - * Represents a resource and its values. - * - * @param subjectIri the resource IRI. - * @param assertions assertions about the resource (direct statements). - * @param isMainResource indicates if this represents a top level resource or a referred resource (depending on the query). - * @param userPermission the permission that the requesting user has on the resource. - * @param valuePropertyAssertions assertions about value properties. - */ + * Represents a resource and its values. + * + * @param subjectIri the resource IRI. + * @param assertions assertions about the resource (direct statements). + * @param isMainResource indicates if this represents a top level resource or a referred resource (depending on the query). + * @param userPermission the permission that the requesting user has on the resource. + * @param valuePropertyAssertions assertions about value properties. + */ case class ResourceWithValueRdfData(subjectIri: IRI, assertions: FlatPredicateObjects, isMainResource: Boolean, - userPermission: EntityPermission, + userPermission: Option[EntityPermission], valuePropertyAssertions: RdfPropertyValues) extends RdfData /** - * Represents a mapping including information about the standoff entities. - * May include a default XSL transformation. - * - * @param mapping the mapping from XML to standoff and vice versa. - * @param standoffEntities information about the standoff entities referred to in the mapping. - * @param XSLTransformation the default XSL transformation to convert the resulting XML (e.g., to HTML), if any. - */ + * Represents a mapping including information about the standoff entities. + * May include a default XSL transformation. + * + * @param mapping the mapping from XML to standoff and vice versa. + * @param standoffEntities information about the standoff entities referred to in the mapping. + * @param XSLTransformation the default XSL transformation to convert the resulting XML (e.g., to HTML), if any. + */ case class MappingAndXSLTransformation(mapping: MappingXMLtoStandoff, standoffEntities: StandoffEntityInfoGetResponseV2, XSLTransformation: Option[String]) /** - * A [[SparqlConstructResponse]] may contain both resources and value RDF data objects as well as standoff. - * This method turns a graph (i.e. triples) into a structure organized by the principle of resources and their values, i.e. a map of resource Iris to [[ResourceWithValueRdfData]]. - * The resource Iris represent main resources, dependent resources are contained in the link values as nested structures. - * - * @param constructQueryResults the results of a SPARQL construct query representing resources and their values. - * @return a Map[resource IRI -> [[ResourceWithValueRdfData]]]. - */ - def splitMainResourcesAndValueRdfData(constructQueryResults: SparqlExtendedConstructResponse, requestingUser: UserADM)(implicit stringFormatter: StringFormatter): RdfResources = { + * Represents a tree structure of resources, values and dependent resources returned by a SPARQL CONSTRUCT query. + * + * @param resources a map of resource Iris to [[ResourceWithValueRdfData]]. The resource Iris represent main resources, dependent + * resources are contained in the link values as nested structures. + * @param hiddenResourceIris the IRIs of resources that were hidden because the user does not have permission + * to see them. + */ + case class MainResourcesAndValueRdfData(resources: RdfResources, + hiddenResourceIris: Set[IRI] = Set.empty) + + /** + * A [[SparqlConstructResponse]] may contain both resources and value RDF data objects as well as standoff. + * This method turns a graph (i.e. triples) into a structure organized by the principle of resources and their values, + * i.e. a map of resource Iris to [[ResourceWithValueRdfData]]. + * The resource Iris represent main resources, dependent resources are contained in the link values as nested structures. + * + * @param constructQueryResults the results of a SPARQL construct query representing resources and their values. + * @return an instance of [[MainResourcesAndValueRdfData]]. + */ + def splitMainResourcesAndValueRdfData(constructQueryResults: SparqlExtendedConstructResponse, + requestingUser: UserADM)(implicit stringFormatter: StringFormatter): MainResourcesAndValueRdfData = { // An intermediate data structure containing RDF assertions about an entity and the user's permission on the entity. case class RdfWithUserPermission(assertions: ConstructPredicateObjects, maybeUserPermission: Option[EntityPermission]) @@ -317,20 +330,8 @@ object ConstructResponseUtilV2 { assertions.getOrElse(OntologyConstants.Rdf.Type.toSmartIri, Seq.empty).contains(IriLiteralV2(OntologyConstants.KnoraBase.Resource)) } - // filter out the resources the user does not have permissions to see, including dependent resources. - - val resourceStatementsVisible: Map[IRI, RdfWithUserPermission] = resourceStatements.map { + val flatResourcesWithValues: RdfResources = resourceStatements.map { case (resourceIri: IRI, assertions: ConstructPredicateObjects) => - val maybeUserPermission: Option[EntityPermission] = PermissionUtilADM.getUserPermissionFromConstructAssertionsADM(resourceIri, assertions, requestingUser) - resourceIri -> RdfWithUserPermission(assertions, maybeUserPermission) - }.filter { - case (_: IRI, statements: RdfWithUserPermission) => statements.maybeUserPermission.nonEmpty - } - - val flatResourcesWithValues: RdfResources = resourceStatementsVisible.map { - case (resourceIri: IRI, resourceRdfWithUserPermission: RdfWithUserPermission) => - val assertions: ConstructPredicateObjects = resourceRdfWithUserPermission.assertions - // remove inferred statements (non explicit) returned in the query result // the query returns the following inferred information: // - every resource is a knora-base:Resource @@ -486,18 +487,42 @@ object ConstructResponseUtilV2 { case (pred: SmartIri, objs: Seq[LiteralV2]) => pred -> objs.head } + val userPermission: Option[EntityPermission] = PermissionUtilADM.getUserPermissionFromConstructAssertionsADM(resourceIri, assertions, requestingUser) + // create a map of resource Iris to a `ResourceWithValueRdfData` resourceIri -> ResourceWithValueRdfData( subjectIri = resourceIri, assertions = resourceAssertions, isMainResource = isMainResource, - userPermission = resourceRdfWithUserPermission.maybeUserPermission.get, + userPermission = userPermission, valuePropertyAssertions = valuePropertyToValueObject ) } + // Identify the resources that the user has permission to see. + + val (visibleResources: RdfResources, hiddenResources: RdfResources) = flatResourcesWithValues.partition { + case (_: IRI, resource: ResourceWithValueRdfData) => resource.userPermission.nonEmpty + } + + val mainResourceIrisVisible: Set[IRI] = visibleResources.collect { + case (resourceIri: IRI, resource: ResourceWithValueRdfData) if resource.isMainResource => resourceIri + }.toSet + + val mainResourceIrisNotVisible: Set[IRI] = hiddenResources.collect { + case (resourceIri: IRI, resource: ResourceWithValueRdfData) if resource.isMainResource => resourceIri + }.toSet + + val dependentResourceIrisVisible: Set[IRI] = visibleResources.collect { + case (resourceIri: IRI, resource: ResourceWithValueRdfData) if !resource.isMainResource => resourceIri + }.toSet + + val dependentResourceIrisNotVisible: Set[IRI] = hiddenResources.collect { + case (resourceIri: IRI, resource: ResourceWithValueRdfData) if !resource.isMainResource => resourceIri + }.toSet + // get incoming links for each resource: a map of resource IRIs to resources that link to it - val incomingLinksForResource: Map[IRI, RdfResources] = flatResourcesWithValues.map { + val incomingLinksForResource: Map[IRI, RdfResources] = visibleResources.map { case (resourceIri: IRI, values: ResourceWithValueRdfData) => // get all incoming links for resourceIri @@ -540,128 +565,205 @@ object ConstructResponseUtilV2 { resourceIri -> incomingLinksForRes } - /** - * Given a resource IRI, finds any link values in the resource, and recursively embeds the target resource in each link value. - * - * @param resourceIri the IRI of the resource to start with. - * @param alreadyTraversed a set (initially empty) of the IRIs of resources that this function has already - * traversed, to prevent an infinite loop if a cycle is encountered. - * @return the same resource, with any nested resources attached to it. - */ - def nestResources(resourceIri: IRI, alreadyTraversed: Set[IRI] = Set.empty[IRI]): ResourceWithValueRdfData = { - val resource = flatResourcesWithValues(resourceIri) - - val transformedValuePropertyAssertions: RdfPropertyValues = resource.valuePropertyAssertions.map { - case (propIri, values) => - val transformedValues = values.map { - value => - if (value.valueObjectClass.toString == OntologyConstants.KnoraBase.LinkValue) { - val dependentResourceIri: IRI = value.requireIriObject(OntologyConstants.Rdf.Object.toSmartIri) - - if (alreadyTraversed(dependentResourceIri)) { - value - } else { - // If we don't have the dependent resource, that means that the user doesn't have - // permission to see it, or it's been marked as deleted. Just return the link - // value without a nested resource. - if (flatResourcesWithValues.contains(dependentResourceIri)) { - val dependentResource: ResourceWithValueRdfData = nestResources(dependentResourceIri, alreadyTraversed + resourceIri) - - value.copy( - nestedResource = Some(dependentResource) - ) - } else { - value - } - } - } else { - value - } - } + val mainResourcesNested: Map[IRI, ResourceWithValueRdfData] = mainResourceIrisVisible.map { + resourceIri => + val transformedResource = nestResources( + resourceIri = resourceIri, + flatResourcesWithValues = flatResourcesWithValues, + visibleResources = visibleResources, + dependentResourceIrisVisible = dependentResourceIrisVisible, + dependentResourceIrisNotVisible = dependentResourceIrisNotVisible, + incomingLinksForResource = incomingLinksForResource + ) - propIri -> transformedValues - } + resourceIri -> transformedResource + }.toMap + + MainResourcesAndValueRdfData( + resources = mainResourcesNested, + hiddenResourceIris = mainResourceIrisNotVisible ++ dependentResourceIrisNotVisible + ) + } - // incomingLinksForResource contains incoming link values for each resource - // flatResourcesWithValues contains the complete information + /** + * Given a resource IRI, finds any link values in the resource, and recursively embeds the target resource in each link value. + * + * @param resourceIri the IRI of the resource to start with. + * @param flatResourcesWithValues the complete set of resources with their values, before permission filtering. + * @param visibleResources the resources that the user has permission to see. + * @param dependentResourceIrisVisible the IRIs of dependent resources that the user has permission to see. + * @param dependentResourceIrisNotVisible the IRIs of dependent resources that the user does not have permission to see. + * @param incomingLinksForResource a map of resource IRIs to resources that link to each resource. + * @param alreadyTraversed a set (initially empty) of the IRIs of resources that this function has already + * traversed, to prevent an infinite loop if a cycle is encountered. + * @return the same resource, with any nested resources attached to it. + */ + private def nestResources(resourceIri: IRI, + flatResourcesWithValues: RdfResources, + visibleResources: RdfResources, + dependentResourceIrisVisible: Set[IRI], + dependentResourceIrisNotVisible: Set[IRI], + incomingLinksForResource: Map[IRI, RdfResources], + alreadyTraversed: Set[IRI] = Set.empty[IRI])(implicit stringFormatter: StringFormatter): ResourceWithValueRdfData = { + val resource = visibleResources(resourceIri) + + val transformedValuePropertyAssertions: RdfPropertyValues = resource.valuePropertyAssertions.map { + case (propIri: SmartIri, values: Seq[ValueRdfData]) => + val transformedValues: Seq[ValueRdfData] = transformValuesByNestingResources( + resourceIri = resourceIri, + values = values, + flatResourcesWithValues = flatResourcesWithValues, + visibleResources = visibleResources, + dependentResourceIrisVisible = dependentResourceIrisVisible, + dependentResourceIrisNotVisible = dependentResourceIrisNotVisible, + incomingLinksForResource = incomingLinksForResource, + alreadyTraversed = alreadyTraversed + resourceIri + ) - // filter out those resources that already have been processed - // and the main resources (they are already present on the top level of the response) - // - // the main resources point to dependent resources and would be treated as incoming links of dependent resources - // this would create circular dependencies + propIri -> transformedValues + }.filter { + case (_: SmartIri, values: Seq[ValueRdfData]) => + // If we filtered out all the values for the property, filter out the property, too. + values.nonEmpty + } - // resources that point to this resource - val referringResources: RdfResources = incomingLinksForResource(resourceIri).filterNot { - case (incomingResIri: IRI, _: ResourceWithValueRdfData) => - alreadyTraversed(incomingResIri) || flatResourcesWithValues(incomingResIri).isMainResource - } + // incomingLinksForResource contains incoming link values for each resource + // flatResourcesWithValues contains the complete information - // link value assertions that point to this resource - val incomingLinkAssertions: RdfPropertyValues = referringResources.values.foldLeft(emptyRdfPropertyValues) { - case (acc: RdfPropertyValues, assertions: ResourceWithValueRdfData) => + // filter out those resources that already have been processed + // and the main resources (they are already present on the top level of the response) + // + // the main resources point to dependent resources and would be treated as incoming links of dependent resources + // this would create circular dependencies - val values: RdfPropertyValues = assertions.valuePropertyAssertions.flatMap { - case (propIri: SmartIri, values: Seq[ValueRdfData]) => + // resources that point to this resource + val referringResources: RdfResources = incomingLinksForResource(resourceIri).filterNot { + case (incomingResIri: IRI, _: ResourceWithValueRdfData) => + alreadyTraversed(incomingResIri) || flatResourcesWithValues(incomingResIri).isMainResource + } - // check if the property Iri already exists (there could be several instances of the same property) - if (acc.get(propIri).nonEmpty) { - // add values to property Iri (keeping the already existing values) - acc + (propIri -> (acc(propIri) ++ values).sortBy(_.subjectIri)) - } else { - // prop Iri does not exists yet, add it - acc + (propIri -> values.sortBy(_.subjectIri)) - } - } + // link value assertions that point to this resource + val incomingLinkAssertions: RdfPropertyValues = referringResources.values.foldLeft(emptyRdfPropertyValues) { + case (acc: RdfPropertyValues, assertions: ResourceWithValueRdfData) => - values - } + val values: RdfPropertyValues = assertions.valuePropertyAssertions.flatMap { + case (propIri: SmartIri, values: Seq[ValueRdfData]) => - if (incomingLinkAssertions.nonEmpty) { - // create a virtual property representing an incoming link - val incomingProps: (SmartIri, Seq[ValueRdfData]) = OntologyConstants.KnoraBase.HasIncomingLinkValue.toSmartIri -> incomingLinkAssertions.values.toSeq.flatten.map { - linkValue: ValueRdfData => + // check if the property Iri already exists (there could be several instances of the same property) + if (acc.get(propIri).nonEmpty) { + // add values to property Iri (keeping the already existing values) + acc + (propIri -> (acc(propIri) ++ values).sortBy(_.subjectIri)) + } else { + // prop Iri does not exists yet, add it + acc + (propIri -> values.sortBy(_.subjectIri)) + } + } - // get the source of the link value (it points to the resource that is currently processed) - val sourceIri: IRI = linkValue.requireIriObject(OntologyConstants.Rdf.Subject.toSmartIri) - val source = Some(nestResources(resourceIri = sourceIri, alreadyTraversed = alreadyTraversed + resourceIri)) + values + } - linkValue.copy( - nestedResource = source, - isIncomingLink = true + if (incomingLinkAssertions.nonEmpty) { + // create a virtual property representing an incoming link + val incomingProps: (SmartIri, Seq[ValueRdfData]) = OntologyConstants.KnoraBase.HasIncomingLinkValue.toSmartIri -> incomingLinkAssertions.values.toSeq.flatten.map { + linkValue: ValueRdfData => + + // get the source of the link value (it points to the resource that is currently processed) + val sourceIri: IRI = linkValue.requireIriObject(OntologyConstants.Rdf.Subject.toSmartIri) + val source = Some( + nestResources( + resourceIri = sourceIri, + flatResourcesWithValues = flatResourcesWithValues, + visibleResources = visibleResources, + dependentResourceIrisVisible = dependentResourceIrisVisible, + dependentResourceIrisNotVisible = dependentResourceIrisNotVisible, + incomingLinksForResource = incomingLinksForResource, + alreadyTraversed = alreadyTraversed + resourceIri ) - } + ) - resource.copy( - valuePropertyAssertions = transformedValuePropertyAssertions + incomingProps - ) - } else { - resource.copy( - valuePropertyAssertions = transformedValuePropertyAssertions - ) + linkValue.copy( + nestedResource = source, + isIncomingLink = true + ) } + resource.copy( + valuePropertyAssertions = transformedValuePropertyAssertions + incomingProps + ) + } else { + resource.copy( + valuePropertyAssertions = transformedValuePropertyAssertions + ) } + } - val mainResourceIris: Set[IRI] = flatResourcesWithValues.filter { - case (_, resource) => resource.isMainResource // only main resources are present on the top level, dependent resources are nested in the link values - }.map { - case (resourceIri, _) => resourceIri - }.toSet + /** + * Transforms a resource's values by nesting dependent resources in link values. + * + * @param resourceIri the IRI of the resource. + * @param values the values of the resource. + * @param flatResourcesWithValues the complete set of resources with their values, before permission filtering. + * @param visibleResources the resources that the user has permission to see. + * @param dependentResourceIrisVisible the IRIs of dependent resources that the user has permission to see. + * @param dependentResourceIrisNotVisible the IRIs of dependent resources that the user does not have permission to see. + * @param incomingLinksForResource a map of resource IRIs to resources that link to each resource. + * @param alreadyTraversed a set (initially empty) of the IRIs of resources that this function has already + * traversed, to prevent an infinite loop if a cycle is encountered. + * @return the transformed values. + */ + private def transformValuesByNestingResources(resourceIri: IRI, + values: Seq[ValueRdfData], + flatResourcesWithValues: RdfResources, + visibleResources: RdfResources, + dependentResourceIrisVisible: Set[IRI], + dependentResourceIrisNotVisible: Set[IRI], + incomingLinksForResource: Map[IRI, RdfResources], + alreadyTraversed: Set[IRI])(implicit stringFormatter: StringFormatter): Seq[ValueRdfData] = { + values.foldLeft(Vector.empty[ValueRdfData]) { + case (acc: Vector[ValueRdfData], value: ValueRdfData) => + if (value.valueObjectClass.toString == OntologyConstants.KnoraBase.LinkValue) { + val dependentResourceIri: IRI = value.requireIriObject(OntologyConstants.Rdf.Object.toSmartIri) + + if (alreadyTraversed(dependentResourceIri)) { + acc :+ value + } else { + // Do we have the dependent resource? + if (dependentResourceIrisVisible.contains(dependentResourceIri)) { + // Yes. Nest it in the link value. + val dependentResource: ResourceWithValueRdfData = nestResources( + resourceIri = dependentResourceIri, + flatResourcesWithValues = flatResourcesWithValues, + visibleResources = visibleResources, + dependentResourceIrisVisible = dependentResourceIrisVisible, + dependentResourceIrisNotVisible = dependentResourceIrisNotVisible, + incomingLinksForResource = incomingLinksForResource, + alreadyTraversed = alreadyTraversed + ) - mainResourceIris.map { - resourceIri => - val transformedResource = nestResources(resourceIri) - resourceIri -> transformedResource - }.toMap + acc :+ value.copy( + nestedResource = Some(dependentResource) + ) + } else if (dependentResourceIrisNotVisible.contains(dependentResourceIri)) { + // No, because the user doesn't have permission to see it. Skip the link value. + acc + } else { + // We don't have the dependent resource because it is marked as deleted. Just + // return the link value without a nested resource. + acc :+ value + } + } + } else { + acc :+ value + } + } } /** - * Collect all mapping Iris referred to in the given value assertions. - * - * @param valuePropertyAssertions the given assertions (property -> value object). - * @return a set of mapping Iris. - */ + * Collect all mapping Iris referred to in the given value assertions. + * + * @param valuePropertyAssertions the given assertions (property -> value object). + * @return a set of mapping Iris. + */ def getMappingIrisFromValuePropertyAssertions(valuePropertyAssertions: RdfPropertyValues)(implicit stringFormatter: StringFormatter): Set[IRI] = { valuePropertyAssertions.foldLeft(Set.empty[IRI]) { case (acc: Set[IRI], (_: SmartIri, valObjs: Seq[ValueRdfData])) => @@ -689,19 +791,19 @@ object ConstructResponseUtilV2 { } /** - * Given a [[ValueRdfData]], constructs a [[TextValueContentV2]]. This method is used to process a text value - * as returned in an API response, as well as to process a page of standoff markup that is being queried - * separately from its text value. - * - * @param valueObject the given [[ValueRdfData]]. - * @param valueObjectValueHasString the value's `knora-base:valueHasString`. - * @param valueCommentOption the value's comment, if any. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param queryStandoff if `true`, make separate queries to get the standoff for the text value. - * @param responderManager the Knora responder manager. - * @param requestingUser the user making the request. - * @return a [[TextValueContentV2]]. - */ + * Given a [[ValueRdfData]], constructs a [[TextValueContentV2]]. This method is used to process a text value + * as returned in an API response, as well as to process a page of standoff markup that is being queried + * separately from its text value. + * + * @param valueObject the given [[ValueRdfData]]. + * @param valueObjectValueHasString the value's `knora-base:valueHasString`. + * @param valueCommentOption the value's comment, if any. + * @param mappings the mappings needed for standoff conversions and XSL transformations. + * @param queryStandoff if `true`, make separate queries to get the standoff for the text value. + * @param responderManager the Knora responder manager. + * @param requestingUser the user making the request. + * @return a [[TextValueContentV2]]. + */ private def makeTextValueContentV2(resourceIri: IRI, valueObject: ValueRdfData, valueObjectValueHasString: Option[String], @@ -768,17 +870,17 @@ object ConstructResponseUtilV2 { } /** - * Given a [[ValueRdfData]], constructs a [[FileValueContentV2]]. - * - * @param valueType the IRI of the file value type - * @param valueObject the given [[ValueRdfData]]. - * @param valueObjectValueHasString the value's `knora-base:valueHasString`. - * @param valueCommentOption the value's comment, if any. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param responderManager the Knora responder manager. - * @param requestingUser the user making the request. - * @return a [[FileValueContentV2]]. - */ + * Given a [[ValueRdfData]], constructs a [[FileValueContentV2]]. + * + * @param valueType the IRI of the file value type + * @param valueObject the given [[ValueRdfData]]. + * @param valueObjectValueHasString the value's `knora-base:valueHasString`. + * @param valueCommentOption the value's comment, if any. + * @param mappings the mappings needed for standoff conversions and XSL transformations. + * @param responderManager the Knora responder manager. + * @param requestingUser the user making the request. + * @return a [[FileValueContentV2]]. + */ private def makeFileValueContentV2(valueType: IRI, valueObject: ValueRdfData, valueObjectValueHasString: String, @@ -823,20 +925,20 @@ object ConstructResponseUtilV2 { } /** - * Given a [[ValueRdfData]], constructs a [[LinkValueContentV2]]. - * - * @param valueObject the given [[ValueRdfData]]. - * @param valueObjectValueHasString the value's `knora-base:valueHasString`. - * @param valueCommentOption the value's comment, if any. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param queryStandoff if `true`, make separate queries to get the standoff for text values. - * @param versionDate if defined, represents the requested time in the the resources' version history. - * @param responderManager the Knora responder manager. - * @param targetSchema the schema of the response. - * @param settings the application's settings. - * @param requestingUser the user making the request. - * @return a [[LinkValueContentV2]]. - */ + * Given a [[ValueRdfData]], constructs a [[LinkValueContentV2]]. + * + * @param valueObject the given [[ValueRdfData]]. + * @param valueObjectValueHasString the value's `knora-base:valueHasString`. + * @param valueCommentOption the value's comment, if any. + * @param mappings the mappings needed for standoff conversions and XSL transformations. + * @param queryStandoff if `true`, make separate queries to get the standoff for text values. + * @param versionDate if defined, represents the requested time in the the resources' version history. + * @param responderManager the Knora responder manager. + * @param targetSchema the schema of the response. + * @param settings the application's settings. + * @param requestingUser the user making the request. + * @return a [[LinkValueContentV2]]. + */ private def makeLinkValueContentV2(valueObject: ValueRdfData, valueObjectValueHasString: String, valueCommentOption: Option[String], @@ -861,12 +963,10 @@ object ConstructResponseUtilV2 { comment = valueCommentOption ) + // Is there a nested resource in the link value? valueObject.nestedResource match { - case Some(nestedResourceAssertions: ResourceWithValueRdfData) => - - // add information about the referred resource - + // Yes. Construct a ReadResourceV2 representing the nested resource. for { nestedResource <- constructReadResourceV2( resourceIri = referredResourceIri, @@ -880,27 +980,28 @@ object ConstructResponseUtilV2 { settings = settings ) } yield linkValue.copy( - nestedResource = Some(nestedResource) // construct a `ReadResourceV2` + nestedResource = Some(nestedResource) ) - - case None => FastFuture.successful(linkValue) // do not include information about the referred resource + case None => + // There is no nested resource. + FastFuture.successful(linkValue) } } /** - * Given a [[ValueRdfData]], constructs a [[ValueContentV2]], considering the specific type of the given [[ValueRdfData]]. - * - * @param valueObject the given [[ValueRdfData]]. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param queryStandoff if `true`, make separate queries to get the standoff for text values. - * @param versionDate if defined, represents the requested time in the the resources' version history. - * @param responderManager the Knora responder manager. - * @param targetSchema the schema of the response. - * @param settings the application's settings. - * @param requestingUser the user making the request. - * @return a [[ValueContentV2]] representing a value. - */ + * Given a [[ValueRdfData]], constructs a [[ValueContentV2]], considering the specific type of the given [[ValueRdfData]]. + * + * @param valueObject the given [[ValueRdfData]]. + * @param mappings the mappings needed for standoff conversions and XSL transformations. + * @param queryStandoff if `true`, make separate queries to get the standoff for text values. + * @param versionDate if defined, represents the requested time in the the resources' version history. + * @param responderManager the Knora responder manager. + * @param targetSchema the schema of the response. + * @param settings the application's settings. + * @param requestingUser the user making the request. + * @return a [[ValueContentV2]] representing a value. + */ private def createValueContentV2FromValueRdfData(resourceIri: IRI, valueObject: ValueRdfData, mappings: Map[IRI, MappingAndXSLTransformation], @@ -1065,20 +1166,20 @@ object ConstructResponseUtilV2 { } /** - * - * Creates a [[ReadResourceV2]] from a [[ResourceWithValueRdfData]]. - * - * @param resourceIri the IRI of the resource. - * @param resourceWithValueRdfData the Rdf data belonging to the resource. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param queryStandoff if `true`, make separate queries to get the standoff for text values. - * @param versionDate if defined, represents the requested time in the the resources' version history. - * @param responderManager the Knora responder manager. - * @param targetSchema the schema of the response. - * @param settings the application's settings. - * @param requestingUser the user making the request. - * @return a [[ReadResourceV2]]. - */ + * + * Creates a [[ReadResourceV2]] from a [[ResourceWithValueRdfData]]. + * + * @param resourceIri the IRI of the resource. + * @param resourceWithValueRdfData the Rdf data belonging to the resource. + * @param mappings the mappings needed for standoff conversions and XSL transformations. + * @param queryStandoff if `true`, make separate queries to get the standoff for text values. + * @param versionDate if defined, represents the requested time in the the resources' version history. + * @param responderManager the Knora responder manager. + * @param targetSchema the schema of the response. + * @param settings the application's settings. + * @param requestingUser the user making the request. + * @return a [[ReadResourceV2]]. + */ private def constructReadResourceV2(resourceIri: IRI, resourceWithValueRdfData: ResourceWithValueRdfData, mappings: Map[IRI, MappingAndXSLTransformation], @@ -1205,7 +1306,7 @@ object ConstructResponseUtilV2 { attachedToUser = resourceAttachedToUser, projectADM = projectResponse.project, permissions = resourcePermissions, - userPermission = resourceWithValueRdfData.userPermission, + userPermission = resourceWithValueRdfData.userPermission.get, values = valueObjects, creationDate = resourceCreationDate, lastModificationDate = resourceLastModificationDate, @@ -1215,97 +1316,61 @@ object ConstructResponseUtilV2 { } /** - * Creates a response to a full resource request. - * - * @param resourceIri the IRI of the requested resource. - * @param resourceRdfData the results returned by the triplestore. - * @param mappings the mappings needed for standoff conversions and XSL transformations. - * @param queryStandoff if `true`, make separate queries to get the standoff for text values. - * @param versionDate if defined, represents the requested time in the the resources' version history. - * @param responderManager the Knora responder manager. - * @param targetSchema the schema of response. - * @param settings the application's settings. - * @param requestingUser the user making the request. - * @return a [[ReadResourceV2]]. - */ - def createFullResourceResponse(resourceIri: IRI, - resourceRdfData: ResourceWithValueRdfData, - mappings: Map[IRI, MappingAndXSLTransformation], - queryStandoff: Boolean, - versionDate: Option[Instant], - responderManager: ActorRef, - targetSchema: ApiV2Schema, - settings: SettingsImpl, - requestingUser: UserADM)(implicit stringFormatter: StringFormatter, timeout: Timeout, executionContext: ExecutionContext): Future[ReadResourceV2] = { - - constructReadResourceV2( - resourceIri = resourceIri, - resourceWithValueRdfData = resourceRdfData, - mappings = mappings, - queryStandoff = queryStandoff, - versionDate = versionDate, - responderManager = responderManager, - requestingUser = requestingUser, - targetSchema = targetSchema, - settings = settings - ) - } - - /** - * Creates a response to a fulltext or extended search. - * - * @param searchResults the resources that matched the query and the client has permissions to see. - * @param orderByResourceIri the order in which the resources should be returned. - * @param mappings the mappings to convert standoff to XML, if any. - * @param queryStandoff if `true`, make separate queries to get the standoff for text values. - * @param forbiddenResource the ForbiddenResource, if any. - * @param responderManager the Knora responder manager. - * @param targetSchema the schema of response. - * @param settings the application's settings. - * @param requestingUser the user making the request. - * @return a collection of [[ReadResourceV2]] representing the search results. - */ - def createSearchResponse(searchResults: RdfResources, - orderByResourceIri: Seq[IRI], - mappings: Map[IRI, MappingAndXSLTransformation] = Map.empty[IRI, MappingAndXSLTransformation], - queryStandoff: Boolean, - forbiddenResource: Option[ReadResourceV2], - responderManager: ActorRef, - targetSchema: ApiV2Schema, - settings: SettingsImpl, - requestingUser: UserADM)(implicit stringFormatter: StringFormatter, timeout: Timeout, executionContext: ExecutionContext): Future[Vector[ReadResourceV2]] = { - - if (orderByResourceIri.toSet != searchResults.keySet && forbiddenResource.isEmpty) throw AssertionException(s"Not all resources are visible, but forbiddenResource is None") - - // iterate over orderByResourceIris and construct the response in the correct order - val readResourceFutures: Vector[Future[ReadResourceV2]] = orderByResourceIri.map { + * Creates an API response. + * + * @param mainResourcesAndValueRdfData the query results. + * @param orderByResourceIri the order in which the resources should be returned. This sequence + * contains the resource IRIs received from the triplestore before filtering + * for permissions. + * @param mappings the mappings to convert standoff to XML, if any. + * @param queryStandoff if `true`, make separate queries to get the standoff for text values. + * @param versionDate if defined, represents the requested time in the the resources' version history. + * @param responderManager the Knora responder manager. + * @param targetSchema the schema of response. + * @param settings the application's settings. + * @param requestingUser the user making the request. + * @return a collection of [[ReadResourceV2]] representing the search results. + */ + def createApiResponse(mainResourcesAndValueRdfData: MainResourcesAndValueRdfData, + orderByResourceIri: Seq[IRI], + mappings: Map[IRI, MappingAndXSLTransformation] = Map.empty[IRI, MappingAndXSLTransformation], + queryStandoff: Boolean, + calculateMayHaveMoreResults: Boolean, + versionDate: Option[Instant], + responderManager: ActorRef, + targetSchema: ApiV2Schema, + settings: SettingsImpl, + requestingUser: UserADM)(implicit stringFormatter: StringFormatter, timeout: Timeout, executionContext: ExecutionContext): Future[ReadResourcesSequenceV2] = { + + val visibleResourceIris: Seq[IRI] = orderByResourceIri.filter(resourceIri => mainResourcesAndValueRdfData.resources.keySet.contains(resourceIri)) + + // iterate over visibleResourceIris and construct the response in the correct order + val readResourceFutures: Vector[Future[ReadResourceV2]] = visibleResourceIris.map { resourceIri: IRI => + constructReadResourceV2( + resourceIri = resourceIri, + resourceWithValueRdfData = mainResourcesAndValueRdfData.resources(resourceIri), + mappings = mappings, + queryStandoff = queryStandoff, + versionDate = versionDate, + responderManager = responderManager, + targetSchema = targetSchema, + settings = settings, + requestingUser = requestingUser + ) - // the user may not have the permissions to see the resource - // i.e. it may not be contained in searchResults - searchResults.get(resourceIri) match { - case Some(assertions: ResourceWithValueRdfData) => - // sufficient permissions - // add the resource to the list of results - constructReadResourceV2( - resourceIri = resourceIri, - resourceWithValueRdfData = assertions, - mappings = mappings, - queryStandoff = queryStandoff, - versionDate = None, - responderManager = responderManager, - targetSchema = targetSchema, - settings = settings, - requestingUser = requestingUser - ) - - case None => - // include the forbidden resource instead of skipping (the amount of results should be constant -> limit) - Future(forbiddenResource.getOrElse(throw AssertionException(s"Not all resources are visible, but forbiddenResource is None"))) - - } }.toVector - Future.sequence(readResourceFutures) + for { + resources <- Future.sequence(readResourceFutures) + + // If we got a full page of results from the triplestore (before filtering for permissions), there + // might be at least one more page of results that the user could request. + mayHaveMoreResults = calculateMayHaveMoreResults && orderByResourceIri.size == settings.v2ResultsPerPage + } yield ReadResourcesSequenceV2( + resources = resources, + hiddenResourceIris = mainResourcesAndValueRdfData.hiddenResourceIris, + mayHaveMoreResults = mayHaveMoreResults + ) } } \ No newline at end of file diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.jsonld index de89833e3a..15a9ead6d1 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.jsonld @@ -210,50 +210,6 @@ "owl:onDatatype" : { "@id" : "xsd:anyURI" } - }, { - "@id" : "knora-api:ForbiddenResource", - "@type" : "owl:Class", - "rdfs:comment" : "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see.", - "rdfs:label" : "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see.", - "rdfs:subClassOf" : [ { - "@id" : "knora-api:Resource" - }, { - "@type" : "owl:Restriction", - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:arkUrl" - } - }, { - "@type" : "owl:Restriction", - "owl:minCardinality" : 0, - "owl:onProperty" : { - "@id" : "knora-api:hasComment" - } - }, { - "@type" : "owl:Restriction", - "owl:minCardinality" : 0, - "owl:onProperty" : { - "@id" : "knora-api:hasIncomingLink" - } - }, { - "@type" : "owl:Restriction", - "owl:minCardinality" : 0, - "owl:onProperty" : { - "@id" : "knora-api:hasStandoffLinkTo" - } - }, { - "@type" : "owl:Restriction", - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:versionArkUrl" - } - }, { - "@type" : "owl:Restriction", - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "rdfs:label" - } - } ] }, { "@id" : "knora-api:Geom", "@type" : "rdfs:Datatype", @@ -936,6 +892,14 @@ "rdfs:subPropertyOf" : { "@id" : "knora-api:hasLinkTo" } + }, { + "@id" : "knora-api:mayHaveMoreResults", + "@type" : "owl:DatatypeProperty", + "knora-api:objectType" : { + "@id" : "xsd:boolean" + }, + "rdfs:comment" : "Indicates whether more results may be available for a search query", + "rdfs:label" : "May have more results" }, { "@id" : "knora-api:objectType", "@type" : "rdf:Property", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.rdf index 96bb0a57f7..7608d87604 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,7 +51,7 @@ - + 0 @@ -63,7 +63,7 @@ - + 0 @@ -75,7 +75,7 @@ - + 1 @@ -86,7 +86,7 @@ - + 1 @@ -96,7 +96,7 @@ - + 1 @@ -106,29 +106,29 @@ Represents a file containing audio data Representation (Audio) - - - - - - + + + + + + A resource that can store a file Representation - - - - - - + + + + + + - + 1 - + 1 @@ -140,19 +140,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -161,7 +161,7 @@ Color literal - + #([0-9a-fA-F]{3}){1,2} @@ -170,18 +170,18 @@ Represents a file containg 3D data Representation (3D) - - - - - - + + + + + + - + 1 - + 1 @@ -193,19 +193,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -214,7 +214,7 @@ 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)?)? @@ -222,18 +222,18 @@ Representation (Document) - - - - - - + + + + + + - + 1 - + 1 @@ -245,19 +245,19 @@ - + 0 - + 0 - + 1 - + 1 @@ -266,41 +266,6 @@ File URI - - 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 - - Represents a geometry specification in JSON. Geometry specification @@ -311,7 +276,7 @@ Geoname code - + \d{1,8} @@ -321,7 +286,7 @@ Interval literal - + \d+(\.\d+)?,\d+(\.\d+)? @@ -331,27 +296,27 @@ Represents a generic link object Link Object - - - - - - - + + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -363,15 +328,15 @@ - + 0 - + 1 - + 1 @@ -384,22 +349,22 @@ A resource containing moving image data Representation (Movie) - - - - - - + + + + + + - + 1 - + 0 - + 1 @@ -411,15 +376,15 @@ - + 0 - + 1 - + 1 @@ -428,21 +393,21 @@ Represents a geometric region of a resource. The geometry is represented currently as JSON string. Region - - - - - - - - - + + + + + + + + + - + 1 - + 1 @@ -454,11 +419,11 @@ - + 1 - + 1 @@ -470,15 +435,15 @@ - + 0 - + 0 - + 1 @@ -490,19 +455,19 @@ - + 1 - + 1 - + 1 - + 1 @@ -514,39 +479,39 @@ - + 0 - + 0 - + 1 - + 1 - + 1 - + 0 - + 0 - + 1 - + 1 @@ -554,26 +519,26 @@ A resource that can contain a two-dimensional still image file Representation (Image) - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -585,11 +550,11 @@ - + 1 - + 1 @@ -597,26 +562,26 @@ A resource containing a text file Representation (Text) - - - - - - + + + + + + - + 1 - + 0 - + 0 - + 1 @@ -628,11 +593,11 @@ - + 1 - + 1 @@ -640,34 +605,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 @@ -708,6 +673,11 @@ is part of + + + Indicates whether more results may be available for a search query + May have more results + Specifies the required type of the objects of a property Object type diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.ttl index 4b95503a3b..11c7cf5743 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologySimple.ttl @@ -225,29 +225,6 @@ knora-api:File a rdfs:Datatype; rdfs:label "File URI"; owl:onDatatype xsd:anyURI . -knora-api:ForbiddenResource a owl:Class; - rdfs:comment "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."; - rdfs:label "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."; - rdfs:subClassOf knora-api:Resource, [ a owl:Restriction; - owl:cardinality 1; - owl:onProperty knora-api:arkUrl - ], [ a owl:Restriction; - owl:minCardinality 0; - owl:onProperty knora-api:hasComment - ], [ a owl:Restriction; - owl:minCardinality 0; - owl:onProperty knora-api:hasIncomingLink - ], [ a owl:Restriction; - owl:minCardinality 0; - owl:onProperty knora-api:hasStandoffLinkTo - ], [ a owl:Restriction; - owl:cardinality 1; - owl:onProperty knora-api:versionArkUrl - ], [ a owl:Restriction; - owl:cardinality 1; - owl:onProperty rdfs:label - ] . - knora-api:Geom a rdfs:Datatype; rdfs:comment "Represents a geometry specification in JSON."; rdfs:label "Geometry specification"; @@ -518,6 +495,11 @@ knora-api:isPartOf a owl:ObjectProperty; rdfs:label "is part of"; rdfs:subPropertyOf knora-api:hasLinkTo . +knora-api:mayHaveMoreResults a owl:DatatypeProperty; + knora-api:objectType xsd:boolean; + rdfs:comment "Indicates whether more results may be available for a search query"; + rdfs:label "May have more results" . + knora-api:objectType a rdf:Property; rdfs:comment "Specifies the required type of the objects of a property"; rdfs:label "Object type" . diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld index 85200870ea..e80945ac3b 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.jsonld @@ -1603,140 +1603,6 @@ "@id" : "knora-api:versionArkUrl" } } ] - }, { - "@id" : "knora-api:ForbiddenResource", - "@type" : "owl:Class", - "knora-api:isResourceClass" : true, - "rdfs:comment" : "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see.", - "rdfs:label" : "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see.", - "rdfs:subClassOf" : [ { - "@id" : "knora-api:Resource" - }, { - "@type" : "owl:Restriction", - "knora-api:isInherited" : true, - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:arkUrl" - } - }, { - "@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:maxCardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:deletedBy" - } - }, { - "@type" : "owl:Restriction", - "owl:minCardinality" : 0, - "owl:onProperty" : { - "@id" : "knora-api:hasComment" - } - }, { - "@type" : "owl:Restriction", - "knora-api:isInherited" : true, - "owl:minCardinality" : 0, - "owl:onProperty" : { - "@id" : "knora-api:hasIncomingLinkValue" - } - }, { - "@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:maxCardinality" : 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" : "knora-api:userHasPermission" - } - }, { - "@type" : "owl:Restriction", - "knora-api:isInherited" : true, - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:versionArkUrl" - } - }, { - "@type" : "owl:Restriction", - "knora-api:isInherited" : true, - "owl:maxCardinality" : 1, - "owl:onProperty" : { - "@id" : "knora-api:versionDate" - } - }, { - "@type" : "owl:Restriction", - "knora-api:isInherited" : true, - "owl:cardinality" : 1, - "owl:onProperty" : { - "@id" : "rdfs:label" - } - } ] }, { "@id" : "knora-api:GeomValue", "@type" : "owl:Class", @@ -6471,6 +6337,14 @@ }, "rdfs:comment" : "Represents the name of a mapping", "rdfs:label" : "Name of a mapping (will be part of the mapping's Iri)" + }, { + "@id" : "knora-api:mayHaveMoreResults", + "@type" : "owl:DatatypeProperty", + "knora-api:objectType" : { + "@id" : "xsd:boolean" + }, + "rdfs:comment" : "Indicates whether more results may be available for a search query", + "rdfs:label" : "May have more results" }, { "@id" : "knora-api:movingImageFileValueHasDimX", "@type" : "owl:DatatypeProperty", diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf index 7da0eecde7..d13c30275c 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.rdf @@ -17,50 +17,50 @@ A generic class for representing annotations Annotation - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + true Represents something in the world, or an abstract thing Resource - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 @@ -71,7 +71,7 @@ - + true 1 @@ -82,7 +82,7 @@ - + true 1 @@ -93,7 +93,7 @@ - + true 1 @@ -104,7 +104,7 @@ - + true 1 @@ -114,7 +114,7 @@ - + true 1 @@ -124,7 +124,7 @@ - + true 1 @@ -134,7 +134,7 @@ - + 1 @@ -149,7 +149,7 @@ - + true 0 @@ -164,7 +164,7 @@ - + true 1 @@ -173,7 +173,7 @@ - + true 0 @@ -188,7 +188,7 @@ - + true 0 @@ -203,7 +203,7 @@ - + 1 @@ -217,7 +217,7 @@ - + 1 @@ -230,7 +230,7 @@ - + true 1 @@ -240,7 +240,7 @@ - + true 1 @@ -249,7 +249,7 @@ - + true 1 @@ -260,7 +260,7 @@ - + true 1 @@ -271,7 +271,7 @@ - + true 1 @@ -282,7 +282,7 @@ - + true 1 @@ -293,53 +293,53 @@ true Represents an audio file - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + true - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + true 1 - + true 1 - + 1 @@ -351,22 +351,22 @@ - + true 1 - + true 1 - + true 1 - + true 1 @@ -379,7 +379,7 @@ - + true 1 @@ -392,22 +392,22 @@ - + true 1 - + true 1 - + true 1 - + true 1 @@ -418,7 +418,7 @@ - + true 1 @@ -429,7 +429,7 @@ - + true 1 @@ -441,7 +441,7 @@ - + true 1 @@ -453,7 +453,7 @@ - + true 1 @@ -463,85 +463,85 @@ Represents a file containing audio data Representation (Audio) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + true A resource that can store a file Representation - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -556,62 +556,62 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + - + 1 @@ -628,105 +628,105 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -734,7 +734,7 @@ - + 1 @@ -753,87 +753,87 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -842,93 +842,93 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 @@ -938,61 +938,61 @@ Represents a file containg 3D data Representation (3D) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1007,69 +1007,69 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - - - - - - - - - + + + + + + + + + - + 1 @@ -1081,7 +1081,7 @@ - + 1 @@ -1093,7 +1093,7 @@ - + 1 @@ -1105,7 +1105,7 @@ - + 1 @@ -1117,7 +1117,7 @@ - + 1 @@ -1129,7 +1129,7 @@ - + 1 @@ -1141,7 +1141,7 @@ - + 1 @@ -1153,7 +1153,7 @@ - + 1 @@ -1165,7 +1165,7 @@ - + 1 @@ -1182,135 +1182,135 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1318,7 +1318,7 @@ - + 1 @@ -1337,87 +1337,87 @@ Represents an arbitrary-precision decimal 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 @@ -1425,51 +1425,51 @@ true - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1481,7 +1481,7 @@ - + 1 @@ -1493,7 +1493,7 @@ - + 1 @@ -1505,52 +1505,52 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1559,61 +1559,61 @@ true Representation (Document) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1628,287 +1628,174 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - - true - 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 - - - - true - 1 - - - - true - 1 - - - - 0 - - - - true - 0 - - - - true - 1 - - - - true - 0 - - - - true - 0 - - - - true - 1 - - - - true - 1 - - - - true - 1 - - - - true - 1 - - - - true - 1 - - - - true - 1 - - true Represents a geometrical objects as JSON string - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -1920,42 +1807,42 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -1963,47 +1850,47 @@ true - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2015,42 +1902,42 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2058,7 +1945,7 @@ - + 1 @@ -2077,97 +1964,97 @@ Represents an integer 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 - - + + - + 1 @@ -2179,7 +2066,7 @@ - + 1 @@ -2196,93 +2083,93 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2294,72 +2181,72 @@ Represents a generic link object Link Object - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 0 - + true 0 - + 1 @@ -2374,7 +2261,7 @@ - + 1 @@ -2389,47 +2276,47 @@ - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2439,60 +2326,60 @@ A reification node that describes direct links between resources - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2504,7 +2391,7 @@ - + 1 @@ -2516,7 +2403,7 @@ - + 1 @@ -2528,7 +2415,7 @@ - + 1 @@ -2540,103 +2427,103 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 Represents a flat or hierarchical list - - + + - + 1 - + 1 true - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2648,32 +2535,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2682,72 +2569,72 @@ true Represents a moving image file - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -2759,7 +2646,7 @@ - + 1 @@ -2771,7 +2658,7 @@ - + 1 @@ -2783,7 +2670,7 @@ - + 1 @@ -2795,32 +2682,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2830,66 +2717,66 @@ A resource containing moving image data Representation (Movie) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + 1 @@ -2904,47 +2791,47 @@ - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -2956,65 +2843,65 @@ 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 - + true 1 - + true 1 - + 1 @@ -3030,11 +2917,11 @@ - + 1 - + 1 @@ -3049,32 +2936,32 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + 1 @@ -3089,7 +2976,7 @@ - + 1 @@ -3104,67 +2991,67 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -3178,121 +3065,121 @@ - + true 0 - + true 1 - + true 0 - + true 0 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 0 - + 1 - + 0 - + 0 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 @@ -3301,39 +3188,39 @@ Represents a boolean in a TextValue - - - - - - - - - - - + + + + + + + + + + + true Represents a knora-base value type in a TextValue - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 @@ -3343,7 +3230,7 @@ - + true 1 @@ -3353,7 +3240,7 @@ - + true 1 @@ -3363,7 +3250,7 @@ - + true 1 @@ -3375,7 +3262,7 @@ - + true 1 @@ -3385,7 +3272,7 @@ - + true 1 @@ -3395,7 +3282,7 @@ - + true 1 @@ -3405,7 +3292,7 @@ - + true 1 @@ -3416,7 +3303,7 @@ - + true 1 @@ -3428,7 +3315,7 @@ - + true 1 @@ -3443,69 +3330,69 @@ Represents a color 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 @@ -3513,63 +3400,63 @@ true Represents a standoff markup tag - - - - - - - - - - + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3579,117 +3466,117 @@ 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 - + true 1 - + true 1 @@ -3699,69 +3586,69 @@ 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 - + true 1 - + true 1 @@ -3771,69 +3658,69 @@ 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 - + true 1 - + true 1 @@ -3843,39 +3730,39 @@ Represents an internal reference in a TextValue - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -3884,32 +3771,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -3919,75 +3806,75 @@ 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 - + true 1 - + true 1 @@ -3996,39 +3883,39 @@ true Represents a reference to a Knora resource in a TextValue - - - - - - - - - - - + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -4037,73 +3924,73 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 @@ -4112,73 +3999,73 @@ Represents a timestamp 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 @@ -4194,73 +4081,73 @@ 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 - + true 1 - + true 1 @@ -4277,71 +4164,71 @@ 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 - + true 1 - + 1 @@ -4353,7 +4240,7 @@ - + 1 @@ -4365,7 +4252,7 @@ - + 1 @@ -4377,32 +4264,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -4412,81 +4299,81 @@ A resource that can contain a two-dimensional still image file Representation (Image) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 @@ -4501,32 +4388,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -4535,93 +4422,93 @@ 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 - + true 1 - + true 1 - + true 1 - + true 1 @@ -4631,81 +4518,81 @@ A resource containing a text file Representation (Text) - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 @@ -4720,32 +4607,32 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -4753,63 +4640,63 @@ true - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -4821,7 +4708,7 @@ - + 1 @@ -4833,7 +4720,7 @@ - + 1 @@ -4845,7 +4732,7 @@ - + 1 @@ -4857,7 +4744,7 @@ - + 1 @@ -4869,7 +4756,7 @@ - + 1 @@ -4881,7 +4768,7 @@ - + 0 @@ -4893,37 +4780,37 @@ - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + 1 @@ -4932,92 +4819,92 @@ Represents a timestamp - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + 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 - + 1 @@ -5026,140 +4913,140 @@ Represents a URI - - - - - - - - - - - - - - + + + + + + + + + + + + + + - + 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 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 - + 1 @@ -5168,110 +5055,110 @@ 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 1 - + true 1 - + true 0 - + true 1 - + true 0 - + true 0 - + 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 - + true 1 @@ -5403,6 +5290,11 @@ Represents the name of a mapping Name of a mapping (will be part of the mapping's Iri) + + + Indicates whether more results may be available for a search query + May have more results + Specifies the new modification date of a resource diff --git a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl index c912e30620..a64ed08015 100644 --- a/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl +++ b/webapi/src/test/resources/test-data/ontologyR2RV2/knoraApiOntologyWithValueObjects.ttl @@ -1387,83 +1387,6 @@ knora-api:hasDocumentFileValue a owl:ObjectProperty; rdfs:label "has document"; rdfs:subPropertyOf knora-api:hasFileValue . -knora-api:ForbiddenResource a owl:Class; - knora-api:isResourceClass true; - rdfs:comment "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."; - rdfs:label "A ForbiddenResource is a proxy for a resource that the client has insufficient permissions to see."; - rdfs:subClassOf knora-api:Resource, [ a owl:Restriction; - knora-api:isInherited true; - owl:cardinality 1; - owl:onProperty knora-api:arkUrl - ], [ 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: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:maxCardinality 1; - owl:onProperty knora-api:deletedBy - ], [ a owl:Restriction; - owl:minCardinality 0; - owl:onProperty knora-api:hasComment - ], [ a owl:Restriction; - knora-api:isInherited true; - owl:minCardinality 0; - owl:onProperty knora-api:hasIncomingLinkValue - ], [ 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:maxCardinality 1; - owl:onProperty knora-api:isDeleted - ], [ 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 knora-api:userHasPermission - ], [ a owl:Restriction; - knora-api:isInherited true; - owl:cardinality 1; - owl:onProperty knora-api:versionArkUrl - ], [ a owl:Restriction; - knora-api:isInherited true; - owl:maxCardinality 1; - owl:onProperty knora-api:versionDate - ], [ a owl:Restriction; - knora-api:isInherited true; - owl:cardinality 1; - owl:onProperty rdfs:label - ] . - knora-api:GeomValue a owl:Class; knora-api:isValueClass true; rdfs:comment "Represents a geometrical objects as JSON string"; @@ -3836,6 +3759,11 @@ knora-api:mappingHasName a owl:DatatypeProperty; rdfs:comment "Represents the name of a mapping"; rdfs:label "Name of a mapping (will be part of the mapping's Iri)" . +knora-api:mayHaveMoreResults a owl:DatatypeProperty; + knora-api:objectType xsd:boolean; + rdfs:comment "Indicates whether more results may be available for a search query"; + rdfs:label "May have more results" . + knora-api:newModificationDate a owl:DatatypeProperty; knora-api:objectType xsd:dateTimeStamp; rdfs:comment "Specifies the new modification date of a resource"; diff --git a/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneDeletedResource.jsonld b/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneDeletedResource.jsonld new file mode 100644 index 0000000000..760014ef5d --- /dev/null +++ b/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneDeletedResource.jsonld @@ -0,0 +1,82 @@ +{ + "@id" : "http://rdfh.ch/0001/l8f8FVEiSCeq9A1p8gBR-A", + "@type" : "anything:Thing", + "anything:hasInteger" : { + "@id" : "http://rdfh.ch/0001/l8f8FVEiSCeq9A1p8gBR-A/values/a-40v6WiT4GHa79Kqwojjw", + "@type" : "knora-api:IntValue", + "knora-api:arkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6/a=40v6WiT4GHa79Kqwojjw0" + }, + "knora-api:attachedToUser" : { + "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + }, + "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", + "knora-api:intValueAsInt" : 123454321, + "knora-api:userHasPermission" : "V", + "knora-api:valueCreationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "2020-04-07T09:12:56.710717Z" + }, + "knora-api:valueHasComment" : "int value in main resource", + "knora-api:valueHasUUID" : "a-40v6WiT4GHa79Kqwojjw", + "knora-api:versionArkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6/a=40v6WiT4GHa79Kqwojjw0.20200407T091256710717Z" + } + }, + "anything:hasOtherThingValue" : { + "@id" : "http://rdfh.ch/0001/l8f8FVEiSCeq9A1p8gBR-A/values/Nlcc7XWXQtmEITsIRQ5z4w", + "@type" : "knora-api:LinkValue", + "knora-api:arkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6/Nlcc7XWXQtmEITsIRQ5z4wY" + }, + "knora-api:attachedToUser" : { + "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + }, + "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", + "knora-api:linkValueHasTargetIri" : { + "@id" : "http://rdfh.ch/0001/PHbbrEsVR32q5D_ioKt6pA" + }, + "knora-api:userHasPermission" : "V", + "knora-api:valueCreationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "2020-04-07T09:12:56.710717Z" + }, + "knora-api:valueHasComment" : "link value pointing to deleted resource", + "knora-api:valueHasUUID" : "Nlcc7XWXQtmEITsIRQ5z4w", + "knora-api:versionArkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6/Nlcc7XWXQtmEITsIRQ5z4wY.20200407T091256710717Z" + } + }, + "knora-api:arkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6" + }, + "knora-api:attachedToProject" : { + "@id" : "http://rdfh.ch/projects/0001" + }, + "knora-api:attachedToUser" : { + "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + }, + "knora-api:creationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "2020-04-07T09:12:56.710717Z" + }, + "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", + "knora-api:userHasPermission" : "V", + "knora-api:versionArkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/l8f8FVEiSCeq9A1p8gBR=A6.20200407T091256710717Z" + }, + "rdfs:label" : "thing with one deleted thing", + "@context" : { + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#" + } +} \ No newline at end of file diff --git a/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneHiddenResource.jsonld b/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneHiddenResource.jsonld new file mode 100644 index 0000000000..93d9a087b6 --- /dev/null +++ b/webapi/src/test/resources/test-data/resourcesR2RV2/ThingWithOneHiddenResource.jsonld @@ -0,0 +1,56 @@ +{ + "@id" : "http://rdfh.ch/0001/0JhgKcqoRIeRRG6ownArSw", + "@type" : "anything:Thing", + "anything:hasInteger" : { + "@id" : "http://rdfh.ch/0001/0JhgKcqoRIeRRG6ownArSw/values/U1PwfNaVRQebbOSFWNdMqQ", + "@type" : "knora-api:IntValue", + "knora-api:arkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/0JhgKcqoRIeRRG6ownArSwb/U1PwfNaVRQebbOSFWNdMqQ7" + }, + "knora-api:attachedToUser" : { + "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + }, + "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", + "knora-api:intValueAsInt" : 123454321, + "knora-api:userHasPermission" : "V", + "knora-api:valueCreationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "2020-04-07T09:12:56.710717Z" + }, + "knora-api:valueHasComment" : "visible int value in main resource", + "knora-api:valueHasUUID" : "U1PwfNaVRQebbOSFWNdMqQ", + "knora-api:versionArkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/0JhgKcqoRIeRRG6ownArSwb/U1PwfNaVRQebbOSFWNdMqQ7.20200407T091256710717Z" + } + }, + "knora-api:arkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/0JhgKcqoRIeRRG6ownArSwb" + }, + "knora-api:attachedToProject" : { + "@id" : "http://rdfh.ch/projects/0001" + }, + "knora-api:attachedToUser" : { + "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" + }, + "knora-api:creationDate" : { + "@type" : "xsd:dateTimeStamp", + "@value" : "2020-04-07T09:12:56.710717Z" + }, + "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", + "knora-api:userHasPermission" : "V", + "knora-api:versionArkUrl" : { + "@type" : "xsd:anyURI", + "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/0JhgKcqoRIeRRG6ownArSwb.20200407T091256710717Z" + }, + "rdfs:label" : "thing with one hidden thing", + "@context" : { + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "anything" : "http://0.0.0.0:3333/ontology/0001/anything/v2#" + } +} \ No newline at end of file diff --git a/webapi/src/test/resources/test-data/searchR2RV2/NarrFulltextSearch.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/NarrFulltextSearch.jsonld index 28c0cec81b..1cb125689d 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/NarrFulltextSearch.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/NarrFulltextSearch.jsonld @@ -1175,6 +1175,7 @@ }, "rdfs:label" : "i4r" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnum.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnum.jsonld index 2c83b84a24..7c4897c638 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnum.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnum.jsonld @@ -2350,6 +2350,7 @@ }, "rdfs:label" : "b4r" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnumNextOffset.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnumNextOffset.jsonld index 63a102aedd..bfd19a4c03 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnumNextOffset.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/PagesOfNarrenschiffOrderedBySeqnumNextOffset.jsonld @@ -2350,6 +2350,7 @@ }, "rdfs:label" : "c8v" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/resources/test-data/searchR2RV2/ThingWithBooleanOptionalOffset0.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/ThingWithBooleanOptionalOffset0.jsonld index 247fd3c667..d319b4e6dc 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/ThingWithBooleanOptionalOffset0.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/ThingWithBooleanOptionalOffset0.jsonld @@ -623,6 +623,7 @@ }, "rdfs:label" : "A containing thing" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/resources/test-data/searchR2RV2/ThingWithHiddenThing.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/ThingWithHiddenThing.jsonld index 394c91b0b3..9f321c9cd4 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/ThingWithHiddenThing.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/ThingWithHiddenThing.jsonld @@ -1,7 +1,7 @@ { "@id" : "http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg", "@type" : "anything:Thing", - "anything:hasOtherThingValue" : [ { + "anything:hasOtherThingValue" : { "@id" : "http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/MIbQMDn6T12QMS0GDlEDSg", "@type" : "knora-api:LinkValue", "knora-api:arkUrl" : { @@ -48,32 +48,7 @@ "@type" : "xsd:anyURI", "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/55UrkgTKR2SEQgnsLWI9mgR/MIbQMDn6T12QMS0GDlEDSgu.20191129T100000673298Z" } - }, { - "@id" : "http://rdfh.ch/0001/55UrkgTKR2SEQgnsLWI9mg/values/bRS6vcbaQxqU-DF0pWhZog", - "@type" : "knora-api:LinkValue", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/55UrkgTKR2SEQgnsLWI9mgR/bRS6vcbaQxqU=DF0pWhZogS" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" - }, - "knora-api:hasPermissions" : "V knora-admin:UnknownUser|M knora-admin:ProjectMember", - "knora-api:linkValueHasTargetIri" : { - "@id" : "http://rdfh.ch/0001/IwMDbs0KQsaxSRUTl2cAIQ" - }, - "knora-api:userHasPermission" : "V", - "knora-api:valueCreationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2019-11-29T10:00:00.673298Z" - }, - "knora-api:valueHasComment" : "link value pointing to hidden resource", - "knora-api:valueHasUUID" : "bRS6vcbaQxqU-DF0pWhZog", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/55UrkgTKR2SEQgnsLWI9mgR/bRS6vcbaQxqU=DF0pWhZogS.20191129T100000673298Z" - } - } ], + }, "knora-api:arkUrl" : { "@type" : "xsd:anyURI", "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/55UrkgTKR2SEQgnsLWI9mgR" diff --git a/webapi/src/test/resources/test-data/searchR2RV2/ThingsWithOptionalDecimalGreaterThan1.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/ThingsWithOptionalDecimalGreaterThan1.jsonld index b3200fa68f..540e86ebe5 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/ThingsWithOptionalDecimalGreaterThan1.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/ThingsWithOptionalDecimalGreaterThan1.jsonld @@ -483,30 +483,6 @@ "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=with=pictureE.20160302T150510Z" }, "rdfs:label" : "A thing with a picture" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" }, { "@id" : "http://rdfh.ch/0001/a-thing-with-text-valuesLanguage", "@type" : "anything:Thing", @@ -579,79 +555,8 @@ "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/cL5AwEioRLOm6Vrqwl1RmQ8.20161017T171604915Z" }, "rdfs:label" : "Oscar" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithforbiddenResource.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithHiddenResource.jsonld similarity index 90% rename from webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithforbiddenResource.jsonld rename to webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithHiddenResource.jsonld index 062d311c5f..14f1dd204d 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithforbiddenResource.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/searchResponseWithHiddenResource.jsonld @@ -1,29 +1,5 @@ { "@graph" : [ { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" - }, { "@id" : "http://rdfh.ch/0001/a-thing-with-text-valuesLanguage", "@type" : "anything:Thing", "knora-api:arkUrl" : { diff --git a/webapi/src/test/resources/test-data/searchR2RV2/thingWithOptionalDateSortedDesc.jsonld b/webapi/src/test/resources/test-data/searchR2RV2/thingWithOptionalDateSortedDesc.jsonld index 95ccd1e0c3..9f3b67cfff 100644 --- a/webapi/src/test/resources/test-data/searchR2RV2/thingWithOptionalDateSortedDesc.jsonld +++ b/webapi/src/test/resources/test-data/searchR2RV2/thingWithOptionalDateSortedDesc.jsonld @@ -551,30 +551,6 @@ "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/a=thing=with=pictureE.20160302T150510Z" }, "rdfs:label" : "A thing with a picture" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" }, { "@id" : "http://rdfh.ch/0001/a-thing-with-text-valuesLanguage", "@type" : "anything:Thing", @@ -647,55 +623,8 @@ "@value" : "http://0.0.0.0:3336/ark:/72163/1/0001/cL5AwEioRLOm6Vrqwl1RmQ8.20161017T171604915Z" }, "rdfs:label" : "Oscar" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" - }, { - "@id" : "http://rdfh.ch/0000/forbiddenResource", - "@type" : "knora-api:ForbiddenResource", - "knora-api:arkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV" - }, - "knora-api:attachedToProject" : { - "@id" : "http://www.knora.org/ontology/knora-admin#SystemProject" - }, - "knora-api:attachedToUser" : { - "@id" : "http://rdfh.ch/users/root" - }, - "knora-api:creationDate" : { - "@type" : "xsd:dateTimeStamp", - "@value" : "2017-10-06T11:05:37Z" - }, - "knora-api:hasPermissions" : "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|V knora-admin:UnknownUser", - "knora-api:userHasPermission" : "V", - "knora-api:versionArkUrl" : { - "@type" : "xsd:anyURI", - "@value" : "http://0.0.0.0:3336/ark:/72163/1/0000/forbiddenResourceV.20171006T110537Z" - }, - "rdfs:label" : "This resource is a proxy for a resource you are not allowed to see (may depend on the context: query path)" } ], + "knora-api:mayHaveMoreResults" : true, "@context" : { "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", "knora-api" : "http://api.knora.org/ontology/knora-api/v2#", diff --git a/webapi/src/test/scala/org/knora/webapi/CoreSpec.scala b/webapi/src/test/scala/org/knora/webapi/CoreSpec.scala index f33afcfab4..2d51bacdbd 100644 --- a/webapi/src/test/scala/org/knora/webapi/CoreSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/CoreSpec.scala @@ -20,6 +20,7 @@ package org.knora.webapi import akka.actor.{ActorRef, ActorSystem, Props} +import akka.event.LoggingAdapter import akka.pattern.ask import akka.stream.ActorMaterializer import akka.testkit.{ImplicitSender, TestKit} @@ -35,7 +36,7 @@ import org.knora.webapi.util.{StartupUtils, StringFormatter} import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike} import scala.concurrent.duration._ -import scala.concurrent.{Await, ExecutionContext} +import scala.concurrent.{Await, ExecutionContext, Future} import scala.language.postfixOps object CoreSpec { @@ -79,7 +80,7 @@ abstract class CoreSpec(_system: ActorSystem) extends TestKit(_system) with Core // needs to be initialized early on StringFormatter.initForTest() - val log = akka.event.Logging(system, this.getClass) + val log: LoggingAdapter = akka.event.Logging(system, this.getClass) lazy val appActor: ActorRef = system.actorOf(Props(new ApplicationActor with LiveManagers), name = APPLICATION_MANAGER_ACTOR_NAME) diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala index 5dc4365cac..d8b4cd1562 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/ResourcesRouteV2E2ESpec.scala @@ -330,6 +330,40 @@ class ResourcesRouteV2E2ESpec extends E2ESpec(ResourcesRouteV2E2ESpec.config) { ) } + "perform a full resource request with a link to a resource that the user doesn't have permission to see" in { + val request = Get(s"$baseApiUrl/v2/resources/${URLEncoder.encode("http://rdfh.ch/0001/0JhgKcqoRIeRRG6ownArSw", "UTF-8")}") + + val response: HttpResponse = singleAwaitingRequest(request) + val responseAsString = responseToString(response) + assert(response.status == StatusCodes.OK, responseAsString) + val expectedAnswerJSONLD = readOrWriteTextFile(responseAsString, new File("src/test/resources/test-data/resourcesR2RV2/ThingWithOneHiddenResource.jsonld"), writeTestDataFiles) + compareJSONLDForResourcesResponse(expectedJSONLD = expectedAnswerJSONLD, receivedJSONLD = responseAsString) + + // Check that the resource corresponds to the ontology. + instanceChecker.check( + instanceResponse = responseAsString, + expectedClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri, + knoraRouteGet = doGetRequest + ) + } + + "perform a full resource request with a link to a resource that is marked as deleted" in { + val request = Get(s"$baseApiUrl/v2/resources/${URLEncoder.encode("http://rdfh.ch/0001/l8f8FVEiSCeq9A1p8gBR-A", "UTF-8")}") + + val response: HttpResponse = singleAwaitingRequest(request) + val responseAsString = responseToString(response) + assert(response.status == StatusCodes.OK, responseAsString) + val expectedAnswerJSONLD = readOrWriteTextFile(responseAsString, new File("src/test/resources/test-data/resourcesR2RV2/ThingWithOneDeletedResource.jsonld"), writeTestDataFiles) + compareJSONLDForResourcesResponse(expectedJSONLD = expectedAnswerJSONLD, receivedJSONLD = responseAsString) + + // Check that the resource corresponds to the ontology. + instanceChecker.check( + instanceResponse = responseAsString, + expectedClassIri = "http://0.0.0.0:3333/ontology/0001/anything/v2#Thing".toSmartIri, + knoraRouteGet = doGetRequest + ) + } + "perform a full resource request for a past version of a resource, using a URL-encoded xsd:dateTimeStamp" in { val request = Get(s"$baseApiUrl/v2/resources/${URLEncoder.encode("http://rdfh.ch/0001/thing-with-history", "UTF-8")}?version=${URLEncoder.encode("2019-02-12T08:05:10.351Z", "UTF-8")}") val response: HttpResponse = singleAwaitingRequest(request) diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/SearchRouteV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/SearchRouteV2R2RSpec.scala index ec9936fab3..9fc133fe5b 100644 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/SearchRouteV2R2RSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/SearchRouteV2R2RSpec.scala @@ -126,7 +126,7 @@ class SearchRouteV2R2RSpec extends R2RSpec { // the response involves forbidden resource - val expectedAnswerJSONLD = readOrWriteTextFile(responseAs[String], new File("src/test/resources/test-data/searchR2RV2/searchResponseWithforbiddenResource.jsonld"), writeTestDataFiles) + val expectedAnswerJSONLD = readOrWriteTextFile(responseAs[String], new File("src/test/resources/test-data/searchR2RV2/searchResponseWithHiddenResource.jsonld"), writeTestDataFiles) compareJSONLDForResourcesResponse(expectedJSONLD = expectedAnswerJSONLD, receivedJSONLD = responseAs[String]) diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala index 73023aef4c..d347d564a1 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2Spec.scala @@ -65,7 +65,7 @@ object ResourcesResponderV2Spec { class GraphTestData { private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance - val graphForAnythingUser1 = GraphDataGetResponseV2( + val graphForAnythingUser1: GraphDataGetResponseV2 = GraphDataGetResponseV2( edges = Vector( GraphEdgeV2( target = "http://rdfh.ch/0001/tPfZeNMvRVujCQqbIbvO0A", @@ -223,7 +223,7 @@ class GraphTestData { ontologySchema = InternalSchema ) - val graphForIncunabulaUser = GraphDataGetResponseV2( + val graphForIncunabulaUser: GraphDataGetResponseV2 = GraphDataGetResponseV2( edges = Vector( GraphEdgeV2( target = "http://rdfh.ch/0001/tPfZeNMvRVujCQqbIbvO0A", @@ -351,7 +351,7 @@ class GraphTestData { ontologySchema = InternalSchema ) - val graphWithStandoffLink = GraphDataGetResponseV2( + val graphWithStandoffLink: GraphDataGetResponseV2 = GraphDataGetResponseV2( edges = Vector(GraphEdgeV2( target = "http://rdfh.ch/0001/a-thing", propertyIri = "http://www.knora.org/ontology/knora-base#hasStandoffLinkTo".toSmartIri, @@ -372,7 +372,7 @@ class GraphTestData { ontologySchema = InternalSchema ) - val graphWithOneNode = GraphDataGetResponseV2( + val graphWithOneNode: GraphDataGetResponseV2 = GraphDataGetResponseV2( edges = Nil, nodes = Vector(GraphNodeV2( resourceClassIri = "http://www.knora.org/ontology/0001/anything#Thing".toSmartIri, @@ -468,31 +468,8 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! ResourcesGetRequestV2(resourceIris = Seq(resourceIri), targetSchema = ApiV2Complex, requestingUser = anythingUserProfile) expectMsgPF(timeout) { - case response: ReadResourcesSequenceV2 => - resourcesSequenceToResource( - requestedresourceIri = resourceIri, - readResourcesSequence = response, - requestingUser = anythingUserProfile - ) - } - } - - private def resourcesSequenceToResource(requestedresourceIri: IRI, readResourcesSequence: ReadResourcesSequenceV2, requestingUser: UserADM): ReadResourceV2 = { - if (readResourcesSequence.numberOfResources == 0) { - throw AssertionException(s"Expected one resource, <$requestedresourceIri>, but no resources were returned") - } - - if (readResourcesSequence.numberOfResources > 1) { - throw AssertionException(s"More than one resource returned with IRI <$requestedresourceIri>") + case response: ReadResourcesSequenceV2 => response.toResource(resourceIri).toOntologySchema(ApiV2Complex) } - - val resourceInfo = readResourcesSequence.resources.head - - if (resourceInfo.resourceIri == SearchResponderV2Constants.forbiddenResourceIri) { - throw ForbiddenException(s"User ${requestingUser.email} does not have permission to view resource <${resourceInfo.resourceIri}>") - } - - resourceInfo.toOntologySchema(ApiV2Complex) } private def checkCreateResource(inputResource: CreateResourceV2, @@ -860,11 +837,7 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => - val outputResource: ReadResourceV2 = resourcesSequenceToResource( - requestedresourceIri = resourceIri, - readResourcesSequence = response, - requestingUser = anythingUserProfile - ) + val outputResource: ReadResourceV2 = response.toResource(resourceIri).toOntologySchema(ApiV2Complex) checkCreateResource( inputResource = inputResource, @@ -2172,8 +2145,7 @@ class ResourcesResponderV2Spec extends CoreSpec() with ImplicitSender { val isEntityUsedSparql: String = queries.sparql.v2.txt.isEntityUsed( triplestore = settings.triplestoreType, entityIri = resourceIriToErase.get.toSmartIri, - ignoreKnoraConstraints = true, - ignoreRdfSubjectAndObject = false + ignoreKnoraConstraints = true ).toString() storeManager ! SparqlSelectRequest(isEntityUsedSparql) diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2SpecFullData.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2SpecFullData.scala index de2af6af48..4dbbf97faa 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2SpecFullData.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponderV2SpecFullData.scala @@ -12,7 +12,7 @@ import org.knora.webapi.{InternalSchema, SharedTestDataADM} class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { - val expectedReadResourceV2ForZeitgloecklein = ReadResourceV2( + val expectedReadResourceV2ForZeitgloecklein: ReadResourceV2 = ReadResourceV2( label = "Zeitgl\u00F6cklein des Lebens und Leidens Christi", resourceIri = "http://rdfh.ch/0803/c5058f3a", permissions = "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", @@ -219,7 +219,7 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter deletionInfo = None ) - val expectedReadResourceV2ForZeitgloeckleinPreview = ReadResourceV2( + val expectedReadResourceV2ForZeitgloeckleinPreview: ReadResourceV2 = ReadResourceV2( label = "Zeitgl\u00F6cklein des Lebens und Leidens Christi", resourceIri = "http://rdfh.ch/0803/c5058f3a", permissions = "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", @@ -234,7 +234,7 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter deletionInfo = None ) - val expectedReadResourceV2ForReiseInsHeiligeland = ReadResourceV2( + val expectedReadResourceV2ForReiseInsHeiligeland: ReadResourceV2 = ReadResourceV2( label = "Reise ins Heilige Land", resourceIri = "http://rdfh.ch/0803/2a6221216701", permissions = "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", @@ -683,7 +683,7 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter deletionInfo = None ) - val expectedReadResourceV2ForReiseInsHeiligelandPreview = ReadResourceV2( + val expectedReadResourceV2ForReiseInsHeiligelandPreview: ReadResourceV2 = ReadResourceV2( resourceClassIri = "http://www.knora.org/ontology/0803/incunabula#book".toSmartIri, label = "Reise ins Heilige Land", creationDate = Instant.parse("2016-03-02T15:05:21Z"), @@ -698,22 +698,19 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter deletionInfo = None ) - val expectedFullResourceResponseForZeitgloecklein = ReadResourcesSequenceV2( + val expectedFullResourceResponseForZeitgloecklein: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(expectedReadResourceV2ForZeitgloecklein), - numberOfResources = 1 ) - val expectedPreviewResourceResponseForZeitgloecklein = ReadResourcesSequenceV2( + val expectedPreviewResourceResponseForZeitgloecklein: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(expectedReadResourceV2ForZeitgloeckleinPreview), - numberOfResources = 1 ) - val expectedFullResourceResponseForReise = ReadResourcesSequenceV2( + val expectedFullResourceResponseForReise: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(expectedReadResourceV2ForReiseInsHeiligeland), - numberOfResources = 1 ) - val expectedFullResourceResponseForThingWithHistory = ReadResourcesSequenceV2( + val expectedFullResourceResponseForThingWithHistory: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector( ReadResourceV2( versionDate = Some(Instant.parse("2019-02-12T08:05:10Z")), @@ -779,11 +776,10 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter lastModificationDate = Some(Instant.parse("2019-02-13T09:05:10Z")), deletionInfo = None ) - ), - numberOfResources = 1 + ) ) - val expectedCompleteVersionHistoryResponse = ResourceVersionHistoryResponseV2(history = Vector( + val expectedCompleteVersionHistoryResponse: ResourceVersionHistoryResponseV2 = ResourceVersionHistoryResponseV2(history = Vector( ResourceHistoryEntry( versionDate = Instant.parse("2019-02-13T09:05:10Z"), author = "http://rdfh.ch/users/BhkfBc3hTeS_IDo-JgXRbQ" @@ -822,7 +818,7 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter ) )) - val expectedPartialVersionHistoryResponse = ResourceVersionHistoryResponseV2(history = Vector( + val expectedPartialVersionHistoryResponse: ResourceVersionHistoryResponseV2 = ResourceVersionHistoryResponseV2(history = Vector( ResourceHistoryEntry( versionDate = Instant.parse("2019-02-13T09:00:10Z"), author = "http://rdfh.ch/users/9XBCrDV3SRa7kS1WwynB4Q" @@ -853,23 +849,19 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter ) )) - val expectedFullResourceResponseForZeitgloeckleinAndReise = ReadResourcesSequenceV2( - resources = Vector(expectedReadResourceV2ForZeitgloecklein, expectedReadResourceV2ForReiseInsHeiligeland), - numberOfResources = 2 + val expectedFullResourceResponseForZeitgloeckleinAndReise: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + resources = Vector(expectedReadResourceV2ForZeitgloecklein, expectedReadResourceV2ForReiseInsHeiligeland) ) - val expectedPreviewResourceResponseForZeitgloeckleinAndReise = ReadResourcesSequenceV2( - resources = Vector(expectedReadResourceV2ForZeitgloeckleinPreview, expectedReadResourceV2ForReiseInsHeiligelandPreview), - numberOfResources = 2 + val expectedPreviewResourceResponseForZeitgloeckleinAndReise: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + resources = Vector(expectedReadResourceV2ForZeitgloeckleinPreview, expectedReadResourceV2ForReiseInsHeiligelandPreview) ) - val expectedFullResourceResponseForReiseAndZeitgloeckleinInversedOrder = ReadResourcesSequenceV2( - resources = Vector(expectedReadResourceV2ForReiseInsHeiligeland, expectedReadResourceV2ForZeitgloecklein), - numberOfResources = 2 + val expectedFullResourceResponseForReiseAndZeitgloeckleinInversedOrder: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + resources = Vector(expectedReadResourceV2ForReiseInsHeiligeland, expectedReadResourceV2ForZeitgloecklein) ) - val expectedFullResponseResponseForThingWithValueByUuid = ReadResourcesSequenceV2( - numberOfResources = 1, + val expectedFullResponseResponseForThingWithValueByUuid: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(ReadResourceV2( versionDate = None, label = "A thing with version history", @@ -902,8 +894,7 @@ class ResourcesResponderV2SpecFullData(implicit stringFormatter: StringFormatter )) ) - val expectedFullResponseResponseForThingWithValueByUuidAndVersionDate = ReadResourcesSequenceV2( - numberOfResources = 1, + val expectedFullResponseResponseForThingWithValueByUuidAndVersionDate: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(ReadResourceV2( versionDate = None, label = "A thing with version history", diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2.scala index 331687d0c9..e04b7cbacb 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2.scala @@ -32,7 +32,7 @@ object ResourcesResponseCheckerV2 { * @param expected the expected response. */ def compareReadResourcesSequenceV2Response(expected: ReadResourcesSequenceV2, received: ReadResourcesSequenceV2): Unit = { - assert(expected.numberOfResources == received.numberOfResources, "number of resources is not equal") + assert(expected.resources.size == received.resources.size, "number of resources is not equal") assert(expected.resources.size == received.resources.size, "number of resources are not equal") // compare the resources one by one: resources have to returned in the correct order diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2SpecFullData.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2SpecFullData.scala index b30299a0f8..665b81bd3d 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2SpecFullData.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ResourcesResponseCheckerV2SpecFullData.scala @@ -13,7 +13,7 @@ import org.knora.webapi.{InternalSchema, SharedTestDataADM} class ResourcesResponseCheckerV2SpecFullData(implicit stringFormatter: StringFormatter) { // one title is missing - val expectedReadResourceV2ForReiseInsHeiligelandWrong = ReadResourceV2( + val expectedReadResourceV2ForReiseInsHeiligelandWrong: ReadResourceV2 = ReadResourceV2( label = "Reise ins Heilige Land", resourceIri = "http://rdfh.ch/2a6221216701", permissions = "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser", @@ -445,9 +445,8 @@ class ResourcesResponseCheckerV2SpecFullData(implicit stringFormatter: StringFor deletionInfo = None ) - val expectedFullResourceResponseForReiseWrong = ReadResourcesSequenceV2( - resources = Vector(expectedReadResourceV2ForReiseInsHeiligelandWrong), - numberOfResources = 1 + val expectedFullResourceResponseForReiseWrong: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( + resources = Vector(expectedReadResourceV2ForReiseInsHeiligelandWrong) ) } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala index 6a947fea21..3c2f948bd2 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2Spec.scala @@ -123,7 +123,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => // TODO: do better testing once JSON-LD can be converted back into case classes - assert(response.numberOfResources == 18, s"18 books were expected, but ${response.numberOfResources} given.") + assert(response.resources.size == 18, s"18 books were expected, but ${response.resources.size} given.") } } @@ -141,7 +141,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => - assert(response.numberOfResources == 3, s"3 results were expected, but ${response.numberOfResources} given") + assert(response.resources.size == 3, s"3 results were expected, but ${response.resources.size} given") } } @@ -159,7 +159,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case response: ReadResourcesSequenceV2 => - assert(response.numberOfResources == 3, s"3 results were expected, but ${response.numberOfResources} given") + assert(response.resources.size == 3, s"3 results were expected, but ${response.resources.size} given") } } @@ -174,7 +174,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case response: ReadResourcesSequenceV2 => + case response: ResourceCountV2 => assert(response.numberOfResources == 3, s"3 results were expected, but ${response.numberOfResources} given") } @@ -190,7 +190,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case response: ReadResourcesSequenceV2 => + case response: ResourceCountV2 => assert(response.numberOfResources == 3, s"3 results were expected, but ${response.numberOfResources} given") } @@ -208,7 +208,7 @@ class SearchResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case response: ReadResourcesSequenceV2 => response.numberOfResources should ===(19) + case response: ReadResourcesSequenceV2 => response.resources.size should ===(19) } } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala index a222728c03..5011c2613f 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/SearchResponderV2SpecFullData.scala @@ -17,8 +17,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { implicit lazy val system: ActorSystem = ActorSystem("webapi") - val fulltextSearchForNarr = ReadResourcesSequenceV2( - numberOfResources = 25, + val fulltextSearchForNarr: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector( ReadResourceV2( label = "p7v", @@ -773,8 +772,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { ) ) - val fulltextSearchForDinge = ReadResourcesSequenceV2( - numberOfResources = 1, + val fulltextSearchForDinge: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector(ReadResourceV2( label = "Ein Ding f\u00FCr jemanden, dem die Dinge gefallen", resourceIri = "http://rdfh.ch/0001/a-thing-with-text-values", @@ -1465,7 +1463,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { ) // Dear Ben: I am aware of the fact that this code is not formatted properly and I know that this deeply disturbs you. But please leave it like this since otherwise I cannot possibly read and understand this query. - val constructQueryForBooksWithTitleZeitgloecklein = ConstructQuery( + val constructQueryForBooksWithTitleZeitgloecklein: ConstructQuery = ConstructQuery( constructClause = ConstructClause( statements = Vector( StatementPattern(QueryVariable("book"), IriRef("http://api.knora.org/ontology/knora-api/simple/v2#isMainResource".toSmartIri, None), XsdLiteral("true", "http://www.w3.org/2001/XMLSchema#boolean".toSmartIri), None), @@ -1496,8 +1494,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { querySchema = Some(ApiV2Simple) ) - val booksWithTitleZeitgloeckleinResponse = ReadResourcesSequenceV2( - numberOfResources = 2, + val booksWithTitleZeitgloeckleinResponse: ReadResourcesSequenceV2 = ReadResourcesSequenceV2( resources = Vector( ReadResourceV2( label = "Zeitgl\u00F6cklein des Lebens und Leidens Christi", @@ -1563,7 +1560,7 @@ class SearchResponderV2SpecFullData(implicit stringFormatter: StringFormatter) { ) // Dear Ben: please see my comment above - val constructQueryForBooksWithoutTitleZeitgloecklein = ConstructQuery( + val constructQueryForBooksWithoutTitleZeitgloecklein: ConstructQuery = ConstructQuery( constructClause = ConstructClause( statements = Vector( StatementPattern(QueryVariable("book"), IriRef("http://api.knora.org/ontology/knora-api/simple/v2#isMainResource".toSmartIri, None), XsdLiteral("true", "http://www.w3.org/2001/XMLSchema#boolean".toSmartIri), None), diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala index 84e087c759..266a1eea6f 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/ValuesResponderV2Spec.scala @@ -178,13 +178,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case searchResponse: ReadResourcesSequenceV2 => - // Get the resource from the response. - resourcesSequenceToResource( - requestedresourceIri = resourceIri, - readResourcesSequence = searchResponse, - requestingUser = requestingUser - ) + case searchResponse: ReadResourcesSequenceV2 => searchResponse.toResource(resourceIri).toOntologySchema(ApiV2Complex) } } @@ -266,35 +260,12 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) } - private def resourcesSequenceToResource(requestedresourceIri: IRI, readResourcesSequence: ReadResourcesSequenceV2, requestingUser: UserADM): ReadResourceV2 = { - if (readResourcesSequence.numberOfResources == 0) { - throw AssertionException(s"Expected one resource, <$requestedresourceIri>, but no resources were returned") - } - - if (readResourcesSequence.numberOfResources > 1) { - throw AssertionException(s"More than one resource returned with IRI <$requestedresourceIri>") - } - - val resourceInfo = readResourcesSequence.resources.head - - if (resourceInfo.resourceIri == SearchResponderV2Constants.forbiddenResourceIri) { - throw ForbiddenException(s"User ${requestingUser.email} does not have permission to view resource <${resourceInfo.resourceIri}>") - } - - resourceInfo.toOntologySchema(ApiV2Complex) - } - private def getResourceLastModificationDate(resourceIri: IRI, requestingUser: UserADM): Option[Instant] = { responderManager ! ResourcesPreviewGetRequestV2(resourceIris = Seq(resourceIri), targetSchema = ApiV2Complex, requestingUser = requestingUser) expectMsgPF(timeout) { case previewResponse: ReadResourcesSequenceV2 => - val resourcePreview: ReadResourceV2 = resourcesSequenceToResource( - requestedresourceIri = resourceIri, - readResourcesSequence = previewResponse, - requestingUser = requestingUser - ) - + val resourcePreview: ReadResourceV2 = previewResponse.toResource(resourceIri) resourcePreview.lastModificationDate } } @@ -427,8 +398,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -520,7 +490,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -614,7 +584,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -804,7 +774,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -831,7 +801,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -855,7 +825,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -918,7 +888,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1039,7 +1009,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1106,7 +1076,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1232,7 +1202,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1342,7 +1312,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1416,7 +1386,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1485,7 +1455,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1509,7 +1479,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -1578,7 +1548,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1647,7 +1617,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1716,7 +1686,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1787,7 +1757,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! createValueRequest expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -1812,7 +1782,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! createValueRequest expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -1832,7 +1802,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -1857,9 +1827,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - // msg.cause.isInstanceOf[NotFoundException] should ===(true) - msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -1883,9 +1851,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - // msg.cause.isInstanceOf[NotFoundException] should ===(true) - msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -1910,8 +1876,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -1934,7 +1899,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[OntologyConstraintException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[OntologyConstraintException]) } } @@ -1958,7 +1923,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[OntologyConstraintException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[OntologyConstraintException]) } // The cardinality of incunabula:seqnum in incunabula:page is 0-1, and page http://rdfh.ch/0803/4f11adaf already has a seqnum. @@ -1978,8 +1943,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[OntologyConstraintException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[OntologyConstraintException]) } } @@ -2183,7 +2147,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -2208,7 +2172,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -2282,7 +2246,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -2309,7 +2273,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -2336,7 +2300,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -2407,7 +2371,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -2430,7 +2394,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -2453,7 +2417,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -2478,7 +2442,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2618,7 +2582,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2695,7 +2659,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2774,7 +2738,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2798,7 +2762,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2865,7 +2829,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -2932,7 +2896,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3015,7 +2979,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3082,7 +3046,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3149,7 +3113,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3223,7 +3187,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3292,7 +3256,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3317,7 +3281,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[NotFoundException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[NotFoundException]) } } @@ -3386,7 +3350,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3455,7 +3419,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3524,7 +3488,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3600,7 +3564,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! updateValueRequest expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3680,7 +3644,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! updateValueRequest expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3800,7 +3764,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -3835,7 +3799,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { responderManager ! updateValueRequest expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[DuplicateValueException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[DuplicateValueException]) } } @@ -3937,7 +3901,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[ForbiddenException] should ===(true) + assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -3971,8 +3935,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[SipiException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[SipiException]) } } @@ -3994,7 +3957,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } @@ -4036,7 +3999,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[BadRequestException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[BadRequestException]) } } @@ -4115,7 +4078,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { ) expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => msg.cause.isInstanceOf[OntologyConstraintException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[OntologyConstraintException]) } } @@ -4159,8 +4122,7 @@ class ValuesResponderV2Spec extends CoreSpec() with ImplicitSender { expectMsgPF(timeout) { - case msg: akka.actor.Status.Failure => - msg.cause.isInstanceOf[ForbiddenException] should ===(true) + case msg: akka.actor.Status.Failure => assert(msg.cause.isInstanceOf[ForbiddenException]) } } diff --git a/webapi/src/test/scala/org/knora/webapi/util/ConstructResponseUtilV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/util/ConstructResponseUtilV2Spec.scala index 62632ed5f3..a618b7bb81 100644 --- a/webapi/src/test/scala/org/knora/webapi/util/ConstructResponseUtilV2Spec.scala +++ b/webapi/src/test/scala/org/knora/webapi/util/ConstructResponseUtilV2Spec.scala @@ -33,8 +33,8 @@ import scala.concurrent.duration._ import scala.concurrent.{Await, Future} /** - * Tests [[ConstructResponseUtilV2]]. - */ + * Tests [[ConstructResponseUtilV2]]. + */ class ConstructResponseUtilV2Spec extends CoreSpec() with ImplicitSender { private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance private implicit val timeout: Timeout = 10.seconds @@ -47,23 +47,30 @@ class ConstructResponseUtilV2Spec extends CoreSpec() with ImplicitSender { val resourceIri: IRI = "http://rdfh.ch/0803/c5058f3a" val turtleStr: String = FileUtil.readTextFile(new File("src/test/resources/test-data/constructResponseUtilV2/Zeitglöcklein.ttl")) val resourceRequestResponse: SparqlExtendedConstructResponse = SparqlExtendedConstructResponse.parseTurtleResponse(turtleStr, log).get - val queryResultsSeparated: RdfResources = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData(constructQueryResults = resourceRequestResponse, requestingUser = incunabulaUser) + val mainResourcesAndValueRdfData: ConstructResponseUtilV2.MainResourcesAndValueRdfData = ConstructResponseUtilV2.splitMainResourcesAndValueRdfData( + constructQueryResults = resourceRequestResponse, + requestingUser = incunabulaUser + ) - val resourceFuture: Future[ReadResourceV2] = ConstructResponseUtilV2.createFullResourceResponse( - resourceIri = resourceIri, - resourceRdfData = queryResultsSeparated(resourceIri), + val apiResponseFuture: Future[ReadResourcesSequenceV2] = ConstructResponseUtilV2.createApiResponse( + mainResourcesAndValueRdfData = mainResourcesAndValueRdfData, + orderByResourceIri = Seq(resourceIri), mappings = Map.empty, queryStandoff = false, versionDate = None, + calculateMayHaveMoreResults = false, responderManager = responderManager, targetSchema = ApiV2Complex, settings = settings, requestingUser = incunabulaUser ) - val resource: ReadResourceV2 = Await.result(resourceFuture, 10.seconds) - val resourceSequence = ReadResourcesSequenceV2(numberOfResources = 1, resources = Seq(resource)) - ResourcesResponseCheckerV2.compareReadResourcesSequenceV2Response(expected = resourcesResponderV2SpecFullData.expectedFullResourceResponseForZeitgloecklein, received = resourceSequence) + val resourceSequence: ReadResourcesSequenceV2 = Await.result(apiResponseFuture, 10.seconds) + + ResourcesResponseCheckerV2.compareReadResourcesSequenceV2Response( + expected = resourcesResponderV2SpecFullData.expectedFullResourceResponseForZeitgloecklein, + received = resourceSequence + ) } } }