/**
* Copyright (C) 2014 Cohesive Integrations, LLC (info@cohesiveintegrations.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.di2e.ecdr.commons.filter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.di2e.ecdr.commons.constants.SearchConstants;
import org.apache.commons.collections.MapUtils;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import ddf.catalog.data.Metacard;
public class StrictFilterDelegate extends AbstractFilterDelegate<Map<String, String>> {
private static final Logger LOGGER = LoggerFactory.getLogger( StrictFilterDelegate.class );
private static final double DEFAULT_RADIUS_NN = 500000;
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private static final DateTimeFormatter DATE_FORMATTER = ISODateTimeFormat.dateTime();;
private Map<String, String> propertyMap = null;
private Map<String, String> dateTypeMap = null;
private boolean strictMode = false;
public StrictFilterDelegate( boolean strictMode, SupportedGeosOptions supportedGeos, Map<String, String> propMap, Map<String, String> dtMap ) {
super( DEFAULT_RADIUS_NN, supportedGeos );
this.strictMode = strictMode;
propertyMap = propMap;
dateTypeMap = dtMap;
}
@Override
public Map<String, String> handleAnd( List<Map<String, String>> operands ) {
Map<String, String> masterFilter = operands.get( 0 );
if ( operands.size() >= 2 ) {
for ( int i = 1; i < operands.size(); i++ ) {
String masterKeywords = masterFilter.get( SearchConstants.KEYWORD_PARAMETER );
Map<String, String> andedFilter = operands.get( i );
String keyword = andedFilter.get( SearchConstants.KEYWORD_PARAMETER );
if ( keyword != null ) {
if ( keyword.startsWith( "NOT " ) ) {
masterFilter.put( SearchConstants.KEYWORD_PARAMETER, masterKeywords == null ? keyword : "(" + masterKeywords + " " + keyword + ")" );
} else {
masterFilter.put( SearchConstants.KEYWORD_PARAMETER, masterKeywords == null ? keyword : "(" + masterKeywords + " AND " + keyword + ")" );
}
}
String caseSensitive = andedFilter.get( SearchConstants.CASESENSITIVE_PARAMETER );
if ( caseSensitive != null && (caseSensitive.equalsIgnoreCase( "true" ) || caseSensitive.equals( SearchConstants.TRUE_STRING )) ) {
masterFilter.put( SearchConstants.CASESENSITIVE_PARAMETER, SearchConstants.TRUE_STRING );
}
String fuzzyString = andedFilter.get( SearchConstants.FUZZY_PARAMETER );
if ( fuzzyString != null && (fuzzyString.equalsIgnoreCase( "true" ) || fuzzyString.equals( SearchConstants.TRUE_STRING )) ) {
masterFilter.put( SearchConstants.FUZZY_PARAMETER, SearchConstants.TRUE_STRING );
}
// Now add in the Content Types if they exist
String masterContentType = masterFilter.get( Metacard.CONTENT_TYPE );
String andedContentType = andedFilter.get( Metacard.CONTENT_TYPE );
String andedVersion = andedFilter.get( Metacard.CONTENT_TYPE_VERSION );
if ( andedContentType != null ) {
andedFilter.remove( Metacard.CONTENT_TYPE );
if ( masterContentType == null ) {
masterContentType = andedContentType;
if ( !andedContentType.endsWith( "," ) ) {
masterContentType += ":";
}
} else {
masterContentType = masterContentType + "," + andedContentType;
if ( !andedContentType.endsWith( "," ) ) {
masterContentType += ":";
}
}
masterFilter.put( Metacard.CONTENT_TYPE, masterContentType );
}
if ( andedVersion != null ) {
andedFilter.remove( Metacard.CONTENT_TYPE_VERSION );
if ( masterContentType == null || masterContentType.endsWith( "," ) ) {
masterContentType = "*:" + andedVersion;
} else if ( masterContentType.endsWith( ":" ) ) {
masterContentType += andedVersion + ",";
} else {
masterContentType += ":" + andedVersion + ",";
}
masterFilter.put( Metacard.CONTENT_TYPE, masterContentType );
}
masterFilter = combineFilters( masterFilter, andedFilter );
}
}
return masterFilter;
}
@Override
public Map<String, String> handleOr( List<Map<String, String>> operands ) {
Map<String, String> masterFilter = operands.get( 0 );
if ( operands.size() >= 2 ) {
for ( int i = 1; i < operands.size(); i++ ) {
String masterKeywords = masterFilter.get( SearchConstants.KEYWORD_PARAMETER );
Map<String, String> andedFilter = operands.get( i );
String keyword = andedFilter.get( SearchConstants.KEYWORD_PARAMETER );
if ( keyword != null ) {
masterFilter.put( SearchConstants.KEYWORD_PARAMETER, masterKeywords == null ? keyword : "(" + masterKeywords + " OR " + keyword + ")" );
}
String caseSensitive = andedFilter.get( SearchConstants.CASESENSITIVE_PARAMETER );
if ( caseSensitive != null && (caseSensitive.equalsIgnoreCase( "true" ) || caseSensitive.equals( SearchConstants.TRUE_STRING )) ) {
masterFilter.put( SearchConstants.CASESENSITIVE_PARAMETER, SearchConstants.TRUE_STRING );
}
String fuzzy = andedFilter.get( SearchConstants.FUZZY_PARAMETER );
if ( fuzzy != null && (fuzzy.equalsIgnoreCase( "true" ) || fuzzy.equals( SearchConstants.TRUE_STRING )) ) {
masterFilter.put( SearchConstants.FUZZY_PARAMETER, SearchConstants.TRUE_STRING );
}
// Now add in the Content Types if they exist
String masterContentType = masterFilter.get( Metacard.CONTENT_TYPE );
String andedContentType = andedFilter.get( Metacard.CONTENT_TYPE );
if ( andedContentType != null ) {
andedFilter.remove( Metacard.CONTENT_TYPE );
masterContentType = masterContentType == null ? andedContentType : masterContentType + andedContentType;
masterFilter.put( Metacard.CONTENT_TYPE, masterContentType );
}
masterFilter = combineFilters( masterFilter, andedFilter );
}
}
return masterFilter;
}
@Override
public Map<String, String> handleNot( Map<String, String> operand ) {
Map<String, String> filterContainer = new HashMap<String, String>();
String keyword = operand.get( SearchConstants.KEYWORD_PARAMETER );
if ( keyword != null ) {
filterContainer.put( SearchConstants.KEYWORD_PARAMETER, "NOT " + keyword );
}
return filterContainer;
}
@Override
public Map<String, String> handlePropertyLike( String propertyName, String pattern, StringFilterOptions options ) {
return handlePropertyEqualToString( propertyName, pattern, options );
}
@Override
public Map<String, String> handlePropertyEqualToString( String propertyName, String literal, StringFilterOptions options ) {
Map<String, String> filterContainer = new HashMap<String, String>();
if ( handleKeyword( propertyName, literal, filterContainer ) ) {
if ( StringFilterOptions.CASE_SENSITIVE.equals( options ) ) {
filterContainer.put( SearchConstants.CASESENSITIVE_PARAMETER, SearchConstants.TRUE_STRING );
} else if ( StringFilterOptions.FUZZY.equals( options ) ) {
filterContainer.put( SearchConstants.FUZZY_PARAMETER, SearchConstants.TRUE_STRING );
}
} else {
filterContainer.put( MapUtils.getString( propertyMap, propertyName, propertyName ), literal );
}
return filterContainer;
}
@Override
public Map<String, String> handlePropertyIsEqualToNumber( String propertyName, double literal ) {
Map<String, String> filterContainer = new HashMap<String, String>();
filterContainer.put( MapUtils.getString( propertyMap, propertyName, propertyName ), String.valueOf( literal ) );
return filterContainer;
}
@Override
public Map<String, String> handlePropertyIsNotEqualToString( String propertyName, String literal, boolean isCaseSensitive ) {
failIfStrictMode( "handlePropertyIsNotEqualToString" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyIsNotEqualToNumber( String propertyName, double literal ) {
failIfStrictMode( "handlePropertyIsNotEqualToNumber" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyIsGreaterThanString( String propertyName, String literal, boolean inclusive ) {
failIfStrictMode( "handlePropertyIsGreaterThanString" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyIsGreaterThanNumber( String propertyName, double literal, boolean inclusive ) {
failIfStrictMode( "handlePropertyIsGreaterThanNumber" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyIsLessThanString( String propertyName, String literal, boolean inclusive ) {
failIfStrictMode( "handlePropertyIsLessThanString" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyIsLessThanNumber( String propertyName, double literal, boolean inclusive ) {
failIfStrictMode( "handlePropertyIsLessThanNumber" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handlePropertyBetweenString( String propertyName, String lowerBoundary, String upperBoundary ) {
failIfStrictMode( "handlePropertyBetweenString" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handleNumericRange( String propertyName, double lowerBoundary, double upperBoundary ) {
failIfStrictMode( "handleNumericRange" );
return new HashMap<String, String>();
}
@Override
public Map<String, String> handleTimeDuring( String propertyName, Date start, Date end ) {
Map<String, String> filterContainer = new HashMap<String, String>();
filterContainer.put( SearchConstants.STARTDATE_PARAMETER, DATE_FORMATTER.print( start.getTime() ) );
filterContainer.put( SearchConstants.ENDDATE_PARAMETER, DATE_FORMATTER.print( end.getTime() ) );
filterContainer.put( SearchConstants.DATETYPE_PARAMETER, MapUtils.getString( dateTypeMap, propertyName, propertyName ) );
return filterContainer;
}
@Override
public Map<String, String> handleTimeAfter( String propertyName, Date start, boolean inclusive ) {
Map<String, String> filterContainer = new HashMap<String, String>();
filterContainer.put( SearchConstants.STARTDATE_PARAMETER, DATE_FORMATTER.print( start.getTime() ) );
filterContainer.put( SearchConstants.DATETYPE_PARAMETER, MapUtils.getString( dateTypeMap, propertyName, propertyName ) );
return filterContainer;
}
@Override
public Map<String, String> handleTimeBefore( String propertyName, Date end, boolean inclusive ) {
Map<String, String> filterContainer = new HashMap<String, String>();
filterContainer.put( SearchConstants.ENDDATE_PARAMETER, DATE_FORMATTER.print( end.getTime() ) );
filterContainer.put( SearchConstants.DATETYPE_PARAMETER, MapUtils.getString( dateTypeMap, propertyName, propertyName ) );
return filterContainer;
}
@Override
public Map<String, String> handleTimeNotDuring( String propertyName, Date start, Date end ) {
failIfStrictMode( "handleTimeNotDuring" );
Map<String, String> filterContainer = new HashMap<String, String>();
return filterContainer;
}
@Override
public Map<String, String> handleGeospatial( String propertyName, String wkt, GeospatialFilterOptions options ) {
Map<String, String> filterContainer = new HashMap<String, String>();
if ( GeospatialFilterOptions.BBOX.equals( options ) ) {
try {
WKTReader reader = new WKTReader( GEOMETRY_FACTORY );
Envelope envelope = reader.read( wkt ).getEnvelopeInternal();
filterContainer.put( SearchConstants.BOX_PARAMETER, envelope.getMinX() + "," + envelope.getMinY() + "," + envelope.getMaxX() + "," + envelope.getMaxY() );
} catch ( ParseException e ) {
throw new UnsupportedOperationException( "Bounding box parameter was not formated correctly" );
}
} else {
if ( !GeospatialFilterOptions.INTERSECTS.equals( options ) && !GeospatialFilterOptions.WITHIN.equals( options ) ) {
failIfStrictMode( "handleGeospatial" );
}
if ( isKnownGeometryProperty( propertyName ) ) {
filterContainer.put( SearchConstants.GEOMETRY_PARAMETER, wkt );
return filterContainer;
}
LOGGER.info( "Unsupported geospatial query sent in with wkt[{}], propertyName=[{}] and type=[{}]", wkt, propertyName, options );
failIfStrictMode( "handleGeospatial" );
}
return filterContainer;
}
@Override
public Map<String, String> handleGeospatialDistance( String propertyName, String wkt, double distance, GeospatialDistanceFilterOptions options ) {
Map<String, String> filterContainer = new HashMap<String, String>();
if ( options == null || options.equals( GeospatialDistanceFilterOptions.WITHIN ) ) {
if ( isKnownGeometryProperty( propertyName ) ) {
Point point = getPointFromWkt( wkt );
if ( point != null ) {
filterContainer.put( SearchConstants.LATITUDE_PARAMETER, String.valueOf( point.getY() ) );
filterContainer.put( SearchConstants.LONGITUDE_PARAMETER, String.valueOf( point.getX() ) );
filterContainer.put( SearchConstants.RADIUS_PARAMETER, String.valueOf( distance ) );
return filterContainer;
} else {
LOGGER.debug( "Could not translate WKT into point to use for point raduis query wkt=[{}]", wkt );
}
}
}
LOGGER.info( "Unsupported geospatial distance query sent in with wkt[{}], propertyName=[{}] and type=[{}]", wkt, propertyName, options );
failIfStrictMode( "handleGeospatialDistance" );
return filterContainer;
}
@Override
public Map<String, String> handleXpath( String xpath, String literal, StringFilterOptions options ) {
failIfStrictMode( "handleXpath" );
if ( literal == null ) {
literal = "";
}
Map<String, String> filterContainer = new HashMap<String, String>();
filterContainer.put( SearchConstants.KEYWORD_PARAMETER, "{" + xpath + "}:" + literal );
if ( StringFilterOptions.CASE_SENSITIVE.equals( options ) ) {
filterContainer.put( SearchConstants.CASESENSITIVE_PARAMETER, SearchConstants.TRUE_STRING );
}
if ( StringFilterOptions.FUZZY.equals( options ) ) {
filterContainer.put( SearchConstants.FUZZY_PARAMETER, SearchConstants.TRUE_STRING );
}
return filterContainer;
}
public void failIfStrictMode( String method ) {
if ( strictMode ) {
throw new UnsupportedOperationException( method + " not supported directly, so failing because in strictMode" );
}
}
protected Point getPointFromWkt( String wkt ) {
Point point = null;
try {
WKTReader wktReader = new WKTReader( GEOMETRY_FACTORY );
Geometry geometry = wktReader.read( wkt );
if ( geometry instanceof Point ) {
point = (Point) geometry;
} else {
point = geometry.getCentroid();
LOGGER.debug( "Using the centroid for the single point for the point raduis query for oriingal wkt=[{}]", wkt );
}
} catch ( ParseException e ) {
LOGGER.error( "Could not properly parse the string into a WKT object=[" + wkt + "]", e );
}
return point;
}
protected boolean isKnownGeometryProperty( String propertyName ) {
return Metacard.ANY_GEO.equals( propertyName ) || Metacard.GEOGRAPHY.equals( propertyName );
}
protected boolean handleKeyword( String propertyName, String keyword, Map<String, String> filterContainer ) {
if ( Metacard.ANY_TEXT.equals( propertyName ) && keyword != null ) {
keyword = keyword.trim();
if ( keyword.contains( " " ) ) {
keyword = "\"" + keyword + "\"";
}
filterContainer.put( SearchConstants.KEYWORD_PARAMETER, keyword );
return true;
}
return false;
}
protected Map<String, String> combineFilters( Map<String, String> masterFilter, Map<String, String> andedFilter ) {
Set<String> keys = andedFilter.keySet();
for ( String key : keys ) {
if ( !masterFilter.containsKey( key ) ) {
masterFilter.put( key, andedFilter.get( key ) );
}
}
return masterFilter;
}
}