Java tutorial
/** * Copyright (C) 2014 Cohesive Integrations, LLC (info@cohesiveintegrations.com) * Copyright (C) 2016 Pink Summit, LLC (info@pinksummit.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.querylanguage.basic; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.core.MultivaluedMap; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.opengis.filter.Filter; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.parboiled.Parboiled; import org.parboiled.parserunners.ParseRunner; import org.parboiled.parserunners.RecoveringParseRunner; import org.parboiled.support.ParsingResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import com.vividsolutions.jts.io.WKTWriter; import ddf.catalog.data.Metacard; import ddf.catalog.data.Result; import ddf.catalog.filter.FilterBuilder; import ddf.catalog.filter.impl.SortByImpl; import ddf.catalog.source.UnsupportedQueryException; import net.di2e.ecdr.api.config.SortTypeConfiguration; import net.di2e.ecdr.api.query.QueryConfiguration; import net.di2e.ecdr.api.query.QueryCriteria; import net.di2e.ecdr.api.query.QueryLanguage; import net.di2e.ecdr.commons.CDRMetacard; import net.di2e.ecdr.commons.constants.SearchConstants; import net.di2e.ecdr.commons.query.CDRQueryCriteriaImpl; import net.di2e.ecdr.commons.util.DateTypeMap; import net.di2e.ecdr.commons.util.GeospatialUtils; import net.di2e.ecdr.commons.util.SearchUtils; import net.di2e.ecdr.querylanguage.basic.GeospatialCriteria.SpatialOperator; import net.di2e.ecdr.querylanguage.basic.PropertyCriteria.Operator; import net.di2e.ecdr.querylanguage.basic.keywordparser.ASTNode; import net.di2e.ecdr.querylanguage.basic.keywordparser.KeywordTextParser; public class CDRKeywordQueryLanguage implements QueryLanguage { private static final Logger LOGGER = LoggerFactory.getLogger(CDRKeywordQueryLanguage.class); private static Map<String, String> queryParametersMap = null; private FilterBuilder filterBuilder = null; private List<SortTypeConfiguration> sortTypeConfigurationList = null; private DateTypeMap dateTypeMap = null; public CDRKeywordQueryLanguage(FilterBuilder builder, List<SortTypeConfiguration> sortTypeConfigurations, DateTypeMap dateMap) { filterBuilder = builder; sortTypeConfigurationList = sortTypeConfigurations; dateTypeMap = dateMap; queryParametersMap = new HashMap<String, String>(); queryParametersMap.put(SearchConstants.UID_PARAMETER, "geo:uid"); queryParametersMap.put(SearchConstants.RESOURCE_URI_PARAMETER, "ddf:resource-uri"); queryParametersMap.put(SearchConstants.CASESENSITIVE_PARAMETER, "cdrsx:caseSensitive"); queryParametersMap.put(SearchConstants.CONTENT_COLLECTIONS_PARAMETER, "ecdr:collections"); queryParametersMap.put(SearchConstants.FUZZY_PARAMETER, "ecdr:fuzzy"); queryParametersMap.put(SearchConstants.BOX_PARAMETER, "geo:box"); queryParametersMap.put(SearchConstants.LATITUDE_PARAMETER, "geo:lat"); queryParametersMap.put(SearchConstants.LONGITUDE_PARAMETER, "geo:lon"); queryParametersMap.put(SearchConstants.RADIUS_PARAMETER, "geo:radius"); queryParametersMap.put(SearchConstants.GEOMETRY_PARAMETER, "geo:geometry"); queryParametersMap.put(SearchConstants.POLYGON_PARAMETER, "polygon"); queryParametersMap.put(SearchConstants.GEO_RELATION_PARAMETER, "geo:relation"); queryParametersMap.put(SearchConstants.GEO_NAME_PARAMETER, "geo:name"); queryParametersMap.put(SearchConstants.STARTDATE_PARAMETER, "time:start"); queryParametersMap.put(SearchConstants.ENDDATE_PARAMETER, "time:end"); queryParametersMap.put(SearchConstants.DATETYPE_PARAMETER, "cdrsx:dateType"); queryParametersMap.put(SearchConstants.DATE_RELATION_PARAMETER, "time:relation"); queryParametersMap.put(SearchConstants.GEORSS_RESULT_FORMAT_PARAMETER, "ecdr:georssFormat"); queryParametersMap.put(SearchConstants.CONTENT_TYPE_PARAMETER, "ddf:metadata-content-type"); queryParametersMap.put(SearchConstants.TEXTPATH_PARAMETER, "ecdr:textPath"); queryParametersMap.put(SearchConstants.SORTKEYS_PARAMETER, "sru:sortKeys"); } @Override public String getName() { return SearchConstants.CDR_KEYWORD_QUERY_LANGUAGE; } @Override public String getUrlTemplateParameters() { StringBuilder sb = new StringBuilder(); for (Entry<String, String> entry : queryParametersMap.entrySet()) { sb.append("&" + entry.getKey() + "={" + entry.getValue() + "?}"); } return sb.toString(); } @Override public String getLanguageDescription(QueryConfiguration queryConfig) { // @formatter:off String description = "CDR Keyword Basic Query Language" + System.lineSeparator() + "****************************" + System.lineSeparator() + "Usage: To use the CQL query language specify the '" + getName() + "' in the {cdrs:queryLanguage} parameter placeholder." + System.lineSeparator() + " The CDR Keyword Basic query language supports booleans (AND, OR, NOT) and parenthesis in the {os:searchTerms} parameter value" + System.lineSeparator() + " Additionally the parameters below can be used for temporal, geospatial, property, or enhanced keyword searches" + System.lineSeparator() + System.lineSeparator() + "The examples below are only for the keywords that can be used in the {os:searchTerms}. They can be combined with any of the " + "additional parameters defined in the sections that follow. " + System.lineSeparator() + "Examples: ballpark" + System.lineSeparator() + " ballpark AND goodyear" + System.lineSeparator() + " ballpark AND (goodyear or peoria)" + System.lineSeparator() + " " + System.lineSeparator() + " " + System.lineSeparator() + "**** ID/URI Search Parameters ****" + System.lineSeparator() + System.lineSeparator() + "geo:uid - unique identifier of the record, matches the Metacard.ID field" + System.lineSeparator() + System.lineSeparator() + "ddf:resource-uri - URL encoded resource URI value that will be directly matched on, matches the Metacard.RESOURCE_URI field" + System.lineSeparator() + System.lineSeparator() + System.lineSeparator() + "**** Contextual Search Parameters ****" + System.lineSeparator() + System.lineSeparator() + "cdrsx:caseSensitive - boolean (1 or 0) specifying whether or not the keyword search should be case sensitive" + System.lineSeparator() + " default: 0 (false - case insensitive) " + System.lineSeparator() + System.lineSeparator() + "ecdr:fuzzy - boolean (1 or 0) specifying whether or not the keyword search should be fuzzy (fuzzy allows for slight misspellings or derivations to be found)" + System.lineSeparator() + " default: ${defaultFuzzyCustom} (${defaultFuzzy}) " + System.lineSeparator() + System.lineSeparator() + System.lineSeparator() + "**** Geospatial Search Parameters ****" + System.lineSeparator() + System.lineSeparator() + "geo:box - comma delimited list of lat/lon (deg) bounding box coordinates (geo format: geo:bbox ~ west,south,east,north). " + "This is also commonly referred to by minX, minY, maxX, maxY (where longitude is the X-axis, and latitude is the Y-axis)." + System.lineSeparator() + System.lineSeparator() + "geo:lat/lon - latitude and longitude, respectively, in decimal degrees (typical GPS receiver WGS84 coordinates). Should include a 'radius' parameter " + "that specifies the search radius in meters." + System.lineSeparator() + System.lineSeparator() + "geo:radius - the radius (in meters) parameter, used with the lat and lon parameters, specifies the search distance from this point." + System.lineSeparator() + " default: ${defaultRadius}" + System.lineSeparator() + System.lineSeparator() + "geo:geometry - The geometry is defined using the Well Known Text and supports the following 2D geographic shapes: POINT, LINESTRING, POLYGON, MULTIPOINT, " + "MULTILINESTRING, MULTIPOLYGON (the Geometry shall be expressed using the EPSG:4326e)" + System.lineSeparator() + " examples: POINT(1 5)" + System.lineSeparator() + " POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2,2 3,3 3,3 2,2 2))" + System.lineSeparator() + System.lineSeparator() + "geo:polygon - (deprecated) polygon defined as comma separated latitude, longitude pairs, in clockwise order, with the last point being the same as the first " + "in order to close the polygon." + System.lineSeparator() + " example: 45.256,-110.45,46.46,-109.48,43.84,-109.86,45.256,-110.45" + System.lineSeparator() + System.lineSeparator() + "geo:relation - spatial operator for the relation to the result set " + System.lineSeparator() + " default: intersects" + System.lineSeparator() + " allowedValues: 'intersects', 'contains', 'disjoint'" + System.lineSeparator() + System.lineSeparator() + "geo:name - A string describing the location (place name) to perform the search " + System.lineSeparator() + " examples: Washington DC" + System.lineSeparator() + " Baltimore, MD" + System.lineSeparator() + System.lineSeparator() + "**** Temporal Search Parameters ****" + System.lineSeparator() + System.lineSeparator() + "time:start - replaced with a string of the beginning of the time slice of the search (RFC-3339 - Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). " + "Default value of \"1970-01-01T00:00:00Z\" is used when {time:end} is indicated but {time:start} is not specified." + System.lineSeparator() + System.lineSeparator() + "time:end - replaced with a string of the ending of the time slice of the search (RFC-3339 - Date and Time format, i.e. YYYY-MM-DDTHH:mm:ssZ). " + "Current GMT date/time is used when {time:start} is specified but not {time:end}." + System.lineSeparator() + System.lineSeparator() + "time:relation - temporal operation for the relation to the result set" + System.lineSeparator() + " default: intersects" + System.lineSeparator() + " allowedValues: 'intersects', 'contains', 'during', 'disjoint', 'equals'" + System.lineSeparator() + System.lineSeparator() + "cdrsx:dateType - the date type to compare against" + System.lineSeparator() + " default: ${defaultDateType}" + System.lineSeparator() + " allowedValues: ${dateTypeValues}" + System.lineSeparator() + System.lineSeparator() + System.lineSeparator() + "**** Content Collections Search Parameters ****" + System.lineSeparator() + System.lineSeparator() + "ecdr:collections - a comma separated list of content collections to search over. list of content collections can be retrieved by using the Describe spec" + System.lineSeparator() + System.lineSeparator() + System.lineSeparator() + "**** Other Parameters ****" + System.lineSeparator() + System.lineSeparator() + "ecdr:georssFormat - specifies how to return the results that include geospatial data, can be as GML or as Simple GeoRSS" + System.lineSeparator() + " allowedValues: 'simple', 'gml'" + System.lineSeparator() + System.lineSeparator() + "ddf:metadata-content-type - comma separate list that maps to the Metacard.CONTENT_TYPE attribute" + System.lineSeparator() + System.lineSeparator() + "ecdr:textPath - comma separated list of text paths (XPath-like) values to be searched over" + System.lineSeparator() + " example: /ddms:Resource/subtitle (this would return all records that contain an element of subtitle under the ddms:Resource root element" + System.lineSeparator() + System.lineSeparator() + System.lineSeparator() + "**** Sort Order ****" + System.lineSeparator() + System.lineSeparator() + "sru:sortKeys - space-separated list of sort keys, with individual sort keys comprised of a comma-separated sequence of " + "sub-parameters in the order listed below." + System.lineSeparator() + " path - Mandatory. An XPath expression for a tagpath to be used in the sort (wildcards '*' may be supported, see allowed values)" + System.lineSeparator() + " sortSchema - Optional. A short name for a URI identifying an XML schema to which the XPath expression applies" + System.lineSeparator() + " ascending - Optional. Boolean, default 'true'." + System.lineSeparator() + " caseSensitive - Optional. Boolean, default 'false'." + System.lineSeparator() + " missingValue - Optional. Default is 'highValue'." + System.lineSeparator() + " examples: Sort by relevance - score,relevance" + System.lineSeparator() + " Sort by updated time descending - entry/date,,false " + System.lineSeparator() + " Sort by distance - distance,cdrsx" + System.lineSeparator() + " 'path' allowedValues: " + SearchUtils.getAllowedSortValues(sortTypeConfigurationList) + System.lineSeparator(); // @formatter:on boolean fuzzy = queryConfig.isDefaultFuzzySearch(); description = StringUtils.replace(description, "${defaultFuzzy}", String.valueOf(fuzzy), 1); description = StringUtils.replace(description, "${defaultFuzzyCustom}", fuzzy ? SearchConstants.TRUE_STRING : SearchConstants.FALSE_STRING, 1); description = StringUtils.replace(description, "${defaultRadius}", String.valueOf(queryConfig.getDefaultRadius()), 1); description = StringUtils.replace(description, "${defaultDateType}", queryConfig.getDefaultDateType(), 1); description = StringUtils.replace(description, "${dateTypeValues}", dateTypeMap.keySet().toString(), 1); return description; } @Override public boolean isValidQuery(MultivaluedMap<String, String> queryParameters, boolean strict) { boolean isValid = true; //if ( strict ) { // Todo fill this out // queryParameters.get //} return isValid; } @Override public QueryCriteria getQueryCriteria(MultivaluedMap<String, String> queryParameters, QueryConfiguration queryConfig) throws UnsupportedQueryException { try { LOGGER.debug("Parsing query using the CDRKeywordQueryLanguage parser"); List<Filter> filters = new ArrayList<Filter>(); SortBy sortBy = SearchUtils.getSortBy(queryParameters.getFirst(SearchConstants.SORTKEYS_PARAMETER), sortTypeConfigurationList, true); StringBuilder humanReadableQuery = new StringBuilder(); boolean defaultFuzzySearch = queryConfig.isDefaultFuzzySearch(); double defaultRadius = queryConfig.getDefaultRadius(); String defaultDateType = queryConfig.getDefaultDateType(); // keyword parameters TextualCriteria textualCriteria = getTextualCriteria(queryParameters, defaultFuzzySearch); if (textualCriteria != null) { boolean fuzzy = textualCriteria.isFuzzy(); LOGGER.debug( "Attempting to create a Contextual filter with params keywords=[{}], isCaseSensitive=[{}], fuzzy=[{}]", textualCriteria.getKeywords(), textualCriteria.isCaseSensitive(), fuzzy); Filter filter = getContextualFilter(textualCriteria.getKeywords(), textualCriteria.isCaseSensitive(), fuzzy, humanReadableQuery); SearchUtils.addFilter(filters, filter); if (sortBy == null) { sortBy = new SortByImpl(Result.RELEVANCE, SortOrder.DESCENDING); } } // Geospatial query parameters GeospatialCriteria geoCriteria = createGeospatialCriteria( queryParameters.getFirst(SearchConstants.RADIUS_PARAMETER), queryParameters.getFirst(SearchConstants.LATITUDE_PARAMETER), queryParameters.getFirst(SearchConstants.LONGITUDE_PARAMETER), queryParameters.getFirst(SearchConstants.BOX_PARAMETER), queryParameters.getFirst(SearchConstants.GEOMETRY_PARAMETER), queryParameters.getFirst(SearchConstants.POLYGON_PARAMETER), queryParameters.getFirst(SearchConstants.GEO_RELATION_PARAMETER), defaultRadius); if (geoCriteria != null) { LOGGER.debug( "Attempting to create a Geospatial filter with params radius=[{}], latitude=[{}], longitude=[{}], geometry=[{}]", geoCriteria.getRadius(), geoCriteria.getLatitude(), geoCriteria.getLongitude(), geoCriteria.getGeometryWKT()); Filter filter = getGeoFilter(geoCriteria.getRadius(), geoCriteria.getLatitude(), geoCriteria.getLongitude(), geoCriteria.isBBox(), geoCriteria.getGeometryWKT(), geoCriteria.getSpatialOperator(), humanReadableQuery); SearchUtils.addFilter(filters, filter); if (sortBy == null) { sortBy = new SortByImpl(Result.DISTANCE, SortOrder.ASCENDING); } } // Temporal Criteria TemporalCriteria temporalCriteria = createTemporalCriteria( queryParameters.getFirst(SearchConstants.STARTDATE_PARAMETER), queryParameters.getFirst(SearchConstants.ENDDATE_PARAMETER), queryParameters.getFirst(SearchConstants.DATETYPE_PARAMETER), humanReadableQuery, defaultDateType); if (temporalCriteria != null) { LOGGER.debug( "Attempting to create a Temporal filter with params startDate=[{}], endDate=[{}], dateType=[{}]", temporalCriteria.getStartDate(), temporalCriteria.getEndDate(), temporalCriteria.getDateType()); Filter filter = getTemporalFilter(temporalCriteria.getStartDate(), temporalCriteria.getEndDate(), temporalCriteria.getDateType(), humanReadableQuery); SearchUtils.addFilter(filters, filter); } // Property Criteria List<PropertyCriteria> propertyCriteriaList = getPropertyCriteria(queryParameters, queryConfig.getParameterExtensionMap()); if (propertyCriteriaList != null && !propertyCriteriaList.isEmpty()) { for (PropertyCriteria propCriteria : propertyCriteriaList) { LOGGER.debug( "Attempting to create a Property filter with params property=[{}], value=[{}], operator=[{}]", propCriteria.getProperty(), propCriteria.getValue(), propCriteria.getOperator()); Filter filter = getPropertyFilter(propCriteria.getProperty(), propCriteria.getValue(), propCriteria.getOperator(), humanReadableQuery); SearchUtils.addFilter(filters, filter); } } if (filters.isEmpty()) { throw new UnsupportedQueryException( "There was no valid search criteria presented from the user, cannot complete search"); } SearchUtils.logSort(sortBy); return new CDRQueryCriteriaImpl(filterBuilder.allOf(filters), sortBy, humanReadableQuery.toString(), true, queryParameters, new HashMap<String, String>(), queryConfig); } catch (Exception e) { LOGGER.warn(e.getMessage(), e); if (e instanceof UnsupportedQueryException) { throw (UnsupportedQueryException) e; } throw new UnsupportedQueryException("Could not create query criteria from provided query parmaeters", e); } } protected TextualCriteria getTextualCriteria(MultivaluedMap<String, String> queryParameters, boolean defaultFuzzySearch) throws UnsupportedQueryException { String words = queryParameters.getFirst(SearchConstants.KEYWORD_PARAMETER); TextualCriteria textualCriteria = null; if (StringUtils.isNotBlank(words)) { String stringFuzzy = queryParameters.getFirst(SearchConstants.FUZZY_PARAMETER); LOGGER.debug("Attempting to set 'fuzzy' value from request [{}]", stringFuzzy); Boolean fuzzy = SearchUtils.getBoolean(stringFuzzy, null); if (fuzzy == null) { LOGGER.debug("The 'fuzzy' parameter was not specified, defaulting value to [{}]", defaultFuzzySearch); fuzzy = defaultFuzzySearch; } String caseSensitiveString = queryParameters.getFirst(SearchConstants.CASESENSITIVE_PARAMETER); LOGGER.debug("Attempting to set '{}' value from request [{}], will default to false if not boolean", SearchConstants.CASESENSITIVE_PARAMETER, caseSensitiveString); textualCriteria = new TextualCriteria(words, SearchUtils.getBoolean(caseSensitiveString, Boolean.FALSE), fuzzy); } return textualCriteria; } protected Filter getContextualFilter(String keywords, boolean caseSensitive, boolean fuzzy, StringBuilder humanReadableQuery) throws UnsupportedQueryException { Filter filter = null; if (keywords != null) { KeywordTextParser keywordParser = Parboiled.createParser(KeywordTextParser.class); ParseRunner<ASTNode> runner = new RecoveringParseRunner<ASTNode>(keywordParser.inputPhrase()); ParsingResult<ASTNode> parsingResult = runner.run(keywords); if (!parsingResult.hasErrors()) { try { filter = getFilterFromASTNode(parsingResult.resultValue, caseSensitive, fuzzy); } catch (IllegalStateException e) { throw new UnsupportedQueryException("searchTerms parameter [" + keywords + "] was invalid and resulted in the error: " + e.getMessage()); } } else { throw new UnsupportedQueryException( "searchTerms parameter [" + keywords + "] was invalid and resulted in the error: " + parsingResult.parseErrors.get(0).getErrorMessage()); } humanReadableQuery.append(" " + SearchConstants.KEYWORD_PARAMETER + "=[" + keywords + "] " + SearchConstants.CASESENSITIVE_PARAMETER + "=[" + caseSensitive + "] " + SearchConstants.FUZZY_PARAMETER + "=[" + fuzzy + "]"); } return filter; } protected Filter getFilterFromASTNode(ASTNode astNode, boolean caseSensitive, boolean fuzzy) { if (astNode.isKeyword()) { String keyword = astNode.getKeyword(); // this means it is an Text Path if (keyword.startsWith("{") && keyword.contains("}:")) { int endXpath = keyword.lastIndexOf("}:"); String xpath = keyword.substring(1, endXpath); String literal = keyword.substring(endXpath + 2); if (literal.trim().isEmpty()) { return filterBuilder.xpath(xpath).exists(); } else { if (fuzzy) { return filterBuilder.xpath(xpath).like().fuzzyText(literal); } else if (caseSensitive) { return filterBuilder.xpath(xpath).like().caseSensitiveText(literal); } else { return filterBuilder.xpath(xpath).like().text(literal); } } } else { if (fuzzy) { return filterBuilder.attribute(Metacard.ANY_TEXT).like().fuzzyText(astNode.getKeyword()); } else if (caseSensitive) { return filterBuilder.attribute(Metacard.ANY_TEXT).like() .caseSensitiveText(astNode.getKeyword()); } else { return filterBuilder.attribute(Metacard.ANY_TEXT).like().text(astNode.getKeyword()); } } } else if (astNode.isOperator()) { switch (astNode.getOperator()) { case AND: return filterBuilder.allOf(getFilterFromASTNode(astNode.left(), caseSensitive, fuzzy), getFilterFromASTNode(astNode.right(), caseSensitive, fuzzy)); case OR: return filterBuilder.anyOf(getFilterFromASTNode(astNode.left(), caseSensitive, fuzzy), getFilterFromASTNode(astNode.right(), caseSensitive, fuzzy)); case NOT: // since NOT really means AND NOT return filterBuilder.allOf(getFilterFromASTNode(astNode.left(), caseSensitive, fuzzy), filterBuilder.not(getFilterFromASTNode(astNode.right(), caseSensitive, fuzzy))); default: throw new IllegalStateException("Unable to generate Filter from invalid OperatorASTNode."); } } throw new IllegalStateException( "Unable to generate Filter from ASTNode. Found invalid ASTNode in the tree"); } protected GeospatialCriteria createGeospatialCriteria(String rad, String lat, String lon, String box, String geom, String polygon, String geoRelation, double defaultRadius) throws UnsupportedQueryException { GeospatialCriteria geoCriteria = null; if (StringUtils.isNotBlank(box)) { try { String[] bboxArray = box.split(" |,\\p{Space}?"); if (bboxArray.length != 3) { double minX = NumberUtils.createDouble(bboxArray[0]); double minY = NumberUtils.createDouble(bboxArray[1]); double maxX = NumberUtils.createDouble(bboxArray[2]); double maxY = NumberUtils.createDouble(bboxArray[3]); geoCriteria = new GeospatialCriteria(minX, minY, maxX, maxY); } else { throw new UnsupportedQueryException("Invalid values found for bbox [" + box + "]"); } } catch (NumberFormatException e) { LOGGER.warn("Invalid values found for bbox [{}]. Resulted in exception: {}", box, e.getMessage()); throw new UnsupportedQueryException( "Invalid values found for bbox [" + box + "], values must be numeric."); } // Only check lat and lon. If Radius is blank is should be defaulted } else if (StringUtils.isNotBlank(lat) && StringUtils.isNotBlank(lon)) { try { double longitude = NumberUtils.createDouble(lon); double latitude = NumberUtils.createDouble(lat); double radius = StringUtils.isNotBlank(rad) ? NumberUtils.createDouble(rad) : defaultRadius; geoCriteria = new GeospatialCriteria(latitude, longitude, radius); } catch (NumberFormatException e) { LOGGER.warn( "Invalid Number found for lat [{}], lon [{}], and/or radius [{}]. Resulted in exception: {}", lat, lon, rad, e.getMessage()); throw new UnsupportedQueryException("Invalid Number found for lat [" + lat + "], lon [" + lon + "], and/or radius [" + rad + "]."); } } else if (StringUtils.isNotBlank(geom)) { try { WKTReader reader = new WKTReader(); reader.read(geom); } catch (ParseException e) { LOGGER.warn("The following is not a valid WKT String: {}", geom); throw new UnsupportedQueryException("Invalid WKT, cannot create geospatial query."); } geoCriteria = new GeospatialCriteria(geom); } else if (StringUtils.isNotBlank(polygon)) { String wkt = GeospatialUtils.polygonToWKT(polygon); try { WKTReader reader = new WKTReader(); reader.read(wkt); } catch (ParseException e) { LOGGER.warn("The following is not a valid WKT String: {}", wkt); throw new UnsupportedQueryException("Invalid WKT, cannot create geospatial query."); } geoCriteria = new GeospatialCriteria(wkt); } if (geoCriteria != null && geoRelation != null) { SpatialOperator spatialOp = SearchUtils.enumEqualsIgnoreCase(SpatialOperator.class, geoRelation); if (spatialOp != null) { geoCriteria.setSpatialOperator(spatialOp); } } return geoCriteria; } protected Filter getGeoFilter(Double radius, Double latitude, Double longitude, boolean isBbox, String geometry, SpatialOperator operator, StringBuilder humanReadableQuery) throws UnsupportedQueryException { Filter filter = null; if (latitude != null && longitude != null && radius != null) { String wkt = WKTWriter.toPoint(new Coordinate(longitude, latitude)); filter = filterBuilder.attribute(Metacard.ANY_GEO).withinBuffer().wkt(wkt, radius); humanReadableQuery.append(" " + SearchConstants.LATITUDE_PARAMETER + "=[" + latitude + "] " + SearchConstants.LONGITUDE_PARAMETER + "=[" + longitude + "] " + SearchConstants.RADIUS_PARAMETER + "=[" + radius + "]"); } else { filter = getGeoFilter(operator, geometry, isBbox ? SearchConstants.BOX_PARAMETER : SearchConstants.GEOMETRY_PARAMETER, humanReadableQuery); } return filter; } protected Filter getGeoFilter(SpatialOperator operator, String wkt, String geoParameter, StringBuilder humanReadableQuery) throws UnsupportedQueryException { Filter filter = null; if (wkt != null) { if (operator != null) { switch (operator) { case Contains: filter = filterBuilder.attribute(Metacard.ANY_GEO).within().wkt(wkt); break; case Disjoint: throw new UnsupportedQueryException("Geospatial disjoint query is not currently supported"); case Within: filter = filterBuilder.attribute(Metacard.ANY_GEO).containing().wkt(wkt); break; case Overlaps: default: filter = filterBuilder.attribute(Metacard.ANY_GEO).intersecting().wkt(wkt); break; } humanReadableQuery.append(" " + geoParameter + "=[" + wkt + "] " + SearchConstants.GEO_RELATION_PARAMETER + "=[" + operator.toString().toLowerCase() + "]"); } else { filter = filterBuilder.attribute(Metacard.ANY_GEO).intersecting().wkt(wkt); humanReadableQuery.append(" " + geoParameter + "=[" + wkt + "]"); } } return filter; } protected TemporalCriteria createTemporalCriteria(String start, String end, String type, StringBuilder humanReadableQuery, String defaultDateType) throws UnsupportedQueryException { TemporalCriteria temporalCriteria = null; if (StringUtils.isNotBlank(start) || StringUtils.isNotBlank(end)) { Date startDate = SearchUtils.parseDate(start); Date endDate = SearchUtils.parseDate(end); if (startDate != null && endDate != null) { if (startDate.after(endDate)) { throw new UnsupportedQueryException( "Start date value [" + startDate + "] cannot be after endDate [" + endDate + "]"); } } String dateType = null; LOGGER.debug("Getting date type name for type [{}]", type); if (StringUtils.isNotBlank(type)) { if (dateTypeMap.containsKey(type)) { dateType = dateTypeMap.getMappedValue(type); LOGGER.debug( "Date type value received in map for request value [{}], setting internal query value to [{}]", type, dateType); } else { String message = "Date type value not found in map for type [" + type + "], defaulting internal query value to [" + dateType + "]"; LOGGER.warn(message); throw new UnsupportedQueryException(message); } } else { dateType = dateTypeMap.getMappedValue(defaultDateType); LOGGER.debug( "Date type value was not specified in request, defaulting internal query value to [{}]", dateType); } temporalCriteria = new TemporalCriteria(startDate, endDate, dateType); } return temporalCriteria; } protected Filter getTemporalFilter(Date startDate, Date endDate, String type, StringBuilder humanReadableQueryBuilder) throws UnsupportedQueryException { Filter filter = null; if (startDate != null || endDate != null) { if (startDate != null && endDate != null) { if (startDate.after(endDate)) { throw new UnsupportedQueryException( "Start date value [" + startDate + "] cannot be after endDate [" + endDate + "]"); } filter = filterBuilder.attribute(type).during().dates(startDate, endDate); humanReadableQueryBuilder.append(" " + SearchConstants.STARTDATE_PARAMETER + "=[" + startDate + "] " + SearchConstants.ENDDATE_PARAMETER + "=[" + endDate + "] " + SearchConstants.DATETYPE_PARAMETER + "=[" + type + "]"); } else if (startDate != null) { filter = filterBuilder.attribute(type).after().date(startDate); humanReadableQueryBuilder.append(" " + SearchConstants.STARTDATE_PARAMETER + "=[" + startDate + "] " + SearchConstants.DATETYPE_PARAMETER + "=[" + type + "]"); } else if (endDate != null) { filter = filterBuilder.attribute(type).before().date(endDate); humanReadableQueryBuilder.append(" " + SearchConstants.ENDDATE_PARAMETER + "=[" + endDate + "] " + SearchConstants.DATETYPE_PARAMETER + "=[" + type + "]"); } } return filter; } protected List<PropertyCriteria> getPropertyCriteria(MultivaluedMap<String, String> queryParameters, Map<String, String> parameterExtensionMap) { List<PropertyCriteria> criteriaList = new ArrayList<PropertyCriteria>(); for (Entry<String, List<String>> entry : queryParameters.entrySet()) { String key = entry.getKey(); List<String> valueList = entry.getValue(); if (CollectionUtils.isNotEmpty(valueList)) { String value = valueList.get(0); if (StringUtils.isNotBlank(value) && parameterExtensionMap.containsKey(key)) { criteriaList.add(new PropertyCriteria(parameterExtensionMap.get(key), value, Operator.LIKE)); } } } if (queryParameters.containsKey(SearchConstants.CONTENT_COLLECTIONS_PARAMETER)) { String contentCollections = queryParameters.getFirst(SearchConstants.CONTENT_COLLECTIONS_PARAMETER); if (StringUtils.isNotEmpty(contentCollections)) { criteriaList.add(new PropertyCriteria(CDRMetacard.METACARD_CONTENT_COLLECTION_ATTRIBUTE, contentCollections, Operator.LIKE)); } } if (queryParameters.containsKey(SearchConstants.RESOURCE_URI_PARAMETER)) { String uriString = queryParameters.getFirst(SearchConstants.RESOURCE_URI_PARAMETER); if (StringUtils.isNotEmpty(uriString)) { if (uriString.startsWith(SearchConstants.DAD_SCHEME)) { try { String uriSubstring = uriString; StringBuilder sb = new StringBuilder(SearchConstants.DAD_SCHEME); uriSubstring = uriSubstring.substring(SearchConstants.DAD_SCHEME.length()); int index = uriSubstring.indexOf('?'); sb.append(URLEncoder.encode(uriSubstring.substring(0, index), "UTF-8")); sb.append("?"); uriSubstring = uriSubstring.substring(index + 1); index = uriSubstring.indexOf('#'); sb.append(URLEncoder.encode(uriSubstring.substring(0, index), "UTF-8")); sb.append("#"); uriSubstring = uriSubstring.substring(index + 1); sb.append(URLEncoder.encode(uriSubstring, "UTF-8")); uriString = sb.toString(); } catch (UnsupportedEncodingException | RuntimeException e) { LOGGER.warn( "Could parse the 'resource-uri' due to exception so falling back to not parsing: " + e.getMessage()); } } criteriaList.add(new PropertyCriteria(Metacard.RESOURCE_URI, uriString, Operator.EQUALS)); } } if (queryParameters.containsKey(SearchConstants.CONTENT_TYPE_PARAMETER)) { String contentTypesString = queryParameters.getFirst(SearchConstants.CONTENT_TYPE_PARAMETER); if (StringUtils.isNotEmpty(contentTypesString)) { criteriaList.add(new PropertyCriteria(Metacard.CONTENT_TYPE, contentTypesString, Operator.EQUALS)); } } return criteriaList; } protected Filter getPropertyFilter(String property, String value, Operator operator, StringBuilder humanReadableQueryBuilder) { Filter filter = null; if (property != null && operator != null) { if (property.equals(Metacard.CONTENT_TYPE)) { filter = getContentTypeFilter(value); humanReadableQueryBuilder.append(" " + property + "=like[" + value + "] "); } else if (property.equals(CDRMetacard.METACARD_CONTENT_COLLECTION_ATTRIBUTE)) { filter = getContentCollectionsFilter(property, value); humanReadableQueryBuilder.append(" " + property + "=like[" + value + "] "); } else { if (Operator.EQUALS.equals(operator)) { filter = filterBuilder.attribute(property).equalTo().text(value); humanReadableQueryBuilder.append(" " + property + "=[" + value + "] "); } else if (Operator.LIKE.equals(operator)) { filter = filterBuilder.attribute(property).like().text(value); humanReadableQueryBuilder.append(" " + property + "=like[" + value + "] "); } } } return filter; } protected Filter getContentCollectionsFilter(String property, String value) { List<Filter> filterList = new ArrayList<Filter>(); String[] collections = value.split(","); for (String collection : collections) { filterList.add(filterBuilder.attribute(property).like().text(collection)); } return filterBuilder.anyOf(filterList); } protected Filter getContentTypeFilter(String value) { List<Filter> filterList = new ArrayList<Filter>(); String[] contentTypes = value.split(","); for (String contentType : contentTypes) { String[] typeAndVersion = contentType.split(":"); String type = typeAndVersion[0]; if (typeAndVersion.length == 1) { filterList.add(filterBuilder.attribute(Metacard.CONTENT_TYPE).like().text(type)); } else { List<Filter> typeVersionPairs = new ArrayList<Filter>(); String[] versions = typeAndVersion[1].split("\\|"); for (String version : versions) { Filter typeFilter = filterBuilder.attribute(Metacard.CONTENT_TYPE).like().text(type); Filter versionFilter = filterBuilder.attribute(Metacard.CONTENT_TYPE_VERSION).like() .text(version); typeVersionPairs.add(filterBuilder.allOf(typeFilter, versionFilter)); } // Check if we had any type/version pairs and 'OR' them together. if (!typeVersionPairs.isEmpty()) { filterList.add(filterBuilder.anyOf(typeVersionPairs)); } } } return filterBuilder.anyOf(filterList); } }