diff --git a/src/apps/geoserver/gwc/pom.xml b/src/apps/geoserver/gwc/pom.xml index 53eca065a..50b25f864 100644 --- a/src/apps/geoserver/gwc/pom.xml +++ b/src/apps/geoserver/gwc/pom.xml @@ -49,6 +49,19 @@ spring-boot-starter-test test + + org.geoserver.cloud.catalog + gs-cloud-catalog-plugin + ${project.version} + test-jar + test + + + * + * + + + diff --git a/src/apps/geoserver/gwc/src/test/java/org/geoserver/cloud/gwc/app/GeoWebCacheApplicationTest.java b/src/apps/geoserver/gwc/src/test/java/org/geoserver/cloud/gwc/app/GeoWebCacheApplicationTest.java index 0039127cd..af83b7a89 100644 --- a/src/apps/geoserver/gwc/src/test/java/org/geoserver/cloud/gwc/app/GeoWebCacheApplicationTest.java +++ b/src/apps/geoserver/gwc/src/test/java/org/geoserver/cloud/gwc/app/GeoWebCacheApplicationTest.java @@ -5,22 +5,33 @@ package org.geoserver.cloud.gwc.app; import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.http.MediaType.APPLICATION_JSON; -import static org.springframework.http.MediaType.APPLICATION_XML; +import static org.springframework.http.MediaType.*; import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import org.geoserver.catalog.GeoServerCatalogTestData; +import org.geoserver.gwc.controller.GwcUrlHandlerMapping; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.ApplicationContext; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @@ -28,6 +39,23 @@ class GeoWebCacheApplicationTest { @Autowired private TestRestTemplate restTemplate; + @Autowired private ApplicationContext context; + + @TempDir private static Path tmpPath; + + @BeforeAll + static void initializeDatadir() throws URISyntaxException, IOException { + assertThat(tmpPath).isNotNull(); + GeoServerCatalogTestData.unzipGeoserverCatalogTestData(tmpPath); + } + + @DynamicPropertySource + static void dynamicProperties(DynamicPropertyRegistry registry) + throws URISyntaxException, IOException { + File tmpFilePath = tmpPath.toFile(); + registry.add("geoserver.backend.data-directory.location", tmpFilePath::toString); + } + @BeforeEach void before() { restTemplate = restTemplate.withBasicAuth("admin", "geoserver"); @@ -53,10 +81,84 @@ void testRESTPathExtensionContentNegotiation() { testGetRequestContentType("/gwc/rest/layers.xml", APPLICATION_XML); } + @Test + void testGwcUrlHandlerMappingArePresentInTheClasspath() { + assertThat(context.isTypeMatch("gwcDemoUrlHandlerMapping", GwcUrlHandlerMapping.class)) + .as("expected a bean gwcDemoUrlHandlerMapping of type GwcUrlHandlerMapping") + .isTrue(); + assertThat(context.isTypeMatch("gwcRestWebUrlHandlerMapping", GwcUrlHandlerMapping.class)) + .as("expected a bean gwcRestWebUrlHandlerMapping of type GwcUrlHandlerMapping") + .isTrue(); + } + + @Test + void testGeneralGwcHome() { + ResponseEntity response = testGetRequestContentType("/gwc/home", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGeneralGwcDemo() { + ResponseEntity response = testGetRequestContentType("/gwc/demo", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGeneralGwcDemoWithNameSpaceLayer() { + ResponseEntity response = + testGetRequestContentType("/gwc/demo/ne:boundary_lines", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGeneralGwcServiceWmts() { + ResponseEntity response = + testGetRequestContentType("/gwc/service/wmts?REQUEST=GetCapabilities", TEXT_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGwcDemoPrefixedUrls() { + ResponseEntity response = testGetRequestContentType("/ne/gwc/demo", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGwcDemoWorkspacePrefixedUrlsLayer() { + ResponseEntity response = + testGetRequestContentType("/ne/gwc/demo/boundary_lines", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGwcDemoWorkspacePrefixedUrlsWorkspaceLayer() { + ResponseEntity response = + testGetRequestContentType("/ne/gwc/demo/ne:boundary_lines", TEXT_HTML); + assertThat(response.getHeaders().getContentType()).isNotEqualTo(APPLICATION_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + + @Test + void testGwcServicePrefixedUrl() { + ResponseEntity response = + testGetRequestContentType("/ne/gwc/service/wmts?REQUEST=GetCapabilities", TEXT_XML); + assertThat(response.getBody()).doesNotContain("ows:Exception"); + } + protected ResponseEntity testGetRequestContentType(String uri, MediaType expected) { ResponseEntity response = restTemplate.getForEntity(uri, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders().getContentType()).isEqualTo(expected); + assertThat(response.getHeaders().getContentType().isCompatibleWith(expected)) + .as( + String.format( + "expected content-type %s to be compatible with %s", + response.getHeaders().getContentType(), expected)) + .isTrue(); return response; } } diff --git a/src/catalog/plugin/pom.xml b/src/catalog/plugin/pom.xml index 483154e44..1d0bf8735 100644 --- a/src/catalog/plugin/pom.xml +++ b/src/catalog/plugin/pom.xml @@ -116,6 +116,11 @@ org.geotools gt-process-feature + + org.geotools + gt-sample-data + ${gt.version} + com.github.f4b6a3 ulid-creator diff --git a/src/catalog/plugin/src/test/java/org/geoserver/catalog/GeoServerCatalogTestData.java b/src/catalog/plugin/src/test/java/org/geoserver/catalog/GeoServerCatalogTestData.java new file mode 100644 index 000000000..27d511fef --- /dev/null +++ b/src/catalog/plugin/src/test/java/org/geoserver/catalog/GeoServerCatalogTestData.java @@ -0,0 +1,51 @@ +/* + * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the + * GPL 2.0 license, available at the root application directory. + */ +package org.geoserver.catalog; + +import lombok.experimental.UtilityClass; + +import org.apache.commons.io.FileUtils; +import org.geotools.test.TestData; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Path; + +/** + * The purpose of this class is to provide a geoserver datadir which is not fake (it is actually a + * copy of the one from GeoServer upstream), which could be used for testing. + * + *

It copies a zip file into a directory - generally a temporary one - an unzip it, so that it is + * ready for use. + */ +@UtilityClass +public class GeoServerCatalogTestData { + + /** + * This method copies the zipped datadir into the Path object given as argument and unzip it at + * the same place. + * + *

It is the caller's responsability to clean up when the datadir is not needed anymore. + * + *

Note: we have to copy the resource into the directory first, because the method from + * GeoTools which is being used does not support zip URIs nested into jar files. + * + * @param tmpPath the temporary path where the datadir has to be unzipped to. + * @throws URISyntaxException + * @throws IOException + */ + public static void unzipGeoserverCatalogTestData(Path tmpPath) + throws URISyntaxException, IOException { + InputStream zippedDatadir = + GeoServerCatalogTestData.class.getResourceAsStream("/test-data-directory.zip"); + File tmpDir = tmpPath.toFile(); + File destFile = new File(tmpDir, "test-data-directory.zip"); + FileUtils.copyToFile(zippedDatadir, destFile); + TestData.unzip(destFile, tmpDir); + destFile.delete(); + } +} diff --git a/src/catalog/plugin/src/test/resources/test-data-directory.zip b/src/catalog/plugin/src/test/resources/test-data-directory.zip new file mode 100644 index 000000000..faf9dafed Binary files /dev/null and b/src/catalog/plugin/src/test/resources/test-data-directory.zip differ diff --git a/src/gwc/autoconfigure/src/main/java/org/geoserver/cloud/autoconfigure/web/gwc/GeoWebCacheUIAutoConfiguration.java b/src/gwc/autoconfigure/src/main/java/org/geoserver/cloud/autoconfigure/web/gwc/GeoWebCacheUIAutoConfiguration.java index 39f5aee81..c8849d58a 100644 --- a/src/gwc/autoconfigure/src/main/java/org/geoserver/cloud/autoconfigure/web/gwc/GeoWebCacheUIAutoConfiguration.java +++ b/src/gwc/autoconfigure/src/main/java/org/geoserver/cloud/autoconfigure/web/gwc/GeoWebCacheUIAutoConfiguration.java @@ -6,12 +6,16 @@ import lombok.extern.slf4j.Slf4j; -import org.geoserver.cloud.autoconfigure.gwc.ConditionalOnGeoWebCacheRestConfigEnabled; +import org.geoserver.catalog.Catalog; import org.geoserver.cloud.autoconfigure.gwc.ConditionalOnWebUIEnabled; import org.geoserver.cloud.gwc.config.core.GeoWebCacheConfigurationProperties; +import org.geoserver.cloud.virtualservice.VirtualServiceVerifier; +import org.geoserver.gwc.controller.GwcUrlHandlerMapping; +import org.geoserver.ows.Dispatcher; import org.geowebcache.GeoWebCacheDispatcher; import org.geowebcache.rest.controller.ByteStreamController; import org.gwc.web.rest.GeoWebCacheController; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -28,17 +32,79 @@ public class GeoWebCacheUIAutoConfiguration { } @Bean - GeoWebCacheController gwcController(GeoWebCacheDispatcher gwcDispatcher) { - return new GeoWebCacheController(gwcDispatcher); + GeoWebCacheController gwcController( + Dispatcher geoserverDispatcher, + GeoWebCacheDispatcher geoWebCacheDispatcher, + VirtualServiceVerifier verifier) { + return new GeoWebCacheController(geoserverDispatcher, geoWebCacheDispatcher, verifier); } - /** - * Provide a handler for static web resources if missing, for example, because {@link - * ConditionalOnGeoWebCacheRestConfigEnabled} is disabled - */ + /** ConditionalOnGeoWebCacheRestConfigEnabled} is disabled */ @Bean @ConditionalOnMissingBean(ByteStreamController.class) ByteStreamController byteStreamController() { return new ByteStreamController(); } + + @Bean + VirtualServiceVerifier virtualServiceVerifier(@Qualifier("rawCatalog") Catalog catalog) { + return new VirtualServiceVerifier(catalog); + } + + /** + * GS's src/web/gwc/src/main/java/applicationContext.xml + * + * + * + * + * + * + * + */ + @Bean + @Qualifier("gwcDemoUrlHandlerMapping") + GwcUrlHandlerMapping gwcDemoUrlHandlerMapping(@Qualifier("rawCatalog") Catalog catalog) { + GwcUrlHandlerMapping handler = new GwcUrlHandlerMapping(catalog, "/gwc/demo"); + handler.setAlwaysUseFullPath(true); + handler.setOrder(10); + + return handler; + } + + /* + @Bean + WebMvcRegistrations gwcDemoUrlHandlerMappingRegistrations( + @Qualifier("gwcDemoUrlHandlerMapping") GwcUrlHandlerMapping handler) { + return new WebMvcRegistrations() { + @Override + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + return handler; + } + }; + }*/ + + @Bean + @Qualifier("gwcRestWebUrlHandlerMapping") + GwcUrlHandlerMapping gwcRestWebUrlHandlerMapping(@Qualifier("rawCatalog") Catalog catalog) { + GwcUrlHandlerMapping handler = new GwcUrlHandlerMapping(catalog, "/gwc/rest/web"); + handler.setAlwaysUseFullPath(true); + handler.setOrder(10); + + return handler; + } + + /* + @Bean + WebMvcRegistrations gwcRestWebUrlHandlerMappingRegistrations( + @Qualifier("gwcRestWebUrlHandlerMapping") GwcUrlHandlerMapping handler) { + return new WebMvcRegistrations() { + @Override + public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { + return handler; + } + }; + }*/ } diff --git a/src/gwc/autoconfigure/src/test/java/org/geoserver/cloud/autoconfigure/gwc/web/gwc/GeoWebCacheUIAutoConfigurationTest.java b/src/gwc/autoconfigure/src/test/java/org/geoserver/cloud/autoconfigure/gwc/web/gwc/GeoWebCacheUIAutoConfigurationTest.java new file mode 100644 index 000000000..95e48578e --- /dev/null +++ b/src/gwc/autoconfigure/src/test/java/org/geoserver/cloud/autoconfigure/gwc/web/gwc/GeoWebCacheUIAutoConfigurationTest.java @@ -0,0 +1,42 @@ +/* + * (c) 2024 Open Source Geospatial Foundation - all rights reserved This code is licensed under the + * GPL 2.0 license, available at the root application directory. + */ +package org.geoserver.cloud.autoconfigure.gwc.web.gwc; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.geoserver.cloud.autoconfigure.gwc.GeoWebCacheContextRunner; +import org.geoserver.cloud.autoconfigure.web.gwc.GeoWebCacheUIAutoConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.WebApplicationContextRunner; + +import java.io.File; + +public class GeoWebCacheUIAutoConfigurationTest { + + WebApplicationContextRunner runner; + + @TempDir File tmpDir; + + @BeforeEach + void setUp() throws Exception { + runner = + GeoWebCacheContextRunner.newMinimalGeoWebCacheContextRunner(tmpDir) + .withPropertyValues("gwc.web-ui=true") + .withConfiguration( + AutoConfigurations.of(GeoWebCacheUIAutoConfiguration.class)); + } + + @Test + void beansForLocalWorkspacePathsHandlingArePresent() { + runner.run( + context -> { + assertNotNull(context.getBean("gwcDemoUrlHandlerMapping")); + assertNotNull(context.getBean("gwcRestWebUrlHandlerMapping")); + }); + } +} diff --git a/src/gwc/core/src/main/java/org/geoserver/cloud/gwc/config/core/GeoWebCacheCoreConfiguration.java b/src/gwc/core/src/main/java/org/geoserver/cloud/gwc/config/core/GeoWebCacheCoreConfiguration.java index 0ed23a98a..a14a38987 100644 --- a/src/gwc/core/src/main/java/org/geoserver/cloud/gwc/config/core/GeoWebCacheCoreConfiguration.java +++ b/src/gwc/core/src/main/java/org/geoserver/cloud/gwc/config/core/GeoWebCacheCoreConfiguration.java @@ -17,10 +17,19 @@ import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.ResourceStore; import org.geoserver.platform.resource.Resources; +import org.geowebcache.GSCloudGeoWebCacheDispatcher; +import org.geowebcache.GeoWebCacheDispatcher; import org.geowebcache.config.ConfigurationResourceProvider; +import org.geowebcache.config.ServerConfiguration; import org.geowebcache.config.XMLConfiguration; import org.geowebcache.config.XMLFileResourceProvider; +import org.geowebcache.filter.security.SecurityDispatcher; +import org.geowebcache.grid.GridSetBroker; +import org.geowebcache.layer.TileLayerDispatcher; +import org.geowebcache.stats.RuntimeStats; +import org.geowebcache.storage.BlobStoreAggregator; import org.geowebcache.storage.DefaultStorageFinder; +import org.geowebcache.storage.StorageBroker; import org.geowebcache.util.ApplicationContextProvider; import org.springframework.beans.FatalBeanException; import org.springframework.beans.InvalidPropertyException; @@ -56,7 +65,7 @@ @ImportResource( reader = FilteringXmlBeanDefinitionReader.class, // locations = { - "jar:gs-gwc-[0-9]+.*!/geowebcache-core-context.xml#name=^(?!gwcXmlConfig|gwcDefaultStorageFinder|gwcGeoServervConfigPersister|metastoreRemover).*$" + "jar:gs-gwc-[0-9]+.*!/geowebcache-core-context.xml#name=^(?!gwcXmlConfig|gwcDefaultStorageFinder|geowebcacheDispatcher|gwcGeoServervConfigPersister|metastoreRemover).*$" }) @Slf4j(topic = "org.geoserver.cloud.gwc.config.core") public class GeoWebCacheCoreConfiguration { @@ -66,6 +75,29 @@ SetRequestPathInfoFilter setRequestPathInfoFilter() { return new SetRequestPathInfoFilter(); } + @Bean + GeoWebCacheDispatcher geowebcacheDispatcher( + TileLayerDispatcher tileLayerDispatcher, + GridSetBroker gridSetBroker, + StorageBroker storageBroker, + BlobStoreAggregator blobStoreAggregator, + ServerConfiguration mainConfiguration, + RuntimeStats runtimeStats, + DefaultStorageFinder gwcDefaultStorageFinder, + SecurityDispatcher gwcSecurityDispatcher) { + GSCloudGeoWebCacheDispatcher dispatcher = + new GSCloudGeoWebCacheDispatcher( + tileLayerDispatcher, + gridSetBroker, + storageBroker, + blobStoreAggregator, + mainConfiguration, + runtimeStats); + dispatcher.setSecurityDispatcher(gwcSecurityDispatcher); + dispatcher.setDefaultStorageFinder(gwcDefaultStorageFinder); + return dispatcher; + } + @Bean @ConditionalOnMissingBean(RequestMappingHandlerMapping.class) RequestMappingHandlerMapping requestMappingHandlerMapping() { diff --git a/src/gwc/core/src/main/java/org/geowebcache/GSCloudGeoWebCacheDispatcher.java b/src/gwc/core/src/main/java/org/geowebcache/GSCloudGeoWebCacheDispatcher.java new file mode 100644 index 000000000..71a90d73a --- /dev/null +++ b/src/gwc/core/src/main/java/org/geowebcache/GSCloudGeoWebCacheDispatcher.java @@ -0,0 +1,93 @@ +package org.geowebcache; + +import org.apache.commons.lang.StringUtils; +import org.geowebcache.config.ServerConfiguration; +import org.geowebcache.grid.GridSetBroker; +import org.geowebcache.layer.TileLayerDispatcher; +import org.geowebcache.stats.RuntimeStats; +import org.geowebcache.storage.BlobStoreAggregator; +import org.geowebcache.storage.StorageBroker; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletRequest; + +public class GSCloudGeoWebCacheDispatcher extends GeoWebCacheDispatcher { + + /** + * Should be invoked through Spring + * + * @param tileLayerDispatcher + * @param gridSetBroker + * @param storageBroker + * @param blobStoreAggregator + * @param mainConfiguration + * @param runtimeStats + */ + public GSCloudGeoWebCacheDispatcher( + TileLayerDispatcher tileLayerDispatcher, + GridSetBroker gridSetBroker, + StorageBroker storageBroker, + BlobStoreAggregator blobStoreAggregator, + ServerConfiguration mainConfiguration, + RuntimeStats runtimeStats) { + super( + tileLayerDispatcher, + gridSetBroker, + storageBroker, + blobStoreAggregator, + mainConfiguration, + runtimeStats); + } + + @Override + String normalizeURL(HttpServletRequest request) { + String normalized = request.getRequestURI(); + List elements = Arrays.asList(normalized.split("/")); + + // only /home /service and /demo are handled. + + // at the end, we should have + // /{home|service|demo}/{...} + + // requestURI will always begin with a "/", so elements + // will be either: + // [ "", "gwc", "service|home|demo", "layer (prefixed or not)" ] + // [ "", "{namespace}", "gwc", "service|home|demo", "layer (prefixed or not)" ] + // [ "", "whateveer", "path", "{namespace}", "gwc", "service|home|demo", "layer (prefixed or + // not)" ] + // ... + + boolean isNamespacePrefixed = (elements.indexOf("gwc") > 1); + if (!isNamespacePrefixed) { + return "/" + elements.stream().skip(2).collect(Collectors.joining("/")); + } else { + String srv = elements.get(elements.indexOf("gwc") + 1); + + String extraPath = + elements.stream() + .skip(elements.indexOf("gwc") + 2) + .collect(Collectors.joining("/")); + + // If demo is requested, we have to provide + // a fully-qualified layer name. + if ("demo".equals(srv)) { + String namespace = elements.get(elements.indexOf("gwc") - 1); + // extraPath is empty: return /demo + if (StringUtils.isEmpty(extraPath)) return "/demo"; + if (extraPath.startsWith(namespace + ":")) { + return String.format("/%s/%s", srv, extraPath); + } else { + return String.format("/%s/%s:%s", srv, namespace, extraPath); + } + } + + if (StringUtils.isEmpty(extraPath)) { + return String.format("/%s", srv); + } + return String.format("/%s/%s", srv, extraPath); + } + } +} diff --git a/src/gwc/core/src/test/java/org/geowebcache/GSCloudGeoWebCacheDispatcherTest.java b/src/gwc/core/src/test/java/org/geowebcache/GSCloudGeoWebCacheDispatcherTest.java new file mode 100644 index 000000000..68b75189b --- /dev/null +++ b/src/gwc/core/src/test/java/org/geowebcache/GSCloudGeoWebCacheDispatcherTest.java @@ -0,0 +1,70 @@ +package org.geowebcache; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.geowebcache.config.ServerConfiguration; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.servlet.http.HttpServletRequest; + +public class GSCloudGeoWebCacheDispatcherTest { + + private final GSCloudGeoWebCacheDispatcher toTest = + new GSCloudGeoWebCacheDispatcher( + null, null, null, null, Mockito.mock(ServerConfiguration.class), null); + + private final GeoWebCacheDispatcher regularDispatcher = + new GSCloudGeoWebCacheDispatcher( + null, null, null, null, Mockito.mock(ServerConfiguration.class), null); + + public @Test void testNormalizeUrl() { + List testedPaths = + List.of( + "/gwc/demo/flup:top", + "/flup/gwc/demo/top", + "/path/flup/gwc/demo/top", + "/path/flup/gwc/demo/flup:top", + "/gwc/home", + "/gwc/service/aaa", + "/some/path/ns/gwc/service/aaa", + "/", + "", + "/flup/gwc/home", + "/flup/gwc/service", + "/ne/gwc/service/wmts", + "/ne/gwc/demo", + "/ne/gwc/demo/boundary_lines"); + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + + List results = + testedPaths.stream() + .map( + i -> { + Mockito.when(request.getRequestURI()).thenReturn(i); + return toTest.normalizeURL(request); + }) + .collect(Collectors.toList()); + + assertThat(results) + .isEqualTo( + List.of( + "/demo/flup:top", + "/demo/flup:top", + "/demo/flup:top", + "/demo/flup:top", + "/home", + "/service/aaa", + "/service/aaa", + "/", + "/", + "/home", + "/service", + "/service/wmts", + "/demo", + "/demo/ne:boundary_lines")); + } +} diff --git a/src/gwc/services/pom.xml b/src/gwc/services/pom.xml index 408746cfb..27dff27bc 100644 --- a/src/gwc/services/pom.xml +++ b/src/gwc/services/pom.xml @@ -21,5 +21,9 @@ org.geoserver gs-gwc-rest + + org.geoserver.cloud + gs-cloud-starter-webmvc + diff --git a/src/gwc/services/src/main/java/org/gwc/web/rest/GeoWebCacheController.java b/src/gwc/services/src/main/java/org/gwc/web/rest/GeoWebCacheController.java index 021920b80..a1f03d242 100644 --- a/src/gwc/services/src/main/java/org/gwc/web/rest/GeoWebCacheController.java +++ b/src/gwc/services/src/main/java/org/gwc/web/rest/GeoWebCacheController.java @@ -7,12 +7,14 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.geoserver.cloud.virtualservice.VirtualServiceVerifier; import org.geoserver.gwc.dispatch.GeoServerGWCDispatcherController; +import org.geoserver.ows.Dispatcher; import org.geowebcache.GeoWebCacheDispatcher; import org.geowebcache.controller.GeoWebCacheDispatcherController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.PathVariable; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -25,21 +27,34 @@ *

Copied from {@link GeoServerGWCDispatcherController} */ @Controller -@RequestMapping("/gwc") @RequiredArgsConstructor public class GeoWebCacheController { - private final @NonNull GeoWebCacheDispatcher gwcDispatcher; + private final @NonNull Dispatcher geoserverDispatcher; + + private final @NonNull GeoWebCacheDispatcher geoWebCacheDispatcher; + + private final @NonNull VirtualServiceVerifier virtualServiceVerifier; + + @GetMapping(path = {"/gwc", "/gwc/home", "/gwc/demo/**", "/gwc/proxy/**"}) + public void handleGet(HttpServletRequest request, HttpServletResponse response) + throws Exception { + geoWebCacheDispatcher.handleRequest(request, response); + } @GetMapping( path = { - "", - "/home", - "/demo/**", - "/proxy/**", + "/{namespace}/gwc", + "/{namespace}/gwc/home", + "/{namespace}/gwc/demo/**", + "/{namespace}/gwc/proxy/**" }) - public void handleGet(HttpServletRequest request, HttpServletResponse response) + public void handlePrefixedNamespaceGet( + @PathVariable String namespace, + HttpServletRequest request, + HttpServletResponse response) throws Exception { - gwcDispatcher.handleRequest(request, response); + virtualServiceVerifier.checkVirtualService(namespace); + geoWebCacheDispatcher.handleRequest(request, response); } }