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
+ 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);
}
}