Skip to content

Commit

Permalink
Support collection and product specific filters
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Mar 23, 2017
1 parent c67c1a1 commit c56cef4
Show file tree
Hide file tree
Showing 13 changed files with 473 additions and 45 deletions.
Expand Up @@ -104,7 +104,7 @@ public List<Parameter<?>> getProductSearchParameters(final String parentId) thro


OpenSearchAccess access = getOpenSearchAccess(); OpenSearchAccess access = getOpenSearchAccess();
FeatureType productSchema = access.getProductSource().getSchema(); FeatureType productSchema = access.getProductSource().getSchema();
searchParameters.addAll(getSearchParametersByClass(ProductClass.EO_GENERIC, productSchema)); searchParameters.addAll(getSearchParametersByClass(ProductClass.EOP_GENERIC, productSchema));
if (collectionClass != null) { if (collectionClass != null) {
searchParameters.addAll(getSearchParametersByClass(collectionClass, productSchema)); searchParameters.addAll(getSearchParametersByClass(collectionClass, productSchema));
} }
Expand Down
Expand Up @@ -9,8 +9,13 @@
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;


import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geotools.data.Parameter; import org.geotools.data.Parameter;
import org.geotools.feature.NameImpl;
import org.geotools.referencing.CRS; import org.geotools.referencing.CRS;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.expression.PropertyName;
import org.opengis.referencing.FactoryException; import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.CoordinateReferenceSystem;


Expand Down Expand Up @@ -191,4 +196,30 @@ public static String getParameterName(Parameter p) {
} }
return name; return name;
} }

/**
* Builds the {@link PropertyName} for the given OpenSearch parameter
*
* @param parameter
* @return
*/
public static PropertyName getFilterPropertyFor(FilterFactory2 ff, Parameter<?> parameter) {
String prefix = getParameterPrefix(parameter);
String namespace = null;

if(EO_PREFIX.equals(prefix)) {
namespace = OpenSearchAccess.EO_NAMESPACE;
} else {
// product parameter maybe?
for (OpenSearchAccess.ProductClass pc : OpenSearchAccess.ProductClass.values()) {
if (pc.getPrefix().equals(prefix)) {
namespace = pc.getNamespace();
}
}
}

// the name
String name = getParameterName(parameter);
return ff.property(new NameImpl(namespace, name));
}
} }
Expand Up @@ -4,7 +4,17 @@
*/ */
package org.geoserver.opensearch.eo.kvp; package org.geoserver.opensearch.eo.kvp;


import static org.geoserver.opensearch.eo.OpenSearchParameters.*; import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_BOX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LAT;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_LON;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_NAME;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_RADIUS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.GEO_UID;
import static org.geoserver.opensearch.eo.OpenSearchParameters.SEARCH_TERMS;
import static org.geoserver.opensearch.eo.OpenSearchParameters.START_INDEX;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_END;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_RELATION;
import static org.geoserver.opensearch.eo.OpenSearchParameters.TIME_START;


import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
Expand Down Expand Up @@ -47,6 +57,7 @@
import org.opengis.filter.FilterFactory2; import org.opengis.filter.FilterFactory2;
import org.opengis.filter.MultiValuedFilter.MatchAction; import org.opengis.filter.MultiValuedFilter.MatchAction;
import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName; import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.DWithin; import org.opengis.filter.spatial.DWithin;
import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.CoordinateReferenceSystem;
Expand All @@ -62,6 +73,15 @@
*/ */
public class SearchRequestKvpReader extends KvpRequestReader { public class SearchRequestKvpReader extends KvpRequestReader {


static final Pattern FULL_RANGE_PATTERN = Pattern
.compile("^(\\[|\\])([^,\\[\\]]+),([^,\\\\[\\\\]]+)(\\[|\\])$");

static final Pattern LEFT_RANGE_PATTERN = Pattern.compile("^(\\[|\\])([^,\\[\\]]+)$");

static final Pattern RIGHT_RANGE_PATTERN = Pattern.compile("^([^,\\\\[\\\\]]+)(\\[|\\])$");

static final Pattern COMMA_SEPARATED = Pattern.compile("\\s*,\\s*");

private static final Hints SAFE_CONVERSION_HINTS = new Hints(ConverterFactory.SAFE_CONVERSION, private static final Hints SAFE_CONVERSION_HINTS = new Hints(ConverterFactory.SAFE_CONVERSION,
true); true);


Expand All @@ -78,7 +98,7 @@ public class SearchRequestKvpReader extends KvpRequestReader {
private OpenSearchEoService oseo; private OpenSearchEoService oseo;


private GeoServer gs; private GeoServer gs;

private TimeParser timeParser = new TimeParser(); private TimeParser timeParser = new TimeParser();


public SearchRequestKvpReader(GeoServer gs, OpenSearchEoService service) { public SearchRequestKvpReader(GeoServer gs, OpenSearchEoService service) {
Expand Down Expand Up @@ -182,8 +202,8 @@ private Filter readFilter(Map rawKvp, Collection<Parameter<?>> parameters) throw
filter = buildLatLonDistanceFilter(rawKvp); filter = buildLatLonDistanceFilter(rawKvp);
} else if (GEO_NAME.key.equals(parameter.key)) { } else if (GEO_NAME.key.equals(parameter.key)) {
filter = buildNameDistanceFilter(rawKvp); filter = buildNameDistanceFilter(rawKvp);
} else if (isProductParameter(parameter)) { } else if (isEoParameter(parameter)) {
filter = buildProductFilter(parameter, value); filter = buildEoFilter(parameter, value);
} }
if (filter != null) { if (filter != null) {
filters.add(filter); filters.add(filter);
Expand Down Expand Up @@ -218,7 +238,8 @@ private Filter buildTimeFilter(Map rawKvp) {
} }
if (start == null && rawStart != null) { if (start == null && rawStart != null) {
throw new OWS20Exception( throw new OWS20Exception(
"Invalid expression for start time, use a ISO time or date instead: " + rawStart, "Invalid expression for start time, use a ISO time or date instead: "
+ rawStart,
OWS20Exception.OWSExceptionCode.InvalidParameterValue, TIME_START.key); OWS20Exception.OWSExceptionCode.InvalidParameterValue, TIME_START.key);
} }
if (end == null && rawEnd != null) { if (end == null && rawEnd != null) {
Expand All @@ -236,9 +257,9 @@ private Filter buildTimeFilter(Map rawKvp) {
OWS20Exception.OWSExceptionCode.InvalidParameterValue, "relation"); OWS20Exception.OWSExceptionCode.InvalidParameterValue, "relation");
} }
} }

// default if null // default if null
if(relation == null) { if (relation == null) {
relation = DateRelation.intersects; relation = DateRelation.intersects;
} }


Expand Down Expand Up @@ -288,31 +309,32 @@ private Filter buildTimeFilter(Map rawKvp) {


case intersects: case intersects:
// the resource overlaps the specified range // the resource overlaps the specified range
fStart = FF.or(FF.greaterOrEqual(endProperty, FF.literal(start)), FF.isNull(endProperty)); fStart = FF.or(FF.greaterOrEqual(endProperty, FF.literal(start)),
FF.isNull(endProperty));
fEnd = FF.or(FF.lessOrEqual(startProperty, FF.literal(end)), FF.isNull(startProperty)); fEnd = FF.or(FF.lessOrEqual(startProperty, FF.literal(end)), FF.isNull(startProperty));

if(start == null) { if (start == null) {
return fEnd; return fEnd;
} else if(end == null) { } else if (end == null) {
return fStart; return fStart;
} else { } else {
return FF.and(fStart, fEnd); return FF.and(fStart, fEnd);
} }

case equals: case equals:
// the resource has the same range as requested // the resource has the same range as requested
if(start == null) { if (start == null) {
fStart = FF.isNull(startProperty); fStart = FF.isNull(startProperty);
} else { } else {
fStart = FF.equals(startProperty, FF.literal(start)); fStart = FF.equals(startProperty, FF.literal(start));
} }
if(end == null) { if (end == null) {
fEnd = FF.isNull(endProperty); fEnd = FF.isNull(endProperty);
} else { } else {
fEnd = FF.equals(endProperty, FF.literal(end)); fEnd = FF.equals(endProperty, FF.literal(end));
} }
return FF.and(fStart, fEnd); return FF.and(fStart, fEnd);

default: default:
throw new RuntimeException("Time relation of type " + relation + " not covered yet"); throw new RuntimeException("Time relation of type " + relation + " not covered yet");
} }
Expand Down Expand Up @@ -410,18 +432,24 @@ private <T> T getParameter(String key, Object value, Class<T> targetClass) {
T converted = Converters.convert(value, targetClass, SAFE_CONVERSION_HINTS); T converted = Converters.convert(value, targetClass, SAFE_CONVERSION_HINTS);
if (converted == null) { if (converted == null) {
throw new OWS20Exception( throw new OWS20Exception(
key + " is empty of cannot be converted to a " + targetClass.getSimpleName(), key + " cannot be converted to a " + targetClass.getSimpleName(),
OWSExceptionCode.InvalidParameterValue); OWSExceptionCode.InvalidParameterValue, key);
} }
return converted; return converted;
} }


private boolean isProductParameter(Parameter parameter) { private boolean isEoParameter(Parameter parameter) {
String prefix = OpenSearchParameters.getParameterPrefix(parameter); String prefix = OpenSearchParameters.getParameterPrefix(parameter);
if (prefix == null) { if (prefix == null) {
return false; return false;
} }


// collectin parameter?
if (prefix.equals(OpenSearchParameters.EO_PREFIX)) {
return true;
}

// product parameter?
for (OpenSearchAccess.ProductClass pc : OpenSearchAccess.ProductClass.values()) { for (OpenSearchAccess.ProductClass pc : OpenSearchAccess.ProductClass.values()) {
if (pc.getPrefix().equals(prefix)) { if (pc.getPrefix().equals(prefix)) {
return true; return true;
Expand All @@ -431,8 +459,101 @@ private boolean isProductParameter(Parameter parameter) {
return false; return false;
} }


private Filter buildProductFilter(Parameter<?> parameter, Object value) { private Filter buildEoFilter(Parameter<?> parameter, Object value) {
throw new UnsupportedOperationException("Not implemented yet"); // support two types of filters, equality and range filters
Class<?> type = parameter.getType();

PropertyName pn = OpenSearchParameters.getFilterPropertyFor(FF, parameter);

// for numeric and range parameters check the range syntax
String input = (String) value;
if (Date.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type)) {
Matcher matcher;
if ((matcher = FULL_RANGE_PATTERN.matcher(input)).matches()) {
String opening = matcher.group(1);
String s1 = matcher.group(2);
String s2 = matcher.group(3);
String closing = matcher.group(4);

// parse and check they are actually valid numbers/dates
Object v1 = parseParameter(parameter, s1);
Object v2 = parseParameter(parameter, s2);

Filter f1, f2;
Literal l1 = FF.literal(v1);
Literal l2 = FF.literal(v2);
if ("[".equals(opening)) {
f1 = FF.greaterOrEqual(pn, l1);
} else {
f1 = FF.greater(pn, l1);
}
if ("]".equals(closing)) {
f2 = FF.lessOrEqual(pn, l2);
} else {
f2 = FF.less(pn, l2);
}

return FF.and(f1, f2);
} else if ((matcher = LEFT_RANGE_PATTERN.matcher(input)).matches()) {
String opening = matcher.group(1);
String s1 = matcher.group(2);

// parse and check they are actually valid numbers/dates
Object v1 = parseParameter(parameter, s1);

Literal l1 = FF.literal(v1);
if ("[".equals(opening)) {
return FF.greaterOrEqual(pn, l1);
} else {
return FF.greater(pn, l1);
}
} else if ((matcher = RIGHT_RANGE_PATTERN.matcher(input)).matches()) {
String s2 = matcher.group(1);
String closing = matcher.group(2);

// parse and check they are actually valid numbers/dates
Object v2 = parseParameter(parameter, s2);

Literal l2 = FF.literal(v2);
if ("]".equals(closing)) {
return FF.lessOrEqual(pn, l2);
} else {
return FF.less(pn, l2);
}
}
}

// we got here, it's not a valid range, see if it's a comma separated list vs single value then
if (input.contains(",")) {
String[] splits = COMMA_SEPARATED.split(input);
List<Filter> filters = new ArrayList<>();
for (String split : splits) {
Filter filter = buildEqualityFilter(parameter, pn, split);
filters.add(filter);
}

return FF.or(filters);
} else {
// ok, single equality filter then
Filter filter = buildEqualityFilter(parameter, pn, input);
return filter;
}
}

private Filter buildEqualityFilter(Parameter<?> parameter, PropertyName pn, String input) {
Object converted = parseParameter(parameter, input);
return FF.equal(pn, FF.literal(converted), true);
}

private Object parseParameter(Parameter<?> parameter, String value) {
Object converted = Converters.convert(value, parameter.getType(), SAFE_CONVERSION_HINTS);
if (converted == null) {
throw new OWS20Exception(
value + " of key " + parameter.key + " cannot be converted to a "
+ parameter.getType().getSimpleName(),
OWSExceptionCode.InvalidParameterValue, parameter.key);
}
return converted;
} }


private Collection<Parameter<?>> getSearchParameters(SearchRequest request) throws IOException { private Collection<Parameter<?>> getSearchParameters(SearchRequest request) throws IOException {
Expand Down
Expand Up @@ -219,7 +219,7 @@ private void encodeCollectionEntry(Feature feature, SearchRequest request) {


private void encodeProductEntry(Feature feature, SearchRequest request) { private void encodeProductEntry(Feature feature, SearchRequest request) {
final String identifier = (String) value(feature, final String identifier = (String) value(feature,
OpenSearchAccess.ProductClass.EO_GENERIC.getNamespace(), "identifier"); OpenSearchAccess.ProductClass.EOP_GENERIC.getNamespace(), "identifier");


// encode the generic contents // encode the generic contents
String productIdentifierLink = buildProductIdentifierLink(identifier, request); String productIdentifierLink = buildProductIdentifierLink(identifier, request);
Expand Down
Expand Up @@ -18,6 +18,7 @@
import org.geoserver.opensearch.eo.OSEODescription; import org.geoserver.opensearch.eo.OSEODescription;
import org.geoserver.opensearch.eo.OSEOInfo; import org.geoserver.opensearch.eo.OSEOInfo;
import org.geoserver.opensearch.eo.OpenSearchParameters; import org.geoserver.opensearch.eo.OpenSearchParameters;
import org.geoserver.opensearch.eo.store.OpenSearchAccess;
import org.geoserver.ows.URLMangler.URLType; import org.geoserver.ows.URLMangler.URLType;
import org.geoserver.ows.util.ResponseUtils; import org.geoserver.ows.util.ResponseUtils;
import org.geotools.data.Parameter; import org.geotools.data.Parameter;
Expand Down Expand Up @@ -45,14 +46,18 @@ public OSEODescriptionTranslator(ContentHandler contentHandler) {
@Override @Override
public void encode(Object o) throws IllegalArgumentException { public void encode(Object o) throws IllegalArgumentException {
OSEODescription description = (OSEODescription) o; OSEODescription description = (OSEODescription) o;
element("OpenSearchDescription", () -> describeOpenSearch(description), // Map<String, String> namespaces = new LinkedHashMap<>();
attributes("xmlns", "http://a9.com/-/spec/opensearch/1.1/", // namespaces.put("xmlns", "http://a9.com/-/spec/opensearch/1.1/");
"xmlns:param", namespaces.put("xmlns:param",
"http://a9.com/-/spec/opensearch/extensions/parameters/1.0/", // "http://a9.com/-/spec/opensearch/extensions/parameters/1.0/");
"xmlns:geo", "http://a9.com/-/opensearch/extensions/geo/1.0/", // namespaces.put("xmlns:geo", "http://a9.com/-/opensearch/extensions/geo/1.0/");
"xmlns:time", "http://a9.com/-/opensearch/extensions/time/1.0/", // namespaces.put("xmlns:time", "http://a9.com/-/opensearch/extensions/time/1.0/");
"xmlns:eo", "http://a9.com/-/opensearch/extensions/eo/1.0/" // namespaces.put("xmlns:eo", "http://a9.com/-/opensearch/extensions/eo/1.0/");
)); for (OpenSearchAccess.ProductClass pc : OpenSearchAccess.ProductClass.values()) {
namespaces.put("xmlns:" + pc.getPrefix(), pc.getNamespace());
}
element("OpenSearchDescription", () -> describeOpenSearch(description),
attributes(namespaces));
} }


private void describeOpenSearch(OSEODescription description) { private void describeOpenSearch(OSEODescription description) {
Expand Down Expand Up @@ -115,7 +120,8 @@ public String buildResultsUrl(OSEODescription description, String format) {
return spec; return spec;
}).collect(Collectors.joining("&")); }).collect(Collectors.joining("&"));


return appendQueryString(base, paramSpec + "&httpAccept=" + ResponseUtils.urlEncode(format)); return appendQueryString(base,
paramSpec + "&httpAccept=" + ResponseUtils.urlEncode(format));
} }


private void describeParameters(OSEODescription description) { private void describeParameters(OSEODescription description) {
Expand All @@ -142,7 +148,7 @@ private void describeParameters(OSEODescription description) {
Class type = param.getType(); Class type = param.getType();
if (Integer.class == type) { if (Integer.class == type) {
map.put("pattern", "[+-][0-9]+"); map.put("pattern", "[+-][0-9]+");
} else if(Float.class == type || Double.class == type) { } else if (Float.class == type || Double.class == type) {
map.put("pattern", "[-+]?[0-9]*\\.?[0-9]+"); map.put("pattern", "[-+]?[0-9]*\\.?[0-9]+");
} else if (Date.class.isAssignableFrom(type)) { } else if (Date.class.isAssignableFrom(type)) {
map.put("pattern", map.put("pattern",
Expand Down
Expand Up @@ -150,6 +150,10 @@ private FeatureType buildProductFeatureType(DataStore delegate) throws IOExcepti
for (AttributeDescriptor ad : flatSchema.getAttributeDescriptors()) { for (AttributeDescriptor ad : flatSchema.getAttributeDescriptors()) {
String name = ad.getLocalName(); String name = ad.getLocalName();
String namespaceURI = this.namespaceURI; String namespaceURI = this.namespaceURI;
// hack to avoid changing the whole product attributes prefixes from eo to eop
if (name.startsWith(EO_PREFIX)) {
name = "eop" + name.substring(2);
}
for (ProductClass pc : ProductClass.values()) { for (ProductClass pc : ProductClass.values()) {
String prefix = pc.getPrefix(); String prefix = pc.getPrefix();
if (name.startsWith(prefix)) { if (name.startsWith(prefix)) {
Expand Down
Expand Up @@ -10,8 +10,8 @@
import org.geotools.data.DataStore; import org.geotools.data.DataStore;


/** /**
* Handles idiosyncrasies in database table naming, for the moment only lowercase vs uppercase, later we could extend it to handle property names and * Handles idiosyncrasies in database table naming, for the moment only lowercase vs uppercase,
* the like * later we could extend it to handle property names and the like
*/ */
class LowercasingDataStore extends RetypingDataStore { class LowercasingDataStore extends RetypingDataStore {


Expand Down

0 comments on commit c56cef4

Please sign in to comment.