package org.geoserver.wfs.response;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.namespace.QName;
import net.opengis.wfs.FeatureCollectionType;
import net.opengis.wfs.GetFeatureType;
import net.opengis.wfs.WfsFactory;
import org.geoserver.data.test.MockData;
import org.geoserver.platform.Operation;
import org.geoserver.wfs.WFSTestSupport;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.FeatureCollection;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.FeatureType;
import org.opengis.filter.Filter;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.MultiPolygon;
public class ShapeZipTest extends WFSTestSupport {
private static final QName ALL_TYPES = new QName(MockData.CITE_URI, "AllTypes", MockData.CITE_PREFIX);
private static final QName ALL_DOTS = new QName(MockData.CITE_URI, "All.Types.Dots", MockData.CITE_PREFIX);
private static final QName GEOMMID = new QName(MockData.CITE_URI, "geommid", MockData.CITE_PREFIX);
private static final QName LONGNAMES = new QName(MockData.CITE_URI, "longnames", MockData.CITE_PREFIX);
private static final QName NULLGEOM = new QName(MockData.CITE_URI, "nullgeom", MockData.CITE_PREFIX);
private static final QName DOTS = new QName(MockData.CITE_URI, "dots.in.name", MockData.CITE_PREFIX);
private Operation op;
private GetFeatureType gft;
@Override
protected void populateDataDirectory(MockData dataDirectory) throws Exception {
super.populateDataDirectory(dataDirectory);
Map params = new HashMap();
params.put(MockData.KEY_SRS_NUMBER, "4326");
dataDirectory.addPropertiesType(ALL_TYPES, ShapeZipTest.class.getResource("AllTypes.properties"), params);
dataDirectory.addPropertiesType(ALL_DOTS, ShapeZipTest.class.getResource("All.Types.Dots.properties"), params);
dataDirectory.addPropertiesType(GEOMMID, ShapeZipTest.class.getResource("geommid.properties"), params);
dataDirectory.addPropertiesType(NULLGEOM, ShapeZipTest.class.getResource("nullgeom.properties"), params);
dataDirectory.addPropertiesType(DOTS, ShapeZipTest.class.getResource("dots.in.name.properties"), params);
dataDirectory.addPropertiesType(LONGNAMES, ShapeZipTest.class.getResource("longnames.properties"), params);
}
@Override
protected void setUpInternal() throws Exception {
super.setUpInternal();
gft = WfsFactory.eINSTANCE.createGetFeatureType();
op = new Operation("GetFeature", getServiceDescriptor10(), null, new Object[] {gft});
}
public void testNoNativeProjection() throws Exception {
byte[] zip = writeOut(getFeatureSource(MockData.BASIC_POLYGONS).getFeatures());
checkShapefileIntegrity(new String[] {"BasicPolygons"}, new ByteArrayInputStream(zip));
}
public void testCharset() throws Exception {
FeatureSource<? extends FeatureType, ? extends Feature> fs;
fs = getFeatureSource(MockData.BASIC_POLYGONS);
ShapeZipOutputFormat zip = new ShapeZipOutputFormat();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
FeatureCollectionType fct = WfsFactory.eINSTANCE.createFeatureCollectionType();
fct.getFeature().add(fs.getFeatures());
// add the charset
Map options = new HashMap();
options.put("CHARSET", Charset.forName("ISO-8859-15"));
gft.setFormatOptions(options);
zip.write(fct, bos, op);
checkShapefileIntegrity(new String[] {"BasicPolygons"}, new ByteArrayInputStream(bos.toByteArray()));
assertEquals("ISO-8859-15", getCharset(new ByteArrayInputStream(bos.toByteArray())));
}
public void testMultiType() throws Exception {
byte[] zip = writeOut(getFeatureSource(ALL_TYPES).getFeatures());
final String[] expectedTypes = new String[] {"AllTypesPoint", "AllTypesMPoint", "AllTypesPolygon", "AllTypesLine"};
checkShapefileIntegrity(expectedTypes, new ByteArrayInputStream(zip));
checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
}
public void testMultiTypeDots() throws Exception {
byte[] zip = writeOut(getFeatureSource(ALL_DOTS).getFeatures());
final String[] expectedTypes = new String[] {"All_Types_DotsPoint", "All_Types_DotsMPoint",
"All_Types_DotsPolygon", "All_Types_DotsLine"};
checkShapefileIntegrity(expectedTypes, new ByteArrayInputStream(zip));
checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
}
public void testGeometryInTheMiddle() throws Exception {
byte[] zip = writeOut(getFeatureSource(GEOMMID).getFeatures());
checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
}
public void testNullGeometries() throws Exception {
byte[] zip = writeOut(getFeatureSource(NULLGEOM).getFeatures());
final String[] expectedTypes = new String[] {"nullgeom"};
checkShapefileIntegrity(expectedTypes, new ByteArrayInputStream(zip));
}
public void testLongNames() throws Exception {
byte[] zip = writeOut(getFeatureSource(LONGNAMES).getFeatures());
// check the result is not empty
SimpleFeatureType schema = checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
// check the schema is the expected one
checkLongNamesSchema(schema);
// run it again, we had a bug in which the remapped names changed at each run
zip = writeOut(getFeatureSource(LONGNAMES).getFeatures());
schema = checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
checkLongNamesSchema(schema);
}
void checkLongNamesSchema(SimpleFeatureType schema) {
assertEquals(4, schema.getAttributeCount());
assertEquals("the_geom", schema.getDescriptor(0).getName().getLocalPart());
assertEquals(MultiPolygon.class, schema.getDescriptor(0).getType().getBinding());
assertEquals("FID", schema.getDescriptor(1).getName().getLocalPart());
assertEquals("VERYLONGNA", schema.getDescriptor(2).getName().getLocalPart());
assertEquals("VERYLONGN0", schema.getDescriptor(3).getName().getLocalPart());
}
public void testDots() throws Exception {
byte[] zip = writeOut(getFeatureSource(DOTS).getFeatures());
final String[] expectedTypes = new String[] {"dots_in_name"};
checkShapefileIntegrity(expectedTypes, new ByteArrayInputStream(zip));
checkFieldsAreNotEmpty(new ByteArrayInputStream(zip));
}
public void testEmptyResult() throws Exception {
byte[] zip = writeOut(getFeatureSource(MockData.BASIC_POLYGONS).getFeatures(Filter.EXCLUDE));
checkShapefileIntegrity(new String[] {"BasicPolygons"}, new ByteArrayInputStream(zip));
}
public void testEmptyResulMultiGeom() throws Exception {
byte[] zip = writeOut(getFeatureSource(ALL_DOTS).getFeatures(Filter.EXCLUDE));
final String[] expectedTypes = new String[] {"All_Types_Dots"};
checkShapefileIntegrity(expectedTypes, new ByteArrayInputStream(zip));
boolean foundReadme = false;
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zip));
ZipEntry entry;
while((entry = zis.getNextEntry()) != null) {
foundReadme |= entry.getName().equals("README.TXT");
}
}
/**
* Saves the feature source contents into a zipped shapefile, returns the
* output as a byte array
*/
byte[] writeOut(FeatureCollection fc) throws IOException {
ShapeZipOutputFormat zip = new ShapeZipOutputFormat();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
FeatureCollectionType fct = WfsFactory.eINSTANCE.createFeatureCollectionType();
fct.getFeature().add(fc);
zip.write(fct, bos, op);
return bos.toByteArray();
}
private File createTempFolder(String prefix) throws IOException {
File temp = File.createTempFile(prefix, null);
temp.delete();
temp.mkdir();
return temp;
}
private void copyStream(InputStream inStream, OutputStream outStream)
throws IOException {
int count = 0;
byte[] buf = new byte[8192];
while ((count = inStream.read(buf, 0, 8192)) != -1)
outStream.write(buf, 0, count);
}
private SimpleFeatureType checkFieldsAreNotEmpty(InputStream in) throws IOException {
ZipInputStream zis = new ZipInputStream(in);
ZipEntry entry = null;
File tempFolder = createTempFolder("shp_");
String shapeFileName = "";
while ((entry = zis.getNextEntry()) != null) {
final String name = entry.getName();
String outName = tempFolder.getAbsolutePath() + File.separatorChar
+ name;
// store .shp file name
if (name.toLowerCase().endsWith("shp"))
shapeFileName = outName;
// copy each file to temp folder
FileOutputStream outFile = new FileOutputStream(outName);
copyStream(zis, outFile);
outFile.close();
zis.closeEntry();
}
zis.close();
// create a datastore reading the uncompressed shapefile
File shapeFile = new File(shapeFileName);
ShapefileDataStore ds = new ShapefileDataStore(shapeFile.toURL());
FeatureSource<SimpleFeatureType, SimpleFeature> fs = ds
.getFeatureSource();
FeatureCollection<SimpleFeatureType, SimpleFeature> fc = fs
.getFeatures();
SimpleFeatureType schema = fc.getSchema();
Iterator<SimpleFeature> iter = fc.iterator();
try {
// check that every field has a not null or "empty" value
while (iter.hasNext()) {
SimpleFeature f = iter.next();
for (Object attrValue : f.getAttributes()) {
assertNotNull(attrValue);
if (Geometry.class.isAssignableFrom(attrValue.getClass()))
assertFalse("Empty geometry", ((Geometry) attrValue)
.isEmpty());
else
assertFalse("Empty value for attribute", attrValue
.toString().trim().equals(""));
}
}
} finally {
fc.close(iter);
tempFolder.delete();
}
return schema;
}
private void checkShapefileIntegrity(String[] typeNames, final InputStream in) throws IOException {
ZipInputStream zis = new ZipInputStream(in);
ZipEntry entry = null;
final String[] extensions = new String[] {".shp", ".shx", ".dbf", ".prj", ".cst"};
Set names = new HashSet();
for (String name : typeNames) {
for (String extension : extensions) {
names.add(name + extension);
}
}
while((entry = zis.getNextEntry()) != null) {
final String name = entry.getName();
assertTrue("Missing " + name, names.contains(name));
names.remove(name);
zis.closeEntry();
}
zis.close();
}
private String getCharset(final InputStream in) throws IOException {
ZipInputStream zis = new ZipInputStream(in);
ZipEntry entry = null;
byte[] bytes = new byte[1024];
while((entry = zis.getNextEntry()) != null) {
if(entry.getName().endsWith(".cst")) {
zis.read(bytes);
}
}
zis.close();
if(bytes == null)
return null;
else
return new String(bytes).trim();
}
}