Skip to content

Commit

Permalink
ogcapi - first shot on introducing a ogcapi webservice
Browse files Browse the repository at this point in the history
Roughly inspired from the wms webservice.

It still does not work as intended, but provides ogc/* requests to the
upstream OGCAPI bean.
  • Loading branch information
pmauduit committed Aug 16, 2024
1 parent 235ce9d commit 9deb005
Show file tree
Hide file tree
Showing 20 changed files with 618 additions and 1 deletion.
15 changes: 15 additions & 0 deletions compose/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,18 @@ services:
condition: service_started
required: true

ogcapi:
extends:
file: templates.yml
service: gstemplate
image: geoservercloud/geoserver-cloud-ogcapi:${TAG}
environment:
JAVA_OPTS: "${JAVA_OPTS_OGCAPI}"
depends_on:
rabbitmq:
condition: service_healthy
required: true
discovery:
condition: service_started
required: true

10 changes: 10 additions & 0 deletions compose/standalone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,13 @@ services:
required: true
volumes:
- config:/etc/geoserver

ogcapi:
environment:
SPRING_PROFILES_ACTIVE: "${GEOSERVER_DEFAULT_PROFILES},standalone"
depends_on:
rabbitmq:
condition: service_healthy
required: true
volumes:
- config:/etc/geoserver
11 changes: 10 additions & 1 deletion docker-build/geoserver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,13 @@ services:
context: ../src/apps/geoserver/gwc/
args:
TAG: ${TAG}


ogcapi:
image: geoservercloud/geoserver-cloud-ogcapi:${TAG}
build:
no_cache: true
pull: false
context: ../src/apps/geoserver/ogcapi/
args:
TAG: ${TAG}

Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;

import org.geoserver.platform.GeoServerResourceLoader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand Down
18 changes: 18 additions & 0 deletions src/apps/geoserver/ogcapi/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ARG TAG=latest
FROM geoservercloud/gs-cloud-base-jre:$TAG as builder

ARG JAR_FILE=target/gs-cloud-*-bin.jar

COPY ${JAR_FILE} application.jar

RUN java -Djarmode=layertools -jar application.jar extract

##########
FROM geoservercloud/gs-cloud-base-geoserver-image:$TAG

# WORKDIR already set to /opt/app/bin

COPY --from=builder dependencies/ ./
COPY --from=builder snapshot-dependencies/ ./
COPY --from=builder spring-boot-loader/ ./
COPY --from=builder application/ ./
3 changes: 3 additions & 0 deletions src/apps/geoserver/ogcapi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ogcapi

A OGCAPI microservice, based on the GeoServer ogc-api plugin.
Binary file added src/apps/geoserver/ogcapi/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
140 changes: 140 additions & 0 deletions src/apps/geoserver/ogcapi/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.geoserver.cloud.apps</groupId>
<artifactId>gs-cloud-services</artifactId>
<version>${revision}</version>
</parent>
<artifactId>gs-cloud-ogcapi</artifactId>
<packaging>jar</packaging>
<name>ogcapi-service</name>
<properties>
<start-class>org.geoserver.cloud.ogcapi.app.OgcApiApplication</start-class>
</properties>
<dependencies>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-webmvc</artifactId>
<exclusions>
<exclusion>
<groupId>org.geoserver</groupId>
<artifactId>gs-gwc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-core</artifactId>

</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-features</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-maps</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-styles</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-tiles</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-images</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-changeset</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-tiled-features</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-dggs-clickhouse</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-dggs-core</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-dggs</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-web-dggs</artifactId>
</dependency>
<!-- TODO revisit: deactivated because in the need for a 'wcs20Service' bean
<dependency>
<groupId>org.geoserver.community</groupId>
<artifactId>gs-ogcapi-coverages</artifactId>
</dependency>
-->
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-wms</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-wms-extensions</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-vector-formats</artifactId>
</dependency>
<dependency>
<groupId>org.geoserver.cloud</groupId>
<artifactId>gs-cloud-starter-raster-formats</artifactId>
</dependency>
<dependency>
<!-- provides GWC integration with WMS-C -->
<groupId>org.geoserver.cloud.gwc</groupId>
<artifactId>gwc-cloud-spring-boot-starter</artifactId>
</dependency>
<dependency>
<!-- jdbcconfig can't create the tables with a newer version than this old one used in geoserver -->
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<goals>
<goal>copy-resources</goal>
</goals>
<!-- here the phase you need -->
<phase>validate</phase>
<configuration>
<outputDirectory>${basedir}/target/config</outputDirectory>
<resources>
<resource>
<directory>${maven.multiModuleProjectDirectory}/config/</directory>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* (c) 2020 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.ogcapi;

import org.geoserver.cloud.config.factory.FilteringXmlBeanDefinitionReader;
import org.geoserver.cloud.ogcapi.controller.OgcApiController;
import org.geoserver.ogcapi.APIDispatcher;
import org.geoserver.ogcapi.APIRequestInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

@Configuration
@ImportResource( //
reader = FilteringXmlBeanDefinitionReader.class, //
locations = { //
"jar:gs-ogcapi-.*!/applicationContext.xml",
"jar:gs-wms-.*!/applicationContext.xml#name="
+ OgcApiApplicationAutoConfiguration.WMS_BEANS_BLACKLIST, //
"jar:gs-wfs-.*!/applicationContext.xml#name="
+ OgcApiApplicationAutoConfiguration.WFS_BEANS_WHITELIST
})
public class OgcApiApplicationAutoConfiguration {

static final String WFS_BEANS_WHITELIST =
"""
^(\
gml.*OutputFormat\
|bboxKvpParser\
|featureIdKvpParser\
|filter.*_KvpParser\
|cqlKvpParser\
|maxFeatureKvpParser\
|sortByKvpParser\
|xmlConfiguration.*\
|gml[1-9]*SchemaBuilder\
|wfsXsd.*\
|wfsSqlViewKvpParser\
).*$\
""";

/** wms beans black-list */
static final String WMS_BEANS_BLACKLIST =
"""
^(?!\
legendSample\
).*$\
""";

public @Bean OgcApiController OgcApiController() {
return new OgcApiController();
}

private @Autowired APIDispatcher apiDispatcher;

@Bean
SetRequestPathInfoFilter setRequestPathInfoFilter() {
return new SetRequestPathInfoFilter();
}

@Bean
Filter attachApiDispatcherFilter() {
return new Filter() {
@Override
public void doFilter(
ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
APIRequestInfo requestInfo =
new APIRequestInfo(
(HttpServletRequest) servletRequest,
(HttpServletResponse) servletResponse,
apiDispatcher);
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
throw new IllegalStateException("Request attributes are not set");
}
requestAttributes.setAttribute(
APIRequestInfo.KEY, requestInfo, RequestAttributes.SCOPE_REQUEST);
filterChain.doFilter(servletRequest, servletResponse);
}
};
}

static class SetRequestPathInfoFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

request = adaptRequest((HttpServletRequest) request);
chain.doFilter(request, response);
}

/**
*
*
* <ul>
* <li>{@code contextPath} is usually empty, but it'll match the gateways
* ${geoserver.base-path} if such is set, thanks to the
* server.forward-headers-strategy=framework in the application's bootstrap.yml
* <li>{@code pathInfo} is computed beforehand to avoid decorating the HttpServletRequest
* if the request is not for gwc (e.g. an actuator endpoint)
* <li>{@code suffix} is used to strip it out of requestURI and fake the pathInfo gwc
* expects as it assumes the request is being handled by a Dispatcher mapped to /**
* <li>yes, this is odd, the alternative is re-writing GWC without weird assumptions
* </ul>
*/
protected ServletRequest adaptRequest(HttpServletRequest request) {
// full request URI (e.g. '/geoserver/cloud/{workspace}/gwc/service/tms/1.0.0', where
// '/geoserver/cloud' is the context path as given by the gateway's base uri, and
// '/{workspace}/gwc' the suffix after which comes the pathInfo '/service/tms/1.0.0')
final String requestURI = request.getRequestURI();

final int ogcIdx = requestURI.indexOf("/ogc");
if (ogcIdx > -1) {
final String pathToOgc = requestURI.substring(0, ogcIdx + "/ogc".length());
final String pathInfo = requestURI.substring(pathToOgc.length());

return new HttpServletRequestWrapper(request) {
public @Override String getPathInfo() {
return pathInfo;
}
};
}
return request;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* (c) 2020 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.ogcapi.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry
public class OgcApiApplication {

public static void main(String[] args) {
try {
SpringApplication.run(OgcApiApplication.class, args);
} catch (RuntimeException e) {
e.printStackTrace();
System.exit(-1);
}
}
}
Loading

0 comments on commit 9deb005

Please sign in to comment.