From ab7d306ab98d43206043c17e1a3db7186bc35c61 Mon Sep 17 00:00:00 2001
From: Lyn Elisa Goltz <goltz@lat-lon.de>
Date: Thu, 19 Jan 2017 09:38:34 +0100
Subject: [PATCH 1/4] #227 - implemented parsing of properties FILTERPROPERTY
 and FILTERVALUE

---
 .../java/org/deegree/layer/LayerQuery.java    |  41 +++++-
 .../org/deegree/layer/LayerQueryTest.java     | 124 ++++++++++++++++++
 2 files changed, 163 insertions(+), 2 deletions(-)
 create mode 100644 deegree-core/deegree-core-layer/src/test/java/org/deegree/layer/LayerQueryTest.java

diff --git a/deegree-core/deegree-core-layer/src/main/java/org/deegree/layer/LayerQuery.java b/deegree-core/deegree-core-layer/src/main/java/org/deegree/layer/LayerQuery.java
index 6473a18b9d..75173a293f 100644
--- a/deegree-core/deegree-core-layer/src/main/java/org/deegree/layer/LayerQuery.java
+++ b/deegree-core/deegree-core-layer/src/main/java/org/deegree/layer/LayerQuery.java
@@ -36,11 +36,16 @@
 package org.deegree.layer;
 
 import static java.lang.Integer.parseInt;
+import static java.util.Arrays.asList;
 import static org.deegree.commons.utils.MapUtils.DEFAULT_PIXEL_SIZE;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
+import org.deegree.commons.utils.Pair;
 import org.deegree.filter.OperatorFilter;
 import org.deegree.geometry.Envelope;
 import org.deegree.geometry.GeometryFactory;
@@ -57,6 +62,12 @@
  */
 public class LayerQuery {
 
+    public static final String FILTERPROPERTY = "FILTERPROPERTY";
+
+    public static final String FILTERVALUE = "FILTERVALUE";
+
+    public static final String RADIUS = "RADIUS";
+
     private final Envelope envelope;
 
     private final int width, height;
@@ -192,7 +203,7 @@ public int getLayerRadius() {
     }
 
     public Envelope calcClickBox( int radius ) {
-        radius = parameters.get( "RADIUS" ) == null ? radius : parseInt( parameters.get( "RADIUS" ) );
+        radius = parameters.get( RADIUS ) == null ? radius : parseInt( parameters.get( RADIUS ) );
         GeometryFactory fac = new GeometryFactory();
         double dw = envelope.getSpan0() / width;
         double dh = envelope.getSpan1() / height;
@@ -204,4 +215,30 @@ public Envelope calcClickBox( int radius ) {
                                                  envelope.getMax().get1() - ( y - r2 ) * dh },
                                    envelope.getCoordinateSystem() );
     }
-}
+
+    /**
+     * Returns the additional request parameters used for filtering.
+     *
+     * @return the property (first) and the values (second) to filter for, <code>null</code> if at least one parameter is <code>null</code> or empty
+     */
+    public Pair<String, List<String>> requestFilter() {
+        String filterProperty = parameters.get( FILTERPROPERTY );
+        String filterValue = parameters.get( FILTERVALUE );
+        if ( filterProperty == null || filterProperty.isEmpty() || filterValue == null || filterValue.isEmpty() )
+            return null;
+        List<String> filterValues = parseFilterValues( filterValue );
+        return new Pair<String, List<String>>( filterProperty, filterValues );
+    }
+
+    private List<String> parseFilterValues( String filterValue ) {
+        List<String> filterValues = new ArrayList<String>();
+        String[] splittedFilterValue = filterValue.split( "," );
+        for ( String value : splittedFilterValue ) {
+            String trimmedValue = value.trim();
+            if ( !trimmedValue.isEmpty() )
+                filterValues.add( trimmedValue );
+        }
+        return filterValues;
+    }
+
+}
\ No newline at end of file
diff --git a/deegree-core/deegree-core-layer/src/test/java/org/deegree/layer/LayerQueryTest.java b/deegree-core/deegree-core-layer/src/test/java/org/deegree/layer/LayerQueryTest.java
new file mode 100644
index 0000000000..1ccc365549
--- /dev/null
+++ b/deegree-core/deegree-core-layer/src/test/java/org/deegree/layer/LayerQueryTest.java
@@ -0,0 +1,124 @@
+package org.deegree.layer;
+
+import org.deegree.commons.utils.Pair;
+import org.deegree.cs.exceptions.UnknownCRSException;
+import org.deegree.cs.persistence.CRSManager;
+import org.deegree.geometry.Envelope;
+import org.deegree.geometry.SimpleGeometryFactory;
+import org.deegree.geometry.standard.DefaultEnvelope;
+import org.hamcrest.CoreMatchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.deegree.layer.LayerQuery.FILTERPROPERTY;
+import static org.deegree.layer.LayerQuery.FILTERVALUE;
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a>
+ */
+public class LayerQueryTest {
+
+    @Test
+    public void requestFilter()
+                            throws Exception {
+        String filterProperty = "type";
+        String filterValue = "one";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter.getFirst(), is( filterProperty ) );
+        assertThat( requestFilter.getSecond().size(), is( 1 ) );
+        assertThat( requestFilter.getSecond(), hasItem( filterValue ) );
+    }
+
+    @Test
+    public void requestFilter_MultipleValues()
+                            throws Exception {
+        String filterProperty = "type";
+        String filterValue = "one, two";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter.getFirst(), is( filterProperty ) );
+        assertThat( requestFilter.getSecond().size(), is( 2 ) );
+        assertThat( requestFilter.getSecond(), hasItem( "one" ) );
+        assertThat( requestFilter.getSecond(), hasItem( "two" ) );
+    }
+
+    @Test
+    public void requestFilter_MultipleValuesWithMissing()
+                            throws Exception {
+        String filterProperty = "type";
+        String filterValue = "one,,two";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter.getFirst(), is( filterProperty ) );
+        assertThat( requestFilter.getSecond().size(), is( 2 ) );
+        assertThat( requestFilter.getSecond(), hasItem( "one" ) );
+        assertThat( requestFilter.getSecond(), hasItem( "two" ) );
+    }
+
+    @Test
+    public void requestFilter_NullValue()
+                            throws Exception {
+        String filterProperty = null;
+        String filterValue = "one, two";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter, is( nullValue() ) );
+    }
+
+    @Test
+    public void requestFilter_NullProperty()
+                            throws Exception {
+        String filterProperty = "type";
+        String filterValue = null;
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter, is( nullValue() ) );
+    }
+
+    @Test
+    public void requestFilter_EmptyValue()
+                            throws Exception {
+        String filterProperty = "";
+        String filterValue = "one, two";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter, is( nullValue() ) );
+    }
+
+    @Test
+    public void requestFilter_EMptyProperty()
+                            throws Exception {
+        String filterProperty = "type";
+        String filterValue = "";
+        LayerQuery layerQuery = createLayerQuery( filterProperty, filterValue );
+
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        assertThat( requestFilter, is( nullValue() ) );
+    }
+
+    private LayerQuery createLayerQuery( String filterProperty, String filterValue )
+                            throws UnknownCRSException {
+        Map<String, String> parameters = new HashMap<String, String>();
+        parameters.put( FILTERPROPERTY, filterProperty );
+        parameters.put( FILTERVALUE, filterValue );
+        Envelope envelope = new SimpleGeometryFactory().createEnvelope( 5, 12, 6, 11,
+                                                                        CRSManager.lookup( "EPSG:4326" ) );
+        return new LayerQuery( envelope, 300, 200, 1, 2, 3, null, null, parameters, null, null, null, 1 );
+    }
+
+}
\ No newline at end of file

From 23de8f29a9182349df1bad9324a3537d0842852a Mon Sep 17 00:00:00 2001
From: Lyn Elisa Goltz <goltz@lat-lon.de>
Date: Thu, 19 Jan 2017 10:36:35 +0100
Subject: [PATCH 2/4] #228 - create filter from request parameter

---
 deegree-layers/deegree-layers-feature/pom.xml |  8 ++
 .../persistence/feature/FilterBuilder.java    | 51 +++++++++++--
 .../feature/FilterBuilderTest.java            | 76 +++++++++++++++++++
 3 files changed, 130 insertions(+), 5 deletions(-)
 create mode 100644 deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java

diff --git a/deegree-layers/deegree-layers-feature/pom.xml b/deegree-layers/deegree-layers-feature/pom.xml
index fd6e865b41..67ca02c25e 100644
--- a/deegree-layers/deegree-layers-feature/pom.xml
+++ b/deegree-layers/deegree-layers-feature/pom.xml
@@ -55,6 +55,14 @@
       <artifactId>deegree-featurestore-commons</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+    </dependency>
   </dependencies>
 
 </project>
diff --git a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
index a510e26a48..7cc9cd213b 100644
--- a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
+++ b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
@@ -44,16 +44,24 @@ Occam Labs UG (haftungsbeschränkt)
 import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2;
 import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2_OR_3;
 
+import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 
 import org.deegree.commons.ows.exception.OWSException;
 import org.deegree.commons.tom.gml.property.PropertyType;
+import org.deegree.commons.tom.primitive.PrimitiveValue;
+import org.deegree.commons.utils.Pair;
 import org.deegree.feature.types.FeatureType;
 import org.deegree.feature.types.property.GeometryPropertyType;
+import org.deegree.filter.Expression;
 import org.deegree.filter.Filters;
+import org.deegree.filter.MatchAction;
 import org.deegree.filter.Operator;
 import org.deegree.filter.OperatorFilter;
+import org.deegree.filter.comparison.ComparisonOperator;
+import org.deegree.filter.comparison.PropertyIsEqualTo;
+import org.deegree.filter.expression.Literal;
 import org.deegree.filter.expression.ValueReference;
 import org.deegree.filter.logical.And;
 import org.deegree.filter.logical.Or;
@@ -63,24 +71,28 @@ Occam Labs UG (haftungsbeschränkt)
 import org.deegree.style.se.unevaluated.Style;
 import org.deegree.style.utils.Styles;
 
+import javax.xml.namespace.QName;
+
 /**
  * Responsible for building feature layer filters.
- * 
+ *
  * @author <a href="mailto:schmitz@occamlabs.de">Andreas Schmitz</a>
  * @author last edited by: $Author: stranger $
- * 
  * @version $Revision: $, $Date: $
  */
 class FilterBuilder {
 
-    static OperatorFilter buildFilterForMap(OperatorFilter filter, Style style, LayerQuery query, DimensionFilterBuilder dimFilterBuilder, List<String> headers) throws OWSException{
+    static OperatorFilter buildFilterForMap( OperatorFilter filter, Style style, LayerQuery query,
+                                             DimensionFilterBuilder dimFilterBuilder, List<String> headers )
+                            throws OWSException {
         style = style.filter( query.getScale() );
         filter = Filters.and( filter, Styles.getStyleFilters( style, query.getScale() ) );
         filter = Filters.and( filter, query.getFilter() );
         filter = Filters.and( filter, dimFilterBuilder.getDimensionFilter( query.getDimensions(), headers ) );
+        filter = Filters.and( filter, buildRequestFilter( query ) );
         return filter;
     }
-    
+
     static OperatorFilter buildFilter( Operator operator, FeatureType ft, Envelope clickBox ) {
         if ( ft == null ) {
             if ( operator == null ) {
@@ -113,11 +125,40 @@ private static LinkedList<Operator> findOperators( FeatureType ft, Envelope clic
         LinkedList<Operator> list = new LinkedList<Operator>();
         for ( PropertyType pt : ft.getPropertyDeclarations() ) {
             if ( pt instanceof GeometryPropertyType
-                 && ( ( (GeometryPropertyType) pt ).getCoordinateDimension() == DIM_2 || ( (GeometryPropertyType) pt ).getCoordinateDimension() == DIM_2_OR_3 ) ) {
+                 && ( ( (GeometryPropertyType) pt ).getCoordinateDimension() == DIM_2 ||
+                      ( (GeometryPropertyType) pt ).getCoordinateDimension() == DIM_2_OR_3 ) ) {
                 list.add( new Intersects( new ValueReference( pt.getName() ), clickBox ) );
             }
         }
         return list;
     }
 
+    static OperatorFilter buildRequestFilter( LayerQuery layerQuery ) {
+        Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
+        if ( requestFilter == null )
+            return null;
+
+        List<ComparisonOperator> operators = createOperators( requestFilter );
+        if ( operators.isEmpty() )
+            return null;
+        if ( operators.size() == 1 )
+            return new OperatorFilter( operators.get( 0 ) );
+        return new OperatorFilter( new Or( operators.toArray( new Operator[operators.size()] ) ) );
+    }
+
+    private static List<ComparisonOperator> createOperators( Pair<String, List<String>> requestFilter ) {
+        List<ComparisonOperator> operators = new ArrayList<ComparisonOperator>();
+        String filterProperty = requestFilter.getFirst();
+        List<String> filterValues = requestFilter.getSecond();
+        for ( String filterValue : filterValues ) {
+            Expression filterPropertyExpression = new ValueReference( new QName( filterProperty ) );
+            Expression filterValueExpression = new Literal<PrimitiveValue>( filterValue );
+            PropertyIsEqualTo isEqualTo = new PropertyIsEqualTo( filterPropertyExpression,
+                                                                 filterValueExpression, false,
+                                                                 MatchAction.ALL );
+            operators.add( isEqualTo );
+        }
+        return operators;
+    }
+
 }
diff --git a/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java b/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java
new file mode 100644
index 0000000000..bb4446195f
--- /dev/null
+++ b/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java
@@ -0,0 +1,76 @@
+package org.deegree.layer.persistence.feature;
+
+import org.deegree.commons.utils.Pair;
+import org.deegree.filter.OperatorFilter;
+import org.deegree.filter.comparison.PropertyIsEqualTo;
+import org.deegree.filter.logical.Or;
+import org.deegree.layer.LayerQuery;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz </a>
+ */
+public class FilterBuilderTest {
+
+    @Test
+    public void testBuildRequestFilter() {
+        String filterProperty = "type";
+        List<String> filterValues = Collections.singletonList( "one" );
+
+        LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+
+        assertThat( operatorFilter.getOperator(), instanceOf( PropertyIsEqualTo.class ) );
+    }
+
+    @Test
+    public void testBuildRequestFilter_MultipleValues() {
+        String filterProperty = "type";
+        List<String> filterValues = Arrays.asList( new String[] { "one", "two" } );
+        LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+
+        assertThat( operatorFilter.getOperator(), instanceOf( Or.class ) );
+    }
+
+    @Test
+    public void testBuildRequestFilter_EmptyValues() {
+        String filterProperty = "type";
+        List<String> filterValues = Collections.emptyList();
+        LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+
+        assertThat( operatorFilter, is( nullValue() ) );
+    }
+
+    @Test
+    public void testBuildRequestFilter_NullRequestFilter() {
+        LayerQuery layerQuery = mockLayerQuery( null );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+
+        assertThat( operatorFilter, is( nullValue() ) );
+    }
+
+    private LayerQuery mockLayerQuery( String filterProperty, List<String> filterValues ) {
+        Pair<String, List<String>> requestFilter = new Pair<String, List<String>>( filterProperty, filterValues );
+
+        return mockLayerQuery( requestFilter );
+    }
+
+    private LayerQuery mockLayerQuery( Pair<String, List<String>> requestFilter ) {
+        LayerQuery layerQueryMock = Mockito.mock( LayerQuery.class );
+        Mockito.when( layerQueryMock.requestFilter() ).thenReturn( requestFilter );
+        return layerQueryMock;
+    }
+
+}
\ No newline at end of file

From 873b1a2b35b5a4521d6b2ff0fdebdd0d30563763 Mon Sep 17 00:00:00 2001
From: Lyn Elisa Goltz <goltz@lat-lon.de>
Date: Sat, 28 Jan 2017 17:19:44 +0100
Subject: [PATCH 3/4] #235 - ignore filter if property is null

---
 .../persistence/feature/FeatureLayer.java     | 26 +++++-----
 .../persistence/feature/FilterBuilder.java    | 48 ++++++++++++++-----
 .../feature/FilterBuilderTest.java            | 30 ++++++++++--
 3 files changed, 75 insertions(+), 29 deletions(-)

diff --git a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FeatureLayer.java b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FeatureLayer.java
index 488c49531d..c0973e40a3 100644
--- a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FeatureLayer.java
+++ b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FeatureLayer.java
@@ -40,17 +40,6 @@
  ----------------------------------------------------------------------------*/
 package org.deegree.layer.persistence.feature;
 
-import static org.deegree.filter.Filters.addBBoxConstraint;
-import static org.deegree.layer.persistence.feature.FilterBuilder.buildFilterForMap;
-import static org.deegree.style.utils.Styles.getStyleFilters;
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
 import org.deegree.commons.ows.exception.OWSException;
 import org.deegree.feature.persistence.FeatureStore;
 import org.deegree.feature.persistence.query.Query;
@@ -69,6 +58,16 @@
 import org.deegree.style.utils.Styles;
 import org.slf4j.Logger;
 
+import javax.xml.namespace.QName;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.deegree.filter.Filters.addBBoxConstraint;
+import static org.deegree.layer.persistence.feature.FilterBuilder.buildFilterForMap;
+import static org.deegree.style.utils.Styles.getStyleFilters;
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * 
  * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
@@ -129,13 +128,16 @@ public FeatureLayerData mapQuery( final LayerQuery query, List<String> headers )
             geomProp = null;
         }
 
+
         QName ftName = featureType == null ? style.getFeatureType() : featureType;
         if ( ftName != null && featureStore.getSchema().getFeatureType( ftName ) == null ) {
             LOG.warn( "FeatureType '" + ftName + "' is not known to the FeatureStore." );
             return null;
         }
 
-        filter = Filters.repair( filter, AppSchemas.collectProperyNames( featureStore.getSchema(), ftName ) );
+        Set<QName> propertyNames = AppSchemas.collectProperyNames( featureStore.getSchema(), ftName );
+        filter = FilterBuilder.appendRequestFilter( filter, query, propertyNames );
+        filter = Filters.repair( filter, propertyNames );
 
         QueryBuilder builder = new QueryBuilder( featureStore, filter, ftName, bbox, query, geomProp, sortBy,
                                                  getMetadata().getName() );
diff --git a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
index 7cc9cd213b..b831bb8af6 100644
--- a/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
+++ b/deegree-layers/deegree-layers-feature/src/main/java/org/deegree/layer/persistence/feature/FilterBuilder.java
@@ -41,13 +41,6 @@ Occam Labs UG (haftungsbeschränkt)
  ----------------------------------------------------------------------------*/
 package org.deegree.layer.persistence.feature;
 
-import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2;
-import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2_OR_3;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-
 import org.deegree.commons.ows.exception.OWSException;
 import org.deegree.commons.tom.gml.property.PropertyType;
 import org.deegree.commons.tom.primitive.PrimitiveValue;
@@ -72,6 +65,14 @@ Occam Labs UG (haftungsbeschränkt)
 import org.deegree.style.utils.Styles;
 
 import javax.xml.namespace.QName;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2;
+import static org.deegree.feature.types.property.GeometryPropertyType.CoordinateDimension.DIM_2_OR_3;
 
 /**
  * Responsible for building feature layer filters.
@@ -89,7 +90,6 @@ static OperatorFilter buildFilterForMap( OperatorFilter filter, Style style, Lay
         filter = Filters.and( filter, Styles.getStyleFilters( style, query.getScale() ) );
         filter = Filters.and( filter, query.getFilter() );
         filter = Filters.and( filter, dimFilterBuilder.getDimensionFilter( query.getDimensions(), headers ) );
-        filter = Filters.and( filter, buildRequestFilter( query ) );
         return filter;
     }
 
@@ -133,12 +133,17 @@ private static LinkedList<Operator> findOperators( FeatureType ft, Envelope clic
         return list;
     }
 
-    static OperatorFilter buildRequestFilter( LayerQuery layerQuery ) {
+    static OperatorFilter appendRequestFilter( OperatorFilter filter, LayerQuery query, Set<QName> propertyNames ) {
+        OperatorFilter requestFilter = buildRequestFilter( query, propertyNames );
+        return Filters.and( filter, requestFilter );
+    }
+
+    static OperatorFilter buildRequestFilter( LayerQuery layerQuery, Set<QName> propertyNames ) {
         Pair<String, List<String>> requestFilter = layerQuery.requestFilter();
         if ( requestFilter == null )
             return null;
 
-        List<ComparisonOperator> operators = createOperators( requestFilter );
+        List<ComparisonOperator> operators = createOperatorsIfPropertyIsKnown( requestFilter, propertyNames );
         if ( operators.isEmpty() )
             return null;
         if ( operators.size() == 1 )
@@ -146,9 +151,18 @@ static OperatorFilter buildRequestFilter( LayerQuery layerQuery ) {
         return new OperatorFilter( new Or( operators.toArray( new Operator[operators.size()] ) ) );
     }
 
-    private static List<ComparisonOperator> createOperators( Pair<String, List<String>> requestFilter ) {
-        List<ComparisonOperator> operators = new ArrayList<ComparisonOperator>();
+    private static List<ComparisonOperator> createOperatorsIfPropertyIsKnown( Pair<String, List<String>> requestFilter,
+                                                                              Set<QName> propertyNames ) {
         String filterProperty = requestFilter.getFirst();
+        if ( propertyIsknown( filterProperty, propertyNames ) ) {
+            return createOperatorsIfPropertyIsKnown( requestFilter, filterProperty );
+        }
+        return Collections.emptyList();
+    }
+
+    private static List<ComparisonOperator> createOperatorsIfPropertyIsKnown( Pair<String, List<String>> requestFilter,
+                                                                              String filterProperty ) {
+        List<ComparisonOperator> operators = new ArrayList<ComparisonOperator>();
         List<String> filterValues = requestFilter.getSecond();
         for ( String filterValue : filterValues ) {
             Expression filterPropertyExpression = new ValueReference( new QName( filterProperty ) );
@@ -161,4 +175,12 @@ private static List<ComparisonOperator> createOperators( Pair<String, List<Strin
         return operators;
     }
 
-}
+    private static boolean propertyIsknown( String filterProperty, Set<QName> propertyNames ) {
+        for ( QName propertyName : propertyNames ) {
+            if ( filterProperty.equals( propertyName.getLocalPart() ) )
+                return true;
+        }
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java b/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java
index bb4446195f..89516aaf82 100644
--- a/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java
+++ b/deegree-layers/deegree-layers-feature/src/test/java/org/deegree/layer/persistence/feature/FilterBuilderTest.java
@@ -8,9 +8,11 @@
 import org.junit.Test;
 import org.mockito.Mockito;
 
+import javax.xml.namespace.QName;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
 import static org.hamcrest.CoreMatchers.is;
@@ -28,7 +30,8 @@ public void testBuildRequestFilter() {
         List<String> filterValues = Collections.singletonList( "one" );
 
         LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
-        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+        Set<QName> propertyNames = createPropertyNames( filterProperty );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery, propertyNames );
 
         assertThat( operatorFilter.getOperator(), instanceOf( PropertyIsEqualTo.class ) );
     }
@@ -38,7 +41,8 @@ public void testBuildRequestFilter_MultipleValues() {
         String filterProperty = "type";
         List<String> filterValues = Arrays.asList( new String[] { "one", "two" } );
         LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
-        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+        Set<QName> propertyNames = createPropertyNames( filterProperty );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery, propertyNames );
 
         assertThat( operatorFilter.getOperator(), instanceOf( Or.class ) );
     }
@@ -48,7 +52,8 @@ public void testBuildRequestFilter_EmptyValues() {
         String filterProperty = "type";
         List<String> filterValues = Collections.emptyList();
         LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
-        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+        Set<QName> propertyNames = createPropertyNames( filterProperty );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery, propertyNames );
 
         assertThat( operatorFilter, is( nullValue() ) );
     }
@@ -56,7 +61,20 @@ public void testBuildRequestFilter_EmptyValues() {
     @Test
     public void testBuildRequestFilter_NullRequestFilter() {
         LayerQuery layerQuery = mockLayerQuery( null );
-        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery );
+        Set<QName> propertyNames = createPropertyNames( "abc" );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery, propertyNames );
+
+        assertThat( operatorFilter, is( nullValue() ) );
+    }
+
+    @Test
+    public void testBuildRequestFilter_PropertyNotKnown() {
+        String filterProperty = "type";
+        List<String> filterValues = Collections.singletonList( "one" );
+
+        LayerQuery layerQuery = mockLayerQuery( filterProperty, filterValues );
+        Set<QName> propertyNames = createPropertyNames( "anothertype" );
+        OperatorFilter operatorFilter = FilterBuilder.buildRequestFilter( layerQuery, propertyNames );
 
         assertThat( operatorFilter, is( nullValue() ) );
     }
@@ -73,4 +91,8 @@ private LayerQuery mockLayerQuery( Pair<String, List<String>> requestFilter ) {
         return layerQueryMock;
     }
 
+    private Set<QName> createPropertyNames( String filterProperty ) {
+        return Collections.singleton( new QName( filterProperty ) );
+    }
+
 }
\ No newline at end of file

From 683e1033ed1e56f20aad46632eb34a1db39c51b6 Mon Sep 17 00:00:00 2001
From: Lyn Elisa Goltz <goltz@lat-lon.de>
Date: Tue, 31 Jan 2017 14:42:43 +0100
Subject: [PATCH 4/4] #230 - added documentation of the new filter feature

---
 .../src/main/asciidoc/webservices.adoc                         | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc b/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc
index 699f33a801..78817a9107 100644
--- a/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc
+++ b/deegree-services/deegree-webservices-handbook/src/main/asciidoc/webservices.adoc
@@ -1460,6 +1460,9 @@ actual extent returned will not be changed!). Use values like 1.1 to
 enlarge the envelope by 5% in each direction (this would be 10% in
 total).
 
+With the two vendorspecific parameter FILTERPROPERTY and FILTERVALUE you can request rendering just a defined list of features. Each feature to be rendered will be identified by the value of a given property. The name of the property is defined by the parameter filterproperty. The name of the property is not qualified so all properties with the given local name will be considered. A list of valid property values will be defined using parameter filtervalue, multiple values must be comma separated. Each layer - or better its underlying data source - requested by a GeMap will evaluated for having a feature with a property with given name and one of the defined values. Just the features matching this filter condition will be rendered. It's quite natural that only layer with an underlying Feature-DataSource can be filtered. The other parameters addressed in the GetMap (e.g. the style) request are not effected by this parameters. If the filter cannot be applied to the layer, e.g. cause it is a raster layer or the data source does not have a matching property, the filter will be ignored. If one the parameters is missing or the value empty, the filter is not applied.
+Example: FILTERPROPERTY=type&FILTERVALUE=stone,wood
+
 [[anchor-xml-request-encoding]]
 ==== XML request encoding