Skip to content

Commit 5239121

Browse files
fernandor777aaime
authored andcommitted
[GEOS-12149] GeoServer WMTS capabilities put query parameters in the wrong place in generated URLs
1 parent ec42ec1 commit 5239121

10 files changed

Lines changed: 568 additions & 49 deletions

File tree

geowebcache/core/src/main/java/org/geowebcache/util/URLMangler.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
*/
1414
package org.geowebcache.util;
1515

16+
import java.util.Collections;
17+
import java.util.Map;
18+
1619
/**
1720
* subset copied from org.geoserver.ows.URLMangler
1821
*
@@ -29,4 +32,33 @@ public interface URLMangler {
2932
* @return the full generated url from the pieces
3033
*/
3134
public String buildURL(String baseURL, String contextPath, String path);
35+
36+
/**
37+
* Allows for a custom url generation strategy while also carrying query parameters separately from the path.
38+
*
39+
* <p>The default implementation preserves the legacy behavior and ignores the query parameter map. New callers
40+
* should prefer overriding this method so propagated parameters can remain separated until the final URL is
41+
* emitted.
42+
*
43+
* @param baseURL the base url, contains the url up to the domain and port
44+
* @param contextPath the servlet context path, like /geoserver/gwc
45+
* @param path the remaining path after the context path
46+
* @param queryParameters propagated query parameters to preserve separately from the path
47+
* @return the generated url (without serialized query parameters) together with the resulting query parameter map,
48+
* so implementations return their result instead of mutating the {@code queryParameters} argument
49+
*/
50+
default UrlAndParams buildURL(
51+
String baseURL, String contextPath, String path, Map<String, String> queryParameters) {
52+
return new UrlAndParams(buildURL(baseURL, contextPath, path), queryParameters);
53+
}
54+
55+
/**
56+
* Result of {@link #buildURL(String, String, String, Map)}: the generated URL (without a serialized query string)
57+
* and the query parameters that should eventually be appended to it.
58+
*/
59+
record UrlAndParams(String url, Map<String, String> queryParameters) {
60+
public UrlAndParams {
61+
queryParameters = queryParameters == null ? Collections.emptyMap() : queryParameters;
62+
}
63+
}
3264
}

geowebcache/core/src/test/java/org/geowebcache/util/NullURLManglerTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.geowebcache.util;
22

3+
import java.util.LinkedHashMap;
4+
import java.util.Map;
35
import org.junit.Assert;
46
import org.junit.Before;
57
import org.junit.Test;
@@ -48,4 +50,17 @@ public void testBuildEmptyContext() throws Exception {
4850
String url = urlMangler.buildURL("http://foo.example.com/", "", "/bar");
4951
Assert.assertEquals("http://foo.example.com/bar", url);
5052
}
53+
54+
@Test
55+
public void testBuildWithQueryParametersLeavesMapUntouched() throws Exception {
56+
Map<String, String> queryParameters = new LinkedHashMap<>();
57+
queryParameters.put("projecttoken", "abc123");
58+
59+
URLMangler.UrlAndParams result =
60+
urlMangler.buildURL("http://foo.example.com/", "/foo", "/bar", queryParameters);
61+
62+
Assert.assertEquals("http://foo.example.com/foo/bar", result.url());
63+
Assert.assertEquals(1, result.queryParameters().size());
64+
Assert.assertEquals("abc123", result.queryParameters().get("projecttoken"));
65+
}
5166
}

geowebcache/wmts/src/main/java/org/geowebcache/service/wmts/WMTSGetCapabilities.java

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ public class WMTSGetCapabilities {
6666

6767
private GridSetBroker gsb;
6868

69-
private String baseUrl;
70-
71-
private String restBaseUrl;
69+
private final WMTSUrls urls;
7270

7371
private final Collection<WMTSExtension> extensions;
7472

@@ -90,21 +88,31 @@ protected WMTSGetCapabilities(
9088
String contextPath,
9189
URLMangler urlMangler,
9290
Collection<WMTSExtension> extensions) {
91+
this(tld, gsb, servReq, buildUrls(servReq, baseUrl, contextPath, urlMangler), extensions);
92+
}
93+
94+
protected WMTSGetCapabilities(
95+
TileLayerDispatcher tld,
96+
GridSetBroker gsb,
97+
HttpServletRequest servReq,
98+
WMTSUrls urls,
99+
Collection<WMTSExtension> extensions) {
93100
this.tld = tld;
94101
this.gsb = gsb;
102+
this.urls = urls;
103+
this.extensions = extensions;
104+
}
95105

106+
private static WMTSUrls buildUrls(
107+
HttpServletRequest servReq, String baseUrl, String contextPath, URLMangler urlMangler) {
96108
String forcedBaseUrl =
97109
ServletUtils.stringFromMap(servReq.getParameterMap(), servReq.getCharacterEncoding(), "base_url");
98-
99-
if (forcedBaseUrl != null) {
100-
this.baseUrl = forcedBaseUrl;
101-
} else {
102-
this.baseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.SERVICE_PATH);
103-
}
104-
105-
this.restBaseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.REST_PATH);
106-
107-
this.extensions = extensions;
110+
String effectiveBaseUrl = forcedBaseUrl != null ? forcedBaseUrl : baseUrl;
111+
URLMangler.UrlAndParams service =
112+
urlMangler.buildURL(effectiveBaseUrl, contextPath, WMTSService.SERVICE_PATH, Collections.emptyMap());
113+
URLMangler.UrlAndParams rest =
114+
urlMangler.buildURL(effectiveBaseUrl, contextPath, WMTSService.REST_PATH, Collections.emptyMap());
115+
return new WMTSUrls(service.url(), service.queryParameters(), rest.url(), rest.queryParameters());
108116
}
109117

110118
protected void writeResponse(HttpServletResponse response, RuntimeStats stats) {
@@ -169,11 +177,18 @@ private String generateGetCapabilities(Charset encoding) {
169177
contents(xml);
170178

171179
xml.indentElement("ServiceMetadataURL")
172-
.attribute("xlink:href", WMTSUtils.getKvpServiceMetadataURL(baseUrl))
180+
.attribute(
181+
"xlink:href",
182+
WMTSUtils.appendQueryParameters(
183+
WMTSUtils.getKvpServiceMetadataURL(urls.serviceBaseUrl()),
184+
urls.serviceQueryParameters()))
173185
.endElement();
174186

175187
xml.indentElement("ServiceMetadataURL")
176-
.attribute("xlink:href", restBaseUrl + "/WMTSCapabilities.xml")
188+
.attribute(
189+
"xlink:href",
190+
WMTSUtils.appendQueryParameters(
191+
urls.restBaseUrl() + "/WMTSCapabilities.xml", urls.restQueryParameters()))
177192
.endElement();
178193

179194
xml.endElement("Capabilities");
@@ -365,9 +380,9 @@ private void serviceProvider(XMLBuilder xml, ServiceInformation servInfo) throws
365380
xml.endElement();
366381
}
367382
} else {
368-
appendTag(xml, "ows:ProviderName", baseUrl, null);
383+
appendTag(xml, "ows:ProviderName", urls.serviceBaseUrl(), null);
369384
xml.indentElement("ows:ProviderSite")
370-
.attribute("xlink:href", baseUrl)
385+
.attribute("xlink:href", urls.serviceBaseUrl())
371386
.endElement();
372387
xml.indentElement("ows:ServiceContact");
373388
appendTag(xml, "ows:IndividualName", "GeoWebCache User", null);
@@ -379,9 +394,9 @@ private void serviceProvider(XMLBuilder xml, ServiceInformation servInfo) throws
379394

380395
private void operationsMetadata(XMLBuilder xml) throws IOException {
381396
xml.indentElement("ows:OperationsMetadata");
382-
operation(xml, "GetCapabilities", baseUrl);
383-
operation(xml, "GetTile", baseUrl);
384-
operation(xml, "GetFeatureInfo", baseUrl);
397+
operation(xml, "GetCapabilities", urls.serviceBaseUrl(), urls.serviceQueryParameters());
398+
operation(xml, "GetTile", urls.serviceBaseUrl(), urls.serviceQueryParameters());
399+
operation(xml, "GetFeatureInfo", urls.serviceBaseUrl(), urls.serviceQueryParameters());
385400
// allow extension to inject their own metadata
386401
for (WMTSExtension extension : extensions) {
387402
List<WMTSExtension.OperationMetadata> operationsMetaData = extension.getExtraOperationsMetadata();
@@ -390,22 +405,27 @@ private void operationsMetadata(XMLBuilder xml) throws IOException {
390405
operation(
391406
xml,
392407
operationMetadata.getName(),
393-
operationMetadata.getBaseUrl() == null ? baseUrl : operationMetadata.getBaseUrl());
408+
operationMetadata.getBaseUrl() == null
409+
? urls.serviceBaseUrl()
410+
: operationMetadata.getBaseUrl(),
411+
urls.serviceQueryParameters());
394412
}
395413
}
396414
extension.encodedOperationsMetadata(xml);
397415
}
398416
xml.endElement("ows:OperationsMetadata");
399417
}
400418

401-
private void operation(XMLBuilder xml, String operationName, String baseUrl) throws IOException {
419+
private void operation(XMLBuilder xml, String operationName, String baseUrl, Map<String, String> queryParameters)
420+
throws IOException {
402421
xml.indentElement("ows:Operation").attribute("name", operationName);
403422
xml.indentElement("ows:DCP");
404423
xml.indentElement("ows:HTTP");
405-
if (baseUrl.contains("?")) {
406-
xml.indentElement("ows:Get").attribute("xlink:href", baseUrl + "&");
424+
String href = WMTSUtils.appendQueryParameters(baseUrl, queryParameters);
425+
if (href.contains("?")) {
426+
xml.indentElement("ows:Get").attribute("xlink:href", href + "&");
407427
} else {
408-
xml.indentElement("ows:Get").attribute("xlink:href", baseUrl + "?");
428+
xml.indentElement("ows:Get").attribute("xlink:href", href + "?");
409429
}
410430
xml.indentElement("ows:Constraint").attribute("name", "GetEncoding");
411431
xml.indentElement("ows:AllowedValues");
@@ -426,7 +446,7 @@ private void contents(XMLBuilder xml) throws IOException {
426446
if (!layer.isEnabled() || !layer.isAdvertised()) {
427447
continue;
428448
}
429-
layer(xml, layer, baseUrl, usedGridsets);
449+
layer(xml, layer, urls.serviceBaseUrl(), usedGridsets);
430450
}
431451

432452
// only dump the gridsets actually used, as the OGC TMS spec introduced many default ones
@@ -488,7 +508,7 @@ private void layer(XMLBuilder xml, TileLayer layer, String baseurl, Set<GridSet>
488508

489509
layerGridSubSets(xml, layer, usedGridsets);
490510

491-
layerResourceUrls(xml, layer, filters, restBaseUrl);
511+
layerResourceUrls(xml, layer, filters, urls.restBaseUrl(), urls.restQueryParameters());
492512

493513
// allow extensions to contribute extra metadata to this layer
494514
for (WMTSExtension extension : extensions) {
@@ -712,7 +732,12 @@ private void layerGridSubSets(XMLBuilder xml, TileLayer layer, Set<GridSet> used
712732
* For each layer discovers the available image formats, feature info formats and dimensions and produce the
713733
* necessary <ResourceURL> elements.
714734
*/
715-
private void layerResourceUrls(XMLBuilder xml, TileLayer layer, List<ParameterFilter> filters, String baseurl)
735+
private void layerResourceUrls(
736+
XMLBuilder xml,
737+
TileLayer layer,
738+
List<ParameterFilter> filters,
739+
String baseurl,
740+
Map<String, String> queryParameters)
716741
throws IOException {
717742
String baseTemplate = baseurl + "/" + layer.getName();
718743
String commonTemplate = baseTemplate + "/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}";
@@ -728,21 +753,25 @@ private void layerResourceUrls(XMLBuilder xml, TileLayer layer, List<ParameterFi
728753
// Extracts image formats
729754
List<String> mimeFormats = WMTSUtils.getLayerFormats(layer);
730755
for (String format : mimeFormats) {
731-
String template = commonTemplate + "?format=" + format + commonDimensions;
756+
String template = WMTSUtils.appendQueryParameters(
757+
commonTemplate + "?format=" + format + commonDimensions, queryParameters);
732758
layerResourceUrlsGen(xml, format, "tile", template);
733759
}
734760
// Extracts feature info formats
735761
List<String> infoFormats = WMTSUtils.getInfoFormats(layer);
736762
for (String format : infoFormats) {
737-
String template = commonTemplate + "/{J}/{I}?format=" + format + commonDimensions;
763+
String template = WMTSUtils.appendQueryParameters(
764+
commonTemplate + "/{J}/{I}?format=" + format + commonDimensions, queryParameters);
738765
layerResourceUrlsGen(xml, format, "FeatureInfo", template);
739766
}
740767
if (layer instanceof TileJSONProvider provider) {
741768
List<String> formatExtensions = WMTSUtils.getLayerFormatsExtensions(layer);
742769
String outputFormat = ApplicationMime.json.getFormat();
743770
if (provider.supportsTileJSON()) {
744771
for (String tileJsonFormat : formatExtensions) {
745-
String template = baseTemplate + "/{style}/tilejson/" + tileJsonFormat + "?format=" + outputFormat;
772+
String template = WMTSUtils.appendQueryParameters(
773+
baseTemplate + "/{style}/tilejson/" + tileJsonFormat + "?format=" + outputFormat,
774+
queryParameters);
746775
layerResourceUrlsGen(xml, outputFormat, "TileJSON", template);
747776
}
748777
}

geowebcache/wmts/src/main/java/org/geowebcache/service/wmts/WMTSService.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,8 +572,8 @@ public void handleRequest(Conveyor conv) throws OWSException, GeoWebCacheExcepti
572572

573573
if (tile.getHint() != null) {
574574
if (tile.getHint().equals(GET_CAPABILITIES)) {
575-
WMTSGetCapabilities wmsGC = new WMTSGetCapabilities(
576-
tld, gsb, tile.servletReq, servletBase, context, urlMangler, extensions);
575+
WMTSUrls urls = buildUrls(servletBase, context);
576+
WMTSGetCapabilities wmsGC = new WMTSGetCapabilities(tld, gsb, tile.servletReq, urls, extensions);
577577
wmsGC.writeResponse(tile.servletResp, stats);
578578

579579
} else if (tile.getHint().equals(GET_FEATUREINFO)) {
@@ -600,13 +600,22 @@ public void handleRequest(Conveyor conv) throws OWSException, GeoWebCacheExcepti
600600
if (!provider.supportsTileJSON()) {
601601
throw new HttpErrorCodeException(404, "TileJSON Not supported");
602602
}
603-
WMTSTileJSON wmtsTileJSON = new WMTSTileJSON(convTile, servletBase, context, style, urlMangler);
603+
WMTSUrls urls = buildUrls(servletBase, context);
604+
WMTSTileJSON wmtsTileJSON = new WMTSTileJSON(convTile, urls, style);
604605
wmtsTileJSON.writeResponse(layer);
605606
}
606607
}
607608
}
608609
}
609610

611+
private WMTSUrls buildUrls(String servletBase, String context) {
612+
URLMangler.UrlAndParams service =
613+
urlMangler.buildURL(servletBase, context, SERVICE_PATH, Collections.emptyMap());
614+
URLMangler.UrlAndParams rest = urlMangler.buildURL(servletBase, context, REST_PATH, Collections.emptyMap());
615+
616+
return new WMTSUrls(service.url(), service.queryParameters(), rest.url(), rest.queryParameters());
617+
}
618+
610619
void addExtension(WMTSExtension extension) {
611620
extensions.add(extension);
612621
}

geowebcache/wmts/src/main/java/org/geowebcache/service/wmts/WMTSTileJSON.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,44 +32,42 @@
3232
import org.geowebcache.layer.meta.VectorLayerMetadata;
3333
import org.geowebcache.mime.ApplicationMime;
3434
import org.geowebcache.mime.MimeType;
35-
import org.geowebcache.util.URLMangler;
3635
import tools.jackson.databind.ObjectMapper;
3736
import tools.jackson.databind.json.JsonMapper;
3837

3938
public class WMTSTileJSON {
4039

4140
private static Logger log = Logging.getLogger(WMTSTileJSON.class.getName());
42-
private final String restBaseUrl;
41+
private final WMTSUrls urls;
4342
private String style;
4443
private ConveyorTile convTile;
4544
private static final String ENDING_DIGITS_REGEX = "([0-9]+)$";
4645
private static final Pattern PATTERN_DIGITS = Pattern.compile(ENDING_DIGITS_REGEX);
4746

48-
public WMTSTileJSON(
49-
ConveyorTile convTile, String baseUrl, String contextPath, String style, URLMangler urlMangler) {
47+
public WMTSTileJSON(ConveyorTile convTile, WMTSUrls urls, String style) {
5048
this.convTile = convTile;
5149
this.style = style;
52-
this.restBaseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.REST_PATH);
50+
this.urls = urls;
5351
}
5452

5553
public void writeResponse(TileLayer layer) {
5654
TileJSONProvider provider = (TileJSONProvider) layer;
5755
TileJSON json = provider.getTileJSON();
5856

59-
List<String> urls = new ArrayList<>();
57+
List<String> tileUrls = new ArrayList<>();
6058
MimeType mimeType = convTile.getMimeType();
6159
Set<String> gridSubSets = layer.getGridSubsets();
6260
for (String gridSubSet : gridSubSets) {
63-
addTileUrl(layer, gridSubSet, mimeType, urls);
61+
addTileUrl(layer, gridSubSet, mimeType, tileUrls);
6462
}
6563
List<VectorLayerMetadata> vectorLayers = json.getLayers();
6664
if (vectorLayers != null && !vectorLayers.isEmpty() && !mimeType.isVector()) {
6765
// Removing vectorLayers info when requesting a raster format
6866
json.setLayers(null);
6967
}
7068

71-
String[] tileUrls = urls.toArray(new String[urls.size()]);
72-
json.setTiles(tileUrls);
69+
String[] serializedTileUrls = tileUrls.toArray(new String[tileUrls.size()]);
70+
json.setTiles(serializedTileUrls);
7371
convTile.servletResp.setStatus(HttpServletResponse.SC_OK);
7472
convTile.servletResp.setContentType(ApplicationMime.json.getMimeType());
7573

@@ -87,7 +85,7 @@ public void writeResponse(TileLayer layer) {
8785
}
8886
}
8987

90-
private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, List<String> urls) {
88+
private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, List<String> tileUrls) {
9189
GridSubset grid = layer.getGridSubset(gridSubSet);
9290
int zoomLevelStart = -1;
9391
int start = -1;
@@ -108,7 +106,7 @@ private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, L
108106
zoomLevelStart = start;
109107
}
110108

111-
String tileUrl = restBaseUrl
109+
String tileUrl = this.urls.restBaseUrl()
112110
+ "/"
113111
+ layer.getName()
114112
+ "/"
@@ -120,6 +118,7 @@ private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, L
120118
+ "/{y}/{x}"
121119
+ "?format="
122120
+ mimeType;
123-
urls.add(tileUrl);
121+
tileUrl = WMTSUtils.appendQueryParameters(tileUrl, this.urls.restQueryParameters());
122+
tileUrls.add(tileUrl);
124123
}
125124
}

0 commit comments

Comments
 (0)