Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
package org.geowebcache.util;

import java.util.Collections;
import java.util.Map;

/**
* subset copied from org.geoserver.ows.URLMangler
*
Expand All @@ -29,4 +32,33 @@ public interface URLMangler {
* @return the full generated url from the pieces
*/
public String buildURL(String baseURL, String contextPath, String path);

/**
* Allows for a custom url generation strategy while also carrying query parameters separately from the path.
*
* <p>The default implementation preserves the legacy behavior and ignores the query parameter map. New callers
* should prefer overriding this method so propagated parameters can remain separated until the final URL is
* emitted.
*
* @param baseURL the base url, contains the url up to the domain and port
* @param contextPath the servlet context path, like /geoserver/gwc
* @param path the remaining path after the context path
* @param queryParameters propagated query parameters to preserve separately from the path
* @return the generated url (without serialized query parameters) together with the resulting query parameter map,
* so implementations return their result instead of mutating the {@code queryParameters} argument
*/
default UrlAndParams buildURL(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep on familiar grounds and expose a method just like the GeoServer one instead:

    /**
     * Callback that can change the contents of the baseURL, the path or the KVP map
     *
     * @param baseURL the base URL, containing host, port and application
     * @param path after the application name
     * @param kvp the GET request parameters
     * @param type URL type (External, resource or service) for consideration during mangling
     */
    public void mangleURL(StringBuilder baseURL, StringBuilder path, Map<String, String> kvp, URLType type);

The kvp is meant to be modified by the callers.

String baseURL, String contextPath, String path, Map<String, String> queryParameters) {
return new UrlAndParams(buildURL(baseURL, contextPath, path), queryParameters);
}

/**
* Result of {@link #buildURL(String, String, String, Map)}: the generated URL (without a serialized query string)
* and the query parameters that should eventually be appended to it.
*/
record UrlAndParams(String url, Map<String, String> queryParameters) {
public UrlAndParams {
queryParameters = queryParameters == null ? Collections.emptyMap() : queryParameters;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.geowebcache.util;

import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
Expand Down Expand Up @@ -48,4 +50,17 @@ public void testBuildEmptyContext() throws Exception {
String url = urlMangler.buildURL("http://foo.example.com/", "", "/bar");
Assert.assertEquals("http://foo.example.com/bar", url);
}

@Test
public void testBuildWithQueryParametersLeavesMapUntouched() throws Exception {
Map<String, String> queryParameters = new LinkedHashMap<>();
queryParameters.put("projecttoken", "abc123");

URLMangler.UrlAndParams result =
urlMangler.buildURL("http://foo.example.com/", "/foo", "/bar", queryParameters);

Assert.assertEquals("http://foo.example.com/foo/bar", result.url());
Assert.assertEquals(1, result.queryParameters().size());
Assert.assertEquals("abc123", result.queryParameters().get("projecttoken"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ public class WMTSGetCapabilities {

private GridSetBroker gsb;

private String baseUrl;

private String restBaseUrl;
private final WMTSUrls urls;

private final Collection<WMTSExtension> extensions;

Expand All @@ -90,21 +88,31 @@ protected WMTSGetCapabilities(
String contextPath,
URLMangler urlMangler,
Collection<WMTSExtension> extensions) {
this(tld, gsb, servReq, buildUrls(servReq, baseUrl, contextPath, urlMangler), extensions);
}

protected WMTSGetCapabilities(
TileLayerDispatcher tld,
GridSetBroker gsb,
HttpServletRequest servReq,
WMTSUrls urls,
Collection<WMTSExtension> extensions) {
this.tld = tld;
this.gsb = gsb;
this.urls = urls;
this.extensions = extensions;
}

private static WMTSUrls buildUrls(
HttpServletRequest servReq, String baseUrl, String contextPath, URLMangler urlMangler) {
String forcedBaseUrl =
ServletUtils.stringFromMap(servReq.getParameterMap(), servReq.getCharacterEncoding(), "base_url");

if (forcedBaseUrl != null) {
this.baseUrl = forcedBaseUrl;
} else {
this.baseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.SERVICE_PATH);
}

this.restBaseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.REST_PATH);

this.extensions = extensions;
String effectiveBaseUrl = forcedBaseUrl != null ? forcedBaseUrl : baseUrl;
URLMangler.UrlAndParams service =
urlMangler.buildURL(effectiveBaseUrl, contextPath, WMTSService.SERVICE_PATH, Collections.emptyMap());
URLMangler.UrlAndParams rest =
urlMangler.buildURL(effectiveBaseUrl, contextPath, WMTSService.REST_PATH, Collections.emptyMap());
return new WMTSUrls(service.url(), service.queryParameters(), rest.url(), rest.queryParameters());
}

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

xml.indentElement("ServiceMetadataURL")
.attribute("xlink:href", WMTSUtils.getKvpServiceMetadataURL(baseUrl))
.attribute(
"xlink:href",
WMTSUtils.appendQueryParameters(
WMTSUtils.getKvpServiceMetadataURL(urls.serviceBaseUrl()),
urls.serviceQueryParameters()))
Comment on lines +182 to +184

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eugh, this is somewhat painful to read... I think it would read bettter if you had it in WMTSUrls instead, something like:

      public String serviceMetadataUrl() {
          return WMTSUtils.getKvpServiceMetadataURL(serviceBaseUrl, serviceQueryParameters);
      }

      public String withServiceQuery(String url) {
          return WMTSUtils.appendQueryParameters(url, serviceQueryParameters);
      }

      public String withRestQuery(String url) {
          return WMTSUtils.appendQueryParameters(url, restQueryParameters);

.endElement();

xml.indentElement("ServiceMetadataURL")
.attribute("xlink:href", restBaseUrl + "/WMTSCapabilities.xml")
.attribute(
"xlink:href",
WMTSUtils.appendQueryParameters(
urls.restBaseUrl() + "/WMTSCapabilities.xml", urls.restQueryParameters()))
.endElement();

xml.endElement("Capabilities");
Expand Down Expand Up @@ -365,9 +380,9 @@ private void serviceProvider(XMLBuilder xml, ServiceInformation servInfo) throws
xml.endElement();
}
} else {
appendTag(xml, "ows:ProviderName", baseUrl, null);
appendTag(xml, "ows:ProviderName", urls.serviceBaseUrl(), null);
xml.indentElement("ows:ProviderSite")
.attribute("xlink:href", baseUrl)
.attribute("xlink:href", urls.serviceBaseUrl())
.endElement();
xml.indentElement("ows:ServiceContact");
appendTag(xml, "ows:IndividualName", "GeoWebCache User", null);
Expand All @@ -379,9 +394,9 @@ private void serviceProvider(XMLBuilder xml, ServiceInformation servInfo) throws

private void operationsMetadata(XMLBuilder xml) throws IOException {
xml.indentElement("ows:OperationsMetadata");
operation(xml, "GetCapabilities", baseUrl);
operation(xml, "GetTile", baseUrl);
operation(xml, "GetFeatureInfo", baseUrl);
operation(xml, "GetCapabilities", urls.serviceBaseUrl(), urls.serviceQueryParameters());
operation(xml, "GetTile", urls.serviceBaseUrl(), urls.serviceQueryParameters());
operation(xml, "GetFeatureInfo", urls.serviceBaseUrl(), urls.serviceQueryParameters());
// allow extension to inject their own metadata
for (WMTSExtension extension : extensions) {
List<WMTSExtension.OperationMetadata> operationsMetaData = extension.getExtraOperationsMetadata();
Expand All @@ -390,22 +405,27 @@ private void operationsMetadata(XMLBuilder xml) throws IOException {
operation(
xml,
operationMetadata.getName(),
operationMetadata.getBaseUrl() == null ? baseUrl : operationMetadata.getBaseUrl());
operationMetadata.getBaseUrl() == null
? urls.serviceBaseUrl()
: operationMetadata.getBaseUrl(),
urls.serviceQueryParameters());
}
}
extension.encodedOperationsMetadata(xml);
}
xml.endElement("ows:OperationsMetadata");
}

private void operation(XMLBuilder xml, String operationName, String baseUrl) throws IOException {
private void operation(XMLBuilder xml, String operationName, String baseUrl, Map<String, String> queryParameters)
throws IOException {
xml.indentElement("ows:Operation").attribute("name", operationName);
xml.indentElement("ows:DCP");
xml.indentElement("ows:HTTP");
if (baseUrl.contains("?")) {
xml.indentElement("ows:Get").attribute("xlink:href", baseUrl + "&");
String href = WMTSUtils.appendQueryParameters(baseUrl, queryParameters);
if (href.contains("?")) {
xml.indentElement("ows:Get").attribute("xlink:href", href + "&");
} else {
xml.indentElement("ows:Get").attribute("xlink:href", baseUrl + "?");
xml.indentElement("ows:Get").attribute("xlink:href", href + "?");
}
xml.indentElement("ows:Constraint").attribute("name", "GetEncoding");
xml.indentElement("ows:AllowedValues");
Expand All @@ -426,7 +446,7 @@ private void contents(XMLBuilder xml) throws IOException {
if (!layer.isEnabled() || !layer.isAdvertised()) {
continue;
}
layer(xml, layer, baseUrl, usedGridsets);
layer(xml, layer, urls.serviceBaseUrl(), usedGridsets);
}

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

layerGridSubSets(xml, layer, usedGridsets);

layerResourceUrls(xml, layer, filters, restBaseUrl);
layerResourceUrls(xml, layer, filters, urls.restBaseUrl(), urls.restQueryParameters());

// allow extensions to contribute extra metadata to this layer
for (WMTSExtension extension : extensions) {
Expand Down Expand Up @@ -712,7 +732,12 @@ private void layerGridSubSets(XMLBuilder xml, TileLayer layer, Set<GridSet> used
* For each layer discovers the available image formats, feature info formats and dimensions and produce the
* necessary <ResourceURL> elements.
*/
private void layerResourceUrls(XMLBuilder xml, TileLayer layer, List<ParameterFilter> filters, String baseurl)
private void layerResourceUrls(
XMLBuilder xml,
TileLayer layer,
List<ParameterFilter> filters,
String baseurl,
Map<String, String> queryParameters)
throws IOException {
String baseTemplate = baseurl + "/" + layer.getName();
String commonTemplate = baseTemplate + "/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}";
Expand All @@ -728,21 +753,25 @@ private void layerResourceUrls(XMLBuilder xml, TileLayer layer, List<ParameterFi
// Extracts image formats
List<String> mimeFormats = WMTSUtils.getLayerFormats(layer);
for (String format : mimeFormats) {
String template = commonTemplate + "?format=" + format + commonDimensions;
String template = WMTSUtils.appendQueryParameters(
commonTemplate + "?format=" + format + commonDimensions, queryParameters);
layerResourceUrlsGen(xml, format, "tile", template);
}
// Extracts feature info formats
List<String> infoFormats = WMTSUtils.getInfoFormats(layer);
for (String format : infoFormats) {
String template = commonTemplate + "/{J}/{I}?format=" + format + commonDimensions;
String template = WMTSUtils.appendQueryParameters(
commonTemplate + "/{J}/{I}?format=" + format + commonDimensions, queryParameters);
layerResourceUrlsGen(xml, format, "FeatureInfo", template);
}
if (layer instanceof TileJSONProvider provider) {
List<String> formatExtensions = WMTSUtils.getLayerFormatsExtensions(layer);
String outputFormat = ApplicationMime.json.getFormat();
if (provider.supportsTileJSON()) {
for (String tileJsonFormat : formatExtensions) {
String template = baseTemplate + "/{style}/tilejson/" + tileJsonFormat + "?format=" + outputFormat;
String template = WMTSUtils.appendQueryParameters(
baseTemplate + "/{style}/tilejson/" + tileJsonFormat + "?format=" + outputFormat,
queryParameters);
layerResourceUrlsGen(xml, outputFormat, "TileJSON", template);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,8 +572,8 @@ public void handleRequest(Conveyor conv) throws OWSException, GeoWebCacheExcepti

if (tile.getHint() != null) {
if (tile.getHint().equals(GET_CAPABILITIES)) {
WMTSGetCapabilities wmsGC = new WMTSGetCapabilities(
tld, gsb, tile.servletReq, servletBase, context, urlMangler, extensions);
WMTSUrls urls = buildUrls(servletBase, context);
WMTSGetCapabilities wmsGC = new WMTSGetCapabilities(tld, gsb, tile.servletReq, urls, extensions);
wmsGC.writeResponse(tile.servletResp, stats);

} else if (tile.getHint().equals(GET_FEATUREINFO)) {
Expand All @@ -600,13 +600,22 @@ public void handleRequest(Conveyor conv) throws OWSException, GeoWebCacheExcepti
if (!provider.supportsTileJSON()) {
throw new HttpErrorCodeException(404, "TileJSON Not supported");
}
WMTSTileJSON wmtsTileJSON = new WMTSTileJSON(convTile, servletBase, context, style, urlMangler);
WMTSUrls urls = buildUrls(servletBase, context);
WMTSTileJSON wmtsTileJSON = new WMTSTileJSON(convTile, urls, style);
wmtsTileJSON.writeResponse(layer);
}
}
}
}

private WMTSUrls buildUrls(String servletBase, String context) {
URLMangler.UrlAndParams service =
urlMangler.buildURL(servletBase, context, SERVICE_PATH, Collections.emptyMap());
URLMangler.UrlAndParams rest = urlMangler.buildURL(servletBase, context, REST_PATH, Collections.emptyMap());

return new WMTSUrls(service.url(), service.queryParameters(), rest.url(), rest.queryParameters());
}

void addExtension(WMTSExtension extension) {
extensions.add(extension);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,44 +32,42 @@
import org.geowebcache.layer.meta.VectorLayerMetadata;
import org.geowebcache.mime.ApplicationMime;
import org.geowebcache.mime.MimeType;
import org.geowebcache.util.URLMangler;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.json.JsonMapper;

public class WMTSTileJSON {

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

public WMTSTileJSON(
ConveyorTile convTile, String baseUrl, String contextPath, String style, URLMangler urlMangler) {
public WMTSTileJSON(ConveyorTile convTile, WMTSUrls urls, String style) {
this.convTile = convTile;
this.style = style;
this.restBaseUrl = urlMangler.buildURL(baseUrl, contextPath, WMTSService.REST_PATH);
this.urls = urls;
}

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

List<String> urls = new ArrayList<>();
List<String> tileUrls = new ArrayList<>();
MimeType mimeType = convTile.getMimeType();
Set<String> gridSubSets = layer.getGridSubsets();
for (String gridSubSet : gridSubSets) {
addTileUrl(layer, gridSubSet, mimeType, urls);
addTileUrl(layer, gridSubSet, mimeType, tileUrls);
}
List<VectorLayerMetadata> vectorLayers = json.getLayers();
if (vectorLayers != null && !vectorLayers.isEmpty() && !mimeType.isVector()) {
// Removing vectorLayers info when requesting a raster format
json.setLayers(null);
}

String[] tileUrls = urls.toArray(new String[urls.size()]);
json.setTiles(tileUrls);
String[] serializedTileUrls = tileUrls.toArray(new String[tileUrls.size()]);
json.setTiles(serializedTileUrls);
convTile.servletResp.setStatus(HttpServletResponse.SC_OK);
convTile.servletResp.setContentType(ApplicationMime.json.getMimeType());

Expand All @@ -87,7 +85,7 @@ public void writeResponse(TileLayer layer) {
}
}

private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, List<String> urls) {
private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, List<String> tileUrls) {
GridSubset grid = layer.getGridSubset(gridSubSet);
int zoomLevelStart = -1;
int start = -1;
Expand All @@ -108,7 +106,7 @@ private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, L
zoomLevelStart = start;
}

String tileUrl = restBaseUrl
String tileUrl = this.urls.restBaseUrl()
+ "/"
+ layer.getName()
+ "/"
Expand All @@ -120,6 +118,7 @@ private void addTileUrl(TileLayer layer, String gridSubSet, MimeType mimeType, L
+ "/{y}/{x}"
+ "?format="
+ mimeType;
urls.add(tileUrl);
tileUrl = WMTSUtils.appendQueryParameters(tileUrl, this.urls.restQueryParameters());
tileUrls.add(tileUrl);
}
}
Loading
Loading