Skip to content

Commit 9e10426

Browse files
authored
Merge branch 'main' into HTM-1852_geotools-35.x
2 parents 35d5cc0 + 2fa3561 commit 9e10426

11 files changed

Lines changed: 147 additions & 21 deletions

File tree

.github/workflows/process-test-results.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525

2626
steps:
2727
- name: 'Download and Extract Artifacts'
28-
uses: dawidd6/action-download-artifact@v17
28+
uses: dawidd6/action-download-artifact@v19
2929
with:
3030
run_id: ${{ github.event.workflow_run.id }}
3131
path: artifacts

.github/workflows/release-drafter.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
contents: write
1717
pull-requests: read
1818
steps:
19-
- uses: release-drafter/release-drafter@v6
19+
- uses: release-drafter/release-drafter@v7
2020
env:
2121
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2222

pom.xml

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SPDX-License-Identifier: MIT
1010
<groupId>org.springframework.boot</groupId>
1111
<artifactId>spring-boot-starter-parent</artifactId>
1212
<!-- when updating please check version overrides using the commands in the properties section -->
13-
<version>4.0.3</version>
13+
<version>4.0.4</version>
1414
</parent>
1515
<groupId>org.tailormap</groupId>
1616
<artifactId>tailormap-api</artifactId>
@@ -123,14 +123,15 @@ SPDX-License-Identifier: MIT
123123
<awaitility.version>4.3.0</awaitility.version>
124124
<commons-codec.version>1.21.0</commons-codec.version>
125125
<commons-lang3.version>3.20.0</commons-lang3.version>
126-
<flyway.version>12.1.0</flyway.version>
127-
<hibernate.version>7.2.6.Final</hibernate.version>
128-
<jackson-2-bom.version>2.21.1</jackson-2-bom.version>
126+
<commons-codec.version>1.21.0</commons-codec.version>
127+
<flyway.version>12.1.1</flyway.version>
128+
<hibernate.version>7.2.7.Final</hibernate.version>
129+
<jackson-2-bom.version>2.21.2</jackson-2-bom.version>
129130
<jackson-bom.version>3.1.0</jackson-bom.version>
130131
<json-path.version>3.0.0</json-path.version>
131132
<junit-jupiter.version>6.0.3</junit-jupiter.version>
132133
<micrometer.version>1.16.4</micrometer.version>
133-
<mssql-jdbc.version>13.2.1.jre11</mssql-jdbc.version>
134+
<mssql-jdbc.version>13.4.0.jre11</mssql-jdbc.version>
134135
<oracle-database.version>23.26.1.0.0</oracle-database.version>
135136
<postgresql.version>42.7.10</postgresql.version>
136137
<maven-clean-plugin.version>3.5.0</maven-clean-plugin.version>
@@ -149,10 +150,10 @@ SPDX-License-Identifier: MIT
149150
<versions-maven-plugin.version>2.21.0</versions-maven-plugin.version>
150151
<jacoco-maven-plugin.version>0.8.14</jacoco-maven-plugin.version>
151152
<dependency-check-maven.version>12.2.0</dependency-check-maven.version>
152-
<modernizer-maven-plugin.version>3.2.0</modernizer-maven-plugin.version>
153+
<modernizer-maven-plugin.version>3.3.0</modernizer-maven-plugin.version>
153154
<maven-fluido-skin.version>2.1.0</maven-fluido-skin.version>
154-
<swagger-ui.version>5.32.0</swagger-ui.version>
155-
<sentry.version>8.35.0</sentry.version>
155+
<swagger-ui.version>5.32.1</swagger-ui.version>
156+
<sentry.version>8.36.0</sentry.version>
156157
<errorProne.version>2.48.0</errorProne.version>
157158
<errorProneFlags>-XepDisableWarningsInGeneratedCode</errorProneFlags>
158159
<errorProneExcludePaths>${project.build.directory}/generated-sources/.*</errorProneExcludePaths>
@@ -1371,7 +1372,7 @@ SPDX-License-Identifier: MIT
13711372
<plugin>
13721373
<groupId>com.diffplug.spotless</groupId>
13731374
<artifactId>spotless-maven-plugin</artifactId>
1374-
<version>3.3.0</version>
1375+
<version>3.4.0</version>
13751376
<configuration>
13761377
<java>
13771378
<palantirJavaFormat>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (C) 2026 B3Partners B.V.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
package org.tailormap.api.configuration.base;
8+
9+
import org.springframework.context.annotation.Bean;
10+
import org.springframework.context.annotation.Configuration;
11+
import org.springframework.context.annotation.Profile;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
14+
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
15+
import org.springframework.security.config.http.SessionCreationPolicy;
16+
import org.springframework.security.web.SecurityFilterChain;
17+
import org.springframework.security.web.savedrequest.NullRequestCache;
18+
import org.springframework.security.web.savedrequest.RequestCache;
19+
20+
@Profile("static-only")
21+
@Configuration
22+
@EnableWebSecurity
23+
public class StaticOnlySecurityConfiguration {
24+
@Bean
25+
public SecurityFilterChain filterChain(HttpSecurity http) {
26+
RequestCache nullRequestCache = new NullRequestCache();
27+
http.csrf(AbstractHttpConfigurer::disable)
28+
.authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll())
29+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.NEVER))
30+
.requestCache((cache) -> cache.requestCache(nullRequestCache));
31+
return http.build();
32+
}
33+
}

src/main/java/org/tailormap/api/controller/FeaturesController.java

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,11 @@ public class FeaturesController implements Constants {
8686
private final FeatureSourceRepository featureSourceRepository;
8787
private final FilterFactory ff = CommonFactoryFinder.getFilterFactory(GeoTools.getDefaultHints());
8888

89-
@Value("${tailormap-api.pageSize:100}")
90-
private int pageSize;
89+
@Value("${tailormap-api.default-page-size:100}")
90+
private int defaultPageSize;
91+
92+
@Value("${tailormap-api.max-page-size:500}")
93+
private int maxPageSize;
9194

9295
@Value("${tailormap-api.feature.info.maxitems:30}")
9396
private int maxFeatures;
@@ -119,6 +122,7 @@ public ResponseEntity<Serializable> getFeatures(
119122
@RequestParam(defaultValue = "false") Boolean simplify,
120123
@RequestParam(required = false) String filter,
121124
@RequestParam(required = false) Integer page,
125+
@RequestParam(required = false) Integer pageSize,
122126
@RequestParam(required = false) String sortBy,
123127
@RequestParam(required = false, defaultValue = "asc") String sortOrder,
124128
@RequestParam(defaultValue = "false") boolean onlyGeometries,
@@ -162,6 +166,7 @@ public ResponseEntity<Serializable> getFeatures(
162166
application,
163167
appLayerSettings,
164168
page,
169+
pageSize,
165170
filter,
166171
sortBy,
167172
sortOrder,
@@ -180,13 +185,17 @@ public ResponseEntity<Serializable> getFeatures(
180185
@NotNull Application application,
181186
@NotNull AppLayerSettings appLayerSettings,
182187
Integer page,
188+
Integer pageSize,
183189
String filterCQL,
184190
String sortBy,
185191
String sortOrder,
186192
boolean onlyGeometries,
187193
boolean skipGeometryOutput,
188194
boolean withAttachments) {
189-
FeaturesResponse featuresResponse = new FeaturesResponse().page(page).pageSize(pageSize);
195+
int requestedPageSize = pageSize != null ? pageSize : defaultPageSize;
196+
requestedPageSize = Math.max(1, requestedPageSize);
197+
int requestPageSize = Math.min(maxPageSize, requestedPageSize);
198+
FeaturesResponse featuresResponse = new FeaturesResponse().page(page).pageSize(requestPageSize);
190199

191200
SimpleFeatureSource fs = null;
192201
try {
@@ -264,8 +273,8 @@ public ResponseEntity<Serializable> getFeatures(
264273
if (sortAttrName != null) {
265274
q.setSortBy(ff.sort(sortAttrName, _sortOrder));
266275
}
267-
q.setMaxFeatures(pageSize);
268-
q.setStartIndex((page - 1) * pageSize);
276+
q.setMaxFeatures(requestPageSize);
277+
q.setStartIndex((page - 1) * requestPageSize);
269278
logger.debug("Attribute query: {}", q);
270279

271280
executeQueryOnFeatureSourceAndClose(

src/main/java/org/tailormap/api/controller/SearchController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class SearchController {
5353
@Value("${tailormap-api.solr-query-timeout-seconds:7}")
5454
private int solrQueryTimeout;
5555

56-
@Value("${tailormap-api.pageSize:100}")
56+
@Value("${tailormap-api.default-page-size:100}")
5757
private int numResultsToReturn;
5858

5959
public SearchController(SearchIndexRepository searchIndexRepository, SolrService solrService) {

src/main/resources/application-static-only.properties

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ spring.web.resources.static-locations=file:/home/cnb/static/,classpath:/static/
1010

1111
spring.main.banner-mode=off
1212

13-
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
13+
spring.autoconfigure.exclude[0]=org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration
14+
spring.autoconfigure.exclude[1]=org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration
15+
spring.autoconfigure.exclude[2]=org.springframework.boot.security.autoconfigure.UserDetailsServiceAutoConfiguration
16+
spring.autoconfigure.exclude[3]=org.springframework.boot.quartz.autoconfigure.QuartzAutoConfiguration
1417

1518
management.endpoints.access.default=none
1619
management.endpoints.web.exposure.exclude=*

src/main/resources/application.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ tailormap-api.security.admin.hashed-password=${ADMIN_HASHED_PASSWORD:#{null}}
2020
tailormap-api.timeout=5000
2121

2222
# page size for features
23-
tailormap-api.pageSize=100
23+
tailormap-api.default-page-size=100
24+
tailormap-api.max-page-size=500
2425
# whether the api should attempt to provide exact feature counts for all WFS requests
2526
# may result in double query execution, once for counting and once for the actual data
2627
tailormap-api.features.wfs_count_exact=false

src/main/resources/openapi/viewer-api.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,13 @@ paths:
945945
schema:
946946
type: integer
947947
minimum: 1
948+
- description: 'Page size to retrieve. Default value is 100. Default maximum value is 500.'
949+
in: query
950+
name: pageSize
951+
required: false
952+
schema:
953+
type: integer
954+
minimum: 1
948955
- description: '
949956
The attribute name (not alias) to sort by.
950957
The name must be of a configured attribute and not a geometry type.

src/test/java/org/tailormap/api/controller/FeaturesControllerIntegrationTest.java

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,12 @@ class FeaturesControllerIntegrationTest {
8888
@Value("${tailormap-api.features.wfs_count_exact:false}")
8989
private boolean exactWfsCounts;
9090

91-
@Value("${tailormap-api.pageSize}")
91+
@Value("${tailormap-api.default-page-size}")
9292
private int pageSize;
9393

94+
@Value("${tailormap-api.max-page-size}")
95+
private int maxPageSize;
96+
9497
static Stream<Arguments> databaseArgumentsProvider() {
9598
return Stream.of(
9699
// docker host,table,url, feature count
@@ -433,6 +436,74 @@ void should_return_empty_featurecollection_for_out_of_range_page_from_wfs() thro
433436
assertEquals(0, features.size(), "there should be 0 provinces in the list");
434437
}
435438

439+
@Test
440+
@WithMockUser(
441+
username = "tm-admin",
442+
authorities = {"admin"})
443+
void should_use_default_page_size_when_not_specified() throws Exception {
444+
final String url = apiBasePath + begroeidterreindeelUrlPostgis;
445+
MvcResult result = mockMvc.perform(get(url).accept(MediaType.APPLICATION_JSON)
446+
.with(setServletPath(url))
447+
.param("page", "1"))
448+
.andExpect(status().isOk())
449+
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
450+
.andExpect(jsonPath("$.page").value(1))
451+
.andExpect(jsonPath("$.pageSize").value(pageSize))
452+
.andExpect(jsonPath("$.features").isArray())
453+
.andExpect(jsonPath("$.features").isNotEmpty())
454+
.andReturn();
455+
456+
List<?> features = JsonPath.read(result.getResponse().getContentAsString(), "$.features");
457+
assertEquals(pageSize, features.size(), "number of returned features should equal the default page size");
458+
}
459+
460+
@Test
461+
@WithMockUser(
462+
username = "tm-admin",
463+
authorities = {"admin"})
464+
void should_use_custom_page_size_when_specified() throws Exception {
465+
int requestedPageSize = pageSize - 1; // smaller than default, so the data set has enough rows
466+
final String url = apiBasePath + begroeidterreindeelUrlPostgis;
467+
MvcResult result = mockMvc.perform(get(url).accept(MediaType.APPLICATION_JSON)
468+
.with(setServletPath(url))
469+
.param("page", "1")
470+
.param("pageSize", String.valueOf(requestedPageSize)))
471+
.andExpect(status().isOk())
472+
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
473+
.andExpect(jsonPath("$.page").value(1))
474+
.andExpect(jsonPath("$.pageSize").value(requestedPageSize))
475+
.andExpect(jsonPath("$.features").isArray())
476+
.andExpect(jsonPath("$.features").isNotEmpty())
477+
.andReturn();
478+
479+
List<?> features = JsonPath.read(result.getResponse().getContentAsString(), "$.features");
480+
assertEquals(
481+
requestedPageSize, features.size(), "number of returned features should equal the requested page size");
482+
}
483+
484+
@Test
485+
@WithMockUser(
486+
username = "tm-admin",
487+
authorities = {"admin"})
488+
void should_cap_page_size_at_max_page_size() throws Exception {
489+
int oversizedPageSize = maxPageSize + 100; // well above the configured maxPageSize
490+
final String url = apiBasePath + begroeidterreindeelUrlPostgis;
491+
MvcResult result = mockMvc.perform(get(url).accept(MediaType.APPLICATION_JSON)
492+
.with(setServletPath(url))
493+
.param("page", "1")
494+
.param("pageSize", String.valueOf(oversizedPageSize)))
495+
.andExpect(status().isOk())
496+
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
497+
.andExpect(jsonPath("$.page").value(1))
498+
.andExpect(jsonPath("$.pageSize").value(maxPageSize))
499+
.andExpect(jsonPath("$.features").isArray())
500+
.andExpect(jsonPath("$.features").isNotEmpty())
501+
.andReturn();
502+
503+
List<?> features = JsonPath.read(result.getResponse().getContentAsString(), "$.features");
504+
assertEquals(maxPageSize, features.size(), "number of returned features should be capped at maxPageSize");
505+
}
506+
436507
@Test
437508
@WithMockUser(
438509
username = "tm-admin",

0 commit comments

Comments
 (0)