/* (c) 2014 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.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import java.io.IOException;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geotools.data.FeatureSource;
import org.geotools.data.complex.AppSchemaDataAccess;
import org.geotools.data.complex.FeatureTypeMapping;
import org.geotools.data.complex.filter.ComplexFilterSplitter;
import org.geotools.data.jdbc.FilterToSQLException;
import org.geotools.filter.FilterFactoryImplNamespaceAware;
import org.geotools.jdbc.JDBCDataStore;
import org.geotools.jdbc.NestedFilterToSQL;
import org.geotools.util.NullProgressListener;
import org.junit.Test;
import org.opengis.filter.Filter;
import org.opengis.filter.PropertyIsEqualTo;
import org.w3c.dom.Document;
/**
* Test whether nested Id's can be used in a filter.
*
* @author Niels Charlier, Curtin University Of Technology *
*/
public class NestedIdSupportTest extends AbstractAppSchemaTestSupport {
@Override
protected NestedIdSupportTestData createTestData() {
return new NestedIdSupportTestData();
}
/**
* Test Nested Id with Feature Chaining
*/
@Test
public void testNestedIdFeatureChaining() {
String xml = "<wfs:GetFeature " //
+ "service=\"WFS\" " //
+ "version=\"1.1.0\" " //
+ "xmlns:cdf=\"http://www.opengis.net/cite/data\" " //
+ "xmlns:ogc=\"http://www.opengis.net/ogc\" " //
+ "xmlns:wfs=\"http://www.opengis.net/wfs\" " //
+ "xmlns:gml=\"http://www.opengis.net/gml\" " //
+ "xmlns:gsml=\""
+ AbstractAppSchemaMockData.GSML_URI
+ "\" " //
+ ">" //
+ "<wfs:Query typeName=\"gsml:MappedFeature\">"
+ "<ogc:Filter>"
+ " <ogc:PropertyIsEqualTo>"
+ " <ogc:PropertyName>gsml:specification/gsml:GeologicUnit/gsml:composition/gsml:CompositionPart/gsml:lithology/gsml:ControlledConcept/@gml:id</ogc:PropertyName>"
+ " <ogc:Literal>cc.1</ogc:Literal>"
+ " </ogc:PropertyIsEqualTo>"
+ " </ogc:Filter>" + "</wfs:Query>" + "</wfs:GetFeature>";
Document doc = postAsDOM("wfs", xml);
LOGGER.info("MappedFeature: WFS GetFeature response:\n" + prettyString(doc));
assertXpathCount(1, "//gsml:MappedFeature", doc);
assertXpathEvaluatesTo("mf4",
"wfs:FeatureCollection/gml:featureMember/gsml:MappedFeature/@gml:id", doc);
}
/**
* Test Nested Id with InlineMapping
* */
@Test
public void testNestedIdInlineMapping() {
String xml = "<wfs:GetFeature " //
+ "service=\"WFS\" " //
+ "version=\"1.1.0\" " //
+ "xmlns:cdf=\"http://www.opengis.net/cite/data\" " //
+ "xmlns:ogc=\"http://www.opengis.net/ogc\" " //
+ "xmlns:wfs=\"http://www.opengis.net/wfs\" " //
+ "xmlns:gml=\"http://www.opengis.net/gml\" " //
+ "xmlns:gsml=\""
+ AbstractAppSchemaMockData.GSML_URI
+ "\" " //
+ ">" //
+ "<wfs:Query typeName=\"gsml:Borehole\">"
+ "<ogc:Filter>"
+ " <ogc:PropertyIsEqualTo>"
+ " <ogc:PropertyName>gsml:indexData/gsml:BoreholeDetails/@gml:id</ogc:PropertyName>"
+ " <ogc:Literal>bh.details.11.sp</ogc:Literal>"
+ " </ogc:PropertyIsEqualTo>" + " </ogc:Filter>"
+ "</wfs:Query>"
+ "</wfs:GetFeature>";
Document doc = postAsDOM("wfs", xml);
LOGGER.info("Borehole: WFS GetFeature response:\n" + prettyString(doc));
assertXpathCount(1, "//gsml:Borehole", doc);
assertXpathEvaluatesTo("11",
"wfs:FeatureCollection/gml:featureMember/gsml:Borehole/@gml:id", doc);
}
@Test
public void testNestedFiltersEncoding() throws IOException, FilterToSQLException {
FeatureTypeInfo ftInfo = getCatalog().getFeatureTypeByName("gsml", "MappedFeature");
FeatureSource fs = ftInfo.getFeatureSource(new NullProgressListener(), null);
AppSchemaDataAccess da = (AppSchemaDataAccess) fs.getDataStore();
FeatureTypeMapping rootMapping = da.getMappingByNameOrElement(ftInfo.getQualifiedName());
// make sure nested filters encoding is enabled, otherwise skip test
assumeTrue(shouldTestNestedFiltersEncoding(rootMapping));
JDBCDataStore store = (JDBCDataStore) rootMapping.getSource().getDataStore();
NestedFilterToSQL nestedFilterToSQL = createNestedFilterEncoder(rootMapping);
FilterFactoryImplNamespaceAware ff = new FilterFactoryImplNamespaceAware();
ff.setNamepaceContext(rootMapping.getNamespaces());
/*
* test filter on nested ID
*/
PropertyIsEqualTo nestedIdFilter = ff
.equals(ff
.property("gsml:specification/gsml:GeologicUnit/gsml:composition/gsml:CompositionPart/gsml:lithology/gsml:ControlledConcept/@gml:id"),
ff.literal("cc.1"));
// Filter involves a single nested attribute --> can be encoded
ComplexFilterSplitter splitter = new ComplexFilterSplitter(store.getFilterCapabilities(),
rootMapping);
splitter.visit(nestedIdFilter, null);
Filter preFilter = splitter.getFilterPre();
Filter postFilter = splitter.getFilterPost();
assertEquals(nestedIdFilter, preFilter);
assertEquals(Filter.INCLUDE, postFilter);
// filter must be "unrolled" (i.e. reverse mapped) first
Filter unrolled = AppSchemaDataAccess.unrollFilter(nestedIdFilter, rootMapping);
// Filter is nested
assertTrue(NestedFilterToSQL.isNestedFilter(unrolled));
String encodedFilter = nestedFilterToSQL.encodeToString(unrolled);
// this is the generated query in PostGIS, but the test limits to check the presence of the
// a few keywords, as the actual SQL is dependent on the underlying database
// EXISTS (SELECT "chain_link_3"."PKEY"
// FROM "appschematest"."CONTROLLEDCONCEPT" "chain_link_3"
// INNER JOIN "appschematest"."COMPOSITIONPART" "chain_link_2" ON "chain_link_2"."ROW_ID" = "chain_link_3"."COMPOSITION_ID"
// INNER JOIN "appschematest"."GEOLOGICUNIT" "chain_link_1" ON "chain_link_1"."COMPONENTPART_ID" = "chain_link_2"."ROW_ID"
// WHERE "chain_link_3"."GML_ID" = 'cc.1' AND "appschematest"."MAPPEDFEATUREPROPERTYFILE"."GEOLOGIC_UNIT_ID" = "chain_link_1"."GML_ID")
assertTrue(encodedFilter.matches("^EXISTS.*SELECT.*FROM.*INNER JOIN.*INNER JOIN.*WHERE.*$"));
assertContainsFeatures(fs.getFeatures(nestedIdFilter), "mf4");
}
}