From 4ee3cc15813caae9188e779f7f317be8927af06f Mon Sep 17 00:00:00 2001 From: i302705 Date: Fri, 9 Oct 2026 15:17:40 +0300 Subject: [PATCH] chore: Refactor ORD resolution --- .../feature/ord/clients/MtxSidecarClient.java | 4 +- .../clients/impl/MtxSidecarClientImpl.java | 13 +- .../com/sap/cds/feature/ord/common/Utils.java | 18 +- .../OrdResourcesProviderConfiguration.java | 36 -- .../{common => core}/OrdJsonInputStream.java | 106 +++-- .../customizers/CdsOrdNodeCustomizer.java | 29 ++ .../impl/EntryPointsCustomizer.java} | 30 +- .../impl/ResourceDefinitionsCustomizer.java} | 31 +- .../core/generators/CdsOrdNodeGenerator.java | 27 ++ .../ord/processor/CdsOrdNodeProcessor.java | 50 --- .../ord/provider/OrdResourcesProvider.java | 18 - .../impl/DynamicOrdResourcesProviderImpl.java | 66 --- .../impl/StaticOrdResourcesProviderImpl.java | 56 --- .../cds/feature/ord/resolver/OrdResolver.java | 13 + .../impl/DynamicOrdDocumentResolver.java | 59 +++ .../DynamicOrdResourceDefinitionResolver.java | 49 +++ .../impl/StaticOrdDocumentResolver.java | 56 +++ .../StaticOrdResourceDefinitionResolver.java | 61 +++ .../feature/ord/servlet/DocumentsServlet.java | 23 +- ....ord.core.customizers.CdsOrdNodeCustomizer | 2 + ....feature.ord.processor.CdsOrdNodeProcessor | 2 - ...m.sap.cds.feature.ord.resolver.OrdResolver | 4 + ...s.services.runtime.CdsRuntimeConfiguration | 1 - .../client/impl/MtxSidecarClientImplTest.java | 45 ++- ...OrdResourcesProviderConfigurationTest.java | 133 ------- .../OrdJsonInputStreamTest.java | 13 +- .../core/impl/EntryPointsProcessorTest.java | 65 +++ .../ResourceDefinitionsCustomizerTest.java} | 49 +-- .../impl/EntryPointsProcessorTest.java | 62 --- .../DynamicOrdResourcesProviderImplTest.java | 300 -------------- .../StaticOrdResourcesProviderImplTest.java | 116 ------ .../ord/servlet/DocumentsServletTest.java | 376 +++++++++--------- 32 files changed, 724 insertions(+), 1189 deletions(-) delete mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfiguration.java rename cds-feature-ord/src/main/java/com/sap/cds/feature/ord/{common => core}/OrdJsonInputStream.java (56%) create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/CdsOrdNodeCustomizer.java rename cds-feature-ord/src/main/java/com/sap/cds/feature/ord/{processor/impl/EntryPointsProcessor.java => core/customizers/impl/EntryPointsCustomizer.java} (56%) rename cds-feature-ord/src/main/java/com/sap/cds/feature/ord/{processor/impl/ResourceDefinitionsProcessor.java => core/customizers/impl/ResourceDefinitionsCustomizer.java} (73%) create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/generators/CdsOrdNodeGenerator.java delete mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/CdsOrdNodeProcessor.java delete mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/OrdResourcesProvider.java delete mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImpl.java delete mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImpl.java create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/OrdResolver.java create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdDocumentResolver.java create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdResourceDefinitionResolver.java create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdDocumentResolver.java create mode 100644 cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdResourceDefinitionResolver.java create mode 100644 cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer delete mode 100644 cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor create mode 100644 cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.resolver.OrdResolver delete mode 100644 cds-feature-ord/src/test/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfigurationTest.java rename cds-feature-ord/src/test/java/com/sap/cds/feature/ord/{common => core}/OrdJsonInputStreamTest.java (77%) create mode 100644 cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/EntryPointsProcessorTest.java rename cds-feature-ord/src/test/java/com/sap/cds/feature/ord/{processor/impl/ResourceDefinitionsProcessorTest.java => core/impl/ResourceDefinitionsCustomizerTest.java} (57%) delete mode 100644 cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessorTest.java delete mode 100644 cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImplTest.java delete mode 100644 cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImplTest.java diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/MtxSidecarClient.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/MtxSidecarClient.java index f108a79..adef00a 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/MtxSidecarClient.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/MtxSidecarClient.java @@ -5,7 +5,7 @@ public interface MtxSidecarClient { - String getOrdDocument(); + byte[] getOrdDocument(); - String getOrdResourceDefinition(String resource); + byte[] getOrdResourceDefinition(String resource); } diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/impl/MtxSidecarClientImpl.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/impl/MtxSidecarClientImpl.java index d69bb7a..65b8205 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/impl/MtxSidecarClientImpl.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/clients/impl/MtxSidecarClientImpl.java @@ -8,9 +8,9 @@ import static com.sap.cds.services.request.RequestContext.getCurrent; import static com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor.getHttpClient; import static com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor.getDestination; -import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.emptySet; import static java.util.stream.Collectors.toSet; +import static org.apache.commons.io.IOUtils.toByteArray; import static org.apache.hc.core5.http.ContentType.APPLICATION_JSON; import com.sap.cds.CdsData; @@ -22,7 +22,6 @@ import java.io.IOException; import java.util.Set; import lombok.RequiredArgsConstructor; -import org.apache.commons.io.IOUtils; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.StringEntity; import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; @@ -38,24 +37,24 @@ public class MtxSidecarClientImpl implements MtxSidecarClient { private final CdsRuntime cdsRuntime; @Override - public String getOrdDocument() { + public byte[] getOrdDocument() { return doPost(API_PATH_GET_ORD_DOCUMENT, asGetOrdDocumentRequestEntity(getCurrent(cdsRuntime))); } @Override - public String getOrdResourceDefinition(String resource) { + public byte[] getOrdResourceDefinition(String resource) { return doPost( API_PATH_GET_ORD_RESOURCE_DEFINITION, asGetOrdResourceDefinitionRequestEntity(getCurrent(cdsRuntime), resource)); } - private String doPost(String path, HttpEntity entity) { + private byte[] doPost(String path, HttpEntity entity) { try { return getHttpClient(getDestination(MTX_PROVISIONING_SERVICE_DESTINATION)) .execute( ClassicRequestBuilder.post(path).setEntity(entity).build(), - response -> IOUtils.toString( - assertSuccessful(response).getEntity().getContent(), UTF_8)); + response -> toByteArray( + assertSuccessful(response).getEntity().getContent())); } catch (IOException exception) { throw new ServiceException("Request to sidecar failed", exception); } diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/Utils.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/Utils.java index 62aeb61..e6b1988 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/Utils.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/Utils.java @@ -7,11 +7,15 @@ import static com.sap.cds.feature.ord.common.Utils.Exceptions.asLocalizedErrorMessage; import static com.sap.cds.services.utils.CdsErrorStatuses.ERROR_READING_ORD_DOCUMENT; import static java.lang.Thread.currentThread; +import static java.util.Objects.isNull; import static java.util.Objects.nonNull; import static java.util.Spliterator.ORDERED; import static java.util.Spliterators.spliteratorUnknownSize; +import static java.util.stream.StreamSupport.stream; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; +import com.sap.cds.feature.ord.core.OrdJsonInputStream; +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; +import com.sap.cds.feature.ord.core.generators.CdsOrdNodeGenerator; import com.sap.cds.services.ServiceException; import com.sap.cds.services.environment.CdsProperties.Model.Provider; import com.sap.cds.services.environment.CdsProperties.ODataV4; @@ -28,7 +32,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.hc.core5.http.ClassicHttpResponse; @@ -82,12 +85,13 @@ public static ClassicHttpResponse assertSuccessful(ClassicHttpResponse response) public static class Streams { public static Stream asStream(Iterable iterable) { - return StreamSupport.stream(iterable.spliterator(), false); + return stream(iterable.spliterator(), false); } public static List asList(Iterator iterator) { - return StreamSupport.stream(spliteratorUnknownSize(iterator, ORDERED), false) - .toList(); + return isNull(iterator) + ? List.of() + : stream(spliteratorUnknownSize(iterator, ORDERED), false).toList(); } } @@ -106,9 +110,9 @@ public static InputStream getResourceAsStream(String path) { } public static InputStream asOrdJsonInputStream( - InputStream inputStream, List cdsOrdNodeProcessors) { + InputStream inputStream, List generators, List customizers) { try { - return new OrdJsonInputStream(inputStream, cdsOrdNodeProcessors); + return new OrdJsonInputStream(inputStream, generators, customizers); } catch (IOException exception) { throw new ErrorStatusException(ERROR_READING_ORD_DOCUMENT, "documents/ord-document", exception); } diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfiguration.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfiguration.java deleted file mode 100644 index dc81ce1..0000000 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfiguration.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.configuration; - -import static com.sap.cds.feature.ord.common.Utils.Streams.asList; -import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; - -import com.sap.cds.feature.ord.clients.impl.MtxSidecarClientImpl; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; -import com.sap.cds.feature.ord.provider.impl.DynamicOrdResourcesProviderImpl; -import com.sap.cds.feature.ord.provider.impl.StaticOrdResourcesProviderImpl; -import com.sap.cds.services.runtime.CdsRuntime; -import com.sap.cds.services.runtime.CdsRuntimeConfiguration; -import com.sap.cds.services.runtime.CdsRuntimeConfigurer; -import com.sap.cds.services.utils.model.DynamicModelUtils; - -public class OrdResourcesProviderConfiguration implements CdsRuntimeConfiguration { - - @Override - public void providers(CdsRuntimeConfigurer configurer) { - CdsRuntime cdsRuntime = configurer.getCdsRuntime(); - DynamicModelUtils dynamicModelUtils = new DynamicModelUtils(cdsRuntime); - - configurer.provider( - new StaticOrdResourcesProviderImpl(cdsRuntime, asList(loadAll(CdsOrdNodeProcessor.class, cdsRuntime)))); - - if (dynamicModelUtils.isModelProviderEnabled()) { - configurer.provider(new DynamicOrdResourcesProviderImpl( - cdsRuntime, - new MtxSidecarClientImpl(cdsRuntime), - dynamicModelUtils, - asList(loadAll(CdsOrdNodeProcessor.class, cdsRuntime)))); - } - } -} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/OrdJsonInputStream.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/OrdJsonInputStream.java similarity index 56% rename from cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/OrdJsonInputStream.java rename to cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/OrdJsonInputStream.java index 0db30ad..53ff728 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/common/OrdJsonInputStream.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/OrdJsonInputStream.java @@ -1,24 +1,27 @@ /* * © 2026 SAP SE or an SAP affiliate company. All rights reserved. */ -package com.sap.cds.feature.ord.common; +package com.sap.cds.feature.ord.core; import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES; import static com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES; -import static com.fasterxml.jackson.core.JsonToken.END_ARRAY; -import static com.fasterxml.jackson.core.JsonToken.END_OBJECT; -import static com.fasterxml.jackson.core.JsonToken.START_ARRAY; import static com.fasterxml.jackson.databind.DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS; import static com.google.common.primitives.Bytes.asList; +import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.isNull; import static java.util.Objects.nonNull; +import static java.util.stream.Collectors.joining; +import static org.apache.commons.lang3.ObjectUtils.firstNonNull; +import static org.apache.commons.lang3.StringUtils.isEmpty; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; +import com.sap.cds.feature.ord.core.generators.CdsOrdNodeGenerator; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -37,15 +40,19 @@ public class OrdJsonInputStream extends InputStream { private final JsonParser parser; private final InputStream inputStream; - private final List nodeProcessors; + private final List generators; + private final List customizers; private JsonToken token; private JsonToken previous; private final Deque buffer = new ArrayDeque<>(); - public OrdJsonInputStream(InputStream inputStream, List nodeProcessors) throws IOException { + public OrdJsonInputStream( + InputStream inputStream, List generators, List customizers) + throws IOException { this.previous = null; - this.nodeProcessors = List.copyOf(nodeProcessors); + this.generators = List.copyOf(generators); + this.customizers = List.copyOf(customizers); this.inputStream = new BufferedInputStream(inputStream); this.parser = MAPPER.getFactory().createParser(this.inputStream); this.token = this.parser.nextToken(); @@ -68,58 +75,46 @@ public int read() throws IOException { return -1; } - if (isFieldName(token)) { - return handleFieldName(); - } - - if (isStructEnd(token)) { - return handleStructEnd(); - } - - if (isStructStart(token)) { - return handleStructStart(); - } - - return handleFieldValue(); + return switch (token) { + case FIELD_NAME -> handleFieldName(); + case END_ARRAY, END_OBJECT -> handleStructEnd(); + case START_ARRAY, START_OBJECT -> handleStructStart(); + default -> handleFieldValue(); + }; } private void appendToBuffer(String... values) { Arrays.stream(values) // - .forEach(value -> buffer.addAll(asList(value.getBytes(UTF_8)))); + .map(value -> value.getBytes(UTF_8)) // + .forEach(value -> buffer.addAll(asList(value))); } private int handleFieldName() throws IOException { String current = parser.currentName(); - List processors = lookupCdsOrdNodeProcessors(current); - JsonToken next = processors.isEmpty() ? null : parser.nextToken(); + List customizers = lookupCustomizers(parser.getParsingContext()); - appendToBuffer( - isStructStart(previous) ? "" : ",", - "\"", - current, - "\": ", - processors.isEmpty() ? "" : process(current, parser.readValueAsTree(), processors)); + appendToBuffer(isStructStart(previous) ? "" : ",", format("\"%s\": ", current)); + if (!customizers.isEmpty() && nonNull(parser.nextToken())) { + appendToBuffer(process(current, parser.readValueAsTree(), customizers)); + } - previous = next == null ? token : (START_ARRAY == next ? END_ARRAY : END_OBJECT); + previous = firstNonNull(parser.currentToken(), parser.getLastClearedToken()); token = parser.nextToken(); return buffer.removeFirst() & 0xFF; } private int handleStructEnd() throws IOException { - JsonToken next = parser.nextToken(); - - if (isNull(next)) { // We're at the end of the JSON file - nodeProcessors.stream().filter(CdsOrdNodeProcessor::canGenerate).forEach(processor -> processor - .process(null, null) - .ifPresent(node -> - appendToBuffer(", \"" + processor.getGeneratedNodeName() + "\": ", node.toPrettyString()))); - } + JsonStreamContext context = parser.getParsingContext(); + String generated = generators.stream() + .filter(generator -> generator.predicate().test(context)) + .map(generator -> generator.generate(context)) + .collect(joining(", ")); - appendToBuffer(token.asString()); + appendToBuffer((isStructStart(previous) || isEmpty(generated) ? "" : ","), generated, token.asString()); - previous = token; - token = (next != null) ? next : parser.nextToken(); + previous = firstNonNull(parser.currentToken(), parser.getLastClearedToken()); + token = parser.nextToken(); return buffer.removeFirst() & 0xFF; } @@ -127,11 +122,9 @@ private int handleStructEnd() throws IOException { private int handleFieldValue() throws IOException { appendToBuffer( isScalarValue(previous) ? "," : "", - isValueString(token) ? "\"" : "", - isValueString(token) ? parser.getValueAsString() : token.asString(), - isValueString(token) ? "\"" : ""); + !isValueString(token) ? parser.getValueAsString() : format("\"%s\"", parser.getValueAsString())); - previous = token; + previous = firstNonNull(parser.currentToken(), parser.getLastClearedToken()); token = parser.nextToken(); return buffer.removeFirst() & 0xFF; @@ -140,22 +133,18 @@ private int handleFieldValue() throws IOException { private int handleStructStart() throws IOException { appendToBuffer(isStructEnd(previous) ? "," : "", token.asString()); - previous = token; + previous = firstNonNull(parser.currentToken(), parser.getLastClearedToken()); token = parser.nextToken(); return buffer.removeFirst() & 0xFF; } - private List lookupCdsOrdNodeProcessors(String node) { - return nodeProcessors.stream() // - .filter(processor -> processor.predicate().test(node)) // + private List lookupCustomizers(JsonStreamContext context) { + return customizers.stream() // + .filter(processor -> processor.predicate().test(context)) // .toList(); } - private static boolean isFieldName(JsonToken token) { - return nonNull(token) && JsonToken.FIELD_NAME == token; - } - private static boolean isStructEnd(JsonToken token) { return nonNull(token) && token.isStructEnd(); } @@ -172,12 +161,9 @@ private static boolean isStructStart(JsonToken token) { return nonNull(token) && token.isStructStart(); } - private static String process(String name, JsonNode node, List processors) { - return processors.stream() - .reduce( - node, - (current, processor) -> processor.process(name, current).orElse(current), - (a, b) -> a) + private static String process(String name, JsonNode node, List customizers) { + return customizers.stream() + .reduce(node, (current, processor) -> processor.customize(name, current), (a, b) -> a) .toPrettyString(); } } diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/CdsOrdNodeCustomizer.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/CdsOrdNodeCustomizer.java new file mode 100644 index 0000000..6210767 --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/CdsOrdNodeCustomizer.java @@ -0,0 +1,29 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.core.customizers; + +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.sap.cds.services.runtime.CdsRuntimeAware; +import java.util.function.Predicate; + +public interface CdsOrdNodeCustomizer extends CdsRuntimeAware { + + /** + * Returns a predicate that determines whether the customizer should process a node with the given + * name. + * + * @return the predicate + */ + Predicate predicate(); + + /** + * Customize the given node. + * + * @param nodeName the name of the node + * @param node the node to process + * @return the processed node + */ + JsonNode customize(String nodeName, JsonNode node); +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessor.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/EntryPointsCustomizer.java similarity index 56% rename from cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessor.java rename to cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/EntryPointsCustomizer.java index e99df24..1fd7358 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessor.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/EntryPointsCustomizer.java @@ -1,24 +1,30 @@ /* * © 2026 SAP SE or an SAP affiliate company. All rights reserved. */ -package com.sap.cds.feature.ord.processor.impl; +package com.sap.cds.feature.ord.core.customizers.impl; import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOdataV4Properties; import static com.sap.cds.feature.ord.common.Utils.Streams.asStream; -import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; import com.sap.cds.services.runtime.CdsRuntime; +import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; /** - * Processor for the entryPoints attribute in the CDS ORD service that adapts the entry points to + * Customizer for the entryPoints attribute in the CDS ORD service that adapts the entry points to * the configured OData V4 endpoint path. */ -public class EntryPointsProcessor implements CdsOrdNodeProcessor { +@NoArgsConstructor +@AllArgsConstructor +public class EntryPointsCustomizer implements CdsOrdNodeCustomizer { private CdsRuntime cdsRuntime; @@ -28,19 +34,19 @@ public void setCdsRuntime(CdsRuntime cdsRuntime) { } @Override - public Predicate predicate() { - return "entryPoints"::equals; + public Predicate predicate() { + return (context) -> Objects.equals("entryPoints", context.getCurrentName()); } @Override - public Optional process(String nodeName, T entryPoints) { + public JsonNode customize(String nodeName, JsonNode entryPoints) { return Optional.ofNullable(entryPoints) // .map(ArrayNode.class::cast) // - .map(this::process); + .map(this::process) // + .orElse(null); } - @SuppressWarnings("unchecked") - private T process(ArrayNode entryPoints) { + private ArrayNode process(ArrayNode entryPoints) { ArrayNode result = JsonNodeFactory.instance.arrayNode(); String oDataPath = getOdataV4Properties(cdsRuntime).getEndpoint().getPath(); @@ -48,6 +54,6 @@ private T process(ArrayNode entryPoints) { .map(node -> node.asText().replace("/odata/v4", oDataPath)) .forEach(path -> result.add(JsonNodeFactory.instance.textNode(path))); - return (T) result; + return result; } } diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessor.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/ResourceDefinitionsCustomizer.java similarity index 73% rename from cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessor.java rename to cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/ResourceDefinitionsCustomizer.java index 0d63f05..163d925 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessor.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/customizers/impl/ResourceDefinitionsCustomizer.java @@ -1,31 +1,36 @@ /* * © 2026 SAP SE or an SAP affiliate company. All rights reserved. */ -package com.sap.cds.feature.ord.processor.impl; +package com.sap.cds.feature.ord.core.customizers.impl; import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; import static com.sap.cds.feature.ord.common.Utils.Streams.asStream; import static org.apache.commons.io.FilenameUtils.concat; -import com.fasterxml.jackson.core.TreeNode; +import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; import com.sap.cds.feature.ord.provider.AuthenticationManagerProvider; import com.sap.cds.services.runtime.CdsRuntime; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; /** * Processor for resource definitions in the CDS ORD service that (a) removes all openApi resource * definitions because there is no runtime support for openApi documents (b) replaces * "accessStrategies" element to comply with the configured security model for metadata endpoints. */ -public class ResourceDefinitionsProcessor implements CdsOrdNodeProcessor { +@NoArgsConstructor +@AllArgsConstructor +public class ResourceDefinitionsCustomizer implements CdsOrdNodeCustomizer { private CdsRuntime cdsRuntime; @@ -35,19 +40,19 @@ public void setCdsRuntime(CdsRuntime cdsRuntime) { } @Override - public Predicate predicate() { - return "resourceDefinitions"::equals; + public Predicate predicate() { + return (context) -> Objects.equals("resourceDefinitions", context.getCurrentName()); } @Override - public Optional process(String nodeName, T resourceDefinitions) { - return Optional.ofNullable(resourceDefinitions) // + public JsonNode customize(String nodeName, JsonNode node) { + return Optional.ofNullable(node) // .map(ArrayNode.class::cast) // - .map(this::process); + .map(this::process) + .orElse(null); } - @SuppressWarnings("unchecked") - private T process(ArrayNode resourceDefinitions) { + private ArrayNode process(ArrayNode resourceDefinitions) { ArrayNode result = JsonNodeFactory.instance.arrayNode(); String apiRoot = getOrdProperties(cdsRuntime).getDocumentsEndpoint().getPath(); AuthenticationManagerProvider provider = cdsRuntime.getProvider(AuthenticationManagerProvider.class); @@ -62,14 +67,14 @@ private T process(ArrayNode resourceDefinitions) { asTextNode(concat( apiRoot, resourceDefinition.get("url").asText()))))); - return (T) result; + return result; } private static TextNode asTextNode(String value) { return JsonNodeFactory.instance.textNode(value); } - private static JsonNode asAccessStrategiesNode(List accessStrategies) { + private static ArrayNode asAccessStrategiesNode(List accessStrategies) { return JsonNodeFactory.instance .arrayNode() .addAll(accessStrategies.stream() diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/generators/CdsOrdNodeGenerator.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/generators/CdsOrdNodeGenerator.java new file mode 100644 index 0000000..8eea74c --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/core/generators/CdsOrdNodeGenerator.java @@ -0,0 +1,27 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.core.generators; + +import com.fasterxml.jackson.core.JsonStreamContext; +import com.sap.cds.services.runtime.CdsRuntimeAware; +import java.util.function.Predicate; + +public interface CdsOrdNodeGenerator extends CdsRuntimeAware { + + /** + * Returns a predicate that determines whether the generator should generate a node with the given + * name. + * + * @return the predicate + */ + Predicate predicate(); + + /** + * Generates a node based on the given context. + * + * @param context the JSON stream context + * @return the generated node as a string + */ + String generate(JsonStreamContext context); +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/CdsOrdNodeProcessor.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/CdsOrdNodeProcessor.java deleted file mode 100644 index be09591..0000000 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/processor/CdsOrdNodeProcessor.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.processor; - -import com.fasterxml.jackson.core.TreeNode; -import com.sap.cds.services.runtime.CdsRuntimeAware; -import java.util.Optional; -import java.util.function.Predicate; - -public interface CdsOrdNodeProcessor extends CdsRuntimeAware { - - /** - * Returns a predicate that determines whether the processor should process a node with the given - * name. - * - * @return the predicate - */ - Predicate predicate(); - - /** - * Returns whether the processor can also generate a node. - * - * @return {@code true} if the processor can generate a node, {@code false} otherwise - */ - default boolean canGenerate() { - return false; - } - - /** - * Returns the name of the generated node. - * - *

By default null is returned. A specific implementation can return a name to generate a node - * on the root level of the ORD, if canGenerate() returns true. - * - * @return the name of the generated node - */ - default String getGeneratedNodeName() { - return null; - } - - /** - * Processes the given node. - * - * @param nodeName the name of the node - * @param node the node to process - * @return the processed node - */ - Optional process(String nodeName, T node); -} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/OrdResourcesProvider.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/OrdResourcesProvider.java deleted file mode 100644 index 4125d58..0000000 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/OrdResourcesProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.provider; - -import com.sap.cds.feature.ord.model.OrdDocument; -import com.sap.cds.services.runtime.CdsProvider; -import java.io.InputStream; - -public interface OrdResourcesProvider extends CdsProvider { - - /** - * Provides the {@link OrdDocument} for the given CAP service. - * - * @return InputStream - */ - InputStream read(String resource, String perspective); -} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImpl.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImpl.java deleted file mode 100644 index bbffe05..0000000 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.provider.impl; - -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; -import static com.sap.cds.feature.ord.common.Utils.Resources.asOrdJsonInputStream; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.commons.io.FilenameUtils.getBaseName; -import static org.apache.commons.io.FilenameUtils.getExtension; - -import com.sap.cds.adapter.edmx.EdmxV4Provider; -import com.sap.cds.feature.ord.clients.MtxSidecarClient; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; -import com.sap.cds.feature.ord.provider.OrdResourcesProvider; -import com.sap.cds.services.runtime.CdsRuntime; -import com.sap.cds.services.utils.model.DynamicModelUtils; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class DynamicOrdResourcesProviderImpl implements OrdResourcesProvider { - - private static final String EDMX_EXTENSION = "edmx"; - private static final String ORD_DOCUMENT_PATH = "documents/ord-document"; - - private final CdsRuntime cdsRuntime; - private final MtxSidecarClient mtxSidecarClient; - private final DynamicModelUtils dynamicModelUtils; - private final List cdsOrdNodeProcessors; - - private OrdResourcesProvider previous; - - @Override - public void setPrevious(OrdResourcesProvider previous) { - this.previous = previous; - } - - @Override - public InputStream read(String resource, String perspective) { - return !dynamicModelUtils.useStaticModel() && Objects.equals(PERSPECTIVE_SYSTEM_INSTANCE, perspective) - ? this.loadResource(resource) - : this.previous.read(resource, perspective); - } - - private InputStream loadResource(String resource) { - if (EDMX_EXTENSION.equalsIgnoreCase(getExtension(resource))) { - return cdsRuntime.getProvider(EdmxV4Provider.class).getEdmx(getBaseName(resource)); - } - - return this.loadFromMTXSidecar(resource); - } - - private InputStream loadFromMTXSidecar(String resource) { - return Objects.equals(ORD_DOCUMENT_PATH, resource) - ? asOrdJsonInputStream( - new ByteArrayInputStream( - mtxSidecarClient.getOrdDocument().getBytes(UTF_8)), - cdsOrdNodeProcessors) - : new ByteArrayInputStream( - mtxSidecarClient.getOrdResourceDefinition(resource).getBytes(UTF_8)); - } -} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImpl.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImpl.java deleted file mode 100644 index 3eced42..0000000 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImpl.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.provider.impl; - -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; -import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; -import static com.sap.cds.feature.ord.common.Utils.Resources.asOrdJsonInputStream; -import static com.sap.cds.feature.ord.common.Utils.Resources.getResourceAsStream; -import static java.util.Objects.nonNull; -import static org.apache.commons.io.FilenameUtils.concat; -import static org.apache.commons.io.FilenameUtils.getBaseName; -import static org.apache.commons.io.FilenameUtils.getExtension; -import static org.apache.commons.io.FilenameUtils.normalize; - -import com.sap.cds.adapter.edmx.EdmxV4Provider; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; -import com.sap.cds.feature.ord.provider.OrdResourcesProvider; -import com.sap.cds.services.runtime.CdsRuntime; -import java.io.InputStream; -import java.util.List; -import java.util.Objects; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class StaticOrdResourcesProviderImpl implements OrdResourcesProvider { - - private static final String EDMX_EXTENSION = "edmx"; - - private final CdsRuntime cdsRuntime; - private final List cdsOrdNodeProcessors; - - @Override - public InputStream read(String resource, String perspective) { - if (nonNull(perspective) && !Objects.equals(PERSPECTIVE_SYSTEM_VERSION, perspective)) { - throw new IllegalArgumentException("Perspective '%s' not supported".formatted(perspective)); - } - - String normalized = normalize(resource.replace(":", "_")); - String ordResourcesRoot = getOrdProperties(cdsRuntime).getOrdResourcesRoot(); - String ordDocumentRelativePath = getOrdProperties(cdsRuntime).getOrdDocumentPath(); - String ordDocumentAbsolutePath = concat(ordResourcesRoot, ordDocumentRelativePath); - - return Objects.equals("documents/ord-document", normalized) - ? asOrdJsonInputStream(getResourceAsStream(ordDocumentAbsolutePath), cdsOrdNodeProcessors) - : loadResourceAsStream(concat(ordResourcesRoot, normalized)); - } - - private InputStream loadResourceAsStream(String path) { - if (EDMX_EXTENSION.equalsIgnoreCase(getExtension(path))) { - return cdsRuntime.getProvider(EdmxV4Provider.class).getEdmx(getBaseName(path)); - } - - return getResourceAsStream(path); - } -} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/OrdResolver.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/OrdResolver.java new file mode 100644 index 0000000..80ce202 --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/OrdResolver.java @@ -0,0 +1,13 @@ +package com.sap.cds.feature.ord.resolver; + +import com.sap.cds.services.runtime.CdsRuntimeAware; +import java.io.InputStream; + +public interface OrdResolver extends CdsRuntimeAware { + + int order(); + + InputStream resolve(String resource); + + boolean isApplicable(String resource, String perspective); +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdDocumentResolver.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdDocumentResolver.java new file mode 100644 index 0000000..dfb0931 --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdDocumentResolver.java @@ -0,0 +1,59 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.resolver.impl; + +import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; +import static com.sap.cds.feature.ord.common.Utils.Resources.asOrdJsonInputStream; +import static com.sap.cds.feature.ord.common.Utils.Streams.asList; +import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; +import static org.apache.commons.io.FilenameUtils.normalize; + +import com.sap.cds.feature.ord.clients.MtxSidecarClient; +import com.sap.cds.feature.ord.clients.impl.MtxSidecarClientImpl; +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; +import com.sap.cds.feature.ord.core.generators.CdsOrdNodeGenerator; +import com.sap.cds.feature.ord.resolver.OrdResolver; +import com.sap.cds.services.runtime.CdsRuntime; +import com.sap.cds.services.utils.model.DynamicModelUtils; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class DynamicOrdDocumentResolver implements OrdResolver { + + private CdsRuntime runtime; + private MtxSidecarClient mtxSidecarClient; + private DynamicModelUtils dynamicModelUtils; + + @Override + public void setCdsRuntime(CdsRuntime runtime) { + this.runtime = runtime; + this.dynamicModelUtils = new DynamicModelUtils(runtime); + this.mtxSidecarClient = new MtxSidecarClientImpl(runtime); + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public InputStream resolve(String resource) { + return asOrdJsonInputStream( + new ByteArrayInputStream(mtxSidecarClient.getOrdDocument()), + asList(loadAll(CdsOrdNodeGenerator.class, runtime)), + asList(loadAll(CdsOrdNodeCustomizer.class, runtime))); + } + + @Override + public boolean isApplicable(String resource, String perspective) { + return !dynamicModelUtils.useStaticModel() // + && Objects.equals(PERSPECTIVE_SYSTEM_INSTANCE, perspective) // + && Objects.equals("documents/ord-document", normalize(resource.replace(":", "_"))); + } +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdResourceDefinitionResolver.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdResourceDefinitionResolver.java new file mode 100644 index 0000000..bca2785 --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/DynamicOrdResourceDefinitionResolver.java @@ -0,0 +1,49 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.resolver.impl; + +import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; +import static org.apache.commons.io.FilenameUtils.normalize; + +import com.sap.cds.feature.ord.clients.MtxSidecarClient; +import com.sap.cds.feature.ord.clients.impl.MtxSidecarClientImpl; +import com.sap.cds.feature.ord.resolver.OrdResolver; +import com.sap.cds.services.runtime.CdsRuntime; +import com.sap.cds.services.utils.model.DynamicModelUtils; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class DynamicOrdResourceDefinitionResolver implements OrdResolver { + + private MtxSidecarClient mtxSidecarClient; + private DynamicModelUtils dynamicModelUtils; + + @Override + public void setCdsRuntime(CdsRuntime runtime) { + this.dynamicModelUtils = new DynamicModelUtils(runtime); + this.mtxSidecarClient = new MtxSidecarClientImpl(runtime); + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public InputStream resolve(String resource) { + return new ByteArrayInputStream(mtxSidecarClient.getOrdResourceDefinition(resource)); + } + + @Override + public boolean isApplicable(String resource, String perspective) { + return !dynamicModelUtils.useStaticModel() // + && Objects.equals(PERSPECTIVE_SYSTEM_INSTANCE, perspective) // + && !Objects.equals("documents/ord-document", normalize(resource.replace(":", "_"))); + } +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdDocumentResolver.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdDocumentResolver.java new file mode 100644 index 0000000..08407a3 --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdDocumentResolver.java @@ -0,0 +1,56 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.resolver.impl; + +import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; +import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; +import static com.sap.cds.feature.ord.common.Utils.Resources.asOrdJsonInputStream; +import static com.sap.cds.feature.ord.common.Utils.Resources.getResourceAsStream; +import static com.sap.cds.feature.ord.common.Utils.Streams.asList; +import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; +import static org.apache.commons.io.FilenameUtils.concat; +import static org.apache.commons.io.FilenameUtils.normalize; + +import com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer; +import com.sap.cds.feature.ord.core.generators.CdsOrdNodeGenerator; +import com.sap.cds.feature.ord.resolver.OrdResolver; +import com.sap.cds.services.environment.CdsProperties.OpenResourceDiscovery; +import com.sap.cds.services.runtime.CdsRuntime; +import java.io.InputStream; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +public class StaticOrdDocumentResolver implements OrdResolver { + + private CdsRuntime runtime; + + @Override + public void setCdsRuntime(CdsRuntime runtime) { + this.runtime = runtime; + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public InputStream resolve(String resource) { + OpenResourceDiscovery properties = getOrdProperties(runtime); + + return asOrdJsonInputStream( + getResourceAsStream(concat(properties.getOrdResourcesRoot(), properties.getOrdDocumentPath())), + asList(loadAll(CdsOrdNodeGenerator.class, runtime)), + asList(loadAll(CdsOrdNodeCustomizer.class, runtime))); + } + + @Override + public boolean isApplicable(String resource, String perspective) { + return Objects.equals(PERSPECTIVE_SYSTEM_VERSION, perspective) + && Objects.equals("documents/ord-document", normalize(resource.replace(":", "_"))); + } +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdResourceDefinitionResolver.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdResourceDefinitionResolver.java new file mode 100644 index 0000000..7d26c1d --- /dev/null +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/resolver/impl/StaticOrdResourceDefinitionResolver.java @@ -0,0 +1,61 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.resolver.impl; + +import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; +import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; +import static com.sap.cds.feature.ord.common.Utils.Resources.getResourceAsStream; +import static com.sap.cds.feature.ord.common.Utils.Resources.resourceExists; +import static org.apache.commons.io.FilenameUtils.concat; +import static org.apache.commons.io.FilenameUtils.getBaseName; +import static org.apache.commons.io.FilenameUtils.getExtension; +import static org.apache.commons.io.FilenameUtils.normalize; +import static org.apache.commons.lang3.StringUtils.isEmpty; + +import com.sap.cds.adapter.edmx.EdmxV4Provider; +import com.sap.cds.feature.ord.resolver.OrdResolver; +import com.sap.cds.services.runtime.CdsRuntime; +import java.io.InputStream; +import java.util.Objects; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class StaticOrdResourceDefinitionResolver implements OrdResolver { + + private static final String EDMX_EXTENSION = "edmx"; + + private CdsRuntime runtime; + + @Override + public void setCdsRuntime(CdsRuntime runtime) { + this.runtime = runtime; + } + + @Override + public int order() { + return Integer.MAX_VALUE; + } + + @Override + public InputStream resolve(String resource) { + String path = resolveResourcePath(resource); + + if (EDMX_EXTENSION.equalsIgnoreCase(getExtension(path))) { + return runtime.getProvider(EdmxV4Provider.class).getEdmx(getBaseName(path)); + } + + return getResourceAsStream(path); + } + + @Override + public boolean isApplicable(String resource, String perspective) { + return resourceExists(resolveResourcePath(resource)) + && (isEmpty(perspective) || Objects.equals(PERSPECTIVE_SYSTEM_VERSION, perspective)) + && !Objects.equals("documents/ord-document", normalize(resource.replace(":", "_"))); + } + + private String resolveResourcePath(String resource) { + return concat(getOrdProperties(runtime).getOrdResourcesRoot(), normalize(resource.replace(":", "_"))); + } +} diff --git a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/servlet/DocumentsServlet.java b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/servlet/DocumentsServlet.java index 64df3f0..fbab1a2 100644 --- a/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/servlet/DocumentsServlet.java +++ b/cds-feature-ord/src/main/java/com/sap/cds/feature/ord/servlet/DocumentsServlet.java @@ -6,15 +6,20 @@ import static com.sap.cds.feature.ord.common.Constants.HEADER_LOCAL_TENANT_ID; import static com.sap.cds.feature.ord.common.Utils.Http.handleException; import static com.sap.cds.feature.ord.common.Utils.Http.header; -import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static com.sap.cds.feature.ord.common.Utils.Streams.asList; +import static com.sap.cds.services.ErrorStatuses.NOT_FOUND; +import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; import static jakarta.servlet.http.HttpServletResponse.SC_OK; +import static java.util.Comparator.comparingInt; import static org.apache.commons.io.FilenameUtils.getExtension; +import static org.apache.commons.lang3.StringUtils.firstNonEmpty; import static org.apache.http.entity.ContentType.APPLICATION_JSON; import static org.apache.http.entity.ContentType.APPLICATION_XML; import static org.apache.http.entity.ContentType.TEXT_PLAIN; import com.sap.cds.feature.ord.provider.AuthenticationManagerProvider; -import com.sap.cds.feature.ord.provider.OrdResourcesProvider; +import com.sap.cds.feature.ord.resolver.OrdResolver; +import com.sap.cds.services.ServiceException; import com.sap.cds.services.runtime.CdsRuntime; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; @@ -60,16 +65,14 @@ private void process(HttpServletRequest request, HttpServletResponse response) { private void processDocument(HttpServletRequest request, HttpServletResponse response) throws IOException { String path = normalize(request.getPathInfo()); - String perspective = request.getParameter("perspective"); ContentType contentType = determineContentType(request.getPathInfo()); - OrdResourcesProvider ordResourcesProvider = cdsRuntime.getProvider(OrdResourcesProvider.class); - - try (InputStream stream = ordResourcesProvider.read(path, perspective)) { - if (stream == null) { - response.setStatus(SC_NOT_FOUND); - return; - } + String perspective = firstNonEmpty(request.getParameter("perspective"), "system-version"); + OrdResolver resolver = asList(loadAll(OrdResolver.class, cdsRuntime)).stream() + .filter(provider -> provider.isApplicable(path, perspective)) + .min(comparingInt(OrdResolver::order)) + .orElseThrow(() -> new ServiceException(NOT_FOUND, "Not Found")); + try (InputStream stream = resolver.resolve(path)) { response.setStatus(SC_OK); response.setContentType(contentType.getMimeType()); response.setCharacterEncoding(contentType.getCharset().toString()); diff --git a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer new file mode 100644 index 0000000..ea5f2a1 --- /dev/null +++ b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.core.customizers.CdsOrdNodeCustomizer @@ -0,0 +1,2 @@ +com.sap.cds.feature.ord.core.customizers.impl.EntryPointsCustomizer +com.sap.cds.feature.ord.core.customizers.impl.ResourceDefinitionsCustomizer diff --git a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor deleted file mode 100644 index 600a65a..0000000 --- a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor +++ /dev/null @@ -1,2 +0,0 @@ -com.sap.cds.feature.ord.processor.impl.EntryPointsProcessor -com.sap.cds.feature.ord.processor.impl.ResourceDefinitionsProcessor diff --git a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.resolver.OrdResolver b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.resolver.OrdResolver new file mode 100644 index 0000000..683793d --- /dev/null +++ b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.feature.ord.resolver.OrdResolver @@ -0,0 +1,4 @@ +com.sap.cds.feature.ord.resolver.impl.StaticOrdDocumentResolver +com.sap.cds.feature.ord.resolver.impl.DynamicOrdDocumentResolver +com.sap.cds.feature.ord.resolver.impl.StaticOrdResourceDefinitionResolver +com.sap.cds.feature.ord.resolver.impl.DynamicOrdResourceDefinitionResolver \ No newline at end of file diff --git a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration index e0ac634..3ef5396 100644 --- a/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration +++ b/cds-feature-ord/src/main/resources/META-INF/services/com.sap.cds.services.runtime.CdsRuntimeConfiguration @@ -1,2 +1 @@ -com.sap.cds.feature.ord.configuration.OrdResourcesProviderConfiguration com.sap.cds.feature.ord.configuration.AuthenticationManagerProviderConfiguration \ No newline at end of file diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/client/impl/MtxSidecarClientImplTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/client/impl/MtxSidecarClientImplTest.java index 1ba4b90..00229ed 100644 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/client/impl/MtxSidecarClientImplTest.java +++ b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/client/impl/MtxSidecarClientImplTest.java @@ -105,8 +105,8 @@ void whenGetOrdDocumentIsCalled_thenCorrectResultIsReturned() throws Throwable { .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedDestinationAccessor( - () -> withMockedApacheHttpClient5Accessor(() -> assertEquals("{}", classUnderTest.getOrdDocument()))); + withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor( + () -> assertEquals("{}", new String(classUnderTest.getOrdDocument(), UTF_8)))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -196,8 +196,8 @@ void givenThatExtensibilityIsEnabled_whenGetOrdDocumentIsCalled_thenCorrectResul .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor( - () -> withMockedApacheHttpClient5Accessor(() -> assertEquals("{}", classUnderTest.getOrdDocument())))); + withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor( + () -> assertEquals("{}", new String(classUnderTest.getOrdDocument(), UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -233,8 +233,8 @@ void givenThatTogglesAreEnabled_whenGetOrdDocumentIsCalled_thenCorrectResultIsRe .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor( - () -> withMockedApacheHttpClient5Accessor(() -> assertEquals("{}", classUnderTest.getOrdDocument())))); + withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor( + () -> assertEquals("{}", new String(classUnderTest.getOrdDocument(), UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -270,8 +270,8 @@ void givenThatTogglesAreEnabled_and_extensibilityIsEnabled_whenGetOrdDocumentIsC .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor( - () -> withMockedApacheHttpClient5Accessor(() -> assertEquals("{}", classUnderTest.getOrdDocument())))); + withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor( + () -> assertEquals("{}", new String(classUnderTest.getOrdDocument(), UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(userInfo).getTenant(); @@ -308,8 +308,9 @@ void whenGetOrdResourceDefinitionIsCalled_thenCorrectResultIsReturned() throws T .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> - assertEquals("{}", classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json")))); + withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> assertEquals( + "{}", + new String(classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"), UTF_8)))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -408,8 +409,12 @@ void givenThatExtensibilityIsEnabled_whenGetOrdResourceDefinitionIsCalled_thenCo .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> - assertEquals("{}", classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"))))); + withMockedRequestContext( + () -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> assertEquals( + "{}", + new String( + classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"), + UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -447,8 +452,12 @@ void givenThatTogglesAreEnabled_whenGetOrdResourceDefinitionIsCalled_thenCorrect .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> - assertEquals("{}", classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"))))); + withMockedRequestContext( + () -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> assertEquals( + "{}", + new String( + classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"), + UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); @@ -486,8 +495,12 @@ void givenThatTogglesAreEnabled_whenGetOrdResourceDefinitionIsCalled_thenCorrect .thenAnswer( in -> in.getArgument(1, HttpClientResponseHandler.class).handleResponse(classicHttpResponse)); - withMockedRequestContext(() -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> - assertEquals("{}", classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"))))); + withMockedRequestContext( + () -> withMockedDestinationAccessor(() -> withMockedApacheHttpClient5Accessor(() -> assertEquals( + "{}", + new String( + classUnderTest.getOrdResourceDefinition("dummy:ord:definition/Dummy.oas3.json"), + UTF_8))))); verifyNoOtherMockInteractions(() -> { verify(provider).isToggles(); diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfigurationTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfigurationTest.java deleted file mode 100644 index b247589..0000000 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/configuration/OrdResourcesProviderConfigurationTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.configuration; - -import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; -import com.sap.cds.feature.ord.provider.impl.DynamicOrdResourcesProviderImpl; -import com.sap.cds.feature.ord.provider.impl.StaticOrdResourcesProviderImpl; -import com.sap.cds.services.environment.CdsEnvironment; -import com.sap.cds.services.environment.CdsProperties; -import com.sap.cds.services.environment.CdsProperties.Model; -import com.sap.cds.services.environment.CdsProperties.Model.Provider; -import com.sap.cds.services.environment.CdsProperties.MultiTenancy; -import com.sap.cds.services.environment.CdsProperties.MultiTenancy.Sidecar; -import com.sap.cds.services.runtime.CdsRuntime; -import com.sap.cds.services.runtime.CdsRuntimeConfigurer; -import com.sap.cds.services.runtime.ExtendedServiceLoader; -import java.util.Collections; -import java.util.List; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockedStatic; - -class OrdResourcesProviderConfigurationTest { - - private Model model; - private Sidecar sidecar; - private Provider provider; - private CdsRuntime cdsRuntime; - private MultiTenancy multiTenancy; - private CdsProperties cdsProperties; - private CdsEnvironment cdsEnvironment; - private CdsRuntimeConfigurer cdsRuntimeConfigurer; - - private OrdResourcesProviderConfiguration classUnderTest; - - @BeforeEach - void setUp() { - model = mock(Model.class); - sidecar = mock(Sidecar.class); - provider = mock(Provider.class); - cdsRuntime = mock(CdsRuntime.class); - multiTenancy = mock(MultiTenancy.class); - cdsProperties = mock(CdsProperties.class); - cdsEnvironment = mock(CdsEnvironment.class); - cdsRuntimeConfigurer = mock(CdsRuntimeConfigurer.class); - - classUnderTest = new OrdResourcesProviderConfiguration(); - - doReturn(provider).when(model).getProvider(); - doReturn(model).when(cdsProperties).getModel(); - doReturn(sidecar).when(multiTenancy).getSidecar(); - doReturn(cdsEnvironment).when(cdsRuntime).getEnvironment(); - doReturn(multiTenancy).when(cdsProperties).getMultiTenancy(); - doReturn(cdsRuntime).when(cdsRuntimeConfigurer).getCdsRuntime(); - doReturn(cdsProperties).when(cdsEnvironment).getCdsProperties(); - } - - @Test - void givenDynamicModelsNotSupported_whenProvidersIsCalled_thenNoOrdResourceProviderIsRegistered() { - try (MockedStatic extendedServiceLoader = mockStatic(ExtendedServiceLoader.class)) { - extendedServiceLoader - .when(() -> loadAll(CdsOrdNodeProcessor.class, cdsRuntime)) - .thenReturn(Collections.emptyIterator()); - - classUnderTest.providers(cdsRuntimeConfigurer); - } - - verify(sidecar).getUrl(); - verify(provider).getUrl(); - verify(model).getProvider(); - verify(cdsProperties).getModel(); - verify(multiTenancy).getSidecar(); - verify(cdsProperties).getMultiTenancy(); - verify(cdsRuntimeConfigurer).getCdsRuntime(); - verify(cdsRuntime, times(2)).getEnvironment(); - verify(cdsEnvironment, times(2)).getCdsProperties(); - verify(cdsRuntimeConfigurer).provider(any(StaticOrdResourcesProviderImpl.class)); - verifyNoMoreInteractions( - model, - sidecar, - provider, - cdsRuntime, - multiTenancy, - cdsProperties, - cdsEnvironment, - cdsRuntimeConfigurer); - } - - @Test - void givenDynamicModelsAreSupported_whenProvidersIsCalled_thenCorrectProvidersAreRegistered() { - doReturn(Boolean.TRUE).when(provider).isExtensibility(); - doReturn("https://dummy.provider.sap.com/").when(provider).getUrl(); - - try (MockedStatic extendedServiceLoader = mockStatic(ExtendedServiceLoader.class)) { - extendedServiceLoader - .when(() -> loadAll(CdsOrdNodeProcessor.class, cdsRuntime)) - .thenAnswer(in -> List.of(mock(CdsOrdNodeProcessor.class)).iterator()); - - classUnderTest.providers(cdsRuntimeConfigurer); - } - - verify(model).getProvider(); - verify(cdsProperties).getModel(); - verify(multiTenancy).getSidecar(); - verify(provider).isExtensibility(); - verify(cdsProperties).getMultiTenancy(); - verify(cdsRuntimeConfigurer).getCdsRuntime(); - verify(provider, times(2)).getUrl(); - verify(cdsRuntime, times(2)).getEnvironment(); - verify(cdsEnvironment, times(2)).getCdsProperties(); - verify(cdsRuntimeConfigurer).provider(any(StaticOrdResourcesProviderImpl.class)); - verify(cdsRuntimeConfigurer).provider(any(DynamicOrdResourcesProviderImpl.class)); - verifyNoMoreInteractions( - model, - sidecar, - provider, - cdsRuntime, - multiTenancy, - cdsProperties, - cdsEnvironment, - cdsRuntimeConfigurer); - } -} diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/common/OrdJsonInputStreamTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/OrdJsonInputStreamTest.java similarity index 77% rename from cds-feature-ord/src/test/java/com/sap/cds/feature/ord/common/OrdJsonInputStreamTest.java rename to cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/OrdJsonInputStreamTest.java index 1f95631..f776cb9 100644 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/common/OrdJsonInputStreamTest.java +++ b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/OrdJsonInputStreamTest.java @@ -1,13 +1,10 @@ /* * © 2026 SAP SE or an SAP affiliate company. All rights reserved. */ -package com.sap.cds.feature.ord.common; +package com.sap.cds.feature.ord.core; -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; import static com.sap.cds.feature.ord.common.Utils.Resources.getResourceAsStream; -import static com.sap.cds.feature.ord.common.Utils.Streams.asList; -import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; import static com.sap.cds.services.utils.cert.UclAuthUtils.AccessStrategy.MTLS; import static org.apache.commons.io.FilenameUtils.concat; import static org.apache.commons.io.FilenameUtils.getBaseName; @@ -15,9 +12,8 @@ import static org.mockito.Mockito.mock; import com.fasterxml.jackson.databind.JsonNode; -import com.sap.cds.feature.ord.processor.CdsOrdNodeProcessor; import com.sap.cds.feature.ord.provider.AuthenticationManagerProvider; -import com.sap.cds.feature.ord.provider.impl.StaticOrdResourcesProviderImpl; +import com.sap.cds.feature.ord.resolver.impl.StaticOrdDocumentResolver; import com.sap.cds.impl.parser.JsonParser; import com.sap.cds.services.runtime.CdsRuntime; import com.sap.cds.services.runtime.CdsRuntimeConfigurer; @@ -48,10 +44,9 @@ void setUp() { void processingOrdDocumentIsSuccessful() throws IOException { String ordDocumentPath = getOrdProperties(cdsRuntime).getOrdDocumentPath(); String document = concat("documents", getBaseName(ordDocumentPath)); - StaticOrdResourcesProviderImpl provider = - new StaticOrdResourcesProviderImpl(cdsRuntime, asList(loadAll(CdsOrdNodeProcessor.class, cdsRuntime))); + StaticOrdDocumentResolver provider = new StaticOrdDocumentResolver(cdsRuntime); - try (InputStream is = provider.read(document, PERSPECTIVE_SYSTEM_VERSION)) { + try (InputStream is = provider.resolve(document)) { JsonNode actual = JsonParser.parseJson(new String(is.readAllBytes())); JsonNode expected = load("ord/ord-document-target.json"); diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/EntryPointsProcessorTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/EntryPointsProcessorTest.java new file mode 100644 index 0000000..e70176c --- /dev/null +++ b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/EntryPointsProcessorTest.java @@ -0,0 +1,65 @@ +/* + * © 2026 SAP SE or an SAP affiliate company. All rights reserved. + */ +package com.sap.cds.feature.ord.core.impl; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import com.fasterxml.jackson.core.JsonStreamContext; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.TextNode; +import com.sap.cds.feature.ord.core.customizers.impl.EntryPointsCustomizer; +import com.sap.cds.services.environment.CdsProperties; +import com.sap.cds.services.impl.environment.SimplePropertiesProvider; +import com.sap.cds.services.runtime.CdsRuntime; +import com.sap.cds.services.runtime.CdsRuntimeConfigurer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class EntryPointsProcessorTest { + + private CdsRuntime runtime; + private JsonStreamContext jsonStreamContext; + private EntryPointsCustomizer classUnderTest; + + @BeforeEach + void setUp() { + CdsProperties properties = new CdsProperties(); + properties.getOdataV4().getEndpoint().setPath("/api"); + + this.jsonStreamContext = mock(JsonStreamContext.class); + this.runtime = CdsRuntimeConfigurer.create(new SimplePropertiesProvider(properties)) + .serviceConfigurations() + .eventHandlerConfigurations() + .complete(); + + this.classUnderTest = new EntryPointsCustomizer(this.runtime); + } + + @Test + void testPredicate() { + doReturn("entryPoints").when(jsonStreamContext).getCurrentName(); + + assertThat(this.classUnderTest.predicate().test(this.jsonStreamContext), is(true)); + } + + @Test + void testPredicate_Negative() { + doReturn("resourceDefinitions").when(jsonStreamContext).getCurrentName(); + + assertThat(this.classUnderTest.predicate().test(this.jsonStreamContext), is(false)); + } + + @Test + void testProcess() { + ArrayNode node = JsonNodeFactory.instance.arrayNode().add(new TextNode("/odata/v4/admin")); + + var entryPoints = this.classUnderTest.customize("entryPoints", node); + + assertThat(entryPoints.toString(), is("[\"/api/admin\"]")); + } +} diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessorTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/ResourceDefinitionsCustomizerTest.java similarity index 57% rename from cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessorTest.java rename to cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/ResourceDefinitionsCustomizerTest.java index aebe8c6..0cd3251 100644 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/ResourceDefinitionsProcessorTest.java +++ b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/core/impl/ResourceDefinitionsCustomizerTest.java @@ -1,7 +1,7 @@ /* * © 2026 SAP SE or an SAP affiliate company. All rights reserved. */ -package com.sap.cds.feature.ord.processor.impl; +package com.sap.cds.feature.ord.core.impl; import static com.sap.cds.feature.ord.common.Utils.Resources.getResourceAsStream; import static com.sap.cds.services.utils.cert.UclAuthUtils.AccessStrategy.MTLS; @@ -12,28 +12,32 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.sap.cds.feature.ord.core.customizers.impl.ResourceDefinitionsCustomizer; import com.sap.cds.feature.ord.provider.AuthenticationManagerProvider; import com.sap.cds.impl.parser.JsonParser; import com.sap.cds.services.runtime.CdsRuntime; import com.sap.cds.services.runtime.CdsRuntimeConfigurer; import java.io.IOException; import java.util.List; -import java.util.function.Predicate; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -class ResourceDefinitionsProcessorTest { +class ResourceDefinitionsCustomizerTest { private ObjectNode ord; private CdsRuntime runtime; private ArrayNode resourceDefinitions; + private JsonStreamContext jsonStreamContext; + private ResourceDefinitionsCustomizer classUnderTest; private AuthenticationManagerProvider authenticationManagerProvider; @BeforeEach - void prepare() throws IOException { + void setUp() throws IOException { + this.jsonStreamContext = mock(JsonStreamContext.class); this.authenticationManagerProvider = mock(AuthenticationManagerProvider.class); this.ord = JsonParser.parseJson(IOUtils.toString(getResourceAsStream("ord/ord-document.json"), UTF_8)); this.resourceDefinitions = (ArrayNode) ord.at("/apiResources/0/resourceDefinitions"); @@ -42,38 +46,32 @@ void prepare() throws IOException { .eventHandlerConfigurations() .provider(authenticationManagerProvider) .complete(); + + this.classUnderTest = new ResourceDefinitionsCustomizer(this.runtime); } @Test void testPredicate() { - var accessStrategyProcessor = new ResourceDefinitionsProcessor(); - accessStrategyProcessor.setCdsRuntime(this.runtime); - Predicate predicate = accessStrategyProcessor.predicate(); + doReturn("resourceDefinitions").when(jsonStreamContext).getCurrentName(); - assertThat(predicate.test("resourceDefinitions"), is(true)); + assertThat(classUnderTest.predicate().test(jsonStreamContext), is(true)); } @Test void testPredicate_Negative() { - var accessStrategyProcessor = new ResourceDefinitionsProcessor(); - accessStrategyProcessor.setCdsRuntime(this.runtime); - Predicate predicate = accessStrategyProcessor.predicate(); + doReturn("integrationDependencies").when(jsonStreamContext).getCurrentName(); - assertThat(predicate.test("integrationDependencies"), is(false)); + assertThat(classUnderTest.predicate().test(jsonStreamContext), is(false)); } @Test void testProcess_AccessStrategy_mTLS() { doReturn(List.of(MTLS.getValue())).when(authenticationManagerProvider).getAccessStrategies(); - var resourceDefinitionsProcessor = new ResourceDefinitionsProcessor(); - resourceDefinitionsProcessor.setCdsRuntime(this.runtime); - var newResourceDefinitions = resourceDefinitionsProcessor.process("resourceDefinitions", resourceDefinitions); - - newResourceDefinitions.get().forEach(resourceDefinition -> { - ArrayNode strategies = (ArrayNode) resourceDefinition.get("accessStrategies"); - assertThat(strategies.toString(), is("[{\"type\":\"sap:cmp-mtls:v1\"}]")); - }); + classUnderTest + .customize("resourceDefinitions", resourceDefinitions) // + .forEach(rd -> + assertThat(rd.get("accessStrategies").toString(), is("[{\"type\":\"sap:cmp-mtls:v1\"}]"))); } @Test @@ -86,13 +84,8 @@ void testProcess_AccessStrategy_Open() { .getAuthentication() .setAuthenticateMetadataEndpoints(false); - var resourceDefinitionsProcessor = new ResourceDefinitionsProcessor(); - resourceDefinitionsProcessor.setCdsRuntime(this.runtime); - var newResourceDefinitions = resourceDefinitionsProcessor.process("resourceDefinitions", resourceDefinitions); - - newResourceDefinitions.get().forEach(resourceDefinition -> { - ArrayNode strategies = (ArrayNode) resourceDefinition.get("accessStrategies"); - assertThat(strategies.toString(), is("[{\"type\":\"open\"}]")); - }); + classUnderTest + .customize("resourceDefinitions", resourceDefinitions) // + .forEach(rd -> assertThat(rd.get("accessStrategies").toString(), is("[{\"type\":\"open\"}]"))); } } diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessorTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessorTest.java deleted file mode 100644 index 5dc6deb..0000000 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/processor/impl/EntryPointsProcessorTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.processor.impl; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.sap.cds.services.environment.CdsProperties; -import com.sap.cds.services.impl.environment.SimplePropertiesProvider; -import com.sap.cds.services.runtime.CdsRuntime; -import com.sap.cds.services.runtime.CdsRuntimeConfigurer; -import java.util.function.Predicate; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EntryPointsProcessorTest { - - private CdsRuntime runtime; - - @BeforeEach - void prepare() { - CdsProperties properties = new CdsProperties(); - properties.getOdataV4().getEndpoint().setPath("/api"); - - this.runtime = CdsRuntimeConfigurer.create(new SimplePropertiesProvider(properties)) - .serviceConfigurations() - .eventHandlerConfigurations() - .complete(); - } - - @Test - void testPredicate() { - var entryPointsProcessor = new EntryPointsProcessor(); - entryPointsProcessor.setCdsRuntime(this.runtime); - Predicate predicate = entryPointsProcessor.predicate(); - - assertThat(predicate.test("entryPoints"), is(true)); - } - - @Test - void testPredicate_Negative() { - var entryPointsProcessor = new EntryPointsProcessor(); - entryPointsProcessor.setCdsRuntime(this.runtime); - Predicate predicate = entryPointsProcessor.predicate(); - - assertThat(predicate.test("integrationDependencies"), is(false)); - } - - @Test - void testProcess() { - var entryPointsProcessor = new EntryPointsProcessor(); - entryPointsProcessor.setCdsRuntime(this.runtime); - ArrayNode node = JsonNodeFactory.instance.arrayNode(); - node.add(JsonNodeFactory.instance.textNode("/odata/v4/admin")); - var entryPoints = entryPointsProcessor.process("entryPoints", node); - - assertThat(entryPoints.orElseThrow().toString(), is("[\"/api/admin\"]")); - } -} diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImplTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImplTest.java deleted file mode 100644 index 21b45ed..0000000 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/DynamicOrdResourcesProviderImplTest.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.provider.impl; - -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.commons.io.IOUtils.toInputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import com.sap.cds.adapter.edmx.EdmxV4Provider; -import com.sap.cds.feature.ord.clients.MtxSidecarClient; -import com.sap.cds.feature.ord.provider.OrdResourcesProvider; -import com.sap.cds.services.environment.CdsEnvironment; -import com.sap.cds.services.environment.CdsProperties; -import com.sap.cds.services.environment.CdsProperties.OpenResourceDiscovery; -import com.sap.cds.services.runtime.CdsRuntime; -import com.sap.cds.services.utils.model.DynamicModelUtils; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.List; -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class DynamicOrdResourcesProviderImplTest { - - private CdsRuntime cdsRuntime; - private CdsProperties cdsProperties; - private CdsEnvironment cdsEnvironment; - private EdmxV4Provider edmxV4Provider; - private MtxSidecarClient mtxSidecarClient; - private DynamicModelUtils dynamicModelUtils; - private OrdResourcesProvider ordResourcesProvider; - private OpenResourceDiscovery openResourceDiscovery; - - private DynamicOrdResourcesProviderImpl classUnderTest; - - @BeforeEach - void setUp() { - cdsRuntime = mock(CdsRuntime.class); - cdsProperties = mock(CdsProperties.class); - cdsEnvironment = mock(CdsEnvironment.class); - edmxV4Provider = mock(EdmxV4Provider.class); - mtxSidecarClient = mock(MtxSidecarClient.class); - dynamicModelUtils = mock(DynamicModelUtils.class); - ordResourcesProvider = mock(OrdResourcesProvider.class); - openResourceDiscovery = mock(OpenResourceDiscovery.class); - classUnderTest = - new DynamicOrdResourcesProviderImpl(cdsRuntime, mtxSidecarClient, dynamicModelUtils, List.of()); - - classUnderTest.setPrevious(ordResourcesProvider); - doReturn(cdsEnvironment).when(cdsRuntime).getEnvironment(); - doReturn(openResourceDiscovery).when(cdsProperties).getOrd(); - doReturn(cdsProperties).when(cdsEnvironment).getCdsProperties(); - doReturn(edmxV4Provider).when(cdsRuntime).getProvider(EdmxV4Provider.class); - } - - @Test - void givenThatStaticModelShouldBeUsed_and_perspectiveIsSystemVersion_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(true).when(dynamicModelUtils).useStaticModel(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION); - - assertEquals( - "{}", - IOUtils.toString(classUnderTest.read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void givenThatStaticModelShouldBeUsed_and_perspectiveIsSystemInstance_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(true).when(dynamicModelUtils).useStaticModel(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("documents/ord-document", PERSPECTIVE_SYSTEM_INSTANCE); - - assertEquals( - "{}", - IOUtils.toString(classUnderTest.read("documents/ord-document", PERSPECTIVE_SYSTEM_INSTANCE), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("documents/ord-document", PERSPECTIVE_SYSTEM_INSTANCE); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatDynamicModelShouldBeUsed_and_perspectiveIsSystemVersion_and_ordDocumentIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(false).when(dynamicModelUtils).useStaticModel(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION); - - assertEquals( - "{}", - IOUtils.toString(classUnderTest.read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("documents/ord-document", PERSPECTIVE_SYSTEM_VERSION); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatDynamicModelShouldBeUsed_and_perspectiveIsSystemInstance_and_ordDocumentIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(false).when(dynamicModelUtils).useStaticModel(); - doReturn("{}").when(mtxSidecarClient).getOrdDocument(); - - assertEquals( - "{}", - IOUtils.toString(classUnderTest.read("documents/ord-document", PERSPECTIVE_SYSTEM_INSTANCE), UTF_8)); - - verify(mtxSidecarClient).getOrdDocument(); - verify(dynamicModelUtils).useStaticModel(); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatStaticModelShouldBeUsed_and_perspectiveIsSystemVersion_and_resourceDefinitionIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(true).when(dynamicModelUtils).useStaticModel(); - doReturn("ord/").when(openResourceDiscovery).getOrdResourcesRoot(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION); - - assertEquals( - "{}", - IOUtils.toString( - classUnderTest.read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatStaticModelShouldBeUsed_and_perspectiveIsSystemInstance_and_resourceDefinitionIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(true).when(dynamicModelUtils).useStaticModel(); - doReturn("ord/").when(openResourceDiscovery).getOrdResourcesRoot(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_INSTANCE); - - assertEquals( - "{}", - IOUtils.toString( - classUnderTest.read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_INSTANCE), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_INSTANCE); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatDynamicModelShouldBeUsed_and_perspectiveIsSystemInstance_and_resourceDefinitionIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn("ord/").when(openResourceDiscovery).getOrdResourcesRoot(); - doReturn(false).when(dynamicModelUtils).useStaticModel(); - doReturn("{}").when(mtxSidecarClient).getOrdResourceDefinition("test:ord:service/test.oas3.json"); - - assertEquals( - "{}", - IOUtils.toString( - classUnderTest.read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_INSTANCE), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(mtxSidecarClient).getOrdResourceDefinition("test:ord:service/test.oas3.json"); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatDynamicModelShouldBeUsed_and_perspectiveIsSystemVersion_and_resourceDefinitionIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn("ord/").when(openResourceDiscovery).getOrdResourcesRoot(); - doReturn(false).when(dynamicModelUtils).useStaticModel(); - doReturn(new ByteArrayInputStream("{}".getBytes())) - .when(ordResourcesProvider) - .read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION); - - assertEquals( - "{}", - IOUtils.toString( - classUnderTest.read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(ordResourcesProvider).read("test:ord:service/test.oas3.json", PERSPECTIVE_SYSTEM_VERSION); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } - - @Test - void - givenThatDynamicModelShouldBeUsed_and_perspectiveIsSystemInstance_and_edmxResourceDefinitionIsRequested_whenReadIsCalled_thenCorrectResultIsReturned() - throws IOException { - doReturn(false).when(dynamicModelUtils).useStaticModel(); - doReturn(toInputStream("", UTF_8)).when(edmxV4Provider).getEdmx("test"); - doReturn("ord/").when(openResourceDiscovery).getOrdResourcesRoot(); - - assertEquals( - "", - IOUtils.toString( - classUnderTest.read("test:ord:service/test.edmx", PERSPECTIVE_SYSTEM_INSTANCE), UTF_8)); - - verify(dynamicModelUtils).useStaticModel(); - verify(edmxV4Provider).getEdmx("test"); - verify(cdsRuntime).getProvider(EdmxV4Provider.class); - verifyNoMoreInteractions( - cdsRuntime, - cdsProperties, - cdsEnvironment, - edmxV4Provider, - mtxSidecarClient, - dynamicModelUtils, - ordResourcesProvider, - openResourceDiscovery); - } -} diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImplTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImplTest.java deleted file mode 100644 index b4e49da..0000000 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/provider/impl/StaticOrdResourcesProviderImplTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * © 2026 SAP SE or an SAP affiliate company. All rights reserved. - */ -package com.sap.cds.feature.ord.provider.impl; - -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_VERSION; -import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getOrdProperties; -import static com.sap.cds.services.runtime.CdsRuntimeConfigurer.create; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.apache.commons.io.FilenameUtils.concat; -import static org.apache.commons.io.FilenameUtils.getBaseName; -import static org.apache.commons.io.IOUtils.toInputStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import com.sap.cds.adapter.edmx.EdmxV4Provider; -import com.sap.cds.impl.parser.JsonParser; -import com.sap.cds.services.runtime.CdsRuntime; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; -import java.util.Map; -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("unchecked") -class StaticOrdResourcesProviderImplTest { - - @Test - void givenPerspectiveIsNull_whenGetDocumentIsCalled_thenCorrectResultIsReturned() { - CdsRuntime runtime = create().complete(); - String document = - concat("documents", getBaseName(getOrdProperties(runtime).getOrdDocumentPath())); - InputStream is = new StaticOrdResourcesProviderImpl(runtime, List.of()).read(document, null); - - assertNotNull(is); - - Map ord = (Map) JsonParser.map(new InputStreamReader(is)); - // just test a few properties to ensure it´s the right document - assertEquals("1.9", ord.get("openResourceDiscovery")); - assertEquals("this is an application description", ord.get("description")); - - List> integrationDependencies = - (List>) ord.get("integrationDependencies"); - assertEquals(1, integrationDependencies.size()); - - Map integrationDependency = integrationDependencies.get(0); - assertEquals("sap.cdsjavacpoc:integrationDependency:RawEvent:v1", integrationDependency.get("ordId")); - } - - @Test - void givenPerspectiveIsSystemVersion_whenGetDocumentIsCalled_thenCorrectResultIsReturned() { - CdsRuntime runtime = create().complete(); - String document = - concat("documents", getBaseName(getOrdProperties(runtime).getOrdDocumentPath())); - InputStream is = - new StaticOrdResourcesProviderImpl(runtime, List.of()).read(document, PERSPECTIVE_SYSTEM_VERSION); - - assertNotNull(is); - - Map ord = (Map) JsonParser.map(new InputStreamReader(is)); - // just test a few properties to ensure it´s the right document - assertEquals("1.9", ord.get("openResourceDiscovery")); - assertEquals("this is an application description", ord.get("description")); - - List> integrationDependencies = - (List>) ord.get("integrationDependencies"); - assertEquals(1, integrationDependencies.size()); - - Map integrationDependency = integrationDependencies.get(0); - assertEquals("sap.cdsjavacpoc:integrationDependency:RawEvent:v1", integrationDependency.get("ordId")); - } - - @Test - void givenPerspectiveIsSystemInstance_whenGetDocumentIsCalled_thenCorrectResultIsReturned() { - CdsRuntime runtime = create().complete(); - String document = - concat("documents", getBaseName(getOrdProperties(runtime).getOrdDocumentPath())); - - assertThrows(IllegalArgumentException.class, () -> new StaticOrdResourcesProviderImpl(runtime, List.of()) - .read(document, PERSPECTIVE_SYSTEM_INSTANCE)); - } - - @Test - void givenThatDocumentDoesNotExist_whenGetDocumentIsCalled_thenNullIsReturned() { - assertNull(new StaticOrdResourcesProviderImpl(create().complete(), List.of()) - .read("no-such-ord-document.json", null)); - } - - @Test - void givenPerspectiveIsSystemVersion_and_edmxResource_whenGetDocumentIsCalled_thenCorrectResultIsReturned() - throws IOException { - EdmxV4Provider edmxV4Provider = mock(EdmxV4Provider.class); - doReturn(toInputStream("", UTF_8)).when(edmxV4Provider).getEdmx("test"); - - assertEquals( - "", - IOUtils.toString( - new StaticOrdResourcesProviderImpl( - create().provider(edmxV4Provider).complete(), List.of()) - .read("documents/test_ord_service/test.edmx", PERSPECTIVE_SYSTEM_VERSION), - UTF_8)); - - verify(edmxV4Provider).setPrevious(null); - verify(edmxV4Provider).getEdmx("test"); - verifyNoMoreInteractions(edmxV4Provider); - } -} diff --git a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/servlet/DocumentsServletTest.java b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/servlet/DocumentsServletTest.java index bbb257f..e7117a6 100644 --- a/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/servlet/DocumentsServletTest.java +++ b/cds-feature-ord/src/test/java/com/sap/cds/feature/ord/servlet/DocumentsServletTest.java @@ -4,10 +4,11 @@ package com.sap.cds.feature.ord.servlet; import static com.sap.cds.feature.ord.common.Constants.HEADER_LOCAL_TENANT_ID; -import static com.sap.cds.feature.ord.common.Constants.PERSPECTIVE_SYSTEM_INSTANCE; import static com.sap.cds.feature.ord.common.Utils.CdsRuntimeProperties.getAuthenticationProperties; import static com.sap.cds.services.ErrorStatuses.UNAUTHORIZED; +import static com.sap.cds.services.runtime.ExtendedServiceLoader.loadAll; import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.http.entity.ContentType.APPLICATION_JSON; import static org.apache.http.entity.ContentType.APPLICATION_XML; import static org.apache.http.entity.ContentType.TEXT_PLAIN; @@ -16,31 +17,36 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import com.sap.cds.feature.ord.provider.AuthenticationManagerProvider; -import com.sap.cds.feature.ord.provider.OrdResourcesProvider; +import com.sap.cds.feature.ord.resolver.OrdResolver; import com.sap.cds.services.runtime.CdsRuntime; import com.sap.cds.services.runtime.CdsRuntimeConfigurer; +import com.sap.cds.services.runtime.ExtendedServiceLoader; import com.sap.cds.services.utils.ErrorStatusException; import jakarta.servlet.ServletOutputStream; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.PrintWriter; +import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; class DocumentsServletTest { private CdsRuntime cdsRuntime; + private OrdResolver ordResolver; private PrintWriter printWriter; private HttpServletRequest request; private HttpServletResponse response; private ServletOutputStream servletOutputStream; - private OrdResourcesProvider ordResourcesProvider; private AuthenticationManagerProvider authenticationManagerProvider; private DocumentsServlet classUnderTest; @@ -48,13 +54,12 @@ class DocumentsServletTest { @BeforeEach void setUp() { printWriter = mock(PrintWriter.class); + ordResolver = mock(OrdResolver.class); request = mock(HttpServletRequest.class); response = mock(HttpServletResponse.class); servletOutputStream = mock(ServletOutputStream.class); - ordResourcesProvider = mock(OrdResourcesProvider.class); authenticationManagerProvider = mock(AuthenticationManagerProvider.class); cdsRuntime = CdsRuntimeConfigurer.create() - .provider(ordResourcesProvider) .provider(authenticationManagerProvider) .complete(); @@ -70,193 +75,196 @@ void givenRequestForUnsupportedResourceDefinitionExtension_whenDoGetIsCalled_the classUnderTest.doGet(request, response); verify(request).getRequestURI(); - verify(ordResourcesProvider).setPrevious(null); verify(request).getHeader(HEADER_LOCAL_TENANT_ID); - verify(request).getParameter("perspective"); verify(authenticationManagerProvider).setPrevious(null); verify(request, times(2)).getPathInfo(); verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.unsupported"); verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); } @Test void givenRequestForDynamicPerspectiveForResourceDefinition_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws Exception { - doReturn(servletOutputStream).when(response).getOutputStream(); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); - doReturn("/dummy:service:v1/Dummy.graphql").when(request).getPathInfo(); - doReturn("/ord/v1/dummy:service:v1/Dummy.graphql").when(request).getRequestURI(); - doReturn("system-instance").when(request).getParameter("perspective"); - doReturn(new ByteArrayInputStream("test-document".getBytes())) - .when(ordResourcesProvider) - .read("dummy:service:v1/Dummy.graphql", PERSPECTIVE_SYSTEM_INSTANCE); + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(servletOutputStream).when(response).getOutputStream(); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); + doReturn("/dummy:service:v1/Dummy.graphql").when(request).getPathInfo(); + doReturn("system-instance").when(request).getParameter("perspective"); + doReturn("/ord/v1/dummy:service:v1/Dummy.graphql").when(request).getRequestURI(); + doReturn(true).when(ordResolver).isApplicable("dummy:service:v1/Dummy.graphql", "system-instance"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); + doReturn(new ByteArrayInputStream("test-document".getBytes(UTF_8))) + .when(ordResolver) + .resolve("dummy:service:v1/Dummy.graphql"); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).getOutputStream(); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getHeader(HEADER_LOCAL_TENANT_ID); - verify(request).getParameter("perspective"); - verify(response).setStatus(HttpServletResponse.SC_OK); - verify(authenticationManagerProvider).setPrevious(null); - verify(response).setContentType(TEXT_PLAIN.getMimeType()); - verify(request, times(2)).getPathInfo(); - verify(response).setCharacterEncoding(TEXT_PLAIN.getCharset().toString()); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.graphql"); - verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); - verify(ordResourcesProvider).read("dummy:service:v1/Dummy.graphql", PERSPECTIVE_SYSTEM_INSTANCE); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(request).getRequestURI(); + verify(response).getOutputStream(); + verify(request).getHeader(HEADER_LOCAL_TENANT_ID); + verify(request).getParameter("perspective"); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(authenticationManagerProvider).setPrevious(null); + verify(response).setContentType(TEXT_PLAIN.getMimeType()); + verify(request, times(2)).getPathInfo(); + verify(ordResolver).resolve("dummy:service:v1/Dummy.graphql"); + verify(response).setCharacterEncoding(TEXT_PLAIN.getCharset().toString()); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.graphql"); + verify(ordResolver).isApplicable("dummy:service:v1/Dummy.graphql", "system-instance"); + verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } @Test void givenRequestForGraphqlResourceDefinition_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws Exception { - doReturn(servletOutputStream).when(response).getOutputStream(); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); - doReturn("/dummy:service:v1/Dummy.graphql").when(request).getPathInfo(); - doReturn("/ord/v1/dummy:service:v1/Dummy.graphql").when(request).getRequestURI(); - doReturn(new ByteArrayInputStream("test-document".getBytes())) - .when(ordResourcesProvider) - .read("dummy:service:v1/Dummy.graphql", null); + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(servletOutputStream).when(response).getOutputStream(); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); + doReturn("/dummy:service:v1/Dummy.graphql").when(request).getPathInfo(); + doReturn("/ord/v1/dummy:service:v1/Dummy.graphql").when(request).getRequestURI(); + doReturn(true).when(ordResolver).isApplicable("dummy:service:v1/Dummy.graphql", "system-version"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); + doReturn(new ByteArrayInputStream("test-document".getBytes(UTF_8))) + .when(ordResolver) + .resolve("dummy:service:v1/Dummy.graphql"); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).getOutputStream(); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getParameter("perspective"); - verify(request).getHeader("Local-Tenant-Id"); - verify(response).setStatus(HttpServletResponse.SC_OK); - verify(authenticationManagerProvider).setPrevious(null); - verify(response).setContentType(TEXT_PLAIN.getMimeType()); - verify(request, times(2)).getPathInfo(); - verify(response).setCharacterEncoding(TEXT_PLAIN.getCharset().toString()); - verify(ordResourcesProvider).read("dummy:service:v1/Dummy.graphql", null); - verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.graphql"); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(request).getRequestURI(); + verify(response).getOutputStream(); + verify(request).getParameter("perspective"); + verify(request).getHeader("Local-Tenant-Id"); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(authenticationManagerProvider).setPrevious(null); + verify(response).setContentType(TEXT_PLAIN.getMimeType()); + verify(request, times(2)).getPathInfo(); + verify(ordResolver).resolve("dummy:service:v1/Dummy.graphql"); + verify(response).setCharacterEncoding(TEXT_PLAIN.getCharset().toString()); + verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.graphql"); + verify(ordResolver).isApplicable("dummy:service:v1/Dummy.graphql", "system-version"); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } @Test void givenRequestForEdmxResourceDefinition_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws Exception { - doReturn(servletOutputStream).when(response).getOutputStream(); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); - doReturn("/dummy:service:v1/Dummy.edmx").when(request).getPathInfo(); - doReturn("/ord/v1/dummy:service:v1/Dummy.edmx").when(request).getRequestURI(); - doReturn(new ByteArrayInputStream("test-document".getBytes())) - .when(ordResourcesProvider) - .read("dummy:service:v1/Dummy.edmx", null); + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(servletOutputStream).when(response).getOutputStream(); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); + doReturn("/dummy:service:v1/Dummy.edmx").when(request).getPathInfo(); + doReturn("/ord/v1/dummy:service:v1/Dummy.edmx").when(request).getRequestURI(); + doReturn(true).when(ordResolver).isApplicable("dummy:service:v1/Dummy.edmx", "system-version"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); + doReturn(new ByteArrayInputStream("test-document".getBytes(UTF_8))) + .when(ordResolver) + .resolve("dummy:service:v1/Dummy.edmx"); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).getOutputStream(); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getParameter("perspective"); - verify(request).getHeader("Local-Tenant-Id"); - verify(response).setStatus(HttpServletResponse.SC_OK); - verify(authenticationManagerProvider).setPrevious(null); - verify(response).setContentType(APPLICATION_XML.getMimeType()); - verify(request, times(2)).getPathInfo(); - verify(response).setCharacterEncoding(APPLICATION_XML.getCharset().toString()); - verify(ordResourcesProvider).read("dummy:service:v1/Dummy.edmx", null); - verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.edmx"); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(request).getRequestURI(); + verify(response).getOutputStream(); + verify(request).getParameter("perspective"); + verify(request).getHeader("Local-Tenant-Id"); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(authenticationManagerProvider).setPrevious(null); + verify(response).setContentType(APPLICATION_XML.getMimeType()); + verify(request, times(2)).getPathInfo(); + verify(ordResolver).resolve("dummy:service:v1/Dummy.edmx"); + verify(response).setCharacterEncoding(APPLICATION_XML.getCharset().toString()); + verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.edmx"); + verify(ordResolver).isApplicable("dummy:service:v1/Dummy.edmx", "system-version"); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } @Test void givenRequestForJsonResourceDefinition_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws Exception { - doReturn(servletOutputStream).when(response).getOutputStream(); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); - doReturn("/dummy:service:v1/Dummy.json").when(request).getPathInfo(); - doReturn("/ord/v1/dummy:service:v1/Dummy.json").when(request).getRequestURI(); - doReturn(new ByteArrayInputStream("test-document".getBytes())) - .when(ordResourcesProvider) - .read("dummy:service:v1/Dummy.json", null); + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(servletOutputStream).when(response).getOutputStream(); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(false); + doReturn("/dummy:service:v1/Dummy.json").when(request).getPathInfo(); + doReturn("/ord/v1/dummy:service:v1/Dummy.json").when(request).getRequestURI(); + doReturn(true).when(ordResolver).isApplicable("dummy:service:v1/Dummy.json", "system-version"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); + doReturn(new ByteArrayInputStream("test-document".getBytes(UTF_8))) + .when(ordResolver) + .resolve("dummy:service:v1/Dummy.json"); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).getOutputStream(); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getParameter("perspective"); - verify(request).getHeader("Local-Tenant-Id"); - verify(response).setStatus(HttpServletResponse.SC_OK); - verify(authenticationManagerProvider).setPrevious(null); - verify(request, times(2)).getPathInfo(); - verify(response).setContentType(APPLICATION_JSON.getMimeType()); - verify(response).setCharacterEncoding(APPLICATION_JSON.getCharset().toString()); - verify(ordResourcesProvider).read("dummy:service:v1/Dummy.json", null); - verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.json"); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(request).getRequestURI(); + verify(response).getOutputStream(); + verify(request).getParameter("perspective"); + verify(request).getHeader("Local-Tenant-Id"); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(authenticationManagerProvider).setPrevious(null); + verify(request, times(2)).getPathInfo(); + verify(response).setContentType(APPLICATION_JSON.getMimeType()); + verify(ordResolver).resolve("dummy:service:v1/Dummy.json"); + verify(response).setCharacterEncoding(APPLICATION_JSON.getCharset().toString()); + verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/dummy:service:v1/Dummy.json"); + verify(ordResolver).isApplicable("dummy:service:v1/Dummy.json", "system-version"); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } @Test void givenMtlsAuth_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws Exception { - doReturn(servletOutputStream).when(response).getOutputStream(); - doReturn("/documents/ord-document").when(request).getPathInfo(); - doNothing().when(authenticationManagerProvider).checkAuthorization(anyString()); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(true); - doReturn("/ord/v1/documents/ord-document").when(request).getRequestURI(); - doReturn(new ByteArrayInputStream("test-document".getBytes())) - .when(ordResourcesProvider) - .read("documents/ord-document", null); + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(servletOutputStream).when(response).getOutputStream(); + doReturn("/documents/ord-document").when(request).getPathInfo(); + doNothing().when(authenticationManagerProvider).checkAuthorization(anyString()); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(true); + doReturn("/ord/v1/documents/ord-document").when(request).getRequestURI(); + doReturn(true).when(ordResolver).isApplicable("documents/ord-document", "system-version"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); + doReturn(new ByteArrayInputStream("test-document".getBytes(UTF_8))) + .when(ordResolver) + .resolve("documents/ord-document"); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).getOutputStream(); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getParameter("perspective"); - verify(request).getHeader("Local-Tenant-Id"); - verify(response).setStatus(HttpServletResponse.SC_OK); - verify(authenticationManagerProvider).setPrevious(null); - verify(request, times(2)).getPathInfo(); - verify(response).setContentType(APPLICATION_JSON.getMimeType()); - verify(response).setCharacterEncoding(APPLICATION_JSON.getCharset().toString()); - verify(ordResourcesProvider).read("documents/ord-document", null); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/documents/ord-document"); - verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(request).getRequestURI(); + verify(response).getOutputStream(); + verify(request).getParameter("perspective"); + verify(request).getHeader("Local-Tenant-Id"); + verify(response).setStatus(HttpServletResponse.SC_OK); + verify(authenticationManagerProvider).setPrevious(null); + verify(ordResolver).resolve("documents/ord-document"); + verify(request, times(2)).getPathInfo(); + verify(response).setContentType(APPLICATION_JSON.getMimeType()); + verify(response).setCharacterEncoding(APPLICATION_JSON.getCharset().toString()); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/documents/ord-document"); + verify(servletOutputStream).write("test-document".getBytes(), 0, "test-document".length()); + verify(ordResolver).isApplicable("documents/ord-document", "system-version"); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } @Test @@ -274,45 +282,43 @@ void givenMtlsAuth_and_authorizationFails_whenDoGetIsCalled_thenCorrectResponseI verify(printWriter).close(); verify(response).getWriter(); verify(request).getRequestURI(); - verify(ordResourcesProvider).setPrevious(null); verify(printWriter).write("No authentication"); verify(authenticationManagerProvider).setPrevious(null); verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED); verify(authenticationManagerProvider).checkAuthorization("/ord/v1"); verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); } @Test - void givenFileIsNotFound_whenDoGetIsCalled_thenCorrectResponseIsReturned() { - doReturn("/documents/ord-document").when(request).getPathInfo(); - doNothing().when(authenticationManagerProvider).checkAuthorization(anyString()); - getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(true); - doReturn("/ord/v1/documents/ord-document").when(request).getRequestURI(); - doReturn(null).when(ordResourcesProvider).read("documents/ord-document", null); + void givenFileIsNotFound_whenDoGetIsCalled_thenCorrectResponseIsReturned() throws IOException { + try (MockedStatic extendedServiceLoaderMockedStatic = + mockStatic(ExtendedServiceLoader.class)) { + doReturn(printWriter).when(response).getWriter(); + doReturn("/documents/ord-document").when(request).getPathInfo(); + doNothing().when(authenticationManagerProvider).checkAuthorization(anyString()); + getAuthenticationProperties(cdsRuntime).setAuthenticateMetadataEndpoints(true); + doReturn("/ord/v1/documents/ord-document").when(request).getRequestURI(); + doReturn(false).when(ordResolver).isApplicable("documents/ord-document", "system-version"); + extendedServiceLoaderMockedStatic + .when(() -> loadAll(OrdResolver.class, cdsRuntime)) + .thenReturn(List.of(ordResolver).iterator()); - classUnderTest.doGet(request, response); + classUnderTest.doGet(request, response); - verify(request).getRequestURI(); - verify(response).setStatus(SC_NOT_FOUND); - verify(ordResourcesProvider).setPrevious(null); - verify(request).getParameter("perspective"); - verify(request).getHeader("Local-Tenant-Id"); - verify(authenticationManagerProvider).setPrevious(null); - verify(request, times(2)).getPathInfo(); - verify(ordResourcesProvider).read("documents/ord-document", null); - verify(authenticationManagerProvider).checkAuthorization("/ord/v1/documents/ord-document"); - verifyNoMoreInteractions( - request, - response, - printWriter, - servletOutputStream, - ordResourcesProvider, - authenticationManagerProvider); + verify(printWriter).close(); + verify(response).getWriter(); + verify(request).getRequestURI(); + verify(response).setStatus(SC_NOT_FOUND); + verify(printWriter).write("Not Found"); + verify(request).getParameter("perspective"); + verify(request).getHeader("Local-Tenant-Id"); + verify(authenticationManagerProvider).setPrevious(null); + verify(request, times(2)).getPathInfo(); + verify(ordResolver).isApplicable("documents/ord-document", "system-version"); + verify(authenticationManagerProvider).checkAuthorization("/ord/v1/documents/ord-document"); + verifyNoMoreInteractions( + request, response, printWriter, ordResolver, servletOutputStream, authenticationManagerProvider); + } } }