diff --git a/data/geo/mapping.yaml b/data/geo/mapping.yaml index 5409770c..3ae89ec7 100644 --- a/data/geo/mapping.yaml +++ b/data/geo/mapping.yaml @@ -14,9 +14,6 @@ objectTypeMappings: id: pathMappings: path: id - surface: - pathMappings: - path: area geometry: pathMappings: path: geometry @@ -65,3 +62,10 @@ objectTypeMappings: - path: isSubAddressOf/isPartOf combiner: type: coalesce + + Dimensions: + sourceRoot: city:Building + propertyMappings: + surface: + pathMappings: + path: area \ No newline at end of file diff --git a/data/geo/model.yaml b/data/geo/model.yaml index 4ca1c499..9fbe1562 100644 --- a/data/geo/model.yaml +++ b/data/geo/model.yaml @@ -5,13 +5,13 @@ objectTypes: type: String identifier: true cardinality: 1 - surface: - type: Integer - cardinality: 0..1 geometry: type: Geometry cardinality: 0..1 relations: + dimensions: + target: Dimensions + cardinality: 1 hasAddress: target: Address cardinality: 0..* @@ -36,3 +36,9 @@ objectTypes: fullAddress: type: String cardinality: 1 + + Dimensions: + attributes: + surface: + type: Integer + cardinality: 0..1 \ No newline at end of file diff --git a/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/FetchPlanner.java b/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/FetchPlanner.java index c905f642..24a0d7b1 100644 --- a/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/FetchPlanner.java +++ b/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/FetchPlanner.java @@ -8,6 +8,7 @@ import static nl.geostandaarden.imx.orchestrate.model.ModelUtils.extractKey; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -81,8 +82,23 @@ public FetchPlan plan(CollectionRequest request) { private Set resolveSourcePaths(DataRequest request, ObjectTypeMapping typeMapping, Path basePath) { return request.getSelectedProperties() .stream() - .flatMap(selectedProperty -> resolveSourcePaths(selectedProperty, - typeMapping.getPropertyMapping(selectedProperty.getProperty()), basePath)) + .flatMap(selectedProperty -> { + var propertyMapping = typeMapping.getPropertyMapping(selectedProperty.getProperty()); + + if (propertyMapping.isPresent()) { + return propertyMapping.stream() + .flatMap(mapping -> resolveSourcePaths(selectedProperty, mapping, basePath)); + } + + if (selectedProperty.getProperty() instanceof Relation relation) { + var targetTypeMapping = modelMapping.getObjectTypeMapping( + modelMapping.getTargetType(relation.getTarget())); + return resolveSourcePaths(selectedProperty.getNestedRequest(), targetTypeMapping, basePath) + .stream(); + } + + return Stream.empty(); + }) .collect(toSet()); } @@ -271,7 +287,8 @@ private FilterDefinition createFilterDefinition(ObjectType targetType, Map 1) { throw new OrchestrateException("Currently only a single path mapping is supported when filtering."); diff --git a/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/ObjectResultMapper.java b/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/ObjectResultMapper.java index f8a373c9..25fe9bda 100644 --- a/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/ObjectResultMapper.java +++ b/engine/src/main/java/nl/geostandaarden/imx/orchestrate/engine/fetch/ObjectResultMapper.java @@ -53,7 +53,16 @@ public ObjectResult map(ObjectResult objectResult, DataRequest request) { var property = targetType.getProperty(selectedProperty.getName()); var propertyMapping = targetMapping.getPropertyMapping(property); - var propertyResult = mapProperty(objectResult, property, propertyMapping); + + // TODO: Refactor + if (!propertyMapping.isPresent()) { + var propertyValue = map(objectResult, selectedProperty.getNestedRequest()) + .getProperties(); + properties.put(property.getName(), propertyValue); + return; + } + + var propertyResult = mapProperty(objectResult, property, propertyMapping.get()); if (propertyResult == null) { return; @@ -112,12 +121,12 @@ private Object getRelationLineageValue(Object relationValue) { private ObjectReference getSubjectReferenceFromRelationValue(Object value) { if (value instanceof ObjectResult objectResult && (objectResult.getLineage() != null)) { return objectResult.getLineage() - .getOrchestratedDataElements() - .stream() - .map(OrchestratedDataElement::getSubject) - .findFirst() - .orElseThrow(() -> new OrchestrateException( - String.format("Expected data elements in relation value lineage but was %s", value))); + .getOrchestratedDataElements() + .stream() + .map(OrchestratedDataElement::getSubject) + .findFirst() + .orElseThrow(() -> new OrchestrateException( + String.format("Expected data elements in relation value lineage but was %s", value))); } throw new OrchestrateException(String.format("Expected object result but was %s", value)); } @@ -159,9 +168,9 @@ private Object mapRelation(Object value, DataRequest request) { private PropertyMappingResult mapProperty(ObjectResult objectResult, Property property, PropertyMapping propertyMapping) { var pathMappingResults = propertyMapping.getPathMappings() - .stream() - .map(pathMapping -> pathMappingResult(objectResult, property, pathMapping)) - .toList(); + .stream() + .map(pathMapping -> pathMappingResult(objectResult, property, pathMapping)) + .toList(); var combiner = propertyMapping.getCombiner(); @@ -180,16 +189,16 @@ private PropertyMappingResult mapProperty(ObjectResult objectResult, Property pr .map(PathMappingResult::getPathMappingExecution) .toList()) .build(); - + var pathResults = pathMappingResults.stream() - .map(PathMappingResult::getPathResults) - .flatMap(List::stream) - .toList(); + .map(PathMappingResult::getPathResults) + .flatMap(List::stream) + .toList(); return combiner.apply(pathResults) .withPropertyMappingExecution(propertyMappingExecution); } - + private PathMappingResult pathMappingResult(ObjectResult objectResult, Property property, PathMapping pathMapping) { var pathResults = pathResult(objectResult, property, pathMapping.getPath(), pathMapping.getPath()) .flatMap(pathResult -> resultMapPathResult(pathResult, objectResult, property, pathMapping)) @@ -243,10 +252,10 @@ private Stream pathResult(ObjectResult objectResult, Property proper var pathResult = PathResult.builder() .value(propertyValue) .pathExecution(PathExecution.builder() - .used(path) - .startNode(objectResult.getObjectReference()) - .references(sourceDataElements) - .build()) + .used(path) + .startNode(objectResult.getObjectReference()) + .references(sourceDataElements) + .build()) .build(); return Stream.of(pathResult); @@ -280,14 +289,14 @@ private Stream pathResult(ObjectResult objectResult, Property proper private Stream resultMapPathResult(PathResult pathResult, ObjectResult objectResult, Property property, PathMapping pathMapping) { // TODO: Lazy fetching & multi cardinality var nextedPathResult = pathMapping.getNextPathMappings() - .stream() - .flatMap(nextPathMapping -> nextPathResults(pathResult, objectResult, property, nextPathMapping)) - .findFirst() - .orElse(pathResult); + .stream() + .flatMap(nextPathMapping -> nextPathResults(pathResult, objectResult, property, nextPathMapping)) + .findFirst() + .orElse(pathResult); var mappedPathResult = pathMapping.getResultMappers() - .stream() - .reduce(nextedPathResult, (acc, resultMapper) -> resultMapper.apply(acc, property), noopCombiner()); + .stream() + .reduce(nextedPathResult, (acc, resultMapper) -> resultMapper.apply(acc, property), noopCombiner()); return Stream.of(mappedPathResult); @@ -298,7 +307,7 @@ private Stream nextPathResults(PathResult previousPathResult, Object if (ifMatch != null && ifMatch.test(previousPathResult.getValue())) { return pathMappingResult(objectResult, property, nextPathMapping) - .getPathResults().stream(); + .getPathResults().stream(); } return Stream.of(previousPathResult); diff --git a/engine/src/test/java/nl/geostandaarden/imx/orchestrate/engine/OrchestrateEngineIT.java b/engine/src/test/java/nl/geostandaarden/imx/orchestrate/engine/OrchestrateEngineIT.java index dd9d03c9..80c87152 100644 --- a/engine/src/test/java/nl/geostandaarden/imx/orchestrate/engine/OrchestrateEngineIT.java +++ b/engine/src/test/java/nl/geostandaarden/imx/orchestrate/engine/OrchestrateEngineIT.java @@ -11,7 +11,6 @@ import java.util.Map; import nl.geostandaarden.imx.orchestrate.engine.exchange.CollectionRequest; import nl.geostandaarden.imx.orchestrate.engine.exchange.ObjectRequest; -import nl.geostandaarden.imx.orchestrate.engine.exchange.ObjectResult; import nl.geostandaarden.imx.orchestrate.engine.source.DataRepository; import nl.geostandaarden.imx.orchestrate.ext.spatial.SpatialExtension; import nl.geostandaarden.imx.orchestrate.model.ComponentRegistry; @@ -76,7 +75,9 @@ void fetch() { .objectType("Construction") .objectKey(Map.of("id", "B0001")) .selectProperty("id") - .selectProperty("surface") + .selectObjectProperty("dimensions", builder -> builder + .selectProperty("surface") + .build()) .selectProperty("geometry") .selectCollectionProperty("hasAddress", builder -> builder .selectProperty("postalCode") @@ -119,11 +120,15 @@ void fetch() { var resultMono = engine.fetch(request); StepVerifier.create(resultMono) - .assertNext(result -> assertThat(result).isNotNull() - .extracting(ObjectResult::getLineage) - .extracting(ObjectLineage::getOrchestratedDataElements, as(InstanceOfAssertFactories.COLLECTION)) - .hasSize(4)) - .expectComplete() - .verify(); + .assertNext(result -> { + assertThat(result).isNotNull(); + assertThat(result.getProperties()) + .containsEntry("id", "B0001") + .containsEntry("dimensions", Map.of("surface", 123)); + assertThat(result.getLineage()) + .extracting(ObjectLineage::getOrchestratedDataElements, as(InstanceOfAssertFactories.COLLECTION)) + .hasSize(3); + }) + .verifyComplete(); } } diff --git a/gateway/src/main/java/nl/geostandaarden/imx/orchestrate/gateway/schema/SchemaFactory.java b/gateway/src/main/java/nl/geostandaarden/imx/orchestrate/gateway/schema/SchemaFactory.java index 0fb97e7e..fddd4d47 100644 --- a/gateway/src/main/java/nl/geostandaarden/imx/orchestrate/gateway/schema/SchemaFactory.java +++ b/gateway/src/main/java/nl/geostandaarden/imx/orchestrate/gateway/schema/SchemaFactory.java @@ -157,7 +157,8 @@ private boolean isFilterable(ObjectType objectType, Attribute attribute) { var pathMappings = modelMapping.getObjectTypeMapping(objectType) .getPropertyMapping(attribute) - .getPathMappings(); + .map(PropertyMapping::getPathMappings) + .orElse(List.of()); var firstPath = pathMappings.get(0) .getPath(); diff --git a/gateway/src/test/java/nl/geostandaarden/imx/orchestrate/gateway/GatewayIT.java b/gateway/src/test/java/nl/geostandaarden/imx/orchestrate/gateway/GatewayIT.java index 1fd5a083..c7f13ab7 100644 --- a/gateway/src/test/java/nl/geostandaarden/imx/orchestrate/gateway/GatewayIT.java +++ b/gateway/src/test/java/nl/geostandaarden/imx/orchestrate/gateway/GatewayIT.java @@ -23,7 +23,9 @@ class GatewayIT { { construction(id: "B0002") { id - surface + dimensions { + surface + } hasLineage { orchestratedDataElements { property @@ -65,9 +67,9 @@ void queryReturnsResponse_forGraphQLMediaType() { assertThat(adres).isNotNull() .containsEntry("id", "B0002") - .containsEntry("surface", 195) + .containsEntry("dimensions", Map.of("surface", 195)) .extractingByKey("hasLineage", as(InstanceOfAssertFactories.MAP)) .extractingByKey("orchestratedDataElements", as(InstanceOfAssertFactories.COLLECTION)) - .hasSize(2); + .hasSize(1); } } diff --git a/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ModelMapping.java b/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ModelMapping.java index 76428428..ebe548b3 100644 --- a/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ModelMapping.java +++ b/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ModelMapping.java @@ -6,6 +6,7 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.stream.Stream; import lombok.Builder; import lombok.Getter; import lombok.Singular; @@ -76,13 +77,17 @@ private Model resolveInverseRelations(Model model) { .stream() .flatMap(objectType -> objectType.getProperties(Relation.class) .stream() - .map(relation -> { + .flatMap(relation -> { + if (relation.getInverseName() == null) { + return Stream.empty(); + } + var inverseRelation = InverseRelation.builder() .originRelation(relation) .target(ObjectTypeRef.forType(model.getAlias(), objectType.getName())) .build(); - return Map.entry(relation.getTarget(), inverseRelation); + return Stream.of(Map.entry(relation.getTarget(), inverseRelation)); })) .reduce(model, (acc, targetRelation) -> { var targetObjectType = acc.getObjectType(targetRelation.getKey()) diff --git a/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ObjectTypeMapping.java b/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ObjectTypeMapping.java index 699b8761..759ea3a4 100644 --- a/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ObjectTypeMapping.java +++ b/model/src/main/java/nl/geostandaarden/imx/orchestrate/model/ObjectTypeMapping.java @@ -17,12 +17,11 @@ public final class ObjectTypeMapping { @Singular private final Map propertyMappings; - public PropertyMapping getPropertyMapping(String name) { - return Optional.ofNullable(propertyMappings.get(name)) - .orElseThrow(() -> new ModelException("Attribute mapping not found: " + name)); + public Optional getPropertyMapping(String name) { + return Optional.ofNullable(propertyMappings.get(name)); } - public PropertyMapping getPropertyMapping(Property property) { + public Optional getPropertyMapping(Property property) { return getPropertyMapping(property.getName()); } }