Skip to content

Commit

Permalink
Patch view resource types (#5055)
Browse files Browse the repository at this point in the history
* Patch resource types in blazegraph views

* Patch resource types in ES view values
  • Loading branch information
shinyhappydan authored Jul 5, 2024
1 parent e8b64b6 commit d0bef8f
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, JsonLdContext, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.Configuration

private[blazegraph] object BlazegraphDecoderConfiguration {
object BlazegraphDecoderConfiguration {

def apply(implicit jsonLdApi: JsonLdApi, rcr: RemoteContextResolution): IO[Configuration] = for {
contextValue <- IO.delay { ContextValue(contexts.blazegraph) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.configuration.semiauto.d
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.decoder.{Configuration, JsonLdDecoder}
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.model.Permission
import ch.epfl.bluebrain.nexus.delta.sdk.views.ViewRef
import ch.epfl.bluebrain.nexus.delta.sourcing.Serializer
import ch.epfl.bluebrain.nexus.delta.sourcing.model.Tag.{Latest, UserTag}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.IriFilter
import ch.epfl.bluebrain.nexus.delta.sourcing.query.SelectFilter
import ch.epfl.bluebrain.nexus.delta.sourcing.stream.PipeChain
import io.circe.generic.extras.semiauto.deriveConfiguredEncoder
import io.circe.generic.extras
import io.circe.generic.extras.semiauto.{deriveConfiguredCodec, deriveConfiguredEncoder}
import io.circe.syntax._
import io.circe.{Encoder, Json}
import io.circe.{Codec, Encoder, Json}

/**
* Enumeration of Blazegraph view values.
Expand Down Expand Up @@ -150,4 +152,8 @@ object BlazegraphViewValue {
): JsonLdDecoder[BlazegraphViewValue] =
deriveConfigJsonLdDecoder[BlazegraphViewValue]

object Database {
implicit private val configuration: extras.Configuration = Serializer.circeConfiguration
implicit val bgvvCodec: Codec.AsObject[BlazegraphViewValue] = deriveConfiguredCodec[BlazegraphViewValue]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ object ElasticSearchViewValue {
}

object Database {
implicit val configuration: Configuration = Serializer.circeConfiguration
implicit private val configuration: Configuration = Serializer.circeConfiguration
implicit val valueCodec: Codec.AsObject[ElasticSearchViewValue] = deriveConfiguredCodec[ElasticSearchViewValue]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ class BlazegraphViewProcessor private (
e.id match {
case id if id == defaultViewId => IO.unit // the default view is created on project creation
case _ =>
val patchedSource = viewPatcher.patchAggregateViewSource(e.source)
val patchedSource = viewPatcher.patchBlazegraphViewSource(e.source)
views(event.uuid).flatMap(_.create(e.id, project, patchedSource))
}
case e: BlazegraphViewUpdated =>
e.id match {
case id if id == defaultViewId => IO.unit
case _ =>
val patchedSource = viewPatcher.patchAggregateViewSource(e.source)
val patchedSource = viewPatcher.patchBlazegraphViewSource(e.source)
views(event.uuid).flatMap(_.update(e.id, project, cRev, patchedSource))
}
case e: BlazegraphViewDeprecated =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ class ElasticSearchViewProcessor private (
e.id match {
case id if id == defaultViewId => IO.unit // the default view is created on project creation
case _ =>
val patchedSource = viewPatcher.patchAggregateViewSource(e.source)
val patchedSource = viewPatcher.patchElasticSearchViewSource(e.source)
views(event.uuid).flatMap(_.create(e.id, project, patchedSource))
}
case e: ElasticSearchViewUpdated =>
e.id match {
case id if id == defaultViewId => IO.unit
case _ =>
val patchedSource = viewPatcher.patchAggregateViewSource(e.source)
val patchedSource = viewPatcher.patchElasticSearchViewSource(e.source)
views(event.uuid).flatMap(_.update(e.id, project, cRev, patchedSource))
}
case e: ElasticSearchViewDeprecated =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,47 @@ import io.circe.syntax.EncoderOps

final class ViewPatcher(projectMapper: ProjectMapper, iriPatcher: IriPatcher) {

def patchAggregateViewSource(input: Json): Json =
private def patchGenericViewResourceTypes(input: Json): Json =
root.resourceTypes.arr.each.modify(patchResourceType)(input)

private def patchResourceType(json: Json) =
patchIri(json)
.getOrElse(
throw new IllegalArgumentException(s"Invalid resource type found in Blazegraph view resource types: $json")
)

private def patchIri(json: Json) = {
json
.as[Iri]
.map { iri =>
iriPatcher(iri).asJson
}
}

def patchBlazegraphViewSource(input: Json): Json = {
patchGenericViewResourceTypes(
patchAggregateViewSource(input)
)
}

def patchElasticSearchViewSource(input: Json): Json = {
patchPipelineResourceTypes(
patchGenericViewResourceTypes(
patchAggregateViewSource(input)
)
)
}

private def patchPipelineResourceTypes(input: Json): Json = {
root.pipeline.each.config.each.`https://bluebrain.github.io/nexus/vocabulary/types`.each.`@id`.string
.modify(patchStringIri)(input)
}

private def patchStringIri(stringIri: String): String = {
Iri.apply(stringIri).map(iriPatcher.apply).map(_.toString).getOrElse(stringIri)
}

private def patchAggregateViewSource(input: Json): Json =
root.views.each.obj.modify { view =>
view
.mapAllKeys("project", patchProject)
Expand All @@ -26,11 +66,7 @@ final class ViewPatcher(projectMapper: ProjectMapper, iriPatcher: IriPatcher) {
.getOrElse(throw new IllegalArgumentException(s"Invalid project ref found in aggregate view source: $json"))

private def patchViewId(json: Json) =
json
.as[Iri]
.map { iri =>
iriPatcher(iri).asJson
}
patchIri(json)
.getOrElse(throw new IllegalArgumentException(s"Invalid view id found in aggregate view source: $json"))

}
Original file line number Diff line number Diff line change
@@ -1,18 +1,68 @@
package ch.epfl.bluebrain.nexus.ship.views

import cats.data.NonEmptySet
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue.AggregateElasticSearchViewValue
import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewValue
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewValue.Database._
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.model.BlazegraphViewValue.IndexingBlazegraphViewValue
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.ElasticSearchViewJsonLdSourceDecoder
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.{contexts, ElasticSearchViewValue}
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue.{AggregateElasticSearchViewValue, IndexingElasticSearchViewValue}
import ch.epfl.bluebrain.nexus.delta.rdf.syntax.iriStringContextSyntax
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.ElasticSearchViewValue.Database._
import ch.epfl.bluebrain.nexus.delta.sdk.views.ViewRef
import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef
import ch.epfl.bluebrain.nexus.delta.plugins.elasticsearch.model.contexts.{elasticsearch, elasticsearchMetadata}
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.schemas
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdJavaApi}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.{ApiMappings, ProjectContext}
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution
import ch.epfl.bluebrain.nexus.delta.sdk.views.{PipeStep, ViewRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.model.{IriFilter, ProjectRef}
import ch.epfl.bluebrain.nexus.delta.sourcing.stream.pipes.FilterByType
import ch.epfl.bluebrain.nexus.ship.{IriPatcher, ProjectMapper}
import ch.epfl.bluebrain.nexus.testkit.mu.NexusSuite
import io.circe.syntax.EncoderOps

import java.util.UUID

class ViewPatcherSuite extends NexusSuite {

implicit val rcr: RemoteContextResolution = RemoteContextResolution.fixedIOResource(
elasticsearch -> ContextValue.fromFile("contexts/elasticsearch.json"),
elasticsearchMetadata -> ContextValue.fromFile("contexts/elasticsearch-metadata.json"),
contexts.aggregations -> ContextValue.fromFile("contexts/aggregations.json"),
contexts.elasticsearchIndexing -> ContextValue.fromFile("contexts/elasticsearch-indexing.json"),
Vocabulary.contexts.metadata -> ContextValue.fromFile("contexts/metadata.json"),
Vocabulary.contexts.error -> ContextValue.fromFile("contexts/error.json"),
Vocabulary.contexts.metadata -> ContextValue.fromFile("contexts/metadata.json"),
Vocabulary.contexts.error -> ContextValue.fromFile("contexts/error.json"),
Vocabulary.contexts.shacl -> ContextValue.fromFile("contexts/shacl.json"),
Vocabulary.contexts.statistics -> ContextValue.fromFile("contexts/statistics.json"),
Vocabulary.contexts.offset -> ContextValue.fromFile("contexts/offset.json"),
Vocabulary.contexts.pipeline -> ContextValue.fromFile("contexts/pipeline.json"),
Vocabulary.contexts.tags -> ContextValue.fromFile("contexts/tags.json"),
Vocabulary.contexts.search -> ContextValue.fromFile("contexts/search.json")
)
implicit val api: JsonLdApi = JsonLdJavaApi.strict
private val ref = ProjectRef.unsafe("org", "proj")
private val context = ProjectContext.unsafe(
ApiMappings("_" -> schemas.resources, "resource" -> schemas.resources),
iri"http://localhost/v1/resources/org/proj/_/",
iri"http://schema.org/",
enforceSchema = false
)

implicit private val uuidF: UUIDF = UUIDF.fixed(UUID.randomUUID())

implicit private val resolverContext: ResolverContextResolution = ResolverContextResolution(rcr)

implicit private val caller: Caller = Caller.Anonymous
private val decoder =
ElasticSearchViewJsonLdSourceDecoder(uuidF, resolverContext).unsafeRunSync()

private val project1 = ProjectRef.unsafe("org", "project")
private val viewId1 = iri"https://bbp.epfl.ch/view1"
private val view1 = ViewRef(project1, viewId1)
Expand All @@ -33,13 +83,73 @@ class ViewPatcherSuite extends NexusSuite {
private val projectMapper = ProjectMapper(Map(project2 -> targetProject))
private val viewPatcher = new ViewPatcher(projectMapper, iriPatcher)

private def indexingESViewWithFilterByTypePipeline(types: Iri*): IndexingElasticSearchViewValue = {
IndexingElasticSearchViewValue(
None,
None,
pipeline = filterByTypePipeline(types: _*)
)
}

private def filterByTypePipeline(types: Iri*) = List(
PipeStep(FilterByType(IriFilter.fromSet(types.toSet)))
)

test("Patch the aggregate view") {
val viewAsJson = aggregateView.asJson
val expectedView1 = ViewRef(project1, iri"https://openbrainplatform.com/view1")
val expectedView2 = ViewRef(targetProject, viewId2)
val expectedAggregated = AggregateElasticSearchViewValue(None, None, NonEmptySet.of(expectedView1, expectedView2))
val result = viewPatcher.patchAggregateViewSource(viewAsJson).as[ElasticSearchViewValue]
val result = viewPatcher.patchElasticSearchViewSource(viewAsJson).as[ElasticSearchViewValue]
assertEquals(result, Right(expectedAggregated))
}

test("Patch an ES view's resource types") {
val view = indexingESViewWithFilterByTypePipeline(originalPrefix / "Type1", originalPrefix / "Type2")
val viewAsJson = view.asInstanceOf[ElasticSearchViewValue].asJson
val expectedView = indexingESViewWithFilterByTypePipeline(targetPrefix / "Type1", targetPrefix / "Type2")
val result = viewPatcher.patchElasticSearchViewSource(viewAsJson).as[ElasticSearchViewValue]
assertEquals(result, Right(expectedView))
}

test("Patch a legacy ES view's resource types") {
val sourceJson = json"""{
"@type": "ElasticSearchView",
"resourceTypes": [ "${originalPrefix / "Type1"}", "${originalPrefix / "Type2"}" ],
"mapping": { }
}"""

val (_, originalValue) = decoder(ref, context, sourceJson).accepted
assertEquals(
originalValue.asIndexingValue.map(_.pipeline.filter(_.name.value == "filterByType")),
Some(filterByTypePipeline(originalPrefix / "Type1", originalPrefix / "Type2"))
)

val patchedJson = viewPatcher.patchElasticSearchViewSource(sourceJson)
val (_, patchedValue) = decoder(ref, context, patchedJson).accepted
assertEquals(
patchedValue.asIndexingValue.map(_.pipeline.filter(_.name.value == "filterByType")),
Some(filterByTypePipeline(targetPrefix / "Type1", targetPrefix / "Type2"))
)
}

test("Patch a blazegraph view's resource types") {
val view: BlazegraphViewValue = IndexingBlazegraphViewValue(resourceTypes =
IriFilter.fromSet(Set(iri"https://bbp.epfl.ch/resource1", iri"https://bbp.epfl.ch/resource2"))
)

val patchedView = viewPatcher.patchBlazegraphViewSource(view.asJson).as[BlazegraphViewValue]

assertEquals(
patchedView,
Right(
IndexingBlazegraphViewValue(resourceTypes =
IriFilter.fromSet(
Set(iri"https://openbrainplatform.com/resource1", iri"https://openbrainplatform.com/resource2")
)
)
)
)
}

}

0 comments on commit d0bef8f

Please sign in to comment.