Skip to content

Commit

Permalink
gwc - handles workspace-based paths (geoserver#380)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmauduit committed Sep 29, 2024
1 parent df171dc commit c317285
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 18 deletions.
13 changes: 13 additions & 0 deletions src/apps/geoserver/gwc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,19 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geoserver.cloud.catalog</groupId>
<artifactId>gs-cloud-catalog-plugin</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,52 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

import org.geoserver.catalog.GeoServerCatalogTestData;
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.autoconfigure.web.servlet.WebMvcRegistrations;
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")
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");
Expand All @@ -53,6 +82,49 @@ void testRESTPathExtensionContentNegotiation() {
testGetRequestContentType("/gwc/rest/layers.xml", APPLICATION_XML);
}

@Test
void testGwcUrlHandlerMappingArePresentInTheClasspathAndLocalWorkspaceUrlsAreHandled() {
assertThat(context.isTypeMatch("gwcDemoUrlHandlerMapping", WebMvcRegistrations.class))
.as("expected a bean gwcDemoUrlHandlerMapping of type GwcUrlHandlerMapping")
.isTrue();
assertThat(context.isTypeMatch("gwcRestWebUrlHandlerMapping", WebMvcRegistrations.class))
.as("expected a bean gwcRestWebUrlHandlerMapping of type GwcUrlHandlerMapping")
.isTrue();

ResponseEntity<String> response =
restTemplate.getForEntity("/gwc/demo/ne:world", String.class);

assertThat(response.getStatusCode())
.as(
String.format(
"expected a HTTP return code 200, got %s",
response.getStatusCode()))
.isEqualTo(HttpStatus.OK);
}

@Test
void testGwcNamespacePrefixedDemo() {
ResponseEntity<String> response =
restTemplate.getForEntity("/ne/gwc/demo/world", String.class);

assertThat(response.getStatusCode())
.as(
String.format(
"expected a HTTP return code 200, got %s",
response.getStatusCode()))
.isEqualTo(HttpStatus.OK);

ResponseEntity<String> response2 =
restTemplate.getForEntity("/ne/gwc/demo/ne:world", String.class);

assertThat(response2.getStatusCode())
.as(
String.format(
"expected a HTTP return code 200, got %s",
response2.getStatusCode()))
.isEqualTo(HttpStatus.OK);
}

protected ResponseEntity<String> testGetRequestContentType(String uri, MediaType expected) {
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
Expand Down
5 changes: 5 additions & 0 deletions src/catalog/plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@
<groupId>org.geotools</groupId>
<artifactId>gt-process-feature</artifactId>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-sample-data</artifactId>
<version>${gt.version}</version>
</dependency>
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
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.
*
* <p>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.
*
* <p>It is the caller's responsability to clean up when the datadir is not needed anymore.
*
* <p>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();
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,20 @@

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.geowebcache.GeoWebCacheDispatcher;
import org.geoserver.cloud.virtualservice.VirtualServiceVerifier;
import org.geoserver.gwc.controller.GwcUrlHandlerMapping;
import org.geoserver.ows.Dispatcher;
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.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.PostConstruct;

Expand All @@ -28,17 +33,61 @@ public class GeoWebCacheUIAutoConfiguration {
}

@Bean
GeoWebCacheController gwcController(GeoWebCacheDispatcher gwcDispatcher) {
return new GeoWebCacheController(gwcDispatcher);
GeoWebCacheController gwcController(
Dispatcher geoserverDispatcher, VirtualServiceVerifier verifier) {
return new GeoWebCacheController(geoserverDispatcher, 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
* <!-- Used for workspace-based demo requests so the requests to GS stay workspace-based -->
* <bean id="gwcDemoUrlHandlerMapping"
* class="org.geoserver.gwc.controller.GwcUrlHandlerMapping"> <constructor-arg ref="catalog" />
* <constructor-arg value="/gwc/demo"/> <property name="alwaysUseFullPath" value="true" />
* <property name="order" value="10" /> </bean>
* <!-- Used for workspace-based web requests (i.e. for rest/web/openlayer/ol.js) -->
* <bean id="gwcRestWebUrlHandlerMapping"
* class="org.geoserver.gwc.controller.GwcUrlHandlerMapping"> <constructor-arg ref="catalog" />
* <constructor-arg type="java.lang.String" value="/gwc/rest/web"/> <property
* name="alwaysUseFullPath" value="true" /> <property name="order" value="10" /> </bean>
*/
@Bean
WebMvcRegistrations gwcDemoUrlHandlerMapping(@Qualifier("rawCatalog") Catalog catalog) {
GwcUrlHandlerMapping handler = new GwcUrlHandlerMapping(catalog, "/gwc/demo");
handler.setAlwaysUseFullPath(true);
handler.setOrder(10);

return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return handler;
}
};
}

@Bean
WebMvcRegistrations gwcRestWebUrlHandlerMapping(@Qualifier("rawCatalog") Catalog catalog) {
GwcUrlHandlerMapping handler = new GwcUrlHandlerMapping(catalog, "/gwc/rest/web");
handler.setAlwaysUseFullPath(true);
handler.setOrder(10);

return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return handler;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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"));
});
}
}
4 changes: 4 additions & 0 deletions src/gwc/services/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,9 @@
<groupId>org.geoserver</groupId>
<artifactId>gs-gwc-rest</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-webmvc</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import org.geoserver.cloud.virtualservice.VirtualServiceVerifier;
import org.geoserver.gwc.dispatch.GeoServerGWCDispatcherController;
import org.geowebcache.GeoWebCacheDispatcher;
import org.geoserver.ows.Dispatcher;
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;
Expand All @@ -25,21 +26,32 @@
* <p>Copied from {@link GeoServerGWCDispatcherController}
*/
@Controller
@RequestMapping("/gwc")
@RequiredArgsConstructor
public class GeoWebCacheController {

private final @NonNull GeoWebCacheDispatcher gwcDispatcher;
private final @NonNull Dispatcher geoserverDispatcher;

private final @NonNull VirtualServiceVerifier virtualServiceVerifier;

@GetMapping(path = {"/gwc", "/gwc/home", "/gwc/demo/**", "/gwc/proxy/**"})
public void handleGet(HttpServletRequest request, HttpServletResponse response)
throws Exception {
geoserverDispatcher.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);
geoserverDispatcher.handleRequest(request, response);
}
}

0 comments on commit c317285

Please sign in to comment.