package org.geowebcache.service.tms; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.custommonkey.xmlunit.XMLUnit; import org.custommonkey.xmlunit.XpathEngine; import org.geowebcache.GeoWebCacheDispatcher; import org.geowebcache.config.XMLGridSubset; import org.geowebcache.conveyor.Conveyor; import org.geowebcache.conveyor.ConveyorTile; import org.geowebcache.filter.parameters.ParameterFilter; import org.geowebcache.grid.GridSet; import org.geowebcache.grid.GridSetBroker; import org.geowebcache.grid.GridSubset; import org.geowebcache.grid.SRS; import org.geowebcache.layer.TileLayer; import org.geowebcache.layer.TileLayerDispatcher; import org.geowebcache.mime.MimeType; import org.geowebcache.stats.RuntimeStats; import org.geowebcache.storage.StorageBroker; import org.geowebcache.util.URLMangler; import org.springframework.mock.web.MockHttpServletResponse; import org.w3c.dom.Document; import junit.framework.TestCase; public class TMSServiceTest extends TestCase { private TMSService service; private StorageBroker sb; private TileLayerDispatcher tld; private TileLayerDispatcher customTld; private GridSetBroker gridsetBroker; private URLMangler httpsUrlMangler; private TMSDocumentFactory customFactory; protected void setUp() throws Exception { sb = mock(StorageBroker.class); tld = mock(TileLayerDispatcher.class); customTld = mock(TileLayerDispatcher.class); gridsetBroker = new GridSetBroker(true, true); httpsUrlMangler = new URLMangler() { final Pattern PATTERN = Pattern.compile("http"); @Override public String buildURL(String baseURL, String contextPath, String path) { String url = StringUtils.strip(baseURL, "/") + "/" + StringUtils.strip(contextPath, "/") + "/" + StringUtils.stripStart(path, "/"); url = url.startsWith("https") ? url : PATTERN.matcher(url).replaceFirst("https"); return url; } }; customFactory = new TMSCustomFactoryTest(customTld, gridsetBroker, httpsUrlMangler, TMSCustomFactoryTest.getCatalogInstance()); } private static TileLayer mockTileLayer(TileLayerDispatcher tld, GridSetBroker gridsetBroker, String layerName, List<String> gridSetNames, List<ParameterFilter> parameterFilters) throws Exception { return mockTileLayer(tld, gridsetBroker, layerName, gridSetNames, parameterFilters, true); } private static class TMSCustomFactoryTest extends TMSDocumentFactory { // Custom layer implementation simulating an external source of information to be used // to populate the tile map metadata document static class CustomLayerImplementation { private String name; private String title; private boolean isAuthorized; private List<String> formats; public CustomLayerImplementation(String name, String title, boolean isAuthorized, List<String> formats) { this.name = name; this.title = title; this.isAuthorized = isAuthorized; this.formats = formats; } } private final static List<CustomLayerImplementation> CATALOG_INSTANCE; public static List<CustomLayerImplementation> getCatalogInstance() { return CATALOG_INSTANCE; } static { CATALOG_INSTANCE = new ArrayList<CustomLayerImplementation>(2); CATALOG_INSTANCE.add(new CustomLayerImplementation("customLayer1","Custom Layer1", false, null)); CATALOG_INSTANCE.add(new CustomLayerImplementation("customLayer2","Custom Layer2", true, Arrays.asList("jpeg-png"))); } private List<CustomLayerImplementation> customCatalogLayers; protected TMSCustomFactoryTest(TileLayerDispatcher tld, GridSetBroker gsb, URLMangler urlMangler, List<CustomLayerImplementation> customCatalogLayers) throws Exception { super(tld, gsb, urlMangler, "tilemapservice", StandardCharsets.UTF_8); List<String> gridSetNames = Arrays.asList("EPSG:4326"); TileLayer tileLayer = mockTileLayer(tld, gsb, "customLayer2", gridSetNames, Collections.<ParameterFilter>emptyList()); when(tld.getLayerList()).thenReturn(Arrays.asList(tileLayer)); this.customCatalogLayers = customCatalogLayers; } @Override protected String getTileMapServiceDoc(String baseUrl, String contextPath) { StringBuilder str = new StringBuilder(); str.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"); str.append("<TileMapService version=\"1.0.0\" services=\"" + urlMangler.buildURL(baseUrl, contextPath, "") + "\">\n"); str.append(" <Title>Custom Tile Map Service</Title>\n"); str.append(" <Abstract>A Custom Tile Map Service served by GeoWebCache</Abstract>\n"); str.append(" <TileMaps>\n"); // Custom tileMapService document being populated on top of the // custom external catalog source. for (CustomLayerImplementation layer: customCatalogLayers) { if (layer.isAuthorized) { for (String format : layer.formats) { tileMapsForLayer(str, layer, format, baseUrl, contextPath); } } } str.append(" </TileMaps>\n"); str.append("</TileMapService>\n"); return str.toString(); } protected void tileMapsForLayer(StringBuilder str, CustomLayerImplementation layer, String format, String baseUrl, String contextPath) { str.append(" <TileMap\n"); str.append(" title=\"").append(layer.title).append("\"\n"); str.append(" srs=\"").append("4326").append("\"\n"); str.append(" profile=\"global-geodetic"); str.append("\"\n"); str.append(" href=\""); String tileMapName = layer.name + "@EPSG:4326" + "@" + format; String url= urlMangler.buildURL(baseUrl, contextPath, TMSDocumentFactory.SERVICE_PATH + "/" + tileMapName); str.append(url).append("\" />\n"); } } private static TileLayer mockTileLayer(TileLayerDispatcher tld, GridSetBroker gridsetBroker, String layerName, List<String> gridSetNames, List<ParameterFilter> parameterFilters, boolean advertised) throws Exception { TileLayer tileLayer = mock(TileLayer.class); when(tld.getTileLayer(eq(layerName))).thenReturn(tileLayer); when(tileLayer.getName()).thenReturn(layerName); when(tileLayer.isEnabled()).thenReturn(true); when(tileLayer.isAdvertised()).thenReturn(advertised); final MimeType mimeType1 = MimeType.createFromFormat("image/png"); final MimeType mimeType2 = MimeType.createFromFormat("image/jpeg"); when(tileLayer.getMimeTypes()).thenReturn(Arrays.asList(mimeType1, mimeType2)); Map<String, GridSubset> subsets = new HashMap<String, GridSubset>(); Map<SRS, List<GridSubset>> bySrs = new HashMap<SRS, List<GridSubset>>(); GridSetBroker broker = gridsetBroker; for (String gsetName : gridSetNames) { GridSet gridSet = broker.get(gsetName); XMLGridSubset xmlGridSubset = new XMLGridSubset(); String gridSetName = gridSet.getName(); xmlGridSubset.setGridSetName(gridSetName); GridSubset gridSubSet = xmlGridSubset.getGridSubSet(broker); subsets.put(gsetName, gridSubSet); List<GridSubset> list = bySrs.get(gridSet.getSrs()); if (list == null) { list = new ArrayList<GridSubset>(); bySrs.put(gridSet.getSrs(), list); } list.add(gridSubSet); when(tileLayer.getGridSubset(eq(gsetName))).thenReturn(gridSubSet); } for (SRS srs : bySrs.keySet()) { List<GridSubset> list = bySrs.get(srs); when(tileLayer.getGridSubsetsForSRS(eq(srs))).thenReturn(list); } when(tileLayer.getGridSubsets()).thenReturn(subsets.keySet()); when(tileLayer.getParameterFilters()).thenReturn(parameterFilters); // sanity check for (String gsetName : gridSetNames) { assertTrue(tileLayer.getGridSubsets().contains(gsetName)); assertNotNull(tileLayer.getGridSubset(gsetName)); } return tileLayer; } public void testTileMapServiceDocument() throws Exception { GeoWebCacheDispatcher gwcd = mock(GeoWebCacheDispatcher.class); when(gwcd.getServletPrefix()).thenReturn(null); service = new TMSService(sb, tld, gridsetBroker , mock(RuntimeStats.class)); HttpServletRequest req = mock(HttpServletRequest.class); MockHttpServletResponse resp = new MockHttpServletResponse(); when(req.getCharacterEncoding()).thenReturn("UTF-8"); when(req.getPathInfo()).thenReturn("/service/tms/1.0.0"); when(req.getRequestURI()).thenReturn("/mycontext/service/tms/1.0.0"); when(req.getScheme()).thenReturn("http"); when(req.getServerName()).thenReturn("localhost"); when(req.getServerPort()).thenReturn(8080); when(req.getContextPath()).thenReturn("/mycontext"); when(req.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/mycontext/service/tms/1.0.0")); List<String> gridSetNames = Arrays.asList("EPSG:4326"); TileLayer tileLayer = mockTileLayer(tld, gridsetBroker, "mockLayer", gridSetNames, Collections.<ParameterFilter>emptyList()); when(tld.getLayerList()).thenReturn(Arrays.asList(tileLayer)); Conveyor conv = service.getConveyor(req, resp); assertNotNull(conv); final String layerName = conv.getLayerId(); assertNull(layerName); assertEquals(Conveyor.RequestHandler.SERVICE,conv.reqHandler); service.handleRequest(conv); String result = resp.getContentAsString(); // Ensure the advertised Layer is contained assertTrue(result.contains("mockLayer")); Document doc = XMLUnit.buildTestDocument(result); XpathEngine xpath = XMLUnit.newXpathEngine(); assertEquals("1", xpath.evaluate("count(//TileMapService[contains(@services,'http://localhost:8080/mycontext/')])", doc)); assertEquals("2", xpath.evaluate("count(//TileMap[@title='mockLayer'])", doc)); assertEquals("2", xpath.evaluate("count(//TileMap[@title='mockLayer'][@srs='EPSG:4326'])", doc)); assertEquals("1", xpath.evaluate("count(//TileMap[@title='mockLayer'][contains(@href,'jpeg')])", doc)); assertEquals("1", xpath.evaluate("count(//TileMap[@title='mockLayer'][contains(@href,'png')])", doc)); assertEquals("0", xpath.evaluate("count(//TileMap[@title='mockLayer'][contains(@href,'jpeg-png')])", doc)); } public void testTMSDocumentsWithCustomFactory() throws Exception { GeoWebCacheDispatcher gwcd = mock(GeoWebCacheDispatcher.class); service = new TMSService(sb, mock(RuntimeStats.class), gwcd, customFactory); @SuppressWarnings("unchecked") HttpServletRequest req = mock(HttpServletRequest.class); MockHttpServletResponse resp = new MockHttpServletResponse(); when(req.getCharacterEncoding()).thenReturn("UTF-8"); when(req.getPathInfo()).thenReturn("/service/tms/1.0.0"); when(req.getRequestURI()).thenReturn("/mycontext/service/tms/1.0.0"); when(req.getScheme()).thenReturn("http"); when(req.getServerName()).thenReturn("localhost"); when(req.getServerPort()).thenReturn(8080); when(req.getContextPath()).thenReturn("/mycontext"); when(req.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/mycontext/service/tms/1.0.0")); Conveyor conv = service.getConveyor(req, resp); assertNotNull(conv); final String layerName = conv.getLayerId(); assertNull(layerName); assertEquals(Conveyor.RequestHandler.SERVICE,conv.reqHandler); service.handleRequest(conv); String result = resp.getContentAsString(); // Ensure the custom authorized Layer is contained and the un-authorized is not assertFalse(result.contains("customLayer1")); assertTrue(result.contains("customLayer2")); Document doc = XMLUnit.buildTestDocument(result); XpathEngine xpath = XMLUnit.newXpathEngine(); //Note the https being added by the custom URLMangler assertEquals("1", xpath.evaluate("count(//TileMapService[contains(@services,'https://localhost:8080/mycontext/')])", doc)); assertEquals("1", xpath.evaluate("count(//TileMap[@title='Custom Layer2'])", doc)); assertEquals("1", xpath.evaluate("count(//TileMap[@title='Custom Layer2'][contains(@href,'jpeg-png')])", doc)); assertEquals("0", xpath.evaluate("count(//TileMap[@title='Custom Layer1'])", doc)); req = mock(HttpServletRequest.class); resp = new MockHttpServletResponse(); when(req.getCharacterEncoding()).thenReturn("UTF-8"); when(req.getScheme()).thenReturn("http"); when(req.getServerName()).thenReturn("localhost"); when(req.getServerPort()).thenReturn(8080); when(req.getContextPath()).thenReturn("/mycontext"); when(req.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/mycontext/service/tms/1.0.0/customLayer2@EPSG:4326@jpeg-png")); when(req.getPathInfo()).thenReturn("/service/tms/1.0.0/customLayer2@EPSG:4326@jpeg-png"); when(req.getRequestURI()).thenReturn("/mycontext/service/tms/1.0.0/customLayer2@EPSG:4326@jpeg-png"); conv = service.getConveyor(req, resp); service.handleRequest(conv); result = resp.getContentAsString().replace("\n\n", "\n"); doc = XMLUnit.buildTestDocument(result); xpath = XMLUnit.newXpathEngine(); assertEquals("22", xpath.evaluate("count(//TileSet[contains(@href,'customLayer2')])", doc)); } public void testGetTile() throws Exception { GeoWebCacheDispatcher gwcd = mock(GeoWebCacheDispatcher.class); when(gwcd.getServletPrefix()).thenReturn(null); service = new TMSService(sb, mock(RuntimeStats.class), gwcd, customFactory); HttpServletRequest req = mock(HttpServletRequest.class); MockHttpServletResponse resp = new MockHttpServletResponse(); { List<String> gridSetNames = Arrays.asList("EPSG:4326"); TileLayer tileLayer = mockTileLayer(customFactory.tld, customFactory.gsb, "customLayer2", gridSetNames, null); when(customFactory.tld.getLayerList()).thenReturn(Arrays.asList(tileLayer)); } // Sending a Tile request when(req.getRequestURL()).thenReturn(new StringBuffer("http://localhost:8080/mycontext/service/tms/1.0.0/customLayer2@EPSG%3A4326@jpeg-png/2/1/1.jpeg-png")); when(req.getPathInfo()).thenReturn("/service/tms/1.0.0/customLayer2@EPSG%3A4326@png/2/1/1.jpeg-png"); when(req.getRequestURI()).thenReturn("/mycontext/service/tms/1.0.0/customLayer2@EPSG%3A4326@png/2/1/1.jpeg-png"); when(req.getCharacterEncoding()).thenReturn("UTF-8"); when(req.getScheme()).thenReturn("http"); when(req.getServerName()).thenReturn("localhost"); when(req.getServerPort()).thenReturn(8080); when(req.getContextPath()).thenReturn("/mycontext"); Conveyor conv = service.getConveyor(req, resp); assertNotNull(conv); assertThat(conv, instanceOf(ConveyorTile.class)); } }