/* (c) 2014 - 2016 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.gwc; import static org.geoserver.data.test.MockData.BASIC_POLYGONS; import static org.geoserver.gwc.GWC.tileLayerName; import static org.hamcrest.Matchers.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import javax.servlet.http.HttpServletResponse; import javax.xml.namespace.QName; import org.apache.commons.httpclient.util.DateUtil; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.custommonkey.xmlunit.SimpleNamespaceContext; import org.custommonkey.xmlunit.XMLUnit; import org.custommonkey.xmlunit.XpathEngine; import org.geoserver.catalog.Catalog; import org.geoserver.catalog.CatalogBuilder; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.ResourceInfo; import org.geoserver.catalog.WorkspaceInfo; import org.geoserver.config.GeoServerDataDirectory; import org.geoserver.data.test.MockData; import org.geoserver.data.test.SystemTestData; import org.geoserver.data.test.SystemTestData.LayerProperty; import org.geoserver.gwc.config.GWCConfig; import org.geoserver.gwc.layer.CatalogConfiguration; import org.geoserver.gwc.layer.GeoServerTileLayer; import org.geoserver.gwc.layer.GeoServerTileLayerInfo; import org.geoserver.gwc.wmts.WMTSInfo; import org.geoserver.ows.DispatcherCallback; import org.geoserver.ows.LocalWorkspace; import org.geoserver.platform.GeoServerExtensions; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.GeoServerExtensionsHelper; import org.geoserver.test.GeoServerSystemTestSupport; import org.geoserver.test.TestSetup; import org.geoserver.test.TestSetupFrequency; import org.geoserver.wms.WMSInfo; import org.geotools.feature.NameImpl; import org.geowebcache.GeoWebCacheDispatcher; import org.geowebcache.GeoWebCacheException; import org.geowebcache.GeoWebCacheExtensions; import org.geowebcache.config.ConfigurationException; import org.geowebcache.diskquota.DiskQuotaConfig; import org.geowebcache.diskquota.QuotaStore; import org.geowebcache.diskquota.jdbc.JDBCConfiguration; import org.geowebcache.diskquota.jdbc.JDBCConfiguration.ConnectionPoolConfiguration; import org.geowebcache.diskquota.jdbc.JDBCQuotaStore; import org.geowebcache.grid.BoundingBox; import org.geowebcache.grid.GridSetBroker; import org.geowebcache.grid.GridSubset; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.TileLayerDispatcher; import org.hamcrest.Matchers; import org.junit.BeforeClass; import org.junit.Test; import org.w3c.dom.Document; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @TestSetup(run=TestSetupFrequency.REPEAT) public class GWCIntegrationTest extends GeoServerSystemTestSupport { static final String SIMPLE_LAYER_GROUP = "SIMPLE_LAYER_GROUP"; static final String FLAT_LAYER_GROUP = "flatLayerGroup"; static final String NESTED_LAYER_GROUP = "nestedLayerGroup"; static final String CONTAINER_LAYER_GROUP = "containerLayerGroup"; static final String WORKSPACED_LAYER_GROUP = "workspacedLayerGroup"; static final String TEST_WORKSPACE_NAME = "testWorkspace"; static final String TEST_WORKSPACE_URI = "http://geoserver.org/GWCIntegerationTest/"+TEST_WORKSPACE_NAME; static final String WORKSPACED_STYLE_NAME = "workspacedStyle"; static final String WORKSPACED_STYLE_FILE = "workspacedStyle.sld"; static final String WORKSPACED_LAYER = "workspacedLayer"; static final QName WORKSPACED_LAYER_QNAME = new QName(TEST_WORKSPACE_URI, WORKSPACED_LAYER, TEST_WORKSPACE_NAME); @Override protected void setUpSpring(List<String> springContextLocations) { super.setUpSpring(springContextLocations); springContextLocations.add("gwc-integration-test.xml"); } @Override protected void onSetUp(SystemTestData testData) throws Exception { super.onSetUp(testData); Catalog catalog = getCatalog(); testData.addWorkspace(TEST_WORKSPACE_NAME, TEST_WORKSPACE_URI, catalog); WorkspaceInfo wi = catalog.getWorkspaceByName(TEST_WORKSPACE_NAME); testData.addStyle(wi, WORKSPACED_STYLE_NAME, WORKSPACED_STYLE_FILE, this.getClass(), catalog); assertThat(catalog.getStyleByName(wi, WORKSPACED_STYLE_NAME), Matchers.describedAs("Style %0 should be in workspace %1.", (not(nullValue())), WORKSPACED_STYLE_NAME, TEST_WORKSPACE_NAME)); Map<LayerProperty, Object> props = new HashMap<>(); props.put(LayerProperty.STYLE, WORKSPACED_STYLE_NAME); testData.addVectorLayer(WORKSPACED_LAYER_QNAME, props, this.getClass(), catalog); LayerInfo li = catalog.getLayerByName(getLayerId(WORKSPACED_LAYER_QNAME)); li.setDefaultStyle(catalog.getStyleByName(wi, WORKSPACED_STYLE_NAME)); catalog.save(li); // add a simple layer group with two layers createLayerGroup(SIMPLE_LAYER_GROUP, MockData.BUILDINGS, MockData.BRIDGES); GWC.get().getConfig().setDirectWMSIntegrationEnabled(false); } @Test public void testPngIntegration() throws Exception { String layerId = getLayerId(MockData.BASIC_POLYGONS); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + layerId + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); } @Test public void testRequestReplacement() throws Exception { String layerId = getLayerId(MockData.BASIC_POLYGONS); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + layerId + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=1"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); } @Test public void testWorkspacedStyle() throws Exception { String layerId = getLayerId(WORKSPACED_LAYER_QNAME); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + layerId + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); } @Test public void testGetLegendGraphics() throws Exception { String layerId = getLayerId(MockData.BASIC_POLYGONS); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wms?service=wms&version=1.1.1&request=GetLegendGraphic&layer=" + layerId + "&style=&format=image/png"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); } @Test public void testCachingHeadersSingleLayer() throws Exception { String layerId = getLayerId(MockData.BASIC_POLYGONS); setCachingMetadata(layerId, true, 7200); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + layerId + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals("max-age=7200, must-revalidate", sr.getHeader("Cache-Control")); } @Test public void testCachingHeadersSingleLayerNoHeaders() throws Exception { String layerId = getLayerId(MockData.BASIC_POLYGONS); setCachingMetadata(layerId, false, -1); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + layerId + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertNull(sr.getHeader("Cache-Control")); } @Test public void testCachingHeadersFlatLayerGroup() throws Exception { // set two different caching headers for the two layers String bpLayerId = getLayerId(MockData.BASIC_POLYGONS); setCachingMetadata(bpLayerId, true, 7200); String mpLayerId = getLayerId(MockData.MPOINTS); setCachingMetadata(mpLayerId, true, 1000); // build a flat layer group with them LayerGroupInfo lg = getCatalog().getFactory().createLayerGroup(); lg.setName(FLAT_LAYER_GROUP); lg.getLayers().add(getCatalog().getLayerByName(bpLayerId)); lg.getLayers().add(getCatalog().getLayerByName(mpLayerId)); new CatalogBuilder(getCatalog()).calculateLayerGroupBounds(lg); getCatalog().add(lg); MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + FLAT_LAYER_GROUP + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals("max-age=1000, must-revalidate", sr.getHeader("Cache-Control")); } @Test public void testCachingHeadersNestedLayerGroup() throws Exception { // set two different caching headers for the two layers String bpLayerId = getLayerId(MockData.BASIC_POLYGONS); setCachingMetadata(bpLayerId, true, 7200); String mpLayerId = getLayerId(MockData.MPOINTS); setCachingMetadata(mpLayerId, true, 1000); CatalogBuilder builder = new CatalogBuilder(getCatalog()); // build the nested layer group, only one layer in it LayerGroupInfo nested = getCatalog().getFactory().createLayerGroup(); nested.setName(NESTED_LAYER_GROUP); nested.getLayers().add(getCatalog().getLayerByName(bpLayerId)); builder.calculateLayerGroupBounds(nested); getCatalog().add(nested); // build the container layer group LayerGroupInfo container = getCatalog().getFactory().createLayerGroup(); container.setName(CONTAINER_LAYER_GROUP); container.getLayers().add(getCatalog().getLayerByName(mpLayerId)); container.getLayers().add(getCatalog().getLayerGroupByName(NESTED_LAYER_GROUP)); builder.calculateLayerGroupBounds(container); getCatalog().add(container); // check the caching headers on the nested group MockHttpServletResponse sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + NESTED_LAYER_GROUP + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals("max-age=7200, must-revalidate", sr.getHeader("Cache-Control")); // check the caching headers on the container layer group sr = getAsServletResponse("gwc/service/wmts?request=GetTile&layer=" + CONTAINER_LAYER_GROUP + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals("max-age=1000, must-revalidate", sr.getHeader("Cache-Control")); } private void setCachingMetadata(String layerId, boolean cachingEnabled, int cacheAgeMax) { FeatureTypeInfo ft = getCatalog().getResourceByName(layerId, FeatureTypeInfo.class); ft.getMetadata().put(ResourceInfo.CACHING_ENABLED, cachingEnabled); ft.getMetadata().put(ResourceInfo.CACHE_AGE_MAX, cacheAgeMax); getCatalog().save(ft); } /** * If direct WMS integration is enabled, a GetMap requests that hits the regular WMS but matches * a gwc tile should return with the proper {@code geowebcache-tile-index} HTTP response header. */ @Test public void testDirectWMSIntegration() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String layerName = BASIC_POLYGONS.getPrefix() + ":" + BASIC_POLYGONS.getLocalPart(); String request; MockHttpServletResponse response; request = buildGetMap(true, layerName, "EPSG:4326", null); response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); assertNull(response.getHeader("geowebcache-tile-index")); request = request + "&tiled=true"; response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); } @Test public void testDirectWMSIntegrationResponseHeaders() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String layerName = BASIC_POLYGONS.getPrefix() + ":" + BASIC_POLYGONS.getLocalPart(); String request = buildGetMap(true, layerName, "EPSG:4326", null) + "&tiled=true"; MockHttpServletResponse response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); assertEquals(layerName, response.getHeader("geowebcache-layer")); assertEquals("[0, 0, 0]", response.getHeader("geowebcache-tile-index")); assertEquals("-180.0,-90.0,0.0,90.0", response.getHeader("geowebcache-tile-bounds")); assertEquals("EPSG:4326", response.getHeader("geowebcache-gridset")); assertEquals("EPSG:4326", response.getHeader("geowebcache-crs")); } @Test public void testDirectWMSIntegrationResponseHeaders13() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String layerName = BASIC_POLYGONS.getPrefix() + ":" + BASIC_POLYGONS.getLocalPart(); String request = "wms?service=wms&version=1.3.0&request=GetMap&styles=&layers=" + layerName + "&srs=EPSG:4326&bbox=-90,-180,90,0&format=image/png&width=256&height=256&tiled=true"; MockHttpServletResponse response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); assertEquals(layerName, response.getHeader("geowebcache-layer")); assertEquals("[0, 0, 0]", response.getHeader("geowebcache-tile-index")); assertEquals("-180.0,-90.0,0.0,90.0", response.getHeader("geowebcache-tile-bounds")); assertEquals("EPSG:4326", response.getHeader("geowebcache-gridset")); assertEquals("EPSG:4326", response.getHeader("geowebcache-crs")); } @Test public void testDirectWMSIntegrationIfModifiedSinceSupport() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String layerName = BASIC_POLYGONS.getPrefix() + ":" + BASIC_POLYGONS.getLocalPart(); final String path = buildGetMap(true, layerName, "EPSG:4326", null) + "&tiled=true"; MockHttpServletResponse response = getAsServletResponse(path); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); String lastModifiedHeader = response.getHeader("Last-Modified"); assertNotNull(lastModifiedHeader); Date lastModified = DateUtil.parseDate(lastModifiedHeader); MockHttpServletRequest httpReq = createGetRequest(path); httpReq.addHeader("If-Modified-Since", lastModifiedHeader); response = dispatch(httpReq, "UTF-8"); assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatus()); // set the If-Modified-Since header to some point in the past of the last modified value Date past = new Date(lastModified.getTime() - 5000); String ifModifiedSince = DateUtil.formatDate(past); httpReq = createGetRequest(path); httpReq.addHeader("If-Modified-Since", ifModifiedSince); response = dispatch(httpReq, "UTF-8"); assertEquals(HttpServletResponse.SC_OK, response.getStatus()); Date future = new Date(lastModified.getTime() + 5000); ifModifiedSince = DateUtil.formatDate(future); httpReq = createGetRequest(path); httpReq.addHeader("If-Modified-Since", ifModifiedSince); response = dispatch(httpReq, "UTF-8"); assertEquals(HttpServletResponse.SC_NOT_MODIFIED, response.getStatus()); } private MockHttpServletRequest createGetRequest(final String path) { MockHttpServletRequest httpReq = createRequest(path); httpReq.setMethod("GET"); httpReq.setContent(new byte[] {}); return httpReq; } @Test public void testDirectWMSIntegrationMaxAge() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String layerName = BASIC_POLYGONS.getPrefix() + ":" + BASIC_POLYGONS.getLocalPart(); final String path = buildGetMap(true, layerName, "EPSG:4326", null) + "&tiled=true"; final String qualifiedName = super.getLayerId(BASIC_POLYGONS); final GeoServerTileLayer tileLayer = (GeoServerTileLayer) gwc.getTileLayerByName(qualifiedName); tileLayer.getLayerInfo().getResource().getMetadata().put(ResourceInfo.CACHING_ENABLED, "true"); tileLayer.getLayerInfo().getResource().getMetadata().put(ResourceInfo.CACHE_AGE_MAX, 3456); MockHttpServletResponse response = getAsServletResponse(path); String cacheControl = response.getHeader("Cache-Control"); assertEquals("max-age=3456", cacheControl); assertNotNull(response.getHeader("Last-Modified")); tileLayer.getLayerInfo().getResource().getMetadata().put(ResourceInfo.CACHING_ENABLED, "false"); response = getAsServletResponse(path); cacheControl = response.getHeader("Cache-Control"); assertEquals("no-cache", cacheControl); // make sure a boolean is handled, too - see comment in CachingWebMapService tileLayer.getLayerInfo().getResource().getMetadata().put(ResourceInfo.CACHING_ENABLED, Boolean.FALSE); response = getAsServletResponse(path); cacheControl = response.getHeader("Cache-Control"); assertEquals("no-cache", cacheControl); } @Test public void testDirectWMSIntegrationWithVirtualServices() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String qualifiedName = super.getLayerId(BASIC_POLYGONS); final String localName = BASIC_POLYGONS.getLocalPart(); final TileLayer tileLayer = gwc.getTileLayerByName(qualifiedName); assertNotNull(tileLayer); boolean directWMSIntegrationEndpoint = true; String request = MockData.CITE_PREFIX + "/" + buildGetMap(directWMSIntegrationEndpoint, localName, "EPSG:4326", null, tileLayer) + "&tiled=true"; MockHttpServletResponse response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); assertEquals(qualifiedName, response.getHeader("geowebcache-layer")); } @Test public void testDirectWMSIntegrationWithVirtualServicesAndWorkspacedStyle() throws Exception { final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String qualifiedName = super.getLayerId(WORKSPACED_LAYER_QNAME); final String localName = WORKSPACED_LAYER_QNAME.getLocalPart(); final TileLayer tileLayer = gwc.getTileLayerByName(qualifiedName); assertNotNull(tileLayer); boolean directWMSIntegrationEndpoint = true; String request = TEST_WORKSPACE_NAME + "/" + buildGetMap(directWMSIntegrationEndpoint, localName, "EPSG:4326", null, tileLayer) + "&tiled=true"; MockHttpServletResponse response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); assertEquals(qualifiedName, response.getHeader("geowebcache-layer")); assertThat(response.getHeader("geowebcache-cache-result"), equalToIgnoringCase("MISS")); MockHttpServletResponse response2 = getAsServletResponse(request); assertEquals(200, response2.getStatus()); assertEquals("image/png", response2.getContentType()); assertEquals(qualifiedName, response2.getHeader("geowebcache-layer")); assertThat(response2.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // now try with the style name too, should be another hit request = TEST_WORKSPACE_NAME + "/" + buildGetMap(directWMSIntegrationEndpoint, localName, "EPSG:4326", WORKSPACED_STYLE_NAME, tileLayer) + "&tiled=true"; MockHttpServletResponse response3 = getAsServletResponse(request); assertEquals(200, response3.getStatus()); assertEquals("image/png", response3.getContentType()); assertEquals(qualifiedName, response3.getHeader("geowebcache-layer")); assertThat(response3.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // finally, rename the workspace String oldWorkspaceName = TEST_WORKSPACE_NAME; WorkspaceInfo ws = getCatalog().getWorkspaceByName(oldWorkspaceName); String newWorkspaceName = oldWorkspaceName + "_renamed"; ws.setName(newWorkspaceName); getCatalog().save(ws); // rename the bits in the request, it should be another hit request = newWorkspaceName + "/" + buildGetMap(directWMSIntegrationEndpoint, localName, "EPSG:4326", WORKSPACED_STYLE_NAME, tileLayer) + "&tiled=true"; MockHttpServletResponse response4 = getAsServletResponse(request); assertEquals(200, response4.getStatus()); assertEquals("image/png", response4.getContentType()); assertEquals(newWorkspaceName + ":" + WORKSPACED_LAYER, response4.getHeader("geowebcache-layer")); assertThat(response4.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); } @Test public void testLayerGroupInWorkspace() throws Exception { // the workspace for the tests String workspaceName = MockData.BASIC_POLYGONS.getPrefix(); // build a flat layer group with them, in the test workspace LayerGroupInfo lg = getCatalog().getFactory().createLayerGroup(); lg.setName(WORKSPACED_LAYER_GROUP); String bpLayerId = getLayerId(MockData.BASIC_POLYGONS); String mpLayerId = getLayerId(MockData.LAKES); lg.getLayers().add(getCatalog().getLayerByName(bpLayerId)); lg.getLayers().add(getCatalog().getLayerByName(mpLayerId)); lg.setWorkspace(getCatalog().getWorkspaceByName(workspaceName)); new CatalogBuilder(getCatalog()).calculateLayerGroupBounds(lg); getCatalog().add(lg); // wmts request, use the qualified name, first request, works, but it's a cache miss of // course String request = "gwc/service/wmts?request=GetTile&layer=" + workspaceName + ":" + WORKSPACED_LAYER_GROUP + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"; MockHttpServletResponse sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("MISS")); // run again, it should be a hit sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // try direct integration too final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final TileLayer tileLayer = gwc.getTileLayerByName(lg.prefixedName()); request = buildGetMap(true, lg.prefixedName(), "EPSG:4326", null, tileLayer) + "&tiled=true"; sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals(lg.prefixedName(), sr.getHeader("geowebcache-layer")); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // and direct integration against the workspace local name request = workspaceName + "/" + buildGetMap(true, lg.getName(), "EPSG:4326", null, tileLayer) + "&tiled=true"; sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals(lg.prefixedName(), sr.getHeader("geowebcache-layer")); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // now change the workspace name WorkspaceInfo ws = getCatalog().getWorkspaceByName(workspaceName); String newWorkspaceName = workspaceName + "_renamed"; ws.setName(newWorkspaceName); getCatalog().save(ws); // prepare the wmts request anew, it should be a hit, the cache should be preserved request = "gwc/service/wmts?request=GetTile&layer=" + newWorkspaceName + ":" + WORKSPACED_LAYER_GROUP + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"; sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // and now direct integration String newQualifiedName = newWorkspaceName + ":" + lg.getName(); request = buildGetMap(true, newQualifiedName, "EPSG:4326", null, tileLayer) + "&tiled=true"; sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals("image/png", sr.getContentType()); assertEquals(lg.prefixedName(), sr.getHeader("geowebcache-layer")); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); // and direct integration against the workspace local name request = newWorkspaceName + "/" + buildGetMap(true, lg.getName(), "EPSG:4326", null, tileLayer) + "&tiled=true"; sr = getAsServletResponse(request); assertEquals(200, sr.getStatus()); assertEquals(newQualifiedName, sr.getHeader("geowebcache-layer")); assertThat(sr.getHeader("geowebcache-cache-result"), equalToIgnoringCase("HIT")); } @Test public void testDirectWMSIntegrationWithVirtualServicesHiddenLayer() throws Exception { /* * Nothing special needs to be done at the GWC integration level for this to work. The hard * work should already be done by WMSWorkspaceQualifier so that when the request hits GWC * the layer name is already qualified */ final GWC gwc = GWC.get(); gwc.getConfig().setDirectWMSIntegrationEnabled(true); final String qualifiedName = super.getLayerId(BASIC_POLYGONS); final String localName = BASIC_POLYGONS.getLocalPart(); final TileLayer tileLayer = gwc.getTileLayerByName(qualifiedName); assertNotNull(tileLayer); boolean directWMSIntegrationEndpoint = true; String request = MockData.CDF_PREFIX // asking /geoserver/cdf/wms? for cite:BasicPolygons + "/" + buildGetMap(directWMSIntegrationEndpoint, localName, "EPSG:4326", null, tileLayer) + "&tiled=true"; MockHttpServletResponse response = getAsServletResponse(request); assertEquals(200, response.getStatus()); assertTrue(response.getContentType(), response.getContentType().startsWith("application/vnd.ogc.se_xml")); assertTrue(response.getContentAsString(), response.getContentAsString() .contains("Could not find layer cdf:BasicPolygons")); } @Test public void testReloadConfiguration() throws Exception { String path = "/gwc/rest/reload"; String content = "reload_configuration=1"; String contentType = "application/x-www-form-urlencoded"; MockHttpServletResponse response = postAsServletResponse(path, content, contentType); assertEquals(200, response.getStatus()); } @Test public void testBasicIntegration() throws Exception { Catalog cat = getCatalog(); TileLayerDispatcher tld = GeoWebCacheExtensions.bean(TileLayerDispatcher.class); assertNotNull(tld); GridSetBroker gridSetBroker = GeoWebCacheExtensions.bean(GridSetBroker.class); assertNotNull(gridSetBroker); try { tld.getTileLayer(""); } catch (GeoWebCacheException gwce) { } // 1) Check that cite:Lakes is present boolean foundLakes = false; for (TileLayer tl : tld.getLayerList()) { if (tl.getName().equals("cite:Lakes")) { foundLakes = true; break; } } assertTrue(foundLakes); // 2) Check sf:GenerictEntity is present and initialized boolean foudAGF = false; for (TileLayer tl : tld.getLayerList()) { if (tl.getName().equals("sf:AggregateGeoFeature")) { // tl.isInitialized(); foudAGF = true; GridSubset epsg4326 = tl.getGridSubset(gridSetBroker.WORLD_EPSG4326.getName()); assertTrue(epsg4326.getGridSetBounds().equals( new BoundingBox(-180.0, -90.0, 180.0, 90.0))); String mime = tl.getMimeTypes().get(1).getMimeType(); assertTrue(mime.startsWith("image/") || mime.startsWith("application/vnd.google-earth.kml+xml")); } } assertTrue(foudAGF); // 3) Basic get LayerInfo li = cat.getLayerByName(super.getLayerId(MockData.MPOINTS)); String layerName = tileLayerName(li); TileLayer tl = tld.getTileLayer(layerName); assertEquals(layerName, tl.getName()); // 4) Removal of LayerInfo from catalog cat.remove(li); assertNull(cat.getLayerByName(tl.getName())); try { tld.getTileLayer(layerName); fail("Layer should not exist"); } catch (GeoWebCacheException gwce) { assertTrue(true); } } private String buildGetMap(final boolean directWMSIntegrationEndpoint, final String layerName, final String gridsetId, String styles) { final GWC gwc = GWC.get(); final TileLayer tileLayer = gwc.getTileLayerByName(layerName); return buildGetMap(directWMSIntegrationEndpoint, layerName, gridsetId, styles, tileLayer); } private String buildGetMap(final boolean directWMSIntegrationEndpoint, final String queryLayerName, final String gridsetId, String styles, final TileLayer tileLayer) { final GridSubset gridSubset = tileLayer.getGridSubset(gridsetId); long[] coverage = gridSubset.getCoverage(0); long[] tileIndex = { coverage[0], coverage[1], coverage[4] }; BoundingBox bounds = gridSubset.boundsFromIndex(tileIndex); final String endpoint = directWMSIntegrationEndpoint ? "wms" : "gwc/service/wms"; StringBuilder sb = new StringBuilder(endpoint); sb.append("?service=WMS&request=GetMap&version=1.1.1&format=image/png"); sb.append("&layers=").append(queryLayerName); sb.append("&srs=").append(gridSubset.getSRS()); sb.append("&width=").append(gridSubset.getGridSet().getTileWidth()); sb.append("&height=").append(gridSubset.getGridSet().getTileHeight()); sb.append("&styles="); if (styles != null) { sb.append(styles); } sb.append("&bbox=").append(bounds.toString()); return sb.toString(); } /** * See GEOS-5092, check server startup is not hurt by a tile layer out of sync (say someone * manually removed the GeoServer layer) */ @Test public void testMissingGeoServerLayerAtStartUp() throws Exception { Catalog catalog = getCatalog(); GWC mediator = GWC.get(); final String layerName = getLayerId(BASIC_POLYGONS); LayerInfo layerInfo = catalog.getLayerByName(layerName); assertNotNull(layerInfo); TileLayer tileLayer = mediator.getTileLayerByName(layerName); assertNotNull(tileLayer); assertTrue(tileLayer.isEnabled()); getCatalog().remove(layerInfo); getGeoServer().reload(); assertNull(catalog.getLayerByName(layerName)); CatalogConfiguration config = GeoServerExtensions.bean(CatalogConfiguration.class); assertNull(config.getTileLayer(layerName)); try { mediator.getTileLayerByName(layerName); fail("Expected IAE"); } catch (IllegalArgumentException e) { assertTrue(true); } } @Test public void testRemoveLayerAfterReload() throws Exception { Catalog cat = getCatalog(); TileLayerDispatcher tld = GeoWebCacheExtensions.bean(TileLayerDispatcher.class); LayerInfo li = cat.getLayerByName(super.getLayerId(MockData.MPOINTS)); String layerName = tileLayerName(li); assertNotNull(tld.getTileLayer(layerName)); // force reload getGeoServer().reload(); // now remove the layer and check it has been removed from GWC as well cat.remove(li); try { tld.getTileLayer(layerName); fail("Layer should not exist"); } catch (GeoWebCacheException gwce) { // fine } } @Test public void testDiskQuotaStorage() throws Exception { // normal state, quota is not enabled by default GWC gwc = GWC.get(); ConfigurableQuotaStoreProvider provider = GeoServerExtensions.bean(ConfigurableQuotaStoreProvider.class); DiskQuotaConfig quota = gwc.getDiskQuotaConfig(); JDBCConfiguration jdbc = gwc.getJDBCDiskQuotaConfig(); assertFalse("Disk quota is enabled??", quota.isEnabled()); assertNull("jdbc quota config should be missing", jdbc); assertTrue(getActualStore(provider) instanceof DummyQuotaStore); // enable disk quota in H2 mode quota.setEnabled(true); quota.setQuotaStore("H2"); gwc.saveDiskQuotaConfig(quota, null); GeoServerDataDirectory dd = GeoServerExtensions.bean(GeoServerDataDirectory.class); File jdbcConfigFile = dd.findFile("gwc/geowebcache-diskquota-jdbc.xml"); assertNull("jdbc config should not be there", jdbcConfigFile); File h2DefaultStore = dd.findFile("gwc/diskquota_page_store_h2"); assertNotNull("jdbc store should be there", h2DefaultStore); assertTrue(getActualStore(provider) instanceof JDBCQuotaStore); // disable again and clean up quota.setEnabled(false); gwc.saveDiskQuotaConfig(quota, null); FileUtils.deleteDirectory(h2DefaultStore); // now enable it in JDBC mode, with H2 local storage quota.setEnabled(true); quota.setQuotaStore("JDBC"); jdbc = new JDBCConfiguration(); jdbc.setDialect("H2"); ConnectionPoolConfiguration pool = new ConnectionPoolConfiguration(); pool.setDriver("org.h2.Driver"); pool.setUrl("jdbc:h2:./target/quota-h2"); pool.setUsername("sa"); pool.setPassword(""); pool.setMinConnections(1); pool.setMaxConnections(1); pool.setMaxOpenPreparedStatements(50); jdbc.setConnectionPool(pool); gwc.saveDiskQuotaConfig(quota, jdbc); jdbcConfigFile = dd.findFile("gwc/geowebcache-diskquota-jdbc.xml"); assertNotNull("jdbc config should be there", jdbcConfigFile); assertNull("jdbc store should be there", dd.findDataFile("gwc/diskquota_page_store_h2")); File newQuotaStore = new File("./target/quota-h2.data.db"); assertTrue(newQuotaStore.exists()); FileInputStream fis = null; try { fis = new FileInputStream(jdbcConfigFile); Document dom = dom(fis); print(dom); String storedPassword = XMLUnit.newXpathEngine().evaluate("/gwcJdbcConfiguration/connectionPool/password", dom); // check the password has been encoded properly assertTrue(storedPassword.startsWith("crypt1:")); } finally { IOUtils.closeQuietly(fis); } } private QuotaStore getActualStore(ConfigurableQuotaStoreProvider provider) throws ConfigurationException, IOException { return ((ConfigurableQuotaStore) provider.getQuotaStore()).getStore(); } @Test public void testPreserveHeaders() throws Exception { // the code defaults to localhost:8080/geoserver, but the tests work otherwise GeoWebCacheDispatcher dispatcher = GeoServerExtensions.bean(GeoWebCacheDispatcher.class); // dispatcher.setServletPrefix("http://localhost/geoserver/"); MockHttpServletResponse response = getAsServletResponse("gwc/service/wms?service=wms&version=1.1.0&request=GetCapabilities"); System.out.println(response.getContentAsString()); assertEquals("application/vnd.ogc.wms_xml", response.getContentType()); assertEquals("inline;filename=wms-getcapabilities.xml", response.getHeader("content-disposition")); } @Test public void testGutter() throws Exception { GeoServerTileLayer tileLayer = (GeoServerTileLayer) GWC.get().getTileLayerByName(getLayerId(BASIC_POLYGONS)); GeoServerTileLayerInfo info = tileLayer.getInfo(); info.setGutter(100); GWC.get().save(tileLayer); String request = "gwc/service/wms?LAYERS=cite%3ABasicPolygons&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=0,0,11.25,11.25&WIDTH=256&HEIGHT=256"; BufferedImage image = getAsImage(request, "image/png"); // with GEOS-5786 active we would have gotten back a 356px image assertEquals(256, image.getWidth()); assertEquals(256, image.getHeight()); } @Test public void testSaveConfig() throws Exception { GWCConfig config = GWC.get().getConfig(); // set a large gutter config.setGutter(100); // save the config GWC.get().saveConfig(config); // force a reload getGeoServer().reload(); // grab the config, make sure it was saved as expected assertEquals(100, GWC.get().getConfig().getGutter()); } @Test public void testRenameWorkspace() throws Exception { String wsName = MockData.CITE_PREFIX; String wsRenamed = MockData.CITE_PREFIX + "Renamed"; Catalog catalog = getCatalog(); WorkspaceInfo ws = catalog.getWorkspaceByName(wsName); try { // collect all the layer names that are in the CITE workspace List<String> layerNames = new ArrayList<String>(); for (LayerInfo layer : catalog.getLayers()) { if(wsName.equals(layer.getResource().getStore().getWorkspace().getName())) { String prefixedName = layer.prefixedName(); try { // filter out geometryless layers and other stuff that cannot be hanlded by GWC GWC.get().getTileLayerByName(prefixedName); layerNames.add(layer.getName()); } catch(IllegalArgumentException e) { // fine, we are skipping layers that cannot be handled } } } // rename the workspace ws.setName(wsRenamed); catalog.save(ws); // check all the preview layers have been renamed too for (String name : layerNames) { String prefixedName = wsRenamed + ":" + name; GWC.get().getTileLayerByName(prefixedName); } } finally { if(wsRenamed.equals(ws.getName())) { ws.setName(wsName); catalog.save(ws); } } } /** * Test that removing a layer from the catalog also removes its tile cache. */ @Test public void testRemoveCachedLayer() throws Exception { // the prefixed name of the layer under test String layerName = getLayerId(MockData.BASIC_POLYGONS); assertEquals("cite:BasicPolygons", layerName); // resource path to cache directory (FileBlobStore) String cacheDirectory = "gwc/cite_BasicPolygons"; // resource path to cached tile (FileBlobStore) String cachedTile = "gwc/cite_BasicPolygons/EPSG_4326_00/0_0/00_00.png"; GeoServerResourceLoader loader = getResourceLoader(); // cache directory and cached tile should not yet exist assertNull("Unexpected cache directory " + cacheDirectory, loader.find(cacheDirectory)); assertNull("Unexpected cached tile " + cachedTile, loader.find(cachedTile)); // trigger tile caching with a WMTS request MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts" // + "?request=GetTile" // + "&layer=" + layerName // + "&format=image/png" // + "&tilematrixset=EPSG:4326" // + "&tilematrix=EPSG:4326:0" // + "&tilerow=0" // + "&tilecol=0"); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); // cache directory and cached tile should now be present assertNotNull("Missing cache directory " + cacheDirectory, loader.find(cacheDirectory)); assertNotNull("Missing cached tile " + cachedTile, loader.find(cachedTile)); // remove layer from the catalog, which should also remove cache directory and thus cached tile getCatalog().remove(getCatalog().getLayerByName(layerName)); // cache directory and cached tile should now not exist assertNull("Unexpected cache directory " + cacheDirectory, loader.find(cacheDirectory)); assertNull("Unexpected cached tile " + cachedTile, loader.find(cachedTile)); } @Test public void testGetCapabilitiesWithLocalWorkspace() throws Exception { // initiating the xpath engine Map<String, String> namespaces = new HashMap<>(); namespaces.put("xlink", "http://www.w3.org/1999/xlink"); namespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); namespaces.put("ows", "http://www.opengis.net/ows/1.1"); namespaces.put("wmts", "http://www.opengis.net/wmts/1.0"); XMLUnit.setXpathNamespaceContext(new SimpleNamespaceContext(namespaces)); XpathEngine xpath = XMLUnit.newXpathEngine(); // getting capabilities document for CITE workspace Document document = getAsDOM(MockData.CITE_PREFIX + "/gwc/service/wmts?request=GetCapabilities"); // checking get capabilities result for CITE workspace List<LayerInfo> citeLayers = getWorkspaceLayers(MockData.CITE_PREFIX); assertThat(Integer.parseInt(xpath.evaluate("count(//wmts:Contents/wmts:Layer)", document)), greaterThan(0)); assertThat(Integer.parseInt(xpath.evaluate("count(//wmts:Contents/wmts:Layer)", document)), lessThanOrEqualTo(citeLayers.size())); assertThat(xpath.evaluate("count(//wmts:Contents/wmts:Layer[ows:Identifier='" + MockData.BUILDINGS.getLocalPart() + "'])", document), is("1")); } @Test public void testGetCapabilitiesWithLocalLayer() throws Exception { // initiating the xpath engine Map<String, String> namespaces = new HashMap<>(); namespaces.put("xlink", "http://www.w3.org/1999/xlink"); namespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); namespaces.put("ows", "http://www.opengis.net/ows/1.1"); namespaces.put("wmts", "http://www.opengis.net/wmts/1.0"); XMLUnit.setXpathNamespaceContext(new SimpleNamespaceContext(namespaces)); XpathEngine xpath = XMLUnit.newXpathEngine(); // getting capabilities document for CITE workspace Document document = getAsDOM(MockData.CITE_PREFIX + "/" + MockData.BUILDINGS.getLocalPart() + "/gwc/service/wmts?request=GetCapabilities"); // checking get capabilities result for CITE workspace List<LayerInfo> citeLayers = getWorkspaceLayers(MockData.CITE_PREFIX); assertThat(Integer.parseInt(xpath.evaluate("count(//wmts:Contents/wmts:Layer)", document)), equalTo(1)); assertThat(xpath.evaluate("count(//wmts:Contents/wmts:Layer[ows:Identifier='" + MockData.BUILDINGS.getLocalPart() + "'])", document), is("1")); } @Test public void testGetCapabilitiesWithLocalGroup() throws Exception { // initiating the xpath engine Map<String, String> namespaces = new HashMap<>(); namespaces.put("xlink", "http://www.w3.org/1999/xlink"); namespaces.put("xsi", "http://www.w3.org/2001/XMLSchema-instance"); namespaces.put("ows", "http://www.opengis.net/ows/1.1"); namespaces.put("wmts", "http://www.opengis.net/wmts/1.0"); XMLUnit.setXpathNamespaceContext(new SimpleNamespaceContext(namespaces)); XpathEngine xpath = XMLUnit.newXpathEngine(); // getting capabilities document for CITE workspace Document document = getAsDOM(SIMPLE_LAYER_GROUP + "/gwc/service/wmts?request=GetCapabilities"); // checking get capabilities result for CITE workspace assertThat(Integer.parseInt(xpath.evaluate("count(//wmts:Contents/wmts:Layer)", document)), equalTo(1)); assertThat(xpath.evaluate("count(//wmts:Contents/wmts:Layer[ows:Identifier='" + SIMPLE_LAYER_GROUP + "'])", document), is("1")); } @Test public void testGetTileWithLocalWorkspace() throws Exception { // perform a get tile request using a virtual service MockHttpServletResponse response = getAsServletResponse(MockData.CITE_PREFIX + "/gwc/service/wmts?request=GetTile&layer=" + MockData.BASIC_POLYGONS.getLocalPart() + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); // redo the same request response = getAsServletResponse(MockData.CITE_PREFIX + "/gwc/service/wmts?request=GetTile&layer=" + MockData.BASIC_POLYGONS.getLocalPart() + "&format=image/png&tilematrixset=EPSG:4326&tilematrix=EPSG:4326:0&tilerow=0&tilecol=0"); assertEquals(200, response.getStatus()); assertEquals("image/png", response.getContentType()); // check that we got an hit String cacheResult = (String) response.getHeaderValue("geowebcache-cache-result"); assertThat(cacheResult, notNullValue()); assertThat(cacheResult, is("HIT")); } /** * Helper method that will return the layers that belong to a certain workspace. */ private List<LayerInfo> getWorkspaceLayers(String workspaceName) { List<LayerInfo> layers = new ArrayList<>(); for (LayerInfo layer : getCatalog().getLayers()) { WorkspaceInfo workspace = layer.getResource().getStore().getWorkspace(); if(workspace != null && workspace.getName().equals(workspaceName)) { layers.add(layer); } } return layers; } @Test public void testWMTSEnabling() throws Exception { // store original value to restore it boolean initialValue = getGeoServer().getService(WMTSInfo.class).isEnabled(); try { LocalWorkspace.set(null); WMTSInfo wmtsInfo = getGeoServer().getService(WMTSInfo.class); wmtsInfo.setEnabled(false); getGeoServer().save(wmtsInfo); MockHttpServletResponse response = getAsServletResponse("gwc/service/wmts?service=wmts&version=1.0.0&request=GetCapabilities"); assertEquals(400, response.getStatus()); wmtsInfo.setEnabled(true); getGeoServer().save(wmtsInfo); response = getAsServletResponse("gwc/service/wmts?service=wmts&version=1.0.0&request=GetCapabilities"); assertEquals(200, response.getStatus()); } finally { // restoring initial configuration value getGeoServer().getService(WMTSInfo.class).setEnabled(initialValue); LocalWorkspace.set(null); } } @Test public void testGetCapabilitiesRequest() throws Exception { // getting the capabilities document MockHttpServletResponse response = getAsServletResponse("/gwc/service/wmts?request=GetCapabilities"); // check that the request was successful assertThat(response.getStatus(), is(200)); } /** * Helper method that creates a layer group using the provided name and layers names. */ private void createLayerGroup(String layerGroupName, QName... layersNames) throws Exception { // get layers that match the layers names List<LayerInfo> layers = Arrays.stream(layersNames) .map(layerName -> getCatalog().getLayerByName(new NameImpl(layerName))) .collect(Collectors.toList()); // create a new layer group using the provided name LayerGroupInfo layerGroup = getCatalog().getFactory().createLayerGroup(); layerGroup.setName(layerGroupName); // add the provided layers for (LayerInfo layerInfo : layers) { layerGroup.getLayers().add(layerInfo); } // set the layer group bounds by merging all layers bounds CatalogBuilder catalogBuilder = new CatalogBuilder(getCatalog()); catalogBuilder.calculateLayerGroupBounds(layerGroup); getCatalog().add(layerGroup); } }