From feee68338137a4c792bff7fb90faaccc2d48b364 Mon Sep 17 00:00:00 2001 From: Pierre Mauduit Date: Thu, 27 Apr 2023 17:24:42 +0200 Subject: [PATCH] ogcapi - first shot on introducing a ogcapi webservice Roughly inspired from the wms webservice. It still does not work as intended, but provides ogc/* requests to the upstream OGCAPI bean. --- compose/compose.yml | 15 ++ compose/standalone.yml | 10 ++ docker-build/geoserver.yml | 11 +- src/apps/geoserver/ogcapi/Dockerfile | 18 +++ src/apps/geoserver/ogcapi/README.md | 3 + src/apps/geoserver/ogcapi/img.png | Bin 0 -> 46836 bytes src/apps/geoserver/ogcapi/pom.xml | 140 +++++++++++++++++ .../OgcApiApplicationAutoConfiguration.java | 142 ++++++++++++++++++ .../cloud/ogcapi/app/OgcApiApplication.java | 23 +++ .../ogcapi/controller/OgcApiController.java | 24 +++ .../main/resources/META-INF/spring.factories | 2 + .../ogcapi/src/main/resources/bootstrap.yml | 52 +++++++ .../OgcApiApplicationDataDirectoryTest.java | 53 +++++++ .../ogcapi/app/OgcApiApplicationTest.java | 19 +++ .../src/test/resources/bootstrap-test.yml | 19 +++ .../test/resources/bootstrap-testdatadir.yml | 5 + .../src/test/resources/logback-test.xml | 15 ++ src/apps/geoserver/pom.xml | 1 + src/pom.xml | 65 ++++++++ 19 files changed, 616 insertions(+), 1 deletion(-) create mode 100644 src/apps/geoserver/ogcapi/Dockerfile create mode 100644 src/apps/geoserver/ogcapi/README.md create mode 100644 src/apps/geoserver/ogcapi/img.png create mode 100644 src/apps/geoserver/ogcapi/pom.xml create mode 100644 src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/autoconfigure/ogcapi/OgcApiApplicationAutoConfiguration.java create mode 100644 src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/app/OgcApiApplication.java create mode 100644 src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/controller/OgcApiController.java create mode 100644 src/apps/geoserver/ogcapi/src/main/resources/META-INF/spring.factories create mode 100644 src/apps/geoserver/ogcapi/src/main/resources/bootstrap.yml create mode 100644 src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationDataDirectoryTest.java create mode 100644 src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationTest.java create mode 100644 src/apps/geoserver/ogcapi/src/test/resources/bootstrap-test.yml create mode 100644 src/apps/geoserver/ogcapi/src/test/resources/bootstrap-testdatadir.yml create mode 100644 src/apps/geoserver/ogcapi/src/test/resources/logback-test.xml diff --git a/compose/compose.yml b/compose/compose.yml index 732ab555e..4b8caff0a 100644 --- a/compose/compose.yml +++ b/compose/compose.yml @@ -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 + diff --git a/compose/standalone.yml b/compose/standalone.yml index 728b083f2..68534073c 100644 --- a/compose/standalone.yml +++ b/compose/standalone.yml @@ -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 diff --git a/docker-build/geoserver.yml b/docker-build/geoserver.yml index 46d000d33..7f875c015 100644 --- a/docker-build/geoserver.yml +++ b/docker-build/geoserver.yml @@ -63,4 +63,13 @@ services: context: ../src/apps/geoserver/gwc/ args: TAG: ${TAG} - \ No newline at end of file + + ogcapi: + image: geoservercloud/geoserver-cloud-ogcapi:${TAG} + build: + no_cache: true + pull: false + context: ../src/apps/geoserver/ogcapi/ + args: + TAG: ${TAG} + diff --git a/src/apps/geoserver/ogcapi/Dockerfile b/src/apps/geoserver/ogcapi/Dockerfile new file mode 100644 index 000000000..0d942b6ba --- /dev/null +++ b/src/apps/geoserver/ogcapi/Dockerfile @@ -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/ ./ diff --git a/src/apps/geoserver/ogcapi/README.md b/src/apps/geoserver/ogcapi/README.md new file mode 100644 index 000000000..3671f0e12 --- /dev/null +++ b/src/apps/geoserver/ogcapi/README.md @@ -0,0 +1,3 @@ +# ogcapi + +A OGCAPI microservice, based on the GeoServer ogc-api plugin. diff --git a/src/apps/geoserver/ogcapi/img.png b/src/apps/geoserver/ogcapi/img.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb09ff6c205900a15d46cf4ee4b9a6b2fc24abc GIT binary patch literal 46836 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 + + 4.0.0 + + org.geoserver.cloud.apps + gs-cloud-services + ${revision} + + gs-cloud-ogcapi + jar + ogcapi-service + + org.geoserver.cloud.ogcapi.app.OgcApiApplication + + + + org.geoserver.cloud + gs-cloud-starter-webmvc + + + org.geoserver + gs-gwc + + + + + org.geoserver.community + gs-ogcapi-core + + + + org.geoserver.community + gs-ogcapi-features + + + org.geoserver.community + gs-ogcapi-maps + + + org.geoserver.community + gs-ogcapi-styles + + + org.geoserver.community + gs-ogcapi-tiles + + + org.geoserver.community + gs-ogcapi-images + + + org.geoserver.community + gs-ogcapi-changeset + + + org.geoserver.community + gs-ogcapi-tiled-features + + + org.geoserver.community + gs-dggs-clickhouse + + + org.geoserver.community + gs-dggs-core + + + org.geoserver.community + gs-ogcapi-dggs + + + org.geoserver.community + gs-web-dggs + + + + org.geoserver + gs-wms + + + org.geoserver.cloud + gs-cloud-starter-wms-extensions + + + org.geoserver.cloud + gs-cloud-starter-security + + + org.geoserver.cloud + gs-cloud-starter-vector-formats + + + org.geoserver.cloud + gs-cloud-starter-raster-formats + + + + org.geoserver.cloud.gwc + gwc-cloud-spring-boot-starter + + + + com.h2database + h2 + 1.4.200 + test + + + + + + maven-resources-plugin + + + copy-resources + + copy-resources + + + validate + + ${basedir}/target/config + + + ${maven.multiModuleProjectDirectory}/config/ + false + + + + + + + + + diff --git a/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/autoconfigure/ogcapi/OgcApiApplicationAutoConfiguration.java b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/autoconfigure/ogcapi/OgcApiApplicationAutoConfiguration.java new file mode 100644 index 000000000..12778c1ee --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/autoconfigure/ogcapi/OgcApiApplicationAutoConfiguration.java @@ -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); + } + + /** + * + * + *
    + *
  • {@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 + *
  • {@code pathInfo} is computed beforehand to avoid decorating the HttpServletRequest + * if the request is not for gwc (e.g. an actuator endpoint) + *
  • {@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 /** + *
  • yes, this is odd, the alternative is re-writing GWC without weird assumptions + *
+ */ + 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; + } + } +} diff --git a/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/app/OgcApiApplication.java b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/app/OgcApiApplication.java new file mode 100644 index 000000000..03ddb49c0 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/app/OgcApiApplication.java @@ -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); + } + } +} diff --git a/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/controller/OgcApiController.java b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/controller/OgcApiController.java new file mode 100644 index 000000000..3b51b32d8 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/main/java/org/geoserver/cloud/ogcapi/controller/OgcApiController.java @@ -0,0 +1,24 @@ +/* + * (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.controller; + +import org.geoserver.ogcapi.APIDispatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Controller +public class OgcApiController { + + private @Autowired APIDispatcher apiDispatcher; + + @RequestMapping(path = {"/**"}) + public void handle(HttpServletRequest request, HttpServletResponse response) throws Exception { + apiDispatcher.handleRequest(request, response); + } +} diff --git a/src/apps/geoserver/ogcapi/src/main/resources/META-INF/spring.factories b/src/apps/geoserver/ogcapi/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..8e0815dab --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.geoserver.cloud.autoconfigure.ogcapi.OgcApiApplicationAutoConfiguration \ No newline at end of file diff --git a/src/apps/geoserver/ogcapi/src/main/resources/bootstrap.yml b/src/apps/geoserver/ogcapi/src/main/resources/bootstrap.yml new file mode 100644 index 000000000..f293303c3 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/main/resources/bootstrap.yml @@ -0,0 +1,52 @@ +info: + component: Ogc API Service + instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.instance_id:${spring.cloud.client.ip-address}}:${server.port}} +server: + port: 8080 + # Let spring-boot's ForwardedHeaderFilter take care of reflecting the client-originated protocol and address in the HttpServletRequest + forward-headers-strategy: framework + servlet.context-path: / + error: + include-message: always + include-binding-errors: always + whitelabel: + enabled: true +management.server.port: 8081 +geoserver.base-path: /ogc +spring: + config: + import: + # import definition of common bootstrap configuration profiles + - classpath:gs_cloud_bootstrap_profiles.yml + # load externalized configuration from geoserver.yml + name: geoserver + # and always include the service specific settings from the profile + profiles.include: ogcapi_service + # also ask for geoserver.yml when loading the externalized config through a config-server + cloud.config.name: geoserver + main: + banner-mode: off + allow-bean-definition-overriding: true + allow-circular-references: true # false by default since spring-boot 2.6.0, breaks geoserver initialization + web-application-type: servlet + application: + name: ogcapi-service + autoconfigure: + exclude: + - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration + - org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration + - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration + - org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration + - org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration + - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration + - org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration + - org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration + +# override default of true, this service does not use the registry (when eureka client is enabled) +eureka.client.fetch-registry: false + +--- +# local profile, used for development only. Other settings like config and eureka urls in gs_cloud_bootstrap_profiles.yml +spring.config.activate.on-profile: local +server.port: 9102 +management.server.port: 8102 diff --git a/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationDataDirectoryTest.java b/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationDataDirectoryTest.java new file mode 100644 index 000000000..26f4fb2ba --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationDataDirectoryTest.java @@ -0,0 +1,53 @@ +/* + * (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.geoserver.platform.GeoServerResourceLoader; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; + +import java.io.File; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ActiveProfiles({"test", "testdatadir"}) +class OgcApiApplicationDataDirectoryTest extends OgcApiApplicationTest { + + private static @TempDir File tmpDataDir; + + @Configuration + static class ContextConfiguration { + // TODO why are these beans needed ? + @Bean + public GeoServerResourceLoader geoserverResourceLoader() { + return new GeoServerResourceLoader(tmpDataDir); + } + + @Bean + public ServletWebServerFactory servletWebServerFactory() { + return new TomcatServletWebServerFactory(); + } + } + + @DynamicPropertySource + static void registerPgProperties(@NotNull DynamicPropertyRegistry registry) { + String datadir = tmpDataDir.getAbsolutePath(); + registry.add("data_directory", () -> datadir); + registry.add("gwc.wms-integration", () -> "true"); + } + + @Test + void testLoadApplicationContextSuccessfully() { + // everything went fine ? good + } +} diff --git a/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationTest.java b/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationTest.java new file mode 100644 index 000000000..5f8f34677 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/test/java/org/geoserver/cloud/ogcapi/app/OgcApiApplicationTest.java @@ -0,0 +1,19 @@ +/* + * (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 static org.assertj.core.api.Assertions.assertThat; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; + +abstract class OgcApiApplicationTest { + + protected @Autowired ConfigurableApplicationContext context; + + protected void expecteBean(String name, Class type) { + assertThat(context.getBean(name)).isInstanceOf(type); + } +} diff --git a/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-test.yml b/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-test.yml new file mode 100644 index 000000000..39afb5de7 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-test.yml @@ -0,0 +1,19 @@ +spring: + main: + banner-mode: off + allow-bean-definition-overriding: true + allow-circular-references: true # false by default since spring-boot 2.6.0, breaks geoserver initialization + cloud.config.enabled: false + cloud.config.discovery.enabled: false + cloud.discovery.enabled: false + cloud.bus.enabled: false +eureka.client.enabled: false + +logging: + level: + root: WARN + org.geoserver.platform: error + org.geoserver.cloud: info + org.geoserver.cloud.config.factory: info + org.springframework.test: error + diff --git a/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-testdatadir.yml b/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-testdatadir.yml new file mode 100644 index 000000000..7c9d79162 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/test/resources/bootstrap-testdatadir.yml @@ -0,0 +1,5 @@ +geoserver: + backend: + data-directory: + enabled: true + location: ${data_directory:${java.io.tmpdir}/geoserver_cloud_data_directory} diff --git a/src/apps/geoserver/ogcapi/src/test/resources/logback-test.xml b/src/apps/geoserver/ogcapi/src/test/resources/logback-test.xml new file mode 100644 index 000000000..92a70edd5 --- /dev/null +++ b/src/apps/geoserver/ogcapi/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/geoserver/pom.xml b/src/apps/geoserver/pom.xml index ac99df5da..526f9b26c 100644 --- a/src/apps/geoserver/pom.xml +++ b/src/apps/geoserver/pom.xml @@ -12,6 +12,7 @@ wfs wms + ogcapi wcs wps restconfig diff --git a/src/pom.xml b/src/pom.xml index 85f9d07b1..5b64dc022 100644 --- a/src/pom.xml +++ b/src/pom.xml @@ -235,6 +235,71 @@ + + org.geoserver.community + gs-ogcapi-core + ${gs.version} + + + org.geoserver.community + gs-ogcapi-features + ${gs.version} + + + org.geoserver.community + gs-ogcapi-maps + ${gs.version} + + + org.geoserver.community + gs-ogcapi-styles + ${gs.version} + + + org.geoserver.community + gs-ogcapi-tiles + ${gs.version} + + + org.geoserver.community + gs-ogcapi-images + ${gs.version} + + + org.geoserver.community + gs-ogcapi-changeset + ${gs.version} + + + org.geoserver.community + gs-ogcapi-tiled-features + ${gs.version} + + + org.geoserver.community + gs-dggs-clickhouse + ${gs.version} + + + org.geoserver.community + gs-dggs-core + ${gs.version} + + + org.geoserver.community + gs-ogcapi-dggs + ${gs.version} + + + org.geoserver.community + gs-web-dggs + ${gs.version} + + + org.geoserver.community + gs-ogcapi-coverages + ${gs.version} + org.geoserver gs-kml