/* Copyright (c) 2001 - 2009 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.catalog.rest; import static org.custommonkey.xmlunit.XMLAssert.*; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import net.sf.json.JSON; import net.sf.json.JSONObject; import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.Keyword; import org.geoserver.catalog.LayerInfo; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.opengis.feature.type.FeatureType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import com.mockrunner.mock.web.MockHttpServletResponse; import com.vividsolutions.jts.geom.MultiPolygon; public class FeatureTypeTest extends CatalogRESTTestSupport { public void testGetAllByWorkspace() throws Exception { Document dom = getAsDOM( "/rest/workspaces/sf/featuretypes.xml"); assertEquals( catalog.getFeatureTypesByNamespace( catalog.getNamespaceByPrefix( "sf") ).size(), dom.getElementsByTagName( "featureType").getLength() ); } void addPropertyDataStore(boolean configureFeatureType) throws Exception { ByteArrayOutputStream zbytes = new ByteArrayOutputStream(); ZipOutputStream zout = new ZipOutputStream( zbytes ); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( bytes ) ); writer.write( "_=name:String,pointProperty:Point\n" ); writer.write( "pdsa.0='zero'|POINT(0 0)\n"); writer.write( "pdsa.1='one'|POINT(1 1)\n"); writer.flush(); zout.putNextEntry( new ZipEntry( "pdsa.properties") ); zout.write( bytes.toByteArray() ); bytes.reset(); writer.write( "_=name:String,pointProperty:Point\n" ); writer.write( "pdsb.0='two'|POINT(2 2)\n"); writer.write( "pdsb.1='trhee'|POINT(3 3)\n"); writer.flush(); zout.putNextEntry( new ZipEntry( "pdsb.properties" ) ); zout.write( bytes.toByteArray() ); zout.flush(); zout.close(); String q = "configure=" + (configureFeatureType ? "all" : "none"); put( "/rest/workspaces/gs/datastores/pds/file.properties?" + q, zbytes.toByteArray(), "application/zip"); } void addGeomlessPropertyDataStore(boolean configureFeatureType) throws Exception { ByteArrayOutputStream zbytes = new ByteArrayOutputStream(); ZipOutputStream zout = new ZipOutputStream( zbytes ); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( bytes ) ); writer.write( "_=name:String,intProperty:Integer\n" ); writer.write( "ngpdsa.0='zero'|0\n"); writer.write( "ngpdsa.1='one'|1\n"); writer.flush(); zout.putNextEntry( new ZipEntry( "ngpdsa.properties") ); zout.write( bytes.toByteArray() ); bytes.reset(); writer.write( "_=name:String,intProperty:Integer\n" ); writer.write( "ngpdsb.0='two'|2\n"); writer.write( "ngpdsb.1='trhee'|3\n"); writer.flush(); zout.putNextEntry( new ZipEntry( "ngpdsb.properties" ) ); zout.write( bytes.toByteArray() ); zout.flush(); zout.close(); String q = "configure=" + (configureFeatureType ? "all" : "none"); put( "/rest/workspaces/gs/datastores/ngpds/file.properties?" + q, zbytes.toByteArray(), "application/zip"); } public void testGetAllByDataStore() throws Exception { addPropertyDataStore(true); Document dom = getAsDOM( "/rest/workspaces/gs/datastores/pds/featuretypes.xml"); assertEquals( 2, dom.getElementsByTagName( "featureType").getLength() ); assertXpathEvaluatesTo( "1", "count(//featureType/name[text()='pdsa'])", dom ); assertXpathEvaluatesTo( "1", "count(//featureType/name[text()='pdsb'])", dom ); } public void testGetAllAvailable() throws Exception { addPropertyDataStore(false); Document dom = getAsDOM( "/rest/workspaces/gs/datastores/pds/featuretypes.xml?list=available"); assertXpathEvaluatesTo("1", "count(//featureTypeName[text()='pdsa'])", dom); assertXpathEvaluatesTo("1", "count(//featureTypeName[text()='pdsb'])", dom); } public void testGetAllAvailableWithGeometryOnly() throws Exception { addGeomlessPropertyDataStore(false); Document dom = getAsDOM( "/rest/workspaces/gs/datastores/ngpds/featuretypes.xml?list=available"); assertXpathEvaluatesTo("2", "count(//featureTypeName)", dom); dom = getAsDOM( "/rest/workspaces/gs/datastores/ngpds/featuretypes.xml?list=available_with_geom"); assertXpathEvaluatesTo("0", "count(//featureTypeName)", dom); } public void testPutAllUnauthorized() throws Exception { assertEquals( 405, putAsServletResponse("/rest/workspaces/sf/datastores/sf/featuretypes").getStatusCode() ); } public void testDeleteAllUnauthorized() throws Exception { assertEquals( 405, deleteAsServletResponse("/rest/workspaces/sf/datastores/sf/featuretypes").getStatusCode() ); } public void testPostAsXML() throws Exception { Document dom = getAsDOM( "wfs?request=getfeature&typename=sf:pdsa"); assertEquals( "ows:ExceptionReport", dom.getDocumentElement().getNodeName()); addPropertyDataStore(false); String xml = "<featureType>"+ "<name>pdsa</name>"+ "<nativeName>pdsa</nativeName>"+ "<srs>EPSG:4326</srs>" + "<nativeCRS>EPSG:4326</nativeCRS>" + "<nativeBoundingBox>"+ "<minx>0.0</minx>"+ "<maxx>1.0</maxx>"+ "<miny>0.0</miny>"+ "<maxy>1.0</maxy>"+ "<crs>EPSG:4326</crs>" + "</nativeBoundingBox>"+ "<store>pds</store>" + "</featureType>"; MockHttpServletResponse response = postAsServletResponse( "/rest/workspaces/gs/datastores/pds/featuretypes/", xml, "text/xml"); assertEquals( 201, response.getStatusCode() ); assertNotNull( response.getHeader( "Location") ); assertTrue( response.getHeader("Location").endsWith( "/workspaces/gs/datastores/pds/featuretypes/pdsa" ) ); dom = getAsDOM( "wfs?request=getfeature&typename=gs:pdsa"); assertEquals( "wfs:FeatureCollection", dom.getDocumentElement().getNodeName()); assertEquals( 2, dom.getElementsByTagName( "gs:pdsa").getLength()); } public void testPostAsJSON() throws Exception { Document dom = getAsDOM( "wfs?request=getfeature&typename=sf:pdsa"); assertEquals( "ows:ExceptionReport", dom.getDocumentElement().getNodeName()); addPropertyDataStore(false); String json = "{" + "'featureType':{" + "'name':'pdsa'," + "'nativeName':'pdsa'," + "'srs':'EPSG:4326'," + "'nativeBoundingBox':{" + "'minx':0.0," + "'maxx':1.0," + "'miny':0.0," + "'maxy':1.0," + "'crs':'EPSG:4326'" + "}," + "'nativeCRS':'EPSG:4326'," + "'store':'pds'" + "}" + "}"; MockHttpServletResponse response = postAsServletResponse( "/rest/workspaces/gs/datastores/pds/featuretypes/", json, "text/json"); assertEquals( 201, response.getStatusCode() ); assertNotNull( response.getHeader( "Location") ); assertTrue( response.getHeader("Location").endsWith( "/workspaces/gs/datastores/pds/featuretypes/pdsa" ) ); dom = getAsDOM( "wfs?request=getfeature&typename=gs:pdsa"); assertEquals( "wfs:FeatureCollection", dom.getDocumentElement().getNodeName()); assertEquals( 2, dom.getElementsByTagName( "gs:pdsa").getLength()); } public void testPostToResource() throws Exception { addPropertyDataStore(true); String xml = "<featureType>"+ "<name>pdsa</name>"+ "</featureType>"; MockHttpServletResponse response = postAsServletResponse( "/rest/workspaces/gs/datastores/pds/featuretypes/pdsa", xml, "text/xml"); assertEquals( 405, response.getStatusCode() ); } public void testGetAsXML() throws Exception { Document dom = getAsDOM( "/rest/workspaces/sf/featuretypes/PrimitiveGeoFeature.xml"); assertEquals( "featureType", dom.getDocumentElement().getNodeName() ); assertXpathEvaluatesTo("PrimitiveGeoFeature", "/featureType/name", dom); assertXpathEvaluatesTo( "EPSG:4326", "/featureType/srs", dom); assertEquals( CRS.decode( "EPSG:4326" ).toWKT(), xp.evaluate( "/featureType/nativeCRS", dom ) ); FeatureTypeInfo ft = catalog.getFeatureTypeByName( "sf", "PrimitiveGeoFeature" ); /* ReferencedEnvelope re = ft.getNativeBoundingBox(); assertXpathEvaluatesTo( re.getMinX()+"" , "/featureType/nativeBoundingBox/minx", dom ); assertXpathEvaluatesTo( re.getMaxX()+"" , "/featureType/nativeBoundingBox/maxx", dom ); assertXpathEvaluatesTo( re.getMinY()+"" , "/featureType/nativeBoundingBox/miny", dom ); assertXpathEvaluatesTo( re.getMaxY()+"" , "/featureType/nativeBoundingBox/maxy", dom ); */ ReferencedEnvelope re = ft.getLatLonBoundingBox(); assertXpathEvaluatesTo( re.getMinX()+"" , "/featureType/latLonBoundingBox/minx", dom ); assertXpathEvaluatesTo( re.getMaxX()+"" , "/featureType/latLonBoundingBox/maxx", dom ); assertXpathEvaluatesTo( re.getMinY()+"" , "/featureType/latLonBoundingBox/miny", dom ); assertXpathEvaluatesTo( re.getMaxY()+"" , "/featureType/latLonBoundingBox/maxy", dom ); } public void testGetAsJSON() throws Exception { JSON json = getAsJSON( "/rest/workspaces/sf/featuretypes/PrimitiveGeoFeature.json"); JSONObject featureType = ((JSONObject)json).getJSONObject("featureType"); assertNotNull(featureType); assertEquals( "PrimitiveGeoFeature", featureType.get("name") ); assertEquals( CRS.decode("EPSG:4326").toWKT(), featureType.get( "nativeCRS") ); assertEquals( "EPSG:4326", featureType.get( "srs") ); } public void testGetAsHTML() throws Exception { Document dom = getAsDOM( "/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature.html"); } public void testPut() throws Exception { String xml = "<featureType>" + "<title>new title</title>" + "</featureType>"; MockHttpServletResponse response = putAsServletResponse("/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature", xml, "text/xml"); assertEquals( 200, response.getStatusCode() ); Document dom = getAsDOM("/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature.xml"); assertXpathEvaluatesTo("new title", "/featureType/title", dom ); FeatureTypeInfo ft = catalog.getFeatureTypeByName( "sf", "PrimitiveGeoFeature"); assertEquals( "new title", ft.getTitle() ); } public void testPutNonExistant() throws Exception { String xml = "<featureType>" + "<title>new title</title>" + "</featureType>"; MockHttpServletResponse response = putAsServletResponse("/rest/workspaces/sf/datastores/sf/featuretypes/NonExistant", xml, "text/xml"); assertEquals( 404, response.getStatusCode() ); } public void testDelete() throws Exception { assertNotNull( catalog.getFeatureTypeByName("sf", "PrimitiveGeoFeature")); for (LayerInfo l : catalog.getLayers( catalog.getFeatureTypeByName("sf", "PrimitiveGeoFeature") ) ) { catalog.remove(l); } assertEquals( 200, deleteAsServletResponse( "/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature").getStatusCode()); assertNull( catalog.getFeatureTypeByName("sf", "PrimitiveGeoFeature")); } public void testDeleteNonExistant() throws Exception { assertEquals( 404, deleteAsServletResponse( "/rest/workspaces/sf/datastores/sf/featuretypes/NonExistant").getStatusCode()); } public void testDeleteRecursive() throws Exception { assertNotNull(catalog.getFeatureTypeByName("sf", "PrimitiveGeoFeature")); assertNotNull(catalog.getLayerByName("sf:PrimitiveGeoFeature")); assertEquals(403, deleteAsServletResponse( "/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature").getStatusCode()); assertEquals( 200, deleteAsServletResponse( "/rest/workspaces/sf/datastores/sf/featuretypes/PrimitiveGeoFeature?recurse=true").getStatusCode()); assertNull(catalog.getFeatureTypeByName("sf", "PrimitiveGeoFeature")); assertNull(catalog.getLayerByName("sf:PrimitiveGeoFeature")); } public void testPostGeometrylessFeatureType() throws Exception { addGeomlessPropertyDataStore(false); String xml = "<featureType>" + "<name>ngpdsa</name>" + "</featureType>"; MockHttpServletResponse response = postAsServletResponse("/rest/workspaces/gs/datastores/ngpds/featuretypes", xml, "text/xml"); assertEquals( 201, response.getStatusCode() ); assertNotNull( response.getHeader( "Location") ); assertTrue( response.getHeader("Location").endsWith( "/workspaces/gs/datastores/ngpds/featuretypes/ngpdsa" ) ); } public void testCreateFeatureType() throws Exception { String xml = "<featureType>\n" + " <name>states</name>\n" + " <nativeName>states</nativeName>\n" + " <namespace>\n" + " <name>cite</name>\n" + " </namespace>\n" + " <title>USA Population</title>\n" + " <srs>EPSG:4326</srs>\n" + " <attributes>\n" + " <attribute>\n" + " <name>the_geom</name>\n" + " <binding>com.vividsolutions.jts.geom.MultiPolygon</binding>\n" + " </attribute>\n" + " <attribute>\n" + " <name>STATE_NAME</name>\n" + " <binding>java.lang.String</binding>\n" + " <length>25</length>\n" + " </attribute>\n" + " <attribute>\n" + " <name>LAND_KM</name>\n" + " <binding>java.lang.Double</binding>\n" + " </attribute>\n" + " </attributes>\n" + "</featureType>"; MockHttpServletResponse response = postAsServletResponse("/rest/workspaces/cite/datastores/default/featuretypes", xml, "text/xml"); assertEquals( 201, response.getStatusCode() ); assertNotNull( response.getHeader( "Location") ); assertTrue( response.getHeader("Location").endsWith( "/workspaces/cite/datastores/default/featuretypes/states" ) ); FeatureTypeInfo ft = catalog.getFeatureTypeByName("cite", "states"); assertNotNull(ft); FeatureType schema = ft.getFeatureType(); assertEquals("states", schema.getName().getLocalPart()); assertEquals(catalog.getNamespaceByPrefix("cite").getURI(), schema.getName().getNamespaceURI()); assertEquals(3, schema.getDescriptors().size()); assertNotNull(schema.getDescriptor("the_geom")); assertEquals(MultiPolygon.class, schema.getDescriptor("the_geom").getType().getBinding()); assertNotNull(schema.getDescriptor("LAND_KM")); assertEquals(Double.class, schema.getDescriptor("LAND_KM").getType().getBinding()); } public void testPostFillInMetadata() throws Exception { addPropertyDataStore(false); String xml = "<featureType>"+ "<name>pdsa</name>"+ "</featureType>"; MockHttpServletResponse response = postAsServletResponse( "/rest/workspaces/gs/datastores/pds/featuretypes", xml, "text/xml"); assertEquals( 201, response.getStatusCode() ); FeatureTypeInfo ft = catalog.getFeatureTypeByName("gs", "pdsa"); assertNotNull(ft); assertTrue(ft.getKeywords().contains(new Keyword("features"))); } }