From 551a4bdf7348827667f446ed7439f037664f69ce Mon Sep 17 00:00:00 2001 From: ndr_brt Date: Mon, 16 Dec 2024 09:17:55 +0100 Subject: [PATCH] refactor: introduce PortMappings service --- .../DspApiConfigurationExtension.java | 46 +++--- .../DspApiConfigurationExtensionTest.java | 26 ++-- .../ControlApiConfigurationExtension.java | 49 +++--- .../ControlApiConfigurationExtensionTest.java | 21 ++- .../ManagementApiConfigurationExtension.java | 50 ++++--- ...nagementApiConfigurationExtensionTest.java | 18 +-- .../version/VersionApiExtension.java | 40 ++--- .../edc/web/jersey/JerseyRestServiceTest.java | 24 +-- .../testfixtures/RestControllerTestBase.java | 8 +- .../edc/web/jetty/JettyConfiguration.java | 108 ++------------ .../eclipse/edc/web/jetty/JettyExtension.java | 51 +++++-- .../eclipse/edc/web/jetty/JettyService.java | 139 +++++++----------- .../eclipse/edc/web/jetty/PortMapping.java | 62 -------- .../edc/web/jetty/PortMappingsImpl.java | 44 ++++++ .../web/jetty/WebServiceConfigurerImpl.java | 26 ++-- .../edc/web/jetty/JettyConfigurationTest.java | 135 ----------------- .../edc/web/jetty/JettyServiceTest.java | 110 +++----------- .../edc/web/jetty/PortMappingsImplTest.java | 59 ++++++++ .../jetty/WebServiceConfigurerImplTest.java | 20 ++- .../StsAccountsApiConfigurationExtension.java | 43 +++--- .../sts/StsApiConfigurationExtension.java | 39 ++--- .../sts/StsApiConfigurationExtensionTest.java | 24 ++- .../api/DataPlanePublicApiV2Extension.java | 47 +++--- .../org/eclipse/edc/web/spi/WebServer.java | 19 --- .../web/spi/configuration/PortMapping.java | 22 +++ .../web/spi/configuration/PortMappings.java | 38 +++++ .../WebServiceConfiguration.java | 3 + .../configuration/WebServiceConfigurer.java | 9 +- .../spi/configuration/WebServiceSettings.java | 1 + .../configuration/WebServiceSettingsTest.java | 1 + 30 files changed, 546 insertions(+), 736 deletions(-) delete mode 100644 extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMapping.java create mode 100644 extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMappingsImpl.java delete mode 100644 extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyConfigurationTest.java create mode 100644 extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/PortMappingsImplTest.java create mode 100644 spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMapping.java create mode 100644 spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMappings.java rename {data-protocols/dsp/dsp-catalog/lib/dsp-catalog-http-api-lib => spi/common/web-spi}/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java (96%) diff --git a/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java b/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java index 3d1dcf2cab9..ba316d59790 100644 --- a/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java +++ b/data-protocols/dsp/dsp-http-api-configuration/src/main/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtension.java @@ -25,11 +25,12 @@ import org.eclipse.edc.participant.spi.ParticipantIdMapper; import org.eclipse.edc.policy.model.AtomicConstraint; import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; import org.eclipse.edc.runtime.metamodel.annotation.Setting; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.protocol.ProtocolWebhook; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtension; @@ -45,11 +46,10 @@ import org.eclipse.edc.transform.transformer.edc.to.JsonObjectToQuerySpecTransformer; import org.eclipse.edc.transform.transformer.edc.to.JsonValueToGenericTypeTransformer; import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import java.util.Map; @@ -81,25 +81,20 @@ public class DspApiConfigurationExtension implements ServiceExtension { public static final String NAME = "Dataspace Protocol API Configuration Extension"; - @SettingContext("Protocol API context setting key") - private static final String PROTOCOL_CONFIG_KEY = "web.http." + ApiContext.PROTOCOL; - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(PROTOCOL_CONFIG_KEY) - .contextAlias(ApiContext.PROTOCOL) - .defaultPath("/api/dsp") - .defaultPort(8282) - .build(); + + static final String DEFAULT_PROTOCOL_PATH = "/api/protocol"; + static final int DEFAULT_PROTOCOL_PORT = 8282; + @Setting(description = "Configures endpoint for reaching the Protocol API in the form \"\"", key = "edc.dsp.callback.address", required = false) private String callbackAddress; + @Configuration + private DspApiConfiguration apiConfiguration; + @Inject private TypeManager typeManager; @Inject private WebService webService; @Inject - private WebServer webServer; - @Inject - private WebServiceConfigurer configurator; - @Inject private JsonLd jsonLd; @Inject private TypeTransformerRegistry transformerRegistry; @@ -107,6 +102,8 @@ public class DspApiConfigurationExtension implements ServiceExtension { private ParticipantIdMapper participantIdMapper; @Inject private Hostname hostname; + @Inject + private PortMappings portMappings; @Override public String name() { @@ -115,11 +112,10 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var contextConfig = context.getConfig(PROTOCOL_CONFIG_KEY); - var apiConfiguration = configurator.configure(contextConfig, webServer, SETTINGS); - var dspWebhookAddress = ofNullable(callbackAddress).orElseGet(() -> format("http://%s:%s%s", hostname.get(), apiConfiguration.getPort(), apiConfiguration.getPath())); - + var portMapping = new PortMapping(ApiContext.PROTOCOL, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); + var dspWebhookAddress = ofNullable(callbackAddress).orElseGet(() -> format("http://%s:%s%s", hostname.get(), portMapping.port(), portMapping.path())); context.registerService(ProtocolWebhook.class, () -> dspWebhookAddress); var jsonLdMapper = typeManager.getMapper(JSON_LD); @@ -177,7 +173,6 @@ private void registerV08Transformers(ObjectMapper mapper) { dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper)); dspApiTransformerRegistry.register(new JsonObjectFromDataAddressDspaceTransformer(jsonBuilderFactory, mapper)); - } private void registerV2024Transformers(ObjectMapper mapper) { @@ -188,6 +183,15 @@ private void registerV2024Transformers(ObjectMapper mapper) { dspApiTransformerRegistry.register(new JsonObjectFromPolicyTransformer(jsonBuilderFactory, participantIdMapper, true)); dspApiTransformerRegistry.register(new JsonObjectFromDataAddressDspace2024Transformer(jsonBuilderFactory, mapper)); + } + + @Settings + record DspApiConfiguration( + @Setting(key = "web.http." + ApiContext.PROTOCOL + ".port", description = "Port for " + ApiContext.PROTOCOL + " api context", defaultValue = DEFAULT_PROTOCOL_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.PROTOCOL + ".path", description = "Path for " + ApiContext.PROTOCOL + " api context", defaultValue = DEFAULT_PROTOCOL_PATH) + String path + ) { } } diff --git a/data-protocols/dsp/dsp-http-api-configuration/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtensionTest.java b/data-protocols/dsp/dsp-http-api-configuration/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtensionTest.java index 6fff0136e54..dc0cbf944fc 100644 --- a/data-protocols/dsp/dsp-http-api-configuration/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtensionTest.java +++ b/data-protocols/dsp/dsp-http-api-configuration/src/test/java/org/eclipse/edc/protocol/dsp/http/api/configuration/DspApiConfigurationExtensionTest.java @@ -20,16 +20,14 @@ import org.eclipse.edc.spi.protocol.ProtocolWebhook; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.transform.spi.TypeTransformerRegistry; import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -46,7 +44,8 @@ import static org.eclipse.edc.jsonld.spi.Namespaces.DCT_SCHEMA; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; -import static org.eclipse.edc.protocol.dsp.http.api.configuration.DspApiConfigurationExtension.SETTINGS; +import static org.eclipse.edc.protocol.dsp.http.api.configuration.DspApiConfigurationExtension.DEFAULT_PROTOCOL_PATH; +import static org.eclipse.edc.protocol.dsp.http.api.configuration.DspApiConfigurationExtension.DEFAULT_PROTOCOL_PORT; import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_SCOPE_V_08; import static org.eclipse.edc.protocol.dsp.spi.type.DspConstants.DSP_SCOPE_V_2024_1; import static org.eclipse.edc.spi.constants.CoreConstants.EDC_NAMESPACE; @@ -61,18 +60,16 @@ @ExtendWith(DependencyInjectionExtension.class) class DspApiConfigurationExtensionTest { - private final WebServiceConfigurer configurer = mock(); - private final WebServer webServer = mock(); private final WebService webService = mock(); private final TypeManager typeManager = mock(); private final JsonLd jsonLd = mock(); + private final PortMappings portMappings = mock(); @BeforeEach void setUp(ServiceExtensionContext context) { - context.registerService(WebServer.class, webServer); + context.registerService(PortMappings.class, portMappings); context.registerService(WebService.class, webService); - context.registerService(WebServiceConfigurer.class, configurer); context.registerService(TypeManager.class, typeManager); context.registerService(Hostname.class, () -> "hostname"); context.registerService(JsonLd.class, jsonLd); @@ -80,11 +77,6 @@ void setUp(ServiceExtensionContext context) { when(typeTransformerRegistry.forContext(any())).thenReturn(mock()); context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); - var webServiceConfiguration = WebServiceConfiguration.Builder.newInstance() - .path("/path") - .port(1234) - .build(); - when(configurer.configure(any(Config.class), any(), any())).thenReturn(webServiceConfiguration); when(typeManager.getMapper(any())).thenReturn(mock()); } @@ -94,8 +86,8 @@ void shouldComposeProtocolWebhook_whenNotConfigured(DspApiConfigurationExtension extension.initialize(context); - verify(configurer).configure(any(Config.class), eq(webServer), eq(SETTINGS)); - assertThat(context.getService(ProtocolWebhook.class).url()).isEqualTo("http://hostname:1234/path"); + verify(portMappings).register(new PortMapping(ApiContext.PROTOCOL, DEFAULT_PROTOCOL_PORT, DEFAULT_PROTOCOL_PATH)); + assertThat(context.getService(ProtocolWebhook.class).url()).isEqualTo("http://hostname:%s%s".formatted(DEFAULT_PROTOCOL_PORT, DEFAULT_PROTOCOL_PATH)); } @Test @@ -110,7 +102,7 @@ void shouldUseConfiguredProtocolWebhook(ServiceExtensionContext context, ObjectF extension.initialize(context); - verify(configurer).configure(any(Config.class), eq(webServer), eq(SETTINGS)); + verify(portMappings).register(new PortMapping(ApiContext.PROTOCOL, 1234, "/path")); assertThat(context.getService(ProtocolWebhook.class).url()).isEqualTo("http://webhook"); } diff --git a/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java b/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java index adee8847728..d13e9a53a7c 100644 --- a/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java +++ b/extensions/common/api/control-api-configuration/src/main/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtension.java @@ -18,11 +18,12 @@ import org.eclipse.edc.api.auth.spi.AuthenticationRequestFilter; import org.eclipse.edc.api.auth.spi.registry.ApiAuthenticationRegistry; import org.eclipse.edc.jsonld.spi.JsonLd; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; import org.eclipse.edc.runtime.metamodel.annotation.Setting; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtension; @@ -32,12 +33,10 @@ import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.context.ControlApiUrl; import java.io.IOException; @@ -64,23 +63,18 @@ public class ControlApiConfigurationExtension implements ServiceExtension { public static final String NAME = "Control API configuration"; + static final String CONTROL_SCOPE = "CONTROL_API"; + static final int DEFAULT_CONTROL_PORT = 9191; + static final String DEFAULT_CONTROL_PATH = "/api/control"; + private static final String API_VERSION_JSON_FILE = "control-api-version.json"; @Setting(description = "Configures endpoint for reaching the Control API. If it's missing it defaults to the hostname configuration.", key = "edc.control.endpoint", required = false) private String controlEndpoint; - public static final String CONTROL_SCOPE = "CONTROL_API"; - @SettingContext("Control API context setting key") - private static final String CONTROL_CONFIG_KEY = "web.http." + ApiContext.CONTROL; - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(CONTROL_CONFIG_KEY) - .contextAlias(ApiContext.CONTROL) - .defaultPort(9191) - .build(); - private static final String API_VERSION_JSON_FILE = "control-api-version.json"; + @Configuration + private ControlApiConfiguration apiConfiguration; @Inject - private WebServer webServer; - @Inject - private WebServiceConfigurer configurator; + private PortMappings portMappings; @Inject private WebService webService; @Inject @@ -91,7 +85,6 @@ public class ControlApiConfigurationExtension implements ServiceExtension { private TypeManager typeManager; @Inject private ApiAuthenticationRegistry authenticationRegistry; - @Inject private ApiVersionService apiVersionService; @@ -102,10 +95,10 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(CONTROL_CONFIG_KEY); - var controlApiConfiguration = configurator.configure(config, webServer, SETTINGS); + var portMapping = new PortMapping(ApiContext.CONTROL, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); var jsonLdMapper = typeManager.getMapper(JSON_LD); - context.registerService(ControlApiUrl.class, controlApiUrl(context, controlApiConfiguration)); + context.registerService(ControlApiUrl.class, controlApiUrl(context, portMapping)); jsonLd.registerNamespace(EDC_PREFIX, EDC_NAMESPACE, CONTROL_SCOPE); jsonLd.registerNamespace(VOCAB, EDC_NAMESPACE, CONTROL_SCOPE); @@ -134,8 +127,8 @@ private void registerVersionInfo(ClassLoader resourceClassLoader) { } } - private ControlApiUrl controlApiUrl(ServiceExtensionContext context, WebServiceConfiguration config) { - var callbackAddress = ofNullable(controlEndpoint).orElseGet(() -> format("http://%s:%s%s", hostname.get(), config.getPort(), config.getPath())); + private ControlApiUrl controlApiUrl(ServiceExtensionContext context, PortMapping config) { + var callbackAddress = ofNullable(controlEndpoint).orElseGet(() -> format("http://%s:%s%s", hostname.get(), config.port(), config.path())); try { var url = URI.create(callbackAddress); @@ -145,4 +138,14 @@ private ControlApiUrl controlApiUrl(ServiceExtensionContext context, WebServiceC throw new EdcException(e); } } + + @Settings + record ControlApiConfiguration( + @Setting(key = "web.http." + ApiContext.CONTROL + ".port", description = "Port for " + ApiContext.CONTROL + " api context", defaultValue = DEFAULT_CONTROL_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.CONTROL + ".path", description = "Path for " + ApiContext.CONTROL + " api context", defaultValue = DEFAULT_CONTROL_PATH) + String path + ) { + + } } diff --git a/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java b/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java index 0bcdd233935..7cf8f226f09 100644 --- a/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java +++ b/extensions/common/api/control-api-configuration/src/test/java/org/eclipse/edc/connector/api/control/configuration/ControlApiConfigurationExtensionTest.java @@ -25,8 +25,9 @@ import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.spi.types.TypeManager; import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.ApiContext; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.context.ControlApiUrl; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -37,6 +38,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.eclipse.edc.connector.api.control.configuration.ControlApiConfigurationExtension.CONTROL_SCOPE; +import static org.eclipse.edc.connector.api.control.configuration.ControlApiConfigurationExtension.DEFAULT_CONTROL_PATH; +import static org.eclipse.edc.connector.api.control.configuration.ControlApiConfigurationExtension.DEFAULT_CONTROL_PORT; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_PREFIX; import static org.eclipse.edc.jsonld.spi.Namespaces.DSPACE_SCHEMA; @@ -53,24 +56,17 @@ @ExtendWith(DependencyInjectionExtension.class) public class ControlApiConfigurationExtensionTest { - private final WebServiceConfigurer configurator = mock(); + private final PortMappings portMappings = mock(); private final WebService webService = mock(); private final JsonLd jsonLd = mock(); - private final WebServiceConfiguration webServiceConfiguration = WebServiceConfiguration.Builder.newInstance() - .path("/path") - .port(1234) - .build(); - @BeforeEach void setUp(ServiceExtensionContext context) { - context.registerService(WebServiceConfigurer.class, configurator); + context.registerService(PortMappings.class, portMappings); context.registerService(Hostname.class, () -> "hostname"); context.registerService(WebService.class, webService); context.registerService(TypeManager.class, new JacksonTypeManager()); context.registerService(JsonLd.class, jsonLd); - - when(configurator.configure(any(), any(), any())).thenReturn(webServiceConfiguration); } @Test @@ -79,8 +75,9 @@ void shouldComposeControlApiUrl(ControlApiConfigurationExtension extension, Serv extension.initialize(context); + verify(portMappings).register(new PortMapping(ApiContext.CONTROL, DEFAULT_CONTROL_PORT, DEFAULT_CONTROL_PATH)); var url = context.getService(ControlApiUrl.class); - assertThat(url.get().toString()).isEqualTo("http://hostname:1234/path"); + assertThat(url.get().toString()).isEqualTo("http://hostname:%s%s".formatted(DEFAULT_CONTROL_PORT, DEFAULT_CONTROL_PATH)); } @Test diff --git a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java index 19539ac49ca..82e268c593d 100644 --- a/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java +++ b/extensions/common/api/management-api-configuration/src/main/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtension.java @@ -25,11 +25,12 @@ import org.eclipse.edc.connector.controlplane.transform.odrl.from.JsonObjectFromPolicyTransformer; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.participant.spi.ParticipantIdMapper; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Provides; import org.eclipse.edc.runtime.metamodel.annotation.Setting; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtension; @@ -47,12 +48,10 @@ import org.eclipse.edc.transform.transformer.edc.to.JsonValueToGenericTypeTransformer; import org.eclipse.edc.web.jersey.providers.jsonld.JerseyJsonLdInterceptor; import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.context.ManagementApiUrl; import java.io.IOException; @@ -79,31 +78,26 @@ @Extension(ManagementApiConfigurationExtension.NAME) public class ManagementApiConfigurationExtension implements ServiceExtension { - public static final String API_VERSION_JSON_FILE = "management-api-version.json"; public static final String NAME = "Management API configuration"; - public static final String MANAGEMENT_SCOPE = "MANAGEMENT_API"; - - @SettingContext("Management API context setting key") - private static final String MANAGEMENT_CONFIG_KEY = "web.http." + ApiContext.MANAGEMENT; - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(MANAGEMENT_CONFIG_KEY) - .contextAlias(ApiContext.MANAGEMENT) - .defaultPort(8181) - .build(); + static final String MANAGEMENT_SCOPE = "MANAGEMENT_API"; + static final int DEFAULT_MANAGEMENT_PORT = 8181; + static final String DEFAULT_MANAGEMENT_PATH = "/api/management"; + private static final String API_VERSION_JSON_FILE = "management-api-version.json"; private static final boolean DEFAULT_MANAGEMENT_API_ENABLE_CONTEXT = false; + @Setting(description = "Configures endpoint for reaching the Management API, in the format \"\"", key = "edc.management.endpoint", required = false) private String managementApiEndpoint; @Setting(description = "If set enable the usage of management api JSON-LD context.", defaultValue = "" + DEFAULT_MANAGEMENT_API_ENABLE_CONTEXT, key = "edc.management.context.enabled") private boolean managementApiContextEnabled; + @Configuration + private ManagementApiConfiguration apiConfiguration; @Inject private WebService webService; @Inject - private WebServer webServer; - @Inject private ApiAuthenticationRegistry authenticationRegistry; @Inject - private WebServiceConfigurer configurator; + private PortMappings portMappings; @Inject private TypeManager typeManager; @Inject @@ -125,10 +119,10 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(MANAGEMENT_CONFIG_KEY); - var webServiceConfiguration = configurator.configure(config, webServer, SETTINGS); + var portMapping = new PortMapping(ApiContext.MANAGEMENT, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); - context.registerService(ManagementApiUrl.class, managementApiUrl(context, webServiceConfiguration)); + context.registerService(ManagementApiUrl.class, managementApiUrl(context, portMapping)); var authenticationFilter = new AuthenticationRequestFilter(authenticationRegistry, "management-api"); webService.registerResource(ApiContext.MANAGEMENT, authenticationFilter); @@ -183,8 +177,8 @@ private void registerVersionInfo(ClassLoader resourceClassLoader) { } } - private ManagementApiUrl managementApiUrl(ServiceExtensionContext context, WebServiceConfiguration config) { - var callbackAddress = ofNullable(managementApiEndpoint).orElseGet(() -> format("http://%s:%s%s", hostname.get(), config.getPort(), config.getPath())); + private ManagementApiUrl managementApiUrl(ServiceExtensionContext context, PortMapping config) { + var callbackAddress = ofNullable(managementApiEndpoint).orElseGet(() -> format("http://%s:%s%s", hostname.get(), config.port(), config.path())); try { var url = URI.create(callbackAddress); return () -> url; @@ -193,4 +187,14 @@ private ManagementApiUrl managementApiUrl(ServiceExtensionContext context, WebSe throw new EdcException(e); } } + + @Settings + record ManagementApiConfiguration( + @Setting(key = "web.http." + ApiContext.MANAGEMENT + ".port", description = "Port for " + ApiContext.MANAGEMENT + " api context", defaultValue = DEFAULT_MANAGEMENT_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.MANAGEMENT + ".path", description = "Path for " + ApiContext.MANAGEMENT + " api context", defaultValue = DEFAULT_MANAGEMENT_PATH) + String path + ) { + + } } diff --git a/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java b/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java index 9bf6efd0e8a..87eaf8fb460 100644 --- a/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java +++ b/extensions/common/api/management-api-configuration/src/test/java/org/eclipse/edc/connector/api/management/configuration/ManagementApiConfigurationExtensionTest.java @@ -30,8 +30,8 @@ import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -39,8 +39,9 @@ import java.util.Map; +import static org.eclipse.edc.connector.api.management.configuration.ManagementApiConfigurationExtension.DEFAULT_MANAGEMENT_PATH; +import static org.eclipse.edc.connector.api.management.configuration.ManagementApiConfigurationExtension.DEFAULT_MANAGEMENT_PORT; import static org.eclipse.edc.connector.api.management.configuration.ManagementApiConfigurationExtension.MANAGEMENT_SCOPE; -import static org.eclipse.edc.connector.api.management.configuration.ManagementApiConfigurationExtension.SETTINGS; import static org.eclipse.edc.jsonld.spi.JsonLdKeywords.VOCAB; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_PREFIX; import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; @@ -58,7 +59,7 @@ @ExtendWith(DependencyInjectionExtension.class) class ManagementApiConfigurationExtensionTest { - private final WebServiceConfigurer configurer = mock(); + private final PortMappings portMappings = mock(); private final Monitor monitor = mock(); private final WebService webService = mock(); private final JsonLd jsonLd = mock(); @@ -71,7 +72,7 @@ void setUp(ServiceExtensionContext context, ObjectFactory factory) { when(typeTransformerRegistry.forContext(any())).thenReturn(contextTypeTransformerRegistry); when(contextTypeTransformerRegistry.forContext(any())).thenReturn(mock()); context.registerService(WebService.class, webService); - context.registerService(WebServiceConfigurer.class, configurer); + context.registerService(PortMappings.class, portMappings); context.registerService(TypeTransformerRegistry.class, typeTransformerRegistry); context.registerService(TypeManager.class, new JacksonTypeManager()); context.registerService(JsonLd.class, jsonLd); @@ -81,12 +82,10 @@ void setUp(ServiceExtensionContext context, ObjectFactory factory) { @Test void initialize_shouldConfigureAndRegisterResource() { var context = contextWithConfig(ConfigFactory.empty()); - var configuration = WebServiceConfiguration.Builder.newInstance().path("/path").port(1234).build(); - when(configurer.configure(any(), any(), any())).thenReturn(configuration); extension.initialize(context); - verify(configurer).configure(any(), any(), eq(SETTINGS)); + verify(portMappings).register(new PortMapping(ApiContext.MANAGEMENT, DEFAULT_MANAGEMENT_PORT, DEFAULT_MANAGEMENT_PATH)); verify(webService).registerResource(eq(ApiContext.MANAGEMENT), isA(AuthenticationRequestFilter.class)); verify(webService).registerResource(eq(ApiContext.MANAGEMENT), isA(JerseyJsonLdInterceptor.class)); verify(webService).registerResource(eq(ApiContext.MANAGEMENT), isA(ObjectMapperProvider.class)); @@ -100,9 +99,6 @@ void initialize_shouldConfigureAndRegisterResource() { void initialize_withContextEnabled(ObjectFactory factory, ServiceExtensionContext context) { when(context.getConfig()).thenReturn(ConfigFactory.fromMap(Map.of("edc.management.context.enabled", "true"))); - var configuration = WebServiceConfiguration.Builder.newInstance().path("/path").port(1234).build(); - when(configurer.configure(any(), any(), any())).thenReturn(configuration); - factory.constructInstance(ManagementApiConfigurationExtension.class).initialize(context); verify(jsonLd, times(0)).registerNamespace(any(), any(), any()); diff --git a/extensions/common/api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java b/extensions/common/api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java index bb8999e4ed3..5747d22c3a5 100644 --- a/extensions/common/api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java +++ b/extensions/common/api/version-api/src/main/java/org/eclipse/edc/connector/api/management/version/VersionApiExtension.java @@ -16,20 +16,21 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import org.eclipse.edc.connector.api.management.version.v1.VersionApiController; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.apiversion.ApiVersionService; import org.eclipse.edc.spi.system.apiversion.VersionRecord; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import java.io.IOException; import java.util.stream.Stream; @@ -39,17 +40,12 @@ public class VersionApiExtension implements ServiceExtension { public static final String NAME = "Management API: Version Information"; - @SettingContext("Version API context setting key") - private static final String VERSION_CONFIG_KEY = "web.http." + ApiContext.VERSION; - - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(VERSION_CONFIG_KEY) - .contextAlias(ApiContext.VERSION) - .defaultPath("/.well-known/api") - .defaultPort(7171) - .build(); + private static final String DEFAULT_VERSION_PATH = "/.well-known/api"; + private static final int DEFAULT_VERSION_PORT = 7171; private static final String API_VERSION_JSON_FILE = "version-api-version.json"; + @Configuration + private VersionApiConfiguration apiConfiguration; @Inject private WebService webService; @Inject @@ -57,9 +53,7 @@ public class VersionApiExtension implements ServiceExtension { @Inject private ApiVersionService apiVersionService; @Inject - private WebServiceConfigurer configurator; - @Inject - private WebServer webServer; + private PortMappings portMappings; @Override public String name() { @@ -68,8 +62,8 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(VERSION_CONFIG_KEY); - configurator.configure(config, webServer, SETTINGS); + var portMapping = new PortMapping(ApiContext.VERSION, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); webService.registerResource(ApiContext.VERSION, new VersionApiController(apiVersionService)); registerVersionInfo(getClass().getClassLoader()); @@ -88,4 +82,14 @@ private void registerVersionInfo(ClassLoader resourceClassLoader) { throw new EdcException(e); } } + + @Settings + record VersionApiConfiguration( + @Setting(key = "web.http." + ApiContext.VERSION + ".port", description = "Port for " + ApiContext.VERSION + " api context", defaultValue = DEFAULT_VERSION_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.VERSION + ".path", description = "Path for " + ApiContext.VERSION + " api context", defaultValue = DEFAULT_VERSION_PATH) + String path + ) { + + } } diff --git a/extensions/common/http/jersey-core/src/test/java/org/eclipse/edc/web/jersey/JerseyRestServiceTest.java b/extensions/common/http/jersey-core/src/test/java/org/eclipse/edc/web/jersey/JerseyRestServiceTest.java index 1d5d3b4c09e..66fb76acb32 100644 --- a/extensions/common/http/jersey-core/src/test/java/org/eclipse/edc/web/jersey/JerseyRestServiceTest.java +++ b/extensions/common/http/jersey-core/src/test/java/org/eclipse/edc/web/jersey/JerseyRestServiceTest.java @@ -25,7 +25,8 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.web.jetty.JettyConfiguration; import org.eclipse.edc.web.jetty.JettyService; -import org.eclipse.edc.web.jetty.PortMapping; +import org.eclipse.edc.web.jetty.PortMappingsImpl; +import org.eclipse.edc.web.spi.configuration.PortMapping; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -48,7 +49,7 @@ public class JerseyRestServiceTest { private final int httpPort = getFreePort(); - private final Monitor monitor = mock(Monitor.class); + private final Monitor monitor = mock(); private JerseyRestService jerseyRestService; private JettyService jettyService; @@ -60,7 +61,7 @@ void teardown() { @Test @DisplayName("Verifies that a resource is available under the default path") void verifyDefaultContextPath() { - startJetty(PortMapping.getDefault(httpPort)); + startJetty(new PortMapping("default", httpPort, "/api")); jerseyRestService.registerResource(new TestController()); jerseyRestService.start(); @@ -76,7 +77,7 @@ void verifyDefaultContextPath() { void verifyAnotherContextPath() { var anotherPort = getFreePort(); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("path", anotherPort, "/path") ); var pathController = spy(new TestController()); @@ -125,7 +126,7 @@ void verifyFilterForOneContextPath() throws IOException { var anotherPort = getFreePort(); var filterMock = mock(ContainerRequestFilter.class); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("path", anotherPort, "/path") ); @@ -158,7 +159,7 @@ void verifySeparateFilters() { var port1 = getFreePort(); var port2 = getFreePort(); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("foo", port1, "/foo"), new PortMapping("bar", port2, "/bar") ); @@ -199,7 +200,7 @@ void verifySeparateFilters() { void verifySeparateFiltersForDifferentControllers() { var port1 = getFreePort(); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("foo", port1, "/foo") ); // mocking the ContextRequestFilter doesn't work here, Mockito apparently re-uses mocks for the same target class @@ -241,7 +242,7 @@ void verifyIdenticalPathsRaiseException() { var port1 = getFreePort(); var port2 = getFreePort(); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("another", port1, "/foo"), new PortMapping("yet-another", port2, "/foo") ); @@ -258,7 +259,7 @@ void verifyIdenticalPathsRaiseException() { void verifyInvalidContextAlias_shouldThrowException() { var anotherPort = getFreePort(); startJetty( - PortMapping.getDefault(httpPort), + new PortMapping("default", httpPort, "/api"), new PortMapping("another", anotherPort, "/foo") ); @@ -270,8 +271,9 @@ void verifyInvalidContextAlias_shouldThrowException() { private void startJetty(PortMapping... mapping) { var config = new JettyConfiguration(null, null); - Arrays.stream(mapping).forEach(config::portMapping); - jettyService = new JettyService(config, monitor); + var portMappings = new PortMappingsImpl(); + Arrays.stream(mapping).forEach(portMappings::register); + jettyService = new JettyService(config, monitor, portMappings); jerseyRestService = new JerseyRestService(jettyService, new JacksonTypeManager(), JerseyConfiguration.Builder.newInstance().build(), monitor); jettyService.start(); } diff --git a/extensions/common/http/jersey-core/src/testFixtures/java/org/eclipse/edc/web/jersey/testfixtures/RestControllerTestBase.java b/extensions/common/http/jersey-core/src/testFixtures/java/org/eclipse/edc/web/jersey/testfixtures/RestControllerTestBase.java index 692f4ea47e7..ae8cfb3ada9 100644 --- a/extensions/common/http/jersey-core/src/testFixtures/java/org/eclipse/edc/web/jersey/testfixtures/RestControllerTestBase.java +++ b/extensions/common/http/jersey-core/src/testFixtures/java/org/eclipse/edc/web/jersey/testfixtures/RestControllerTestBase.java @@ -23,7 +23,8 @@ import org.eclipse.edc.web.jersey.providers.jsonld.ObjectMapperProvider; import org.eclipse.edc.web.jetty.JettyConfiguration; import org.eclipse.edc.web.jetty.JettyService; -import org.eclipse.edc.web.jetty.PortMapping; +import org.eclipse.edc.web.jetty.PortMappingsImpl; +import org.eclipse.edc.web.spi.configuration.PortMapping; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -44,8 +45,9 @@ public abstract class RestControllerTestBase { @BeforeEach final void startJetty() { var config = new JettyConfiguration(null, null); - config.portMapping(new PortMapping("test", port, "/")); - jetty = new JettyService(config, monitor); + var portMappings = new PortMappingsImpl(); + portMappings.register(new PortMapping("test", port, "/")); + jetty = new JettyService(config, monitor, portMappings); var jerseyService = new JerseyRestService(jetty, new JacksonTypeManager(), mock(JerseyConfiguration.class), monitor); jerseyService.registerResource("test", new ObjectMapperProvider(objectMapper)); jerseyService.registerResource("test", controller()); diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyConfiguration.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyConfiguration.java index ca4c7541419..ef6dcb315ab 100644 --- a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyConfiguration.java +++ b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyConfiguration.java @@ -14,102 +14,14 @@ package org.eclipse.edc.web.jetty; -import org.eclipse.edc.spi.system.configuration.Config; - -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import static org.eclipse.edc.web.spi.configuration.WebServiceConfigurer.WEB_HTTP_PREFIX; - -public class JettyConfiguration { - - public static final String DEFAULT_PATH = "/api"; - public static final String DEFAULT_CONTEXT_NAME = "default"; - public static final int DEFAULT_PORT = 8181; - private final String keystorePassword; - private final String keymanagerPassword; - private final Set portMappings; - - public JettyConfiguration(String keystorePassword, String keymanagerPassword) { - this.keystorePassword = keystorePassword; - this.keymanagerPassword = keymanagerPassword; - portMappings = new HashSet<>(); - } - - public static JettyConfiguration createFromConfig(String keystorePassword, String keymanagerPassword, Config config) { - var jettyConfig = new JettyConfiguration(keystorePassword, keymanagerPassword); - - var subConfig = config.getConfig(WEB_HTTP_PREFIX); - - Map> tempMappings = new HashMap<>(); - subConfig.getRelativeEntries().entrySet().stream() - .map(e -> new AbstractMap.SimpleEntry<>(expandKey(e), e.getValue())) - .forEach(e -> split(tempMappings, e)); - - var portMappings = tempMappings.entrySet().stream() - .map(e -> new PortMapping(e.getKey(), Integer.parseInt(e.getValue().getOrDefault("port", "" + DEFAULT_PORT)), e.getValue().getOrDefault("path", DEFAULT_PATH))) - .collect(Collectors.toSet()); - - jettyConfig.portMappings.addAll(portMappings); - - - if (jettyConfig.getPortMappings().isEmpty()) { - jettyConfig.portMapping(PortMapping.getDefault()); - } - - return jettyConfig; - } - - /** - * converts a map entry, that looks like "something.port" -> 1234, into a map entry, that looks like - * "something" -> ("port" -> "1234") and adds it to an existing map - */ - private static void split(Map> rawMappings, Map.Entry entry) { - - var key = entry.getKey(); - var value = entry.getValue(); - - // only .[port|path] is accepted - if (key.split("\\.").length != 2) { - return; - } - - var lastDotIndex = key.lastIndexOf("."); - var keyNamePart = key.substring(0, lastDotIndex); - var keyComponentPart = key.substring(lastDotIndex + 1); - - var map = rawMappings.computeIfAbsent(keyNamePart, s -> new HashMap<>()); - if (map.containsKey(keyComponentPart)) { - throw new IllegalArgumentException(String.format("A port mapping for web.http.%s already exists, currently mapped to %s", key, map)); - } - map.put(keyComponentPart, value); - - } - - - //prepends the default context name ("default") to a key if necessary - private static String expandKey(Map.Entry entry) { - return entry.getKey().contains(".") ? entry.getKey() : DEFAULT_CONTEXT_NAME + "." + entry.getKey(); - } - - public Set getPortMappings() { - return portMappings; - } - - public void portMapping(PortMapping mapping) { - portMappings.add(mapping); - } - - public String getKeystorePassword() { - return keystorePassword; - } - - public String getKeymanagerPassword() { - return keymanagerPassword; - } - +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; + +@Settings +public record JettyConfiguration( + @Setting(key = "edc.web.https.keystore.password", description = "Keystore password", defaultValue = "password") + String keystorePassword, + @Setting(key = "edc.web.https.keymanager.password", description = "Keymanager password", defaultValue = "password") + String keymanagerPassword +) { } diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyExtension.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyExtension.java index 7143632072c..a264fe5cb25 100644 --- a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyExtension.java +++ b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyExtension.java @@ -14,13 +14,17 @@ package org.eclipse.edc.web.jetty; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Provider; import org.eclipse.edc.runtime.metamodel.annotation.Provides; import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; import java.io.FileInputStream; @@ -34,16 +38,25 @@ public class JettyExtension implements ServiceExtension { - @Setting - private static final String KEYSTORE_PASSWORD = "edc.web.https.keystore.password"; - @Setting - private static final String KEYMANAGER_PASSWORD = "edc.web.https.keymanager.password"; + private static final String DEFAULT_PATH = "/api"; + private static final String DEFAULT_CONTEXT_NAME = "default"; + private static final int DEFAULT_PORT = 8181; @Setting private static final String KEYSTORE_PATH_SETTING = "edc.web.https.keystore.path"; @Setting private static final String KEYSTORE_TYPE_SETTING = "edc.web.https.keystore.type"; private JettyService jettyService; + private final PortMappingsImpl portMappings = new PortMappingsImpl(); + + @Configuration + private JettyConfiguration jettyConfiguration; + @Configuration + private DefaultApiConfiguration apiConfiguration; + @Setting(key = KEYSTORE_PATH_SETTING, description = "Keystore path", required = false) + private String keystorePath; + @Setting(key = KEYSTORE_TYPE_SETTING, description = "Keystore type", defaultValue = "PKCS12") + private String keystoreType; @Override public String name() { @@ -52,24 +65,24 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { + var defaultPortMapping = new PortMapping(DEFAULT_CONTEXT_NAME, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(defaultPortMapping); + var monitor = context.getMonitor(); KeyStore ks = null; - var keystorePath = context.getConfig().getString(KEYSTORE_PATH_SETTING, null); - var configuration = JettyConfiguration.createFromConfig(context.getSetting(KEYSTORE_PASSWORD, "password"), context.getSetting(KEYMANAGER_PASSWORD, "password"), context.getConfig()); if (keystorePath != null) { try { - ks = KeyStore.getInstance(context.getSetting(KEYSTORE_TYPE_SETTING, "PKCS12")); + ks = KeyStore.getInstance(keystoreType); try (var stream = new FileInputStream(keystorePath)) { - ks.load(stream, configuration.getKeystorePassword().toCharArray()); + ks.load(stream, jettyConfiguration.keystorePassword().toCharArray()); } } catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) { throw new EdcException(e); } } - - jettyService = new JettyService(configuration, ks, monitor); + jettyService = new JettyService(jettyConfiguration, ks, monitor, portMappings); context.registerService(JettyService.class, jettyService); context.registerService(WebServer.class, jettyService); } @@ -87,8 +100,24 @@ public void shutdown() { } @Provider + @Deprecated(since = "0.11.0") public WebServiceConfigurer webServiceContextConfigurator(ServiceExtensionContext context) { - return new WebServiceConfigurerImpl(context.getMonitor()); + return new WebServiceConfigurerImpl(context.getMonitor(), portMappings); + } + + @Provider + public PortMappings portMappings() { + return portMappings; + } + + @Settings + record DefaultApiConfiguration( + @Setting(key = "web.http.port", description = "Port for default api context", defaultValue = DEFAULT_PORT + "") + int port, + @Setting(key = "web.http.path", description = "Path for default api context", defaultValue = DEFAULT_PATH) + String path + ) { + } } diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyService.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyService.java index 6504a6e40cd..d54437a3577 100644 --- a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyService.java +++ b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/JettyService.java @@ -21,6 +21,8 @@ import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; @@ -37,14 +39,13 @@ import java.security.KeyStore; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.function.Consumer; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.joining; import static org.eclipse.jetty.servlet.ServletContextHandler.NO_SESSIONS; /** @@ -58,16 +59,18 @@ public class JettyService implements WebServer { private final KeyStore keyStore; private final Map handlers = new HashMap<>(); private final List> connectorConfigurationCallbacks = new ArrayList<>(); + private final PortMappings portMappings; private Server server; - public JettyService(JettyConfiguration configuration, Monitor monitor) { - this(configuration, null, monitor); + public JettyService(JettyConfiguration configuration, Monitor monitor, PortMappings portMappings) { + this(configuration, null, monitor, portMappings); } - public JettyService(JettyConfiguration configuration, KeyStore keyStore, Monitor monitor) { + public JettyService(JettyConfiguration configuration, KeyStore keyStore, Monitor monitor, PortMappings portMappings) { this.configuration = configuration; this.keyStore = keyStore; this.monitor = monitor; + this.portMappings = portMappings; System.setProperty(LOG_ANNOUNCE, "false"); // for websocket endpoints handlers.put("/", new ServletContextHandler(null, "/", NO_SESSIONS)); @@ -76,37 +79,17 @@ public JettyService(JettyConfiguration configuration, KeyStore keyStore, Monitor public void start() { try { server = new Server(); - //create a connector for every port mapping - configuration.getPortMappings().forEach(mapping -> { - if (!mapping.getPath().startsWith("/")) { - throw new IllegalArgumentException("A context path must start with /: " + mapping.getPath()); - } - - ServerConnector connector; - if (Arrays.stream(server.getConnectors()).anyMatch(c -> ((ServerConnector) c).getPort() == mapping.getPort())) { - throw new IllegalArgumentException("A binding for port " + mapping.getPort() + " already exists"); - } - - if (keyStore != null) { - connector = httpsServerConnector(mapping.getPort()); - monitor.debug("HTTPS context '" + mapping.getName() + "' listening on port " + mapping.getPort()); - } else { - connector = httpServerConnector(); - monitor.debug("HTTP context '" + mapping.getName() + "' listening on port " + mapping.getPort()); - } - - connector.setName(mapping.getName()); - connector.setPort(mapping.getPort()); - - configure(connector); - server.addConnector(connector); - - var handler = createHandler(mapping); - handlers.put(mapping.getPath(), handler); - }); + var portMappingsDescription = portMappings.getAll().stream() + .peek(mapping -> { + server.addConnector(createConnector(mapping)); + handlers.put(mapping.path(), createHandler(mapping)); + }) + .map(PortMapping::toString) + .collect(joining(", ")); + server.setHandler(new ContextHandlerCollection(handlers.values().toArray(ServletContextHandler[]::new))); server.start(); - monitor.debug("Port mappings: " + configuration.getPortMappings().stream().map(PortMapping::toString).collect(Collectors.joining(", "))); + monitor.debug("Port mappings: " + portMappingsDescription); } catch (Exception e) { throw new EdcException("Error starting Jetty service", e); } @@ -130,11 +113,11 @@ public void registerServlet(String contextName, Servlet servlet) { servletHolder.setServlet(servlet); servletHolder.setInitOrder(1); - var actualPath = configuration.getPortMappings().stream() - .filter(pm -> Objects.equals(contextName, pm.getName())) + var actualPath = portMappings.getAll().stream() + .filter(pm -> Objects.equals(contextName, pm.name())) .findFirst() .orElseThrow(() -> new IllegalArgumentException("No PortMapping for contextName '" + contextName + "' found")) - .getPath(); + .path(); var servletHandler = getOrCreate(actualPath).getServletHandler(); servletHandler.addServletWithMapping(servletHolder, actualPath); @@ -143,70 +126,60 @@ public void registerServlet(String contextName, Servlet servlet) { servletHandler.addServletWithMapping(servletHolder, actualPath + allPathSpec); } - /** - * Allows adding a {@link PortMapping} that is not defined in the configuration. This can only - * be done before the JettyService is started, i.e. before {@link #start()} is called. - * - * @param contextName name of the port mapping. - * @param port port of the port mapping. - * @param path path of the port mapping. - */ - @Override - public void addPortMapping(String contextName, int port, String path) { - var portMapping = new PortMapping(contextName, port, path); - if (server != null && (server.isStarted() || server.isStarting())) { - return; - } - configuration.getPortMappings().add(portMapping); - } - public void addConnectorConfigurationCallback(Consumer callback) { connectorConfigurationCallbacks.add(callback); } + private @NotNull ServerConnector createConnector(PortMapping mapping) { + ServerConnector connector; + if (keyStore != null) { + connector = new ServerConnector(server, getSslConnectionFactory(), httpsConnectionFactory(mapping.port())); + monitor.debug("HTTPS context '" + mapping.name() + "' listening on port " + mapping.port()); + } else { + connector = new ServerConnector(server, httpConnectionFactory()); + monitor.debug("HTTP context '" + mapping.name() + "' listening on port " + mapping.port()); + } + + connector.setName(mapping.name()); + connector.setPort(mapping.port()); + + connectorConfigurationCallbacks.forEach(c -> c.accept(connector)); + + return connector; + } + @NotNull private ServletContextHandler createHandler(PortMapping mapping) { var handler = new ServletContextHandler(server, "/", NO_SESSIONS); - handler.setVirtualHosts(new String[]{ "@" + mapping.getName() }); + handler.setVirtualHosts(new String[]{ "@" + mapping.name() }); return handler; } @NotNull - private ServerConnector httpsServerConnector(int port) { - var storePassword = configuration.getKeystorePassword(); - var managerPassword = configuration.getKeymanagerPassword(); - - // for reference check: - // https://medium.com/vividcode/enable-https-support-with-self-signed-certificate-for-embedded-jetty-9-d3a86f83e9d9 - var contextFactory = new SslContextFactory.Server(); - contextFactory.setKeyStore(keyStore); - contextFactory.setKeyStorePassword(storePassword); - contextFactory.setKeyManagerPassword(managerPassword); + private HttpConnectionFactory httpConnectionFactory() { + var httpConfiguration = new HttpConfiguration(); + httpConfiguration.setSendServerVersion(false); + return new HttpConnectionFactory(httpConfiguration); + } + private @NotNull HttpConnectionFactory httpsConnectionFactory(int port) { var httpsConfiguration = new HttpConfiguration(); httpsConfiguration.setSecureScheme("https"); httpsConfiguration.setSecurePort(port); httpsConfiguration.addCustomizer(new SecureRequestCustomizer()); - - var httpConnectionFactory = new HttpConnectionFactory(httpsConfiguration); - var sslConnectionFactory = new SslConnectionFactory(contextFactory, HttpVersion.HTTP_1_1.asString()); - return new ServerConnector(server, sslConnectionFactory, httpConnectionFactory); - } - - @NotNull - private ServerConnector httpServerConnector() { - return new ServerConnector(server, httpConnectionFactory()); - } - - private void configure(ServerConnector connector) { - connectorConfigurationCallbacks.forEach(c -> c.accept(connector)); + return new HttpConnectionFactory(httpsConfiguration); } - @NotNull - private HttpConnectionFactory httpConnectionFactory() { - HttpConfiguration https = new HttpConfiguration(); - https.setSendServerVersion(false); - return new HttpConnectionFactory(https); + private @NotNull SslConnectionFactory getSslConnectionFactory() { + var storePassword = configuration.keystorePassword(); + var managerPassword = configuration.keymanagerPassword(); + // for reference check: + // https://medium.com/vividcode/enable-https-support-with-self-signed-certificate-for-embedded-jetty-9-d3a86f83e9d9 + var contextFactory = new SslContextFactory.Server(); + contextFactory.setKeyStore(keyStore); + contextFactory.setKeyStorePassword(storePassword); + contextFactory.setKeyManagerPassword(managerPassword); + return new SslConnectionFactory(contextFactory, HttpVersion.HTTP_1_1.asString()); } private ServletContextHandler getOrCreate(String contextPath) { diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMapping.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMapping.java deleted file mode 100644 index 0b3708ea658..00000000000 --- a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMapping.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2020 - 2022 Microsoft Corporation - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.web.jetty; - -/** - * POJO that contains portmappings for Jetty, consisting of a context alias, a port and a path. - * - * @see JettyConfiguration - * @see JettyService - */ -public class PortMapping { - private final String alias; - private final int port; - private final String path; - - public static PortMapping getDefault() { - return getDefault(JettyConfiguration.DEFAULT_PORT); - } - - public static PortMapping getDefault(int port) { - return new PortMapping(JettyConfiguration.DEFAULT_CONTEXT_NAME, port, JettyConfiguration.DEFAULT_PATH); - } - - public PortMapping(String name, int port, String path) { - alias = name; - this.port = port; - this.path = path; - } - - public String getName() { - return alias; - } - - public int getPort() { - return port; - } - - public String getPath() { - return path; - } - - @Override - public String toString() { - return "{" + - "alias='" + alias + '\'' + - ", port=" + port + - ", path='" + path + '\'' + - '}'; - } -} diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMappingsImpl.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMappingsImpl.java new file mode 100644 index 00000000000..a4baad060ac --- /dev/null +++ b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/PortMappingsImpl.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.web.jetty; + +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PortMappingsImpl implements PortMappings { + + private final Map portMappings = new HashMap<>(); + + @Override + public void register(PortMapping portMapping) { + if (!portMapping.path().startsWith("/")) { + throw new IllegalArgumentException("A context path must start with '/', instead it was: %s ".formatted(portMapping.path())); + } + + if (portMappings.containsKey(portMapping.port())) { + throw new IllegalArgumentException("A binding for port %s already exists".formatted(portMapping.port())); + } + portMappings.put(portMapping.port(), portMapping); + } + + @Override + public List getAll() { + return portMappings.values().stream().toList(); + } +} diff --git a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImpl.java b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImpl.java index 2cffa255379..f07a1612b56 100644 --- a/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImpl.java +++ b/extensions/common/http/jetty-core/src/main/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImpl.java @@ -17,38 +17,44 @@ import org.eclipse.edc.spi.monitor.Monitor; import org.eclipse.edc.spi.system.configuration.Config; -import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; import org.eclipse.edc.web.spi.configuration.WebServiceSettings; import static java.lang.String.format; +@Deprecated(since = "0.11.0") public class WebServiceConfigurerImpl implements WebServiceConfigurer { private final Monitor monitor; + private final PortMappings portMappings; - public WebServiceConfigurerImpl(Monitor monitor) { + public WebServiceConfigurerImpl(Monitor monitor, PortMappings portMappings) { this.monitor = monitor; + this.portMappings = portMappings; } @Override - public WebServiceConfiguration configure(Config config, WebServer webServer, WebServiceSettings settings) { + public WebServiceConfiguration configure(Config config, WebServiceSettings settings) { var apiConfig = settings.apiConfigKey(); + var contextAlias = settings.getContextAlias(); var port = settings.getDefaultPort(); var path = settings.getDefaultPath(); - var contextAlias = settings.getContextAlias(); - if (!config.getEntries().isEmpty()) { - port = config.getInteger("port", port); - path = config.getString("path", path); - } else { + if (config.getEntries().isEmpty()) { monitor.warning("Settings for [%s] and/or [%s] were not provided. Using default value(s) instead." .formatted(apiConfig + ".path", apiConfig + ".path")); - - webServer.addPortMapping(contextAlias, port, path); + portMappings.register(new PortMapping(contextAlias, port, path)); + } else { + port = config.getInteger("port", port); + path = config.getString("path", path); + portMappings.register(new PortMapping(contextAlias, port, path)); } + portMappings.register(new PortMapping(contextAlias, port, path)); + monitor.debug(format("%s API will be available under port=%s, path=%s", contextAlias, port, path)); return WebServiceConfiguration.Builder.newInstance() diff --git a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyConfigurationTest.java b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyConfigurationTest.java deleted file mode 100644 index d2ee29dd5bc..00000000000 --- a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyConfigurationTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2020 - 2022 Microsoft Corporation - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0 - * - * SPDX-License-Identifier: Apache-2.0 - * - * Contributors: - * Microsoft Corporation - initial API and implementation - * - */ - -package org.eclipse.edc.web.jetty; - -import org.assertj.core.api.ThrowableAssert; -import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.junit.jupiter.api.Test; - -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -class JettyConfigurationTest { - - - @Test - void createFromConfig_defaultPort() { - - var res = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of("web.http.port", "1234"))); - assertThat(res.getPortMappings()).hasSize(1).allSatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("default"); - assertThat(pm.getPort()).isEqualTo(1234); - }); - } - - @Test - void createFromConfig_noPortFound() { - var res = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of())); - assertThat(res.getPortMappings()).hasSize(1).allSatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("default"); - assertThat(pm.getPath()).isEqualTo("/api"); - assertThat(pm.getPort()).isEqualTo(8181); - }); - - } - - @Test - void createFromConfig_implicitDefaultAndAnotherPort() { - var res = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of( - "web.http.port", "1234", - "web.http.another.port", "8888", - "web.http.another.path", "/foo/bar" - ))); - - assertThat(res.getPortMappings()).hasSize(2).anySatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("default"); - assertThat(pm.getPort()).isEqualTo(1234); - assertThat(pm.getPath()).isEqualTo("/api"); - }).anySatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("another"); - assertThat(pm.getPort()).isEqualTo(8888); - assertThat(pm.getPath()).isEqualTo("/foo/bar"); - }); - } - - @Test - void createFromConfig_explicitDefaultAndAnotherPort() { - var res = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of( - "web.http.default.port", "1234", - "web.http.another.port", "8888", - "web.http.another.path", "/foo/bar" - ))); - - assertThat(res.getPortMappings()).hasSize(2).anySatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("default"); - assertThat(pm.getPort()).isEqualTo(1234); - assertThat(pm.getPath()).isEqualTo("/api"); - }).anySatisfy(pm -> { - assertThat(pm.getName()).isEqualTo("another"); - assertThat(pm.getPort()).isEqualTo(8888); - assertThat(pm.getPath()).isEqualTo("/foo/bar"); - }); - } - - @Test - void createFromConfig_implicitAndExplicitDefault_shouldThrowException() { - ThrowableAssert.ThrowingCallable doubleDefaultConfig = () -> JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of( - "web.http.port", "1234", - "web.http.default.port", "8888" - ))); - - assertThatThrownBy(doubleDefaultConfig).isInstanceOf(IllegalArgumentException.class).hasMessageStartingWith("A port mapping for web.http.default.port already exists, currently mapped to {port=1234}"); - } - - @Test - void createFromConfig_invalidAliasIsIgnored() { - var result = JettyConfiguration.createFromConfig(null, null, - ConfigFactory.fromMap(Map.of("web.http.this.is.longer.port", "8888"))); - - assertThat(result.getPortMappings()).allSatisfy(p -> assertThat(p).usingRecursiveComparison().isEqualTo(PortMapping.getDefault())); - - } - - // The exception should be thrown when starting Jetty -> Servlet already exists - @Test - void createFromConfig_multipleContextsIdenticalPath_shouldNotThrowException() { - var result = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of( - "web.http.port", "8888", - "web.http.path", "/foo", - "web.http.another.port", "1234", - "web.http.another.name", "test", - "web.http.another.path", "/foo" - ))); - assertThat(result.getPortMappings()).hasSize(2).allMatch(pm -> pm.getPath().equals("/foo")); - - - } - - // The exception should be thrown when starting Jetty -> failed to bind to port - @Test - void createFromConfig_multipleContextsIdenticalPort_shouldNotThrowException() { - var result = JettyConfiguration.createFromConfig(null, null, ConfigFactory.fromMap(Map.of( - "web.http.port", "8888", - "web.http.path", "/foo", - "web.http.another.port", "8888", - "web.http.another.name", "test", - "web.http.another.path", "/another" - ))); - assertThat(result.getPortMappings()).hasSize(2).allMatch(pm -> pm.getPort() == 8888); - - } -} diff --git a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyServiceTest.java b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyServiceTest.java index cf273991a11..c1ee74137d4 100644 --- a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyServiceTest.java +++ b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/JettyServiceTest.java @@ -17,88 +17,66 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.monitor.Monitor; -import org.eclipse.edc.spi.system.configuration.ConfigFactory; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.io.IOException; -import java.net.ConnectException; -import java.util.Map; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; class JettyServiceTest { - private JettyService jettyService; private final Monitor monitor = mock(); + private final PortMappings portMappings = mock(); + private final JettyConfiguration configuration = new JettyConfiguration(null, null); + private final JettyService jettyService = new JettyService(configuration, monitor, portMappings); - @Test - void verifyDefaultPortMapping() { - var config = ConfigFactory.fromMap(Map.of("web.http.port", "7171")); //default port mapping - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); - - jettyService.start(); - - jettyService.registerServlet("default", new TestServlet()); - - given() - .get("http://localhost:7171/api/test/resource") - .then() - .statusCode(200); + @AfterEach + void teardown() { + jettyService.shutdown(); } @Test - @DisplayName("Verifies a custom port mapping") - void verifyCustomPortMapping() { - var config = ConfigFactory.fromMap(Map.of( - "web.http.another.port", "9191", - "web.http.another.path", "/another")); //default port mapping - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); + void shouldRegisterServletOnConfiguredPortMapping() { + var portMapping = new PortMapping("context", 9191, "/path"); + when(portMappings.getAll()).thenReturn(List.of(portMapping)); jettyService.start(); - - jettyService.registerServlet("another", new TestServlet()); + jettyService.registerServlet("context", new TestServlet()); given() - .get("http://localhost:9191/another/test/resource") + .get("http://localhost:9191/path/test/resource") .then() .statusCode(200); - - //verify that there is no default port mapping anymore - assertThatThrownBy(() -> given().get("http://localhost:8872/api/test/resource").then()) - .isInstanceOf(ConnectException.class); } @Test - @DisplayName("Verifies that a custom port mapping and the implicit default mapping is possible") - void verifyDefaultAndCustomPortMapping() { - var config = ConfigFactory.fromMap(Map.of( - "web.http.port", "7171", - "web.http.another.port", "9191", - "web.http.another.path", "/another")); //default port mapping - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); + void shouldConfigureMultipleApiContexts() { + var portMapping = new PortMapping("context", 9191, "/path"); + var anotherPortMapping = new PortMapping("another", 9292, "/another/path"); + when(portMappings.getAll()).thenReturn(List.of(portMapping, anotherPortMapping)); jettyService.start(); - + jettyService.registerServlet("context", new TestServlet()); jettyService.registerServlet("another", new TestServlet()); - jettyService.registerServlet("default", new TestServlet()); given() - .get("http://localhost:9191/another/test/resource") + .get("http://localhost:9191/path/test/resource") .then() .statusCode(200); given() - .get("http://localhost:7171/api/test/resource") + .get("http://localhost:9292/another/path/test/resource") .then() .statusCode(200); } @@ -107,12 +85,10 @@ void verifyDefaultAndCustomPortMapping() { void verifyConnectorConfigurationCallback() { var listener = new JettyListener(); - var config = ConfigFactory.fromMap(Map.of("web.http.port", "7171")); - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); + when(portMappings.getAll()).thenReturn(List.of(new PortMapping("default", 7171, "/api"))); jettyService.addConnectorConfigurationCallback((c) -> c.addBean(listener)); jettyService.start(); - jettyService.registerServlet("default", new TestServlet()); assertThat(listener.getConnectionsOpened()).isEqualTo(0); @@ -125,13 +101,9 @@ void verifyConnectorConfigurationCallback() { @Test void verifyCustomPathRoot() { - var config = ConfigFactory.fromMap(Map.of( - "web.http.port", "7171", - "web.http.path", "/")); - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); + when(portMappings.getAll()).thenReturn(List.of(new PortMapping("default", 7171, "/"))); jettyService.start(); - jettyService.registerServlet("default", new TestServlet()); given() @@ -140,40 +112,6 @@ void verifyCustomPathRoot() { .statusCode(200); } - @Test - void verifyInvalidPathSpecThrowsException() { - var config = ConfigFactory.fromMap(Map.of( - "web.http.port", "7171", - "web.http.another.port", "9191", - "web.http.another.path", "another")); //misses leading slash - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); - - assertThatThrownBy(() -> jettyService.start()).isInstanceOf(EdcException.class) - .hasMessage("Error starting Jetty service") - .hasRootCauseInstanceOf(IllegalArgumentException.class) - .hasRootCauseMessage("A context path must start with /: another"); - } - - @Test - void verifyIdenticalPorts_shouldThrowException() { - var config = ConfigFactory.fromMap(Map.of( - "web.http.first.port", "7171", - "web.http.first.path", "/first", - "web.http.another.port", "7171", - "web.http.another.path", "/another")); - jettyService = new JettyService(JettyConfiguration.createFromConfig(null, null, config), monitor); - - assertThatThrownBy(() -> jettyService.start()).isInstanceOf(EdcException.class) - .hasMessage("Error starting Jetty service") - .hasRootCauseInstanceOf(IllegalArgumentException.class) - .hasRootCauseMessage("A binding for port 7171 already exists"); - } - - @AfterEach - void teardown() { - jettyService.shutdown(); - } - private static class JettyListener extends AbstractLifeCycle implements Connection.Listener { private final AtomicInteger connectionsOpened = new AtomicInteger(); diff --git a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/PortMappingsImplTest.java b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/PortMappingsImplTest.java new file mode 100644 index 00000000000..75d45c9fa53 --- /dev/null +++ b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/PortMappingsImplTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.web.jetty; + +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class PortMappingsImplTest { + + private final PortMappingsImpl portMappings = new PortMappingsImpl(); + + @Test + void shouldReturnNoMappings_whenNoRegistration() { + assertThat(portMappings.getAll()).isEmpty(); + } + + @Test + void shouldReturnRegisteredMappings() { + var mapping = new PortMapping("name", 9292, "/path"); + portMappings.register(mapping); + + var result = portMappings.getAll(); + + assertThat(result).hasSize(1).containsOnly(mapping); + } + + @Test + void shouldThrowException_whenMappingForPortAlreadyExist() { + var mapping = new PortMapping("name", 9292, "/path"); + portMappings.register(mapping); + + var invalidMapping = new PortMapping("invalid", 9292, "/invalid"); + + assertThatThrownBy(() -> portMappings.register(invalidMapping)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void shouldThrowException_whenPathDoesNotStartWithSlash() { + var mapping = new PortMapping("name", 9292, "without/trailing/slash"); + + assertThatThrownBy(() -> portMappings.register(mapping)).isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImplTest.java b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImplTest.java index 0eb07619990..dd23eac0d2c 100644 --- a/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImplTest.java +++ b/extensions/common/http/jetty-core/src/test/java/org/eclipse/edc/web/jetty/WebServiceConfigurerImplTest.java @@ -15,7 +15,8 @@ package org.eclipse.edc.web.jetty; import org.eclipse.edc.spi.system.configuration.ConfigFactory; -import org.eclipse.edc.web.spi.WebServer; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; import org.eclipse.edc.web.spi.configuration.WebServiceSettings; import org.junit.jupiter.api.Test; @@ -24,19 +25,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +@Deprecated(since = "0.11.0") public class WebServiceConfigurerImplTest { private static final String CONFIG = "web.http.test"; private static final String PATH = "/api"; private static final String ALIAS = "test"; private static final int PORT = 8080; - private final WebServer server = mock(); + private final PortMappings portMappings = mock(); - private final WebServiceConfigurer configurator = new WebServiceConfigurerImpl(mock()); + private final WebServiceConfigurer configurator = new WebServiceConfigurerImpl(mock(), portMappings); @Test void verifyConfigure_whenDefaultConfig() { @@ -49,11 +49,9 @@ void verifyConfigure_whenDefaultConfig() { .defaultPort(PORT) .build(); - var actualConfig = configurator.configure(config, server, settings); - - verify(server, times(1)) - .addPortMapping(ALIAS, PORT, PATH); + var actualConfig = configurator.configure(config, settings); + verify(portMappings).register(new PortMapping(ALIAS, PORT, PATH)); assertThat(actualConfig.getPort()).isEqualTo(PORT); assertThat(actualConfig.getPath()).isEqualTo(PATH); } @@ -76,9 +74,9 @@ void verifyConfigure_whenExternalConfig() { .defaultPort(PORT) .build(); - var actualConfig = configurator.configure(config, server, settings); + var actualConfig = configurator.configure(config, settings); - verifyNoInteractions(server); + verify(portMappings).register(new PortMapping(ALIAS, port, path)); assertThat(actualConfig.getPort()).isEqualTo(port); assertThat(actualConfig.getPath()).isEqualTo(path); } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-accounts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/accounts/StsAccountsApiConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-accounts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/accounts/StsAccountsApiConfigurationExtension.java index 1ee6839b93f..e7613b43106 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-accounts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/accounts/StsAccountsApiConfigurationExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-accounts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/accounts/StsAccountsApiConfigurationExtension.java @@ -15,19 +15,20 @@ package org.eclipse.edc.api.iam.identitytrust.sts.accounts; import com.fasterxml.jackson.databind.DeserializationFeature; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.apiversion.ApiVersionService; import org.eclipse.edc.spi.system.apiversion.VersionRecord; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import java.io.IOException; import java.util.stream.Stream; @@ -36,26 +37,18 @@ public class StsAccountsApiConfigurationExtension implements ServiceExtension { public static final String NAME = "Secure Token Service Accounts API configuration"; - private static final int DEFAULT_STS_ACCOUNTS_API_PORT = 9393; - - @SettingContext("Sts API context setting key") - private static final String STS_ACCOUNTS_CONFIG_KEY = "web.http." + ApiContext.STS_ACCOUNTS; - - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(STS_ACCOUNTS_CONFIG_KEY) - .contextAlias(ApiContext.STS_ACCOUNTS) - .defaultPort(DEFAULT_STS_ACCOUNTS_API_PORT) - .build(); + private static final int DEFAULT_STS_ACCOUNTS_PORT = 9393; + private static final String DEFAULT_STS_ACCOUNTS_PATH = "/api/accounts"; private static final String API_VERSION_JSON_FILE = "sts-accounts-api-version.json"; - @Inject - private WebServer webServer; - @Inject - private WebServiceConfigurer configurator; + @Configuration + private StsAccountApiConfiguration apiConfiguration; @Inject private TypeManager typeManager; @Inject private ApiVersionService apiVersionService; + @Inject + private PortMappings portMappings; @Override public String name() { @@ -64,8 +57,8 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(STS_ACCOUNTS_CONFIG_KEY); - configurator.configure(config, webServer, SETTINGS); + var portMapping = new PortMapping(ApiContext.STS_ACCOUNTS, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); registerVersionInfo(getClass().getClassLoader()); } @@ -82,4 +75,14 @@ private void registerVersionInfo(ClassLoader resourceClassLoader) { throw new EdcException(e); } } + + @Settings + record StsAccountApiConfiguration( + @Setting(key = "web.http." + ApiContext.STS_ACCOUNTS + ".port", description = "Port for " + ApiContext.STS_ACCOUNTS + " api context", defaultValue = DEFAULT_STS_ACCOUNTS_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.STS_ACCOUNTS + ".path", description = "Path for " + ApiContext.STS_ACCOUNTS + " api context", defaultValue = DEFAULT_STS_ACCOUNTS_PATH) + String path + ) { + + } } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtension.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtension.java index ce1fa43fa5c..00527c288ba 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtension.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/main/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtension.java @@ -15,19 +15,20 @@ package org.eclipse.edc.api.iam.identitytrust.sts; import com.fasterxml.jackson.databind.DeserializationFeature; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.EdcException; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import org.eclipse.edc.spi.system.apiversion.ApiVersionService; import org.eclipse.edc.spi.system.apiversion.VersionRecord; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import java.io.IOException; import java.util.stream.Stream; @@ -36,22 +37,15 @@ public class StsApiConfigurationExtension implements ServiceExtension { public static final String NAME = "Secure Token Service API configuration"; - private static final int DEFAULT_STS_API_PORT = 9292; + static final int DEFAULT_STS_PORT = 9292; + static final String DEFAULT_STS_PATH = "/api/sts"; - @SettingContext("Sts API context setting key") - private static final String STS_CONFIG_KEY = "web.http." + ApiContext.STS; - - public static final WebServiceSettings SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(STS_CONFIG_KEY) - .contextAlias(ApiContext.STS) - .defaultPort(DEFAULT_STS_API_PORT) - .build(); private static final String API_VERSION_JSON_FILE = "sts-api-version.json"; + @Configuration + private StsApiConfiguration apiConfiguration; @Inject - private WebServer webServer; - @Inject - private WebServiceConfigurer configurator; + private PortMappings portMappings; @Inject private TypeManager typeManager; @Inject @@ -64,8 +58,7 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(STS_CONFIG_KEY); - configurator.configure(config, webServer, SETTINGS); + portMappings.register(new PortMapping(ApiContext.STS, apiConfiguration.port(), apiConfiguration.path())); registerVersionInfo(getClass().getClassLoader()); } @@ -82,4 +75,14 @@ private void registerVersionInfo(ClassLoader resourceClassLoader) { throw new EdcException(e); } } + + @Settings + record StsApiConfiguration( + @Setting(key = "web.http." + ApiContext.STS + ".port", description = "Port for " + ApiContext.STS + " api context", defaultValue = DEFAULT_STS_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.STS + ".path", description = "Path for " + ApiContext.STS + " api context", defaultValue = DEFAULT_STS_PATH) + String path + ) { + + } } diff --git a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtensionTest.java b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtensionTest.java index 5aac58af969..7b607812ba1 100644 --- a/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtensionTest.java +++ b/extensions/common/iam/identity-trust/identity-trust-sts/identity-trust-sts-api/src/test/java/org/eclipse/edc/api/iam/identitytrust/sts/StsApiConfigurationExtensionTest.java @@ -22,44 +22,38 @@ import org.eclipse.edc.spi.system.configuration.Config; import org.eclipse.edc.spi.system.configuration.ConfigFactory; import org.eclipse.edc.spi.types.TypeManager; -import org.eclipse.edc.web.spi.WebService; -import org.eclipse.edc.web.spi.configuration.WebServiceConfiguration; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; +import org.eclipse.edc.web.spi.configuration.ApiContext; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import static org.eclipse.edc.api.iam.identitytrust.sts.StsApiConfigurationExtension.SETTINGS; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.eclipse.edc.api.iam.identitytrust.sts.StsApiConfigurationExtension.DEFAULT_STS_PATH; +import static org.eclipse.edc.api.iam.identitytrust.sts.StsApiConfigurationExtension.DEFAULT_STS_PORT; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; @ExtendWith(DependencyInjectionExtension.class) public class StsApiConfigurationExtensionTest { - private final WebServiceConfigurer configurer = mock(); - private final Monitor monitor = mock(Monitor.class); - private final WebService webService = mock(WebService.class); + private final PortMappings portMappings = mock(); + private final Monitor monitor = mock(); @BeforeEach void setUp(ServiceExtensionContext context) { - context.registerService(WebService.class, webService); - context.registerService(WebServiceConfigurer.class, configurer); + context.registerService(PortMappings.class, portMappings); context.registerService(TypeManager.class, new JacksonTypeManager()); } @Test void initialize_shouldConfigureAndRegisterResource(StsApiConfigurationExtension extension) { var context = contextWithConfig(ConfigFactory.empty()); - var configuration = WebServiceConfiguration.Builder.newInstance().path("/path").port(1234).build(); - when(configurer.configure(any(), any(), any())).thenReturn(configuration); extension.initialize(context); - verify(configurer).configure(any(), any(), eq(SETTINGS)); + verify(portMappings).register(new PortMapping(ApiContext.STS, DEFAULT_STS_PORT, DEFAULT_STS_PATH)); } @NotNull diff --git a/extensions/data-plane/data-plane-public-api-v2/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiV2Extension.java b/extensions/data-plane/data-plane-public-api-v2/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiV2Extension.java index 15b370191ab..ab8454b1f18 100644 --- a/extensions/data-plane/data-plane-public-api-v2/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiV2Extension.java +++ b/extensions/data-plane/data-plane-public-api-v2/src/main/java/org/eclipse/edc/connector/dataplane/api/DataPlanePublicApiV2Extension.java @@ -19,19 +19,19 @@ import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAuthorizationService; import org.eclipse.edc.connector.dataplane.spi.iam.PublicEndpointGeneratorService; import org.eclipse.edc.connector.dataplane.spi.pipeline.PipelineService; +import org.eclipse.edc.runtime.metamodel.annotation.Configuration; import org.eclipse.edc.runtime.metamodel.annotation.Extension; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Setting; -import org.eclipse.edc.runtime.metamodel.annotation.SettingContext; +import org.eclipse.edc.runtime.metamodel.annotation.Settings; import org.eclipse.edc.spi.system.ExecutorInstrumentation; import org.eclipse.edc.spi.system.Hostname; import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import org.eclipse.edc.web.spi.WebServer; import org.eclipse.edc.web.spi.WebService; import org.eclipse.edc.web.spi.configuration.ApiContext; -import org.eclipse.edc.web.spi.configuration.WebServiceConfigurer; -import org.eclipse.edc.web.spi.configuration.WebServiceSettings; +import org.eclipse.edc.web.spi.configuration.PortMapping; +import org.eclipse.edc.web.spi.configuration.PortMappings; import java.util.concurrent.Executors; @@ -44,9 +44,7 @@ public class DataPlanePublicApiV2Extension implements ServiceExtension { public static final String NAME = "Data Plane Public API"; private static final int DEFAULT_PUBLIC_PORT = 8185; - - @SettingContext("Public API context setting key") - private static final String PUBLIC_CONFIG_KEY = "web.http." + ApiContext.PUBLIC; + private static final String DEFAULT_PUBLIC_PATH = "/api/public"; @Setting(description = "Base url of the public API endpoint without the trailing slash. This should point to the public endpoint configured.", required = false, @@ -57,33 +55,21 @@ public class DataPlanePublicApiV2Extension implements ServiceExtension { private String publicApiResponseUrl; private static final int DEFAULT_THREAD_POOL = 10; - private static final WebServiceSettings PUBLIC_SETTINGS = WebServiceSettings.Builder.newInstance() - .apiConfigKey(PUBLIC_CONFIG_KEY) - .contextAlias(ApiContext.PUBLIC) - .defaultPort(DEFAULT_PUBLIC_PORT) - .build(); - - @Inject - private WebServer webServer; + @Configuration + private PublicApiConfiguration apiConfiguration; @Inject - private WebServiceConfigurer webServiceConfigurer; - + private PortMappings portMappings; @Inject private PipelineService pipelineService; - @Inject private WebService webService; - @Inject private ExecutorInstrumentation executorInstrumentation; - @Inject private DataPlaneAuthorizationService authorizationService; - @Inject private PublicEndpointGeneratorService generatorService; - @Inject private Hostname hostname; @@ -94,16 +80,15 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - var config = context.getConfig(PUBLIC_CONFIG_KEY); - var configuration = webServiceConfigurer.configure(config, webServer, PUBLIC_SETTINGS); + var portMapping = new PortMapping(ApiContext.PUBLIC, apiConfiguration.port(), apiConfiguration.path()); + portMappings.register(portMapping); var executorService = executorInstrumentation.instrument( Executors.newFixedThreadPool(DEFAULT_THREAD_POOL), "Data plane proxy transfers" ); - if (publicBaseUrl == null) { - publicBaseUrl = "http://%s:%d%s".formatted(hostname.get(), configuration.getPort(), configuration.getPath()); + publicBaseUrl = "http://%s:%d%s".formatted(hostname.get(), portMapping.port(), portMapping.path()); context.getMonitor().warning("The public API endpoint was not explicitly configured, the default '%s' will be used.".formatted(publicBaseUrl)); } var endpoint = Endpoint.url(publicBaseUrl); @@ -116,4 +101,14 @@ public void initialize(ServiceExtensionContext context) { var publicApiController = new DataPlanePublicApiV2Controller(pipelineService, executorService, authorizationService); webService.registerResource(ApiContext.PUBLIC, publicApiController); } + + @Settings + record PublicApiConfiguration( + @Setting(key = "web.http." + ApiContext.PUBLIC + ".port", description = "Port for " + ApiContext.PUBLIC + " api context", defaultValue = DEFAULT_PUBLIC_PORT + "") + int port, + @Setting(key = "web.http." + ApiContext.PUBLIC + ".path", description = "Path for " + ApiContext.PUBLIC + " api context", defaultValue = DEFAULT_PUBLIC_PATH) + String path + ) { + + } } diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/WebServer.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/WebServer.java index 31f42528a51..53e6746a143 100644 --- a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/WebServer.java +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/WebServer.java @@ -23,18 +23,6 @@ @ExtensionPoint public interface WebServer { - String DEFAULT_CONTEXT_NAME = "default"; - - - /** - * Adds a new port mapping and thus a new API context to this web server. - * - * @param contextName the name of the API context. - * @param port the port of the API context. - * @param path the path of the API context. - */ - void addPortMapping(String contextName, int port, String path); - /** * Adds a new servlet to the specified context name.. * @@ -43,11 +31,4 @@ public interface WebServer { */ void registerServlet(String contextName, Servlet servlet); - /** - * Returns the default context name - */ - default String getDefaultContextName() { - return DEFAULT_CONTEXT_NAME; - } - } diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMapping.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMapping.java new file mode 100644 index 00000000000..0f41f2f942f --- /dev/null +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMapping.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.web.spi.configuration; + +/** + * POJO that contains port mappings for api context, consisting of a context alias, a port and a path. + */ +public record PortMapping(String name, int port, String path) { + +} diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMappings.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMappings.java new file mode 100644 index 00000000000..3151076fa2c --- /dev/null +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/PortMappings.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Cofinity-X + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * + * Contributors: + * Cofinity-X - initial API and implementation + * + */ + +package org.eclipse.edc.web.spi.configuration; + +import java.util.List; + +/** + * PortMapping registry. + */ +public interface PortMappings { + + /** + * Register a PortMapping. + * + * @param portMapping the port mapping. + */ + void register(PortMapping portMapping); + + /** + * Return all the registered port mapping. + * + * @return all the port mappings registered. + */ + List getAll(); + +} diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfiguration.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfiguration.java index 69316f4c9d9..04d7a8f96bb 100644 --- a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfiguration.java +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfiguration.java @@ -18,7 +18,10 @@ /** * WebService configuration returned from {@link WebServiceConfigurer} + * + * @deprecated please use {@link PortMapping}. */ +@Deprecated(since = "0.11.0") public class WebServiceConfiguration { private Integer port; diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfigurer.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfigurer.java index 13360625653..bafb9a91c66 100644 --- a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfigurer.java +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceConfigurer.java @@ -17,12 +17,14 @@ import org.eclipse.edc.runtime.metamodel.annotation.ExtensionPoint; import org.eclipse.edc.spi.system.configuration.Config; -import org.eclipse.edc.web.spi.WebServer; /** * Configure an API extension + * + * @deprecated please use {@link PortMappings} service. */ @ExtensionPoint +@Deprecated(since = "0.11.0") public interface WebServiceConfigurer { String WEB_HTTP_PREFIX = "web.http"; @@ -31,9 +33,10 @@ public interface WebServiceConfigurer { * Build the configuration for an API * * @param config The context configuration - * @param webServer The WebServer * @param settings WebService settings * @return The final webservice configuration + * @deprecated please use {@link PortMappings} */ - WebServiceConfiguration configure(Config config, WebServer webServer, WebServiceSettings settings); + @Deprecated(since = "0.11.0") + WebServiceConfiguration configure(Config config, WebServiceSettings settings); } diff --git a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceSettings.java b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceSettings.java index 01e2ed47c57..ecbda46da79 100644 --- a/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceSettings.java +++ b/spi/common/web-spi/src/main/java/org/eclipse/edc/web/spi/configuration/WebServiceSettings.java @@ -16,6 +16,7 @@ import java.util.Objects; +@Deprecated(since = "0.11.0") public class WebServiceSettings { private String apiConfigKey; diff --git a/data-protocols/dsp/dsp-catalog/lib/dsp-catalog-http-api-lib/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java b/spi/common/web-spi/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java similarity index 96% rename from data-protocols/dsp/dsp-catalog/lib/dsp-catalog-http-api-lib/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java rename to spi/common/web-spi/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java index 7cbface6429..d85e8209b3a 100644 --- a/data-protocols/dsp/dsp-catalog/lib/dsp-catalog-http-api-lib/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java +++ b/spi/common/web-spi/src/test/java/org/eclipse/edc/web/spi/configuration/WebServiceSettingsTest.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; +@Deprecated(since = "0.11.0") class WebServiceSettingsTest { @Test