X-Git-Url: http://git.argeo.org/?a=blobdiff_plain;f=org.argeo.app.geo%2Fsrc%2Forg%2Fargeo%2Fapp%2Fgeo%2Fhttp%2FWfsHttpHandler.java;h=641c14bbed9b4efc40ec6a57def5bf64257754c7;hb=9ef8eb23c32d4a09c688c2e493a6b891cb505f95;hp=5897d567095865c4ac87019b9c7a775ec4f8a1c7;hpb=efda92d76cd7ccce3925763bf95f68d7927ac5c7;p=gpl%2Fargeo-suite.git diff --git a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java index 5897d56..641c14b 100644 --- a/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java +++ b/org.argeo.app.geo/src/org/argeo/app/geo/http/WfsHttpHandler.java @@ -13,6 +13,8 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import javax.xml.namespace.QName; @@ -27,15 +29,28 @@ import org.argeo.app.api.EntityName; import org.argeo.app.api.EntityType; import org.argeo.app.api.WGS84PosName; import org.argeo.app.api.geo.FeatureAdapter; +import org.argeo.app.api.geo.WfsKvp; import org.argeo.app.geo.CqlUtils; import org.argeo.app.geo.GeoJson; import org.argeo.app.geo.GeoUtils; import org.argeo.app.geo.GpxUtils; import org.argeo.app.geo.JTS; +import org.argeo.app.geo.acr.GeoEntityUtils; import org.argeo.cms.acr.json.AcrJsonUtils; +import org.argeo.cms.auth.RemoteAuthUtils; import org.argeo.cms.http.HttpHeader; +import org.argeo.cms.http.RemoteAuthHttpExchange; import org.argeo.cms.http.server.HttpServerUtils; import org.argeo.cms.util.LangUtils; +import org.geotools.api.feature.GeometryAttribute; +import org.geotools.api.feature.simple.SimpleFeature; +import org.geotools.api.feature.simple.SimpleFeatureType; +import org.geotools.api.feature.type.AttributeDescriptor; +import org.geotools.api.feature.type.Name; +import org.geotools.api.referencing.FactoryException; +import org.geotools.api.referencing.crs.CoordinateReferenceSystem; +import org.geotools.api.referencing.operation.MathTransform; +import org.geotools.api.referencing.operation.TransformException; import org.geotools.feature.DefaultFeatureCollection; import org.geotools.feature.NameImpl; import org.geotools.feature.simple.SimpleFeatureBuilder; @@ -46,17 +61,7 @@ import org.geotools.wfs.GML.Version; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Envelope; import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.Point; import org.locationtech.jts.geom.Polygon; -import org.opengis.feature.GeometryAttribute; -import org.opengis.feature.simple.SimpleFeature; -import org.opengis.feature.simple.SimpleFeatureType; -import org.opengis.feature.type.AttributeDescriptor; -import org.opengis.feature.type.Name; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.referencing.operation.MathTransform; -import org.opengis.referencing.operation.TransformException; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; @@ -69,30 +74,54 @@ public class WfsHttpHandler implements HttpHandler { private final static CmsLog log = CmsLog.getLog(WfsHttpHandler.class); private ProvidedRepository contentRepository; - // HTTP parameters - final static String OUTPUT_FORMAT = "outputFormat"; - final static String TYPE_NAMES = "typeNames"; - final static String CQL_FILTER = "cql_filter"; - final static String BBOX = "bbox"; - private final Map featureAdapters = new HashMap<>(); @Override public void handle(HttpExchange exchange) throws IOException { - String path = HttpServerUtils.subPath(exchange); ContentSession session = HttpServerUtils.getContentSession(contentRepository, exchange); - // Content content = session.get(path); - // PARAMETERS + String path = HttpServerUtils.subPath(exchange); + + // content path + final String pathToUse = path; + String fileName = null; + boolean zipped = false; +// int lastSlash = path.lastIndexOf('/'); +// if (lastSlash > 0) { +// fileName = path.substring(lastSlash + 1); +// } +// if (fileName != null) { +// pathToUse = path.substring(0, lastSlash); +// if (path.endsWith(".zip")) { +// zipped = true; +// } +// } else { +// pathToUse = path; +// } + Map> parameters = HttpServerUtils.parseParameters(exchange); - String cql = getKvpParameter(parameters, CQL_FILTER); - String typeNamesStr = getKvpParameter(parameters, TYPE_NAMES); - String outputFormat = getKvpParameter(parameters, OUTPUT_FORMAT); + + // PARAMETERS + String cql = getKvpParameter(parameters, WfsKvp.CQL_FILTER); + String typeNamesStr = getKvpParameter(parameters, WfsKvp.TYPE_NAMES); + String outputFormat = getKvpParameter(parameters, WfsKvp.OUTPUT_FORMAT); if (outputFormat == null) { outputFormat = "application/json"; } - String bboxStr = getKvpParameter(parameters, BBOX); - log.debug(bboxStr); + + // TODO deal with multiple + String formatOption = getKvpParameter(parameters, WfsKvp.FORMAT_OPTIONS); + if (formatOption != null) { + if (formatOption.startsWith(WfsKvp.FILENAME_)) + fileName = formatOption.substring(WfsKvp.FILENAME_.length()); + } + if (fileName != null && fileName.endsWith(".zip")) + zipped = true; + + // bbox + String bboxStr = getKvpParameter(parameters, WfsKvp.BBOX); + if (log.isTraceEnabled()) + log.trace(bboxStr); final Envelope bbox; if (bboxStr != null) { String srs; @@ -127,16 +156,31 @@ public class WfsHttpHandler implements HttpHandler { bbox = null; } - switch (outputFormat) { - case "application/json" -> { - exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/json"); + // response headers + exchange.getResponseHeaders().set(HttpHeader.DATE.getHeaderName(), Long.toString(System.currentTimeMillis())); + + if (fileName != null) { + exchange.getResponseHeaders().set(HttpHeader.CONTENT_DISPOSITION.getHeaderName(), + HttpHeader.ATTACHMENT + ";" + HttpHeader.FILENAME + "=\"" + fileName + "\""); + } - case "GML3" -> { + + // content type + if (zipped) { + exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/zip"); + + } else { + switch (outputFormat) { + case "application/json" -> { + exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/json"); + } + case "GML3" -> { // exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/gml+xml"); - exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/xml"); - } + exchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), "application/xml"); + } - default -> throw new IllegalArgumentException("Unexpected value: " + outputFormat); + default -> throw new IllegalArgumentException("Unexpected value: " + outputFormat); + } } List typeNames = new ArrayList<>(); @@ -155,25 +199,33 @@ public class WfsHttpHandler implements HttpHandler { // QUERY Stream res = session.search((search) -> { if (cql != null) { - CqlUtils.filter(search.from(path), cql); + CqlUtils.filter(search.from(pathToUse), cql); } else { - search.from(path); + search.from(pathToUse); } for (QName typeName : typeNames) { FeatureAdapter featureAdapter = featureAdapters.get(typeName); if (featureAdapter == null) throw new IllegalStateException("No feature adapter found for " + typeName); // f.isContentClass(typeName); - featureAdapter.addConstraintsForFeature((AndFilter) search.getWhere(), typeName); + RemoteAuthUtils.doAs(() -> { + featureAdapter.addConstraintsForFeature((AndFilter) search.getWhere(), typeName); + return null; + }, new RemoteAuthHttpExchange(exchange)); } if (bbox != null) { search.getWhere().any((or) -> { + // box overlap, see + // https://stackoverflow.com/questions/20925818/algorithm-to-check-if-two-boxes-overlap + // isOverlapping = (x1min < x2max AND x2min < x1max AND y1min < y2max AND y2min + // < y1max) + // x1 = entity, x2 = bbox or.all((and) -> { - and.gte(EntityName.minLat, bbox.getMinX()); - and.gte(EntityName.minLon, bbox.getMinY()); - and.lte(EntityName.maxLat, bbox.getMaxX()); - and.lte(EntityName.maxLon, bbox.getMaxY()); + and.lte(EntityName.minLat, bbox.getMaxX()); + and.gte(EntityName.maxLat, bbox.getMinX()); + and.lte(EntityName.minLon, bbox.getMaxY()); + and.gte(EntityName.maxLon, bbox.getMinY()); }); or.all((and) -> { and.gte(WGS84PosName.lat, bbox.getMinX()); @@ -188,7 +240,13 @@ public class WfsHttpHandler implements HttpHandler { exchange.sendResponseHeaders(200, 0); final int BUFFER_SIZE = 100 * 1024; - try (BufferedOutputStream out = new BufferedOutputStream(exchange.getResponseBody(), BUFFER_SIZE)) { + try (OutputStream out = zipped ? new ZipOutputStream(exchange.getResponseBody()) + : new BufferedOutputStream(exchange.getResponseBody(), BUFFER_SIZE)) { + if (out instanceof ZipOutputStream zipOut) { + String unzippedFileName = fileName.substring(0, fileName.length() - ".zip".length()); + zipOut.putNextEntry(new ZipEntry(unzippedFileName)); + } + if ("GML3".equals(outputFormat)) { encodeCollectionAsGML(res, out); } else if ("application/json".equals(outputFormat)) { @@ -203,14 +261,14 @@ public class WfsHttpHandler implements HttpHandler { * * @see https://docs.ogc.org/is/09-025r2/09-025r2.html#19 */ - protected String getKvpParameter(Map> parameters, String key) { + protected String getKvpParameter(Map> parameters, WfsKvp key) { Objects.requireNonNull(key, "KVP key cannot be null"); // let's first try the default (CAML case) which should be more efficient - List values = parameters.get(key); + List values = parameters.get(key.getKey()); if (values == null) { // then let's do an ignore case comparison of the key keys: for (String k : parameters.keySet()) { - if (key.equalsIgnoreCase(k)) { + if (key.getKey().equalsIgnoreCase(k)) { values = parameters.get(k); break keys; } @@ -309,17 +367,13 @@ public class WfsHttpHandler implements HttpHandler { generator.writeEnd();// features array generator.writeEnd().close(); - log.debug("GeoJSon encoding took " + (System.currentTimeMillis() - begin) + " ms."); + if (log.isTraceEnabled()) + log.trace("GeoJSon encoding took " + (System.currentTimeMillis() - begin) + " ms."); } protected Geometry getDefaultGeometry(Content content) { if (content.hasContentClass(EntityType.geopoint)) { - double latitude = content.get(WGS84PosName.lat, Double.class).get(); - double longitude = content.get(WGS84PosName.lon, Double.class).get(); - - Coordinate coordinate = new Coordinate(longitude, latitude); - Point the_geom = JTS.GEOMETRY_FACTORY.createPoint(coordinate); - return the_geom; + return GeoEntityUtils.toPoint(content); } return null; } @@ -426,7 +480,7 @@ public class WfsHttpHandler implements HttpHandler { */ public void addFeatureAdapter(FeatureAdapter featureAdapter, Map properties) { - List typeNames = LangUtils.toStringList(properties.get(TYPE_NAMES)); + List typeNames = LangUtils.toStringList(properties.get(WfsKvp.TYPE_NAMES.getKey())); if (typeNames.isEmpty()) { log.warn("FeatureAdapter " + featureAdapter.getClass() + " does not declare type names. Ignoring it..."); return; @@ -439,7 +493,7 @@ public class WfsHttpHandler implements HttpHandler { } public void removeFeatureAdapter(FeatureAdapter featureAdapter, Map properties) { - List typeNames = LangUtils.toStringList(properties.get(TYPE_NAMES)); + List typeNames = LangUtils.toStringList(properties.get(WfsKvp.TYPE_NAMES.getKey())); if (!typeNames.isEmpty()) { // ignore if noe type name declared return;