From 624a64ebb30135a64dcf88af323727d6fde8211f Mon Sep 17 00:00:00 2001 From: Stefano Costa Date: Mon, 12 Oct 2015 16:07:11 +0200 Subject: [PATCH] [GEOS-7226] Default GML links in Layer Preview page do not work for app-schema layers --- .../app-schema/app-schema-oracle-test/pom.xml | 21 +++ .../app-schema-postgis-test/pom.xml | 21 +++ .../app-schema/app-schema-test/pom.xml | 21 +++ .../test/web/AbstractMapPreviewPageTest.java | 50 ++++++ .../geoserver/test/web/Gml311LinksTest.java | 34 ++++ .../geoserver/test/web/Gml32LinksTest.java | 30 ++++ .../geoserver/web/demo/MapPreviewPage.java | 17 +- .../org/geoserver/web/demo/PreviewLayer.java | 170 +++++++++++++++++- 8 files changed, 353 insertions(+), 11 deletions(-) create mode 100644 src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/AbstractMapPreviewPageTest.java create mode 100644 src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml311LinksTest.java create mode 100644 src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml32LinksTest.java diff --git a/src/extension/app-schema/app-schema-oracle-test/pom.xml b/src/extension/app-schema/app-schema-oracle-test/pom.xml index 9846b12d202..652dc5cadd0 100644 --- a/src/extension/app-schema/app-schema-oracle-test/pom.xml +++ b/src/extension/app-schema/app-schema-oracle-test/pom.xml @@ -162,6 +162,27 @@ com.oracle ojdbc7 + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + org.geoserver.web + gs-web-demo + ${project.version} + test + + + org.geoserver.web + gs-web-demo + ${project.version} + tests + test + diff --git a/src/extension/app-schema/app-schema-postgis-test/pom.xml b/src/extension/app-schema/app-schema-postgis-test/pom.xml index b71c94ef06f..c910361e916 100644 --- a/src/extension/app-schema/app-schema-postgis-test/pom.xml +++ b/src/extension/app-schema/app-schema-postgis-test/pom.xml @@ -157,6 +157,27 @@ 2.0.0-1 test + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + org.geoserver.web + gs-web-demo + ${project.version} + test + + + org.geoserver.web + gs-web-demo + ${project.version} + tests + test + diff --git a/src/extension/app-schema/app-schema-test/pom.xml b/src/extension/app-schema/app-schema-test/pom.xml index df0e85e95ae..4981afa57fe 100644 --- a/src/extension/app-schema/app-schema-test/pom.xml +++ b/src/extension/app-schema/app-schema-test/pom.xml @@ -163,6 +163,27 @@ 2.0.0-1 test + + + org.geoserver.web + gs-web-core + ${project.version} + tests + test + + + org.geoserver.web + gs-web-demo + ${project.version} + test + + + org.geoserver.web + gs-web-demo + ${project.version} + tests + test + diff --git a/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/AbstractMapPreviewPageTest.java b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/AbstractMapPreviewPageTest.java new file mode 100644 index 00000000000..c8080ad94cc --- /dev/null +++ b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/AbstractMapPreviewPageTest.java @@ -0,0 +1,50 @@ +/* (c) 2015 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.test.web; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.wicket.markup.html.link.ExternalLink; +import org.apache.wicket.markup.repeater.data.DataView; +import org.geoserver.web.GeoServerWicketTestSupport; +import org.geoserver.web.demo.MapPreviewPage; + +public class AbstractMapPreviewPageTest extends GeoServerWicketTestSupport { + + protected List EXPECTED_GML_LINKS = new ArrayList(); + + protected AbstractMapPreviewPageTest(List expectedGmlLinks) { + this.EXPECTED_GML_LINKS = expectedGmlLinks; + } + + public void testAppSchemaGmlLinks() { + tester.startPage(MapPreviewPage.class); + tester.assertRenderedPage(MapPreviewPage.class); + + DataView items = (DataView) tester.getComponentFromLastRenderedPage("table:listContainer:items"); + assertNotNull(items); + assertEquals(EXPECTED_GML_LINKS.size(), items.size()); + + // collect GML links model objects + List gmlLinks = new ArrayList(); + for (int i=1; i<=EXPECTED_GML_LINKS.size(); i++) { + ExternalLink gmlLink = (ExternalLink) items.get(i + ":itemProperties:3:component:gml"); + assertNotNull(gmlLink); + gmlLinks.add(gmlLink.getDefaultModelObjectAsString()); + } + + Collections.sort(EXPECTED_GML_LINKS); + Collections.sort(gmlLinks); + // check the two lists match + assertArrayEquals(EXPECTED_GML_LINKS.toArray(new String[] {}), gmlLinks.toArray(new String[] {})); + } +} diff --git a/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml311LinksTest.java b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml311LinksTest.java new file mode 100644 index 00000000000..f9fb464a21f --- /dev/null +++ b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml311LinksTest.java @@ -0,0 +1,34 @@ +/* (c) 2015 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.test.web; + +import java.util.Arrays; + +import org.geoserver.data.test.SystemTestData; +import org.geoserver.test.FeatureChainingMockData; +import org.junit.Test; + +public class Gml311LinksTest extends AbstractMapPreviewPageTest { + + public Gml311LinksTest() { + super(Arrays.asList("../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=gsml:MappedFeature&outputFormat=gml3&maxFeatures=50", + "../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=gsml:GeologicUnit&outputFormat=gml3&maxFeatures=50", + "../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=ex:FirstParentFeature&outputFormat=gml3&maxFeatures=50", + "../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=ex:SecondParentFeature&outputFormat=gml3&maxFeatures=50", + "../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=om:Observation&outputFormat=gml3&maxFeatures=50")); + } + + @Override + protected SystemTestData createTestData() throws Exception { + return new FeatureChainingMockData(); + } + + @Test + public void testGml311Links() { + super.testAppSchemaGmlLinks(); + } + +} diff --git a/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml32LinksTest.java b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml32LinksTest.java new file mode 100644 index 00000000000..2649f1d716e --- /dev/null +++ b/src/extension/app-schema/app-schema-test/src/test/java/org/geoserver/test/web/Gml32LinksTest.java @@ -0,0 +1,30 @@ +/* (c) 2015 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ + +package org.geoserver.test.web; + +import java.util.Arrays; + +import org.geoserver.data.test.SystemTestData; +import org.geoserver.test.Gsml30MockData; +import org.junit.Test; + +public class Gml32LinksTest extends AbstractMapPreviewPageTest { + + public Gml32LinksTest() { + super(Arrays.asList("../ows?service=WFS&version=1.1.0&request=GetFeature&typeName=gsml:MappedFeature&outputFormat=gml32&maxFeatures=50")); + } + + @Override + protected SystemTestData createTestData() throws Exception { + return new Gsml30MockData(); + } + + @Test + public void testGml32Links() { + super.testAppSchemaGmlLinks(); + } + +} diff --git a/src/web/demo/src/main/java/org/geoserver/web/demo/MapPreviewPage.java b/src/web/demo/src/main/java/org/geoserver/web/demo/MapPreviewPage.java index b6814d47ae4..7ddad8f6e6e 100644 --- a/src/web/demo/src/main/java/org/geoserver/web/demo/MapPreviewPage.java +++ b/src/web/demo/src/main/java/org/geoserver/web/demo/MapPreviewPage.java @@ -1,4 +1,4 @@ -/* (c) 2014 Open Source Geospatial Foundation - all rights reserved +/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. @@ -14,9 +14,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.logging.Level; @@ -31,12 +33,12 @@ import org.apache.wicket.markup.repeater.RepeatingView; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; -import org.geoserver.catalog.LayerInfo; -import org.geoserver.config.GeoServer; import org.geoserver.catalog.PublishedType; +import org.geoserver.config.GeoServer; import org.geoserver.ows.util.ResponseUtils; import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerBasePage; +import org.geoserver.web.demo.PreviewLayer.GMLOutputParams; import org.geoserver.web.demo.PreviewLayer.PreviewLayerType; import org.geoserver.web.wicket.GeoServerDataProvider.Property; import org.geoserver.web.wicket.GeoServerTablePanel; @@ -58,6 +60,9 @@ public class MapPreviewPage extends GeoServerBasePage { private transient List availableWMSFormats; private transient List availableWFSFormats; + /** GML output params computation may be expensive, results are cached in this map */ + private transient Map gmlParamsCache = new HashMap(); + public MapPreviewPage() { // output formats for the drop downs final List wmsOutputFormats = getAvailableWMSFormats(); @@ -84,15 +89,11 @@ protected Component getComponentForProperty(String id, Fragment f = new Fragment(id, "commonLinks", MapPreviewPage.this); final String olUrl = layer.getWmsLink() + "&format=application/openlayers"; f.add(new ExternalLink("ol", olUrl, "OpenLayers")); - // kml preview final String kmlUrl = layer.getBaseUrl("wms") + "/kml?layers=" + layer.getName(); f.add(new ExternalLink("kml", kmlUrl, "KML")); - // gml preview (we actually want it only for vector layers) - final String gmlUrl = - layer.getBaseUrl("ows") + "?service=WFS&version=1.0.0&request=GetFeature&typeName=" - + layer.getName() + getMaxFeatures(); + final String gmlUrl = layer.getGmlLink(gmlParamsCache) + getMaxFeatures(); Component gmlLink = new ExternalLink("gml", gmlUrl, "GML"); f.add(gmlLink); gmlLink.setVisible(layer.getType() == PreviewLayerType.Vector); diff --git a/src/web/demo/src/main/java/org/geoserver/web/demo/PreviewLayer.java b/src/web/demo/src/main/java/org/geoserver/web/demo/PreviewLayer.java index 5a1253d029a..a923b516571 100644 --- a/src/web/demo/src/main/java/org/geoserver/web/demo/PreviewLayer.java +++ b/src/web/demo/src/main/java/org/geoserver/web/demo/PreviewLayer.java @@ -1,17 +1,21 @@ -/* (c) 2014 Open Source Geospatial Foundation - all rights reserved +/* (c) 2014 - 2015 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.web.demo; +import java.io.IOException; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.wicket.ResourceReference; import org.geoserver.catalog.Catalog; +import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.LayerGroupInfo; import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.PublishedType; @@ -19,12 +23,18 @@ import org.geoserver.ows.util.ResponseUtils; import org.geoserver.web.CatalogIconFactory; import org.geoserver.web.GeoServerApplication; +import org.geoserver.wfs.xml.GML32OutputFormat; import org.geoserver.wms.DefaultWebMapService; import org.geoserver.wms.GetMapRequest; import org.geoserver.wms.MapLayerInfo; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.gml2.bindings.GML2EncodingUtils; +import org.geotools.util.NullProgressListener; +import org.geotools.util.ProgressListener; import org.geotools.util.logging.Logging; +import org.opengis.feature.type.AttributeType; +import org.opengis.feature.type.FeatureType; +import org.opengis.feature.type.Name; import com.google.common.collect.Iterables; import com.vividsolutions.jts.geom.Envelope; @@ -181,15 +191,19 @@ private List expandLayers(Catalog catalog) { } String getBaseUrl(String service) { + return getBaseUrl(service, false); + } + + String getBaseUrl(String service, boolean useGlobalRef) { String ws = getWorkspace(); - if(ws == null) { + if(ws == null || useGlobalRef) { // global reference return ResponseUtils.buildURL("../", service, null, URLType.SERVICE); } else { return ResponseUtils.buildURL("../", ws + "/" + service, null, URLType.SERVICE); } } - + /** * Given a request and a target format, builds the WMS request * @@ -211,4 +225,154 @@ public String getWmsLink() { + "&width=" + request.getWidth() // + "&height=" + request.getHeight() + "&srs=" + request.getSRS(); } + + /** + * Returns the default GML link for this layer. + * + * @param gmlParamsCache optional map where computed GML output params are cached + * @return + */ + public String getGmlLink(Map gmlParamsCache) { + GMLOutputParams gmlParams = new GMLOutputParams(); + + if (layerInfo != null) { + if (layerInfo.getResource() instanceof FeatureTypeInfo) { + FeatureTypeInfo ftInfo = (FeatureTypeInfo) layerInfo.getResource(); + if (ftInfo.getStore() != null) { + Map connParams = ftInfo.getStore() + .getConnectionParameters(); + if (connParams != null) { + String dbtype = (String) connParams.get("dbtype"); + // app-schema feature types need special treatment + if ("app-schema".equals(dbtype)) { + String mappingUrl = connParams.get("url").toString(); + if (gmlParamsCache != null && gmlParamsCache.containsKey(mappingUrl)) { + // avoid looking up the GML version again + gmlParams = gmlParamsCache.get(mappingUrl); + } else { + // use global OWS service to make sure all secondary namespaces + // are accessible + gmlParams.baseUrl = getBaseUrl("ows", true); + // always use WFS 1.1.0 for app-schema layers + gmlParams.wfsVersion = org.geotools.wfs.v1_1.WFS.getInstance() + .getVersion(); + // determine GML version by inspecting the feature type and its super types + try { + gmlParams.gmlVersion = findGmlVersion(ftInfo); + } catch (IOException e) { + LOGGER.log(Level.FINE, "Could not determine GML version, using default", e); + gmlParams.gmlVersion = null; + } + // store params in cache + if (gmlParamsCache != null) { + gmlParamsCache.put(mappingUrl, gmlParams); + } + } + } + // TODO: do other data stores have any special needs? + else { + } + } + } + } + } + + return buildGmlLink(gmlParams); + } + + /** + * Returns the GML version used in the feature type's definition. + * + *

+ * The method recursively climbs up the type hierarchy of the provided feature type, until it finds AbstractFeatureType. Then, the GML version is + * determined by looking at the namespace URI. + *

+ * + *

+ * Please note that this method does not differentiate between GML 2 and GML 3.1.1, but assumes that "http://www.opengis.net/gml" namespace always + * refers to GML 3.1.1. + *

+ * + * @param ftInfo the feature type info + * @return the GML version used in the feature type definition + * @throws IOException if the underlying datastore instance cannot be retrieved + */ + String findGmlVersion(FeatureTypeInfo ftInfo) throws IOException { + ProgressListener listener = new NullProgressListener(); + Name qName = ftInfo.getQualifiedName(); + FeatureType fType = ftInfo.getStore().getDataStore(listener).getSchema(qName); + return findFeatureTypeGmlVersion(fType); + } + + private String findFeatureTypeGmlVersion(AttributeType featureType) { + if (featureType == null) { + return null; + } + + if (isAbstractFeatureType(featureType)) { + String gmlNamespace = featureType.getName().getNamespaceURI(); + if (org.geotools.gml3.GML.NAMESPACE.equals(gmlNamespace)) { + // GML 3.1.1 + return "gml3"; + } else if (org.geotools.gml3.v3_2.GML.NAMESPACE.equals(gmlNamespace)) { + // GML 3.2 + return GML32OutputFormat.FORMATS.get(0); + } else { + // should never happen + LOGGER.log(Level.FINE, "Cannot determine GML version from AbstractFeatureType type"); + return null; + } + } + + // recursively check super types + AttributeType parent = featureType.getSuper(); + return findFeatureTypeGmlVersion(parent); + } + + private boolean isAbstractFeatureType(AttributeType type) { + if (type == null) { + return false; + } + + Name qName = type.getName(); + String localPart = qName.getLocalPart(); + String ns = qName.getNamespaceURI(); + if ("AbstractFeatureType".equals(localPart) && + (org.geotools.gml3.GML.NAMESPACE.equals(ns) || org.geotools.gml3.v3_2.GML.NAMESPACE + .equals(ns))) { + return true; + } else { + return false; + } + } + + String buildGmlLink(GMLOutputParams gmlParams) { + StringBuilder urlBuilder = new StringBuilder(); + urlBuilder.append(gmlParams.baseUrl).append("?"); + urlBuilder.append("service=WFS").append("&"); + urlBuilder.append("version=").append(gmlParams.wfsVersion).append("&"); + urlBuilder.append("request=GetFeature").append("&"); + urlBuilder.append("typeName=").append(getName()); + if (gmlParams.gmlVersion != null) { + urlBuilder.append("&"); + urlBuilder.append("outputFormat=").append(gmlParams.gmlVersion); + } + + return urlBuilder.toString(); + } + + class GMLOutputParams { + String wfsVersion; + String gmlVersion; + String baseUrl; + + private GMLOutputParams() { + // by default, use WFS 1.0.0 + wfsVersion = org.geotools.wfs.v1_0.WFS.getInstance().getVersion(); + // by default, infer GML version from WFS version + gmlVersion = null; + // by default, use virtual ows services + baseUrl = getBaseUrl("ows"); + } + } }