001: /* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved.
002: * This code is licensed under the GPL 2.0 license, availible at the root
003: * application directory.
004: */
005: package org.geoserver.wfs.response;
006:
007: import net.opengis.wfs.FeatureCollectionType;
008: import net.opengis.wfs.GetFeatureType;
009: import net.opengis.wfs.QueryType;
010: import org.geoserver.ows.util.OwsUtils;
011: import org.geoserver.platform.Operation;
012: import org.geoserver.platform.ServiceException;
013: import org.geoserver.wfs.WFSGetFeatureOutputFormat;
014: import org.geotools.data.FeatureStore;
015: import org.geotools.data.shapefile.ShapefileDataStore;
016: import org.geotools.feature.FeatureCollection;
017: import org.geotools.feature.FeatureType;
018: import java.io.File;
019: import java.io.FileInputStream;
020: import java.io.IOException;
021: import java.io.InputStream;
022: import java.io.OutputStream;
023: import java.util.Iterator;
024: import java.util.logging.Level;
025: import java.util.logging.Logger;
026: import java.util.zip.ZipEntry;
027: import java.util.zip.ZipOutputStream;
028: import javax.xml.namespace.QName;
029:
030: /**
031: *
032: * This class returns a shapefile encoded results of the users's query.
033: *
034: * Based on ShapeFeatureResponseDelegate.java from geoserver 1.5.x
035: *
036: * @author originally authored by Chris Holmes, The Open Planning Project, cholmes@openplans.org
037: * @author ported to gs 1.6.x by Saul Farber, MassGIS, saul.farber@state.ma.us
038: *
039: */
040: public class ShapeZipOutputFormat extends WFSGetFeatureOutputFormat {
041: private final Logger LOGGER = org.geotools.util.logging.Logging
042: .getLogger(this .getClass().toString());
043: private String outputFileName;
044:
045: public ShapeZipOutputFormat() {
046: super ("SHAPE-ZIP");
047: }
048:
049: /**
050: * @see WFSGetFeatureOutputFormat#getMimeType(Object, Operation)
051: */
052: public String getMimeType(Object value, Operation operation)
053: throws ServiceException {
054: return "application/zip";
055: }
056:
057: public String getCapabilitiesElementName() {
058: return "SHAPE-ZIP";
059: }
060:
061: /**
062: * We abuse this method to pre-discover the query typenames so we know what to set in the
063: * content-disposition header.
064: */
065: protected boolean canHandleInternal(Operation operation) {
066: GetFeatureType request = (GetFeatureType) OwsUtils.parameter(
067: operation.getParameters(), GetFeatureType.class);
068: outputFileName = ((QName) ((QueryType) request.getQuery()
069: .get(0)).getTypeName().get(0)).getLocalPart();
070:
071: return true;
072: }
073:
074: public String[][] getHeaders(Object value, Operation operation)
075: throws ServiceException {
076: return (String[][]) new String[][] { { "Content-Disposition",
077: "attachment; filename=" + outputFileName + ".zip" } };
078: }
079:
080: /**
081: * @see WFSGetFeatureOutputFormat#write(Object, OutputStream, Operation)
082: */
083: protected void write(FeatureCollectionType featureCollection,
084: OutputStream output, Operation getFeature)
085: throws IOException, ServiceException {
086: //We might get multiple featurecollections in our response (multiple queries?) so we need to
087: //write out multiple shapefile sets, one for each query response.
088: File tempDir = createTempDirectory();
089: ZipOutputStream zipOut = new ZipOutputStream(output);
090:
091: try {
092: Iterator outputFeatureCollections = featureCollection
093: .getFeature().iterator();
094: FeatureCollection curCollection;
095:
096: while (outputFeatureCollections.hasNext()) {
097: curCollection = (FeatureCollection) outputFeatureCollections
098: .next();
099: writeCollectionToShapefile(curCollection, tempDir);
100:
101: String name = curCollection.getSchema().getTypeName();
102: String outputName = name.replaceAll("\\.", "_");
103:
104: // read in and write out .shp
105: File f = new File(tempDir, name + ".shp");
106: ZipEntry entry = new ZipEntry(outputName + ".shp");
107: zipOut.putNextEntry(entry);
108:
109: InputStream shp_in = new FileInputStream(f);
110: readInWriteOutBytes(zipOut, shp_in);
111: zipOut.closeEntry();
112: shp_in.close();
113:
114: // read in and write out .dbf
115: f = new File(tempDir, name + ".dbf");
116: entry = new ZipEntry(outputName + ".dbf");
117: zipOut.putNextEntry(entry);
118:
119: InputStream dbf_in = new FileInputStream(f);
120: readInWriteOutBytes(zipOut, dbf_in);
121: zipOut.closeEntry();
122: dbf_in.close();
123:
124: // read in and write out .shx
125: f = new File(tempDir, name + ".shx");
126: entry = new ZipEntry(outputName + ".shx");
127: zipOut.putNextEntry(entry);
128:
129: InputStream shx_in = new FileInputStream(f);
130: readInWriteOutBytes(zipOut, shx_in);
131: zipOut.closeEntry();
132: shx_in.close();
133:
134: // if we generated the prj file, include it as well
135: f = new File(tempDir, name + ".prj");
136: if (f.exists()) {
137: entry = new ZipEntry(outputName + ".prj");
138: zipOut.putNextEntry(entry);
139: InputStream prj_in = new FileInputStream(f);
140: readInWriteOutBytes(zipOut, prj_in);
141: zipOut.closeEntry();
142: prj_in.close();
143: }
144: }
145:
146: zipOut.finish();
147: zipOut.flush();
148:
149: // This is an error, because this closes the output stream too... it's
150: // not the right place to do so
151: // zipOut.close();
152: } finally {
153: // make sure we remove the temp directory and its contents completely now
154: if (!removeDirectory(tempDir)) {
155: LOGGER.warning("Could not delete temp directory: "
156: + tempDir.getAbsolutePath());
157: }
158: }
159: }
160:
161: private boolean removeDirectory(File tempDir) {
162: if (!tempDir.exists() || !tempDir.isDirectory()) {
163: return false;
164: }
165:
166: File[] files = tempDir.listFiles();
167:
168: if (files == null) {
169: return false;
170: }
171:
172: for (int i = 0; i < files.length; i++) {
173: if (files[i].isDirectory()) {
174: removeDirectory(files[i]);
175: } else {
176: files[i].delete();
177: }
178: }
179:
180: return tempDir.delete();
181: }
182:
183: /**
184: * readInWriteOutBytes Description: Reads in the bytes from the
185: * input stream and writes them to the output stream.
186: *
187: * @param output
188: * @param in
189: *
190: * @throws IOException
191: */
192: private void readInWriteOutBytes(OutputStream output, InputStream in)
193: throws IOException {
194: int c;
195:
196: while (-1 != (c = in.read())) {
197: output.write(c);
198: }
199: }
200:
201: /**
202: * Write one featurecollection to an appropriately named shapefile.
203: * @param c the featurecollection to write
204: * @param tempDir the temp directory into which it should be written
205: */
206: private void writeCollectionToShapefile(FeatureCollection c,
207: File tempDir) {
208: FeatureType schema = c.getSchema();
209:
210: try {
211: File file = new File(tempDir, schema.getTypeName() + ".shp");
212: ShapefileDataStore sfds = new ShapefileDataStore(file
213: .toURL());
214:
215: try {
216: sfds.createSchema(schema);
217: } catch (NullPointerException e) {
218: LOGGER
219: .warning("Error in shapefile schema. It is possible you don't have a geometry set in the output. \n"
220: + "Please specify a <wfs:PropertyName>geom_column_name</wfs:PropertyName> in the request");
221: throw new ServiceException(
222: "Error in shapefile schema. It is possible you don't have a geometry set in the output.");
223: }
224:
225: FeatureStore store = (FeatureStore) sfds
226: .getFeatureSource(schema.getTypeName());
227: store.addFeatures(c);
228: try {
229: if (schema.getDefaultGeometry().getCoordinateSystem() != null)
230: sfds.forceSchemaCRS(schema.getDefaultGeometry()
231: .getCoordinateSystem());
232: } catch (Exception e) {
233: LOGGER.log(Level.WARNING,
234: "Could not properly create the .prj file", e);
235: }
236: } catch (IOException ioe) {
237: LOGGER.log(Level.WARNING,
238: "Error while writing featuretype '"
239: + schema.getTypeName() + "' to shapefile.",
240: ioe);
241: throw new ServiceException(ioe);
242: }
243: }
244:
245: /**
246: * Creates a temporary directory into which we'll write the various shapefile components for this response.
247: *
248: * Strategy is to leverage the system temp directory, then create a sub-directory.
249: * @return
250: */
251: private File createTempDirectory() {
252: try {
253: File dummyTemp = File.createTempFile("blah", null);
254: String sysTempDir = dummyTemp.getParentFile()
255: .getAbsolutePath();
256: dummyTemp.delete();
257:
258: File reqTempDir = new File(sysTempDir + File.separator
259: + "wfsshptemp" + Math.random());
260: reqTempDir.mkdir();
261:
262: return reqTempDir;
263: } catch (IOException e) {
264: LOGGER
265: .log(
266: Level.WARNING,
267: "Unable to properly create a temporary directory when trying to output a shapefile. Is the system temp directory writeable?",
268: e);
269: throw new ServiceException(
270: "Error in shapefile schema. It is possible you don't have a geometry set in the output.");
271: }
272: }
273: }
|