Java tutorial
/** * Copyright (c) Cohesive Integrations, LLC * * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, either version 3 of the License, or any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. A copy of the GNU Lesser General Public License is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. * **/ package net.di2e.ecdr.commons.query.rest.parsers; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.core.MultivaluedMap; import net.di2e.ecdr.commons.constants.BrokerConstants; import net.di2e.ecdr.commons.constants.SearchConstants; import net.di2e.ecdr.commons.query.GeospatialCriteria; import net.di2e.ecdr.commons.query.PropertyCriteria; import net.di2e.ecdr.commons.query.PropertyCriteria.Operator; import net.di2e.ecdr.commons.query.TemporalCriteria; import net.di2e.ecdr.commons.query.TextualCriteria; import net.di2e.ecdr.commons.query.cache.QueryRequestCache; import net.di2e.ecdr.commons.query.util.GeospatialHelper; import net.di2e.ecdr.commons.sort.SortTypeConfiguration; import net.di2e.ecdr.commons.util.SearchUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; import org.joda.time.format.DateTimeParser; import org.joda.time.format.ISODateTimeFormat; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKTReader; import ddf.catalog.data.Metacard; import ddf.catalog.filter.impl.SortByImpl; import ddf.catalog.source.UnsupportedQueryException; public class BasicQueryParser implements QueryParser { private static final Logger LOGGER = LoggerFactory.getLogger(BasicQueryParser.class); private static final int DEFAULT_QUERYID_CACHE_SIZE = 1000; private static final Map<String, String> DATETYPE_MAP = new HashMap<String, String>(); private List<SortTypeConfiguration> sortTypeConfigurationList; static { DATETYPE_MAP.put("created", Metacard.CREATED); DATETYPE_MAP.put("updated", Metacard.MODIFIED); DATETYPE_MAP.put("posted", SearchConstants.POSTED); DATETYPE_MAP.put("infoCutOff", SearchConstants.INFO_CUT_OFF); DATETYPE_MAP.put("validTil", SearchConstants.VALID_TIL); DATETYPE_MAP.put("temporalCoverage", SearchConstants.TEMPORAL_COVERAGE); DATETYPE_MAP.put("effective", Metacard.EFFECTIVE); } private static final List<String> LANGUAGE_LIST = new ArrayList<String>(); static { LANGUAGE_LIST.add(SearchConstants.CDR_KEYWORD_QUERY_LANGUAGE); LANGUAGE_LIST.add(SearchConstants.CDR_CQL_QUERY_LANGUAGE); } private static DateTimeFormatter formatter; /* * The OpenSearch specification uses RFC 3339, which is a specific profile * of the ISO 8601 standard and corresponds to the second and (as a * "rarely used option") the first parser below. We additionally support the * corresponding ISO 8601 Basic profiles. */ static { DateTimeParser[] parsers = { ISODateTimeFormat.dateTime().getParser(), ISODateTimeFormat.dateTimeNoMillis().getParser(), ISODateTimeFormat.basicDateTime().getParser(), ISODateTimeFormat.basicDateTimeNoMillis().getParser() }; formatter = new DateTimeFormatterBuilder().append(null, parsers).toFormatter(); } private int defaultCount = 100; private long defaultTimeoutMillis = 300000; private String defaultDateType = "effective"; private double defaultRadius = 10000; private String defaultResponseFormat = "atom"; private boolean defaultFuzzySearch = true; private Map<String, String> parameterExtensionMap = SearchUtils.convertToMap("uid=id"); private List<String> parameterPropertyList = new ArrayList<>(); private QueryRequestCache queryRequestCache = null; public BasicQueryParser(List<SortTypeConfiguration> sortTypeConfigurations) { sortTypeConfigurationList = sortTypeConfigurations; queryRequestCache = new QueryRequestCache(DEFAULT_QUERYID_CACHE_SIZE); parameterPropertyList.add("oid"); parameterPropertyList.add("path"); } public void setDefaultResponseFormat(String defaultFormat) { defaultResponseFormat = defaultFormat; LOGGER.debug("Updating the default response format to [{}]", defaultResponseFormat); } public void setDefaultCount(int count) { if (count > 0) { defaultCount = count; LOGGER.debug("Updating the default count to [{}]", defaultCount); } else { LOGGER.warn( "Could not update the default count due to invalid value [{}], the default count will stay at [{}]", count, defaultCount); } } public void setDefaultTimeoutSeconds(long timeout) { if (timeout > 0) { defaultTimeoutMillis = timeout * 1000L; LOGGER.debug("Updating the default timeout to [{}] seconds", timeout); } else { LOGGER.warn( "Could not update the default timeout due to invalid integer [{}], the default timeout will stay at [{}] seconds", timeout, defaultTimeoutMillis / 1000); } } public void setDefaultRadiusMeters(double meters) { if (meters > 0) { defaultRadius = meters; LOGGER.debug("Updating the default radius to [{}]", defaultRadius); } else { LOGGER.warn( "Could not update the default radius due to invalid value [{}], the default radius will stay at [{}]", meters, defaultRadius); } } public void setDefaultDateType(String type) { defaultDateType = type; LOGGER.debug("Updating the default response date type to [{}]", defaultDateType); } public void setDefaultFuzzySearch(boolean fuzzy) { LOGGER.debug("ConfigUpdate: Updating the default fuzzy search from [{}] to [{}]", defaultFuzzySearch, fuzzy); defaultFuzzySearch = fuzzy; } public void setQueryRequestCacheSize(int size) { if (size > -1) { queryRequestCache.updateCacheSize(size); LOGGER.debug("Updating the default query request cache size to [{}]", size); } else { LOGGER.warn("Could not update the default query request cache size due to invalid integer value [{}]", size); } } public void setExtensionMap(List<String> extensionMap) { this.parameterExtensionMap = SearchUtils.convertToMap(extensionMap); } public void setPropertyList(List<String> propertyList) { this.parameterPropertyList = propertyList; } @Override public boolean isValidQuery(MultivaluedMap<String, String> queryParameters, String sourceId) { boolean isValidQuery = true; String queryLang = queryParameters.getFirst(SearchConstants.QUERYLANGUAGE_PARAMETER); if (StringUtils.isNotBlank(queryLang) && !LANGUAGE_LIST.contains(queryLang)) { isValidQuery = false; } else if (!isBooleanNullOrBlank(queryParameters.getFirst(SearchConstants.CASESENSITIVE_PARAMETER))) { isValidQuery = false; } else if (!isBooleanNullOrBlank(queryParameters.getFirst(SearchConstants.STRICTMODE_PARAMETER))) { isValidQuery = false; } else if (!isBooleanNullOrBlank(queryParameters.getFirst(SearchConstants.STATUS_PARAMETER))) { isValidQuery = false; } else if (!isBooleanNullOrBlank(queryParameters.getFirst(SearchConstants.FUZZY_PARAMETER))) { isValidQuery = false; } else { isValidQuery = isUniqueQuery(queryParameters, sourceId); } return isValidQuery; } @Override public Collection<String> getSiteNames(MultivaluedMap<String, String> queryParameters) { List<String> sources = new ArrayList<String>(); return sources; } @Override public boolean isStrictMode(MultivaluedMap<String, String> queryParameters) { String strict = queryParameters.getFirst(SearchConstants.STRICTMODE_PARAMETER); return Boolean.TRUE.equals(getBoolean(strict)); } @Override public boolean isIncludeStatus(MultivaluedMap<String, String> queryParameters) { // Include status is true unless explicitly set to false Boolean boolStatus = getBoolean(queryParameters.getFirst(SearchConstants.STATUS_PARAMETER)); return Boolean.FALSE.equals(boolStatus) ? false : true; } @Override public String getQueryLanguage(MultivaluedMap<String, String> queryParameters) { String queryLanguage = queryParameters.getFirst(SearchConstants.QUERYLANGUAGE_PARAMETER); return queryLanguage == null || queryLanguage.isEmpty() ? null : queryLanguage; } @Override public int getStartIndex(MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { String startIndex = queryParameters.getFirst(SearchConstants.STARTINDEX_PARAMETER); int index = 1; LOGGER.debug("Attempting to set 'startIndex' value from request [{}] to int", startIndex); if (StringUtils.isNotBlank(startIndex)) { try { index = Integer.parseInt(startIndex); } catch (NumberFormatException e) { String message = "Invalid Number found for 'startIndex' [" + startIndex + "]. Resulted in exception: " + e.getMessage(); LOGGER.warn(message); throw new UnsupportedQueryException(message); } } else { LOGGER.debug("'startIndex' parameter was not specified, defaulting value to [{}]", index); } return index < 1 ? 1 : index; } @Override public int getCount(MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { String stringCount = queryParameters.getFirst(SearchConstants.COUNT_PARAMETER); int count = this.defaultCount; LOGGER.debug("Attempting to set 'count' value from request [{}] to int", stringCount); if (StringUtils.isNotBlank(stringCount)) { try { count = Integer.parseInt(stringCount); } catch (NumberFormatException e) { String message = "Invalid Number found for 'count' [" + stringCount + "]. Resulted in exception: " + e.getMessage(); LOGGER.warn(message); throw new UnsupportedQueryException(message); } } else { LOGGER.debug("'count' parameter was not specified, defaulting value to [{}]", count); } return count; } @Override public long getTimeoutMilliseconds(MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { String timeout = queryParameters.getFirst(SearchConstants.TIMEOUT_PARAMETER); long timeoutMilliseconds = this.defaultTimeoutMillis; LOGGER.debug("Attempting to set 'timeout' value from request [" + timeout + "] to long"); if (StringUtils.isNotBlank(timeout)) { try { timeoutMilliseconds = Long.parseLong(timeout); if (timeoutMilliseconds <= 0) { throw new UnsupportedQueryException("The [" + SearchConstants.TIMEOUT_PARAMETER + "] parameter cannot nbe less than 0 and was [" + timeout + "]"); } } catch (NumberFormatException e) { String message = "Invalid Number found for 'timeout' [" + timeout + "]. Resulted in exception: " + e.getMessage(); LOGGER.warn(message); if (isStrictMode(queryParameters)) { throw new UnsupportedQueryException(message); } } } else { LOGGER.debug("'timeout' parameter was not specified, defaulting value to [{}]", timeout); } return timeoutMilliseconds; } @Override public SortBy getSortBy(MultivaluedMap<String, String> queryParameters) { String sortByString = queryParameters.getFirst(SearchConstants.SORTKEYS_PARAMETER); SortBy sortBy = null; if (StringUtils.isNotBlank(sortByString)) { String[] sortValues = sortByString.split(","); String sortKey = sortValues[0]; SortTypeConfiguration sortType = getSortConfiguration(sortKey); if (sortType != null) { String sortAttribute = sortType.getSortAttribute(); SortOrder sortOrder = null; if (sortValues.length >= 3) { if (Boolean.FALSE.toString().equalsIgnoreCase(sortValues[2])) { sortOrder = SortOrder.DESCENDING; } else { sortOrder = SortOrder.ASCENDING; } } else { sortOrder = SortOrder.valueOf(sortType.getSortOrder()); } sortBy = new SortByImpl(sortAttribute, sortOrder); } } return sortBy; } @Override public String getResponseFormat(MultivaluedMap<String, String> queryParameters) { String format = queryParameters.getFirst(SearchConstants.FORMAT_PARAMETER); return StringUtils.isNotBlank(format) ? format : this.defaultResponseFormat; } @Override public GeospatialCriteria getGeospatialCriteria(MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { return 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), isStrictMode(queryParameters)); } @Override public TemporalCriteria getTemporalCriteria(MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { return createTemporalCriteria(queryParameters.getFirst(SearchConstants.STARTDATE_PARAMETER), queryParameters.getFirst(SearchConstants.ENDDATE_PARAMETER), queryParameters.getFirst(SearchConstants.DATETYPE_PARAMETER)); } @Override public TextualCriteria getTextualCriteria(MultivaluedMap<String, String> queryParameters) 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 = getBoolean(stringFuzzy); 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); Boolean caseSensitive = getBoolean(caseSensitiveString); textualCriteria = new TextualCriteria(words, caseSensitive == null ? false : caseSensitive, fuzzy); } return textualCriteria; } @Override public Map<String, Serializable> getQueryProperties(MultivaluedMap<String, String> queryParameters, String sourceId) { Map<String, Serializable> queryProps = new HashMap<String, Serializable>(); String format = queryParameters.getFirst(SearchConstants.FORMAT_PARAMETER); queryProps.put(SearchConstants.FORMAT_PARAMETER, StringUtils.isNotBlank(format) ? format : defaultResponseFormat); for (String key : queryParameters.keySet()) { String value = queryParameters.getFirst(key); if (StringUtils.isNotBlank(value) && parameterPropertyList.contains(key)) { queryProps.put(key, value); } } return queryProps; } @Override public List<PropertyCriteria> getPropertyCriteria(MultivaluedMap<String, String> queryParameters) { List<PropertyCriteria> criteriaList = new ArrayList<PropertyCriteria>(); Set<String> keySet = queryParameters.keySet(); for (String key : keySet) { String value = queryParameters.getFirst(key); if (StringUtils.isNotBlank(value) && parameterExtensionMap.containsKey(key)) { criteriaList.add(new PropertyCriteria(parameterExtensionMap.get(key), value, Operator.LIKE)); } } return criteriaList; } @Override public String getGeoRSSFormat(MultivaluedMap<String, String> queryParameters) { return StringUtils.defaultIfBlank(queryParameters.getFirst(SearchConstants.GEORSS_RESULT_FORMAT_PARAMETER), null); } protected QueryRequestCache getQueryRequestCache() { return queryRequestCache; } protected boolean isUniqueQuery(MultivaluedMap<String, String> queryParameters, String sourceId) { boolean isUniqueQuery = true; isUniqueQuery = queryRequestCache.isQueryIdUnique(queryParameters.getFirst(SearchConstants.OID_PARAMETER)); String path = queryParameters.getFirst(BrokerConstants.PATH_PARAMETER); if (StringUtils.isNotBlank(path)) { String[] pathValues = path.split(","); if (ArrayUtils.contains(pathValues, sourceId)) { isUniqueQuery = false; } } return isUniqueQuery; } protected GeospatialCriteria createGeospatialCriteria(String rad, String lat, String lon, String box, String geom, String polygon, boolean strictMode) throws UnsupportedQueryException { GeospatialCriteria geoCriteria = null; if (StringUtils.isNotBlank(box)) { try { String[] bboxArray = box.split(" |,\\p{Space}?"); if (bboxArray.length != 3) { double minX = Double.parseDouble(bboxArray[0]); double minY = Double.parseDouble(bboxArray[1]); double maxX = Double.parseDouble(bboxArray[2]); double maxY = Double.parseDouble(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()); if (strictMode) { 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 = Double.parseDouble(lon); double latitude = Double.parseDouble(lat); double radius = StringUtils.isNotBlank(rad) ? Double.parseDouble(rad) : this.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()); if (strictMode) { 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 = GeospatialHelper.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); } return geoCriteria; } protected TemporalCriteria createTemporalCriteria(String start, String end, String type) throws UnsupportedQueryException { TemporalCriteria temporalCriteria = null; if (StringUtils.isNotBlank(start) || StringUtils.isNotBlank(end)) { Date startDate = parseDate(start); Date endDate = 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 (DATETYPE_MAP.containsKey(type)) { dateType = DATETYPE_MAP.get(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 = DATETYPE_MAP.get(this.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 Date parseDate(String date) throws UnsupportedQueryException { Date returnDate = null; if (StringUtils.isNotBlank(date)) { try { returnDate = formatter.parseDateTime(date).toDate(); } catch (IllegalArgumentException e) { LOGGER.warn("Could not process date because of invalid format [{}]", date); throw new UnsupportedQueryException("Invalid date format [" + date + "]"); } } return returnDate; } protected boolean isBoolean(String value) { boolean isBoolean = false; if (StringUtils.isNotBlank(value)) { value = value.toLowerCase(); isBoolean = value.equals("false") || value.equals("true") || value.equals("0") || value.equals("1"); } return isBoolean; } protected boolean isBooleanNullOrBlank(String value) { boolean isBoolOrNull = true; if (StringUtils.isNotBlank(value)) { isBoolOrNull = isBoolean(value); } return isBoolOrNull; } protected Boolean getBoolean(String booleanString) { Boolean bool = null; if (booleanString != null) { booleanString = booleanString.trim(); if ("1".equals(booleanString) || Boolean.TRUE.toString().equalsIgnoreCase(booleanString)) { bool = Boolean.TRUE; } else if ("0".equals(booleanString) || Boolean.FALSE.toString().equalsIgnoreCase(booleanString)) { bool = Boolean.FALSE; } } return bool; } private SortTypeConfiguration getSortConfiguration(String sortKey) { for (SortTypeConfiguration sortType : sortTypeConfigurationList) { LOGGER.debug("Comparing incoming sort key of {} with configuration of key {}", sortKey, sortType.getSortKey()); if (sortType.getSortKey().equals(sortKey)) { return sortType; } } return null; } }