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; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; 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.GeospatialCriteria.SpatialOperator; 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.rest.parsers.QueryParser; import net.di2e.ecdr.commons.query.util.keywordparser.ASTNode; import net.di2e.ecdr.commons.query.util.keywordparser.KeywordTextParser; import org.apache.commons.lang.StringUtils; import org.geotools.filter.text.cql2.CQL; import org.geotools.filter.text.cql2.CQLException; import org.opengis.filter.Filter; import org.opengis.filter.FilterVisitor; 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.parserunners.ReportingParseRunner; import org.parboiled.support.ParsingResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vividsolutions.jts.geom.Coordinate; 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.operation.Query; import ddf.catalog.source.UnsupportedQueryException; public class CDRQueryImpl implements Query { // public static final String INCLUDE_THUMBNAIL = "include-thumbnail"; private static final Logger LOGGER = LoggerFactory.getLogger(CDRQueryImpl.class); private Filter queryFilter; private QueryParser queryParser; private StringBuilder humanReadableQueryBuilder = new StringBuilder(); private Collection<String> sources = null; private boolean useDefaultSortIfNotSpecified = false; private boolean isStrictMode = false; private String responseFormat; private int startIndex; private int count; private long timeoutMilliseconds; private SortBy sortBy; private String queryLanguage; private GeospatialCriteria geoCriteria; private TemporalCriteria temporalCriteria; private TextualCriteria textualCriteria; private List<PropertyCriteria> propertyCriteriaList; private String localSourceId = null; public CDRQueryImpl(FilterBuilder filterBuilder, MultivaluedMap<String, String> queryParameters, QueryParser parser, boolean useDefaultSort, String localSourceId) throws UnsupportedQueryException { queryParser = parser; this.localSourceId = localSourceId; this.useDefaultSortIfNotSpecified = useDefaultSort; createQuery(filterBuilder, queryParameters); humanReadableQueryBuilder.append(" " + SearchConstants.STRICTMODE_PARAMETER + "=[" + isStrictMode + "] " + SearchConstants.STARTINDEX_PARAMETER + "=[" + startIndex + "] " + SearchConstants.COUNT_PARAMETER + "=[" + count + "]"); humanReadableQueryBuilder.append(" " + SearchConstants.FORMAT_PARAMETER + "=[" + responseFormat + "]"); sources = queryParser.getSiteNames(queryParameters); if (!sources.isEmpty()) { humanReadableQueryBuilder.append(" " + BrokerConstants.SOURCE_PARAMETER + "=["); StringUtils.join(sources, ", "); humanReadableQueryBuilder.append("]"); } } @Override public boolean evaluate(Object object) { return queryFilter.evaluate(object); } @Override public Object accept(FilterVisitor visitor, Object extraData) { return queryFilter.accept(visitor, extraData); } @Override public int getPageSize() { return count; } @Override public SortBy getSortBy() { return sortBy; } @Override public int getStartIndex() { return startIndex; } @Override public long getTimeoutMillis() { return timeoutMilliseconds; } @Override public boolean requestsTotalResultsCount() { return true; } protected boolean getStrictMode() { return isStrictMode; } public String getResponseFormat() { return responseFormat; } public String getHumanReadableQuery() { return humanReadableQueryBuilder.toString(); } public Collection<String> getSiteNames() { return sources; } protected void createQuery(FilterBuilder filterBuilder, MultivaluedMap<String, String> queryParameters) throws UnsupportedQueryException { List<Filter> filters = new ArrayList<Filter>(); if (!queryParser.isValidQuery(queryParameters, localSourceId)) { throw new UnsupportedQueryException("Invalid query parameters passed in"); } isStrictMode = queryParser.isStrictMode(queryParameters); responseFormat = queryParser.getResponseFormat(queryParameters); startIndex = queryParser.getStartIndex(queryParameters); count = queryParser.getCount(queryParameters); timeoutMilliseconds = queryParser.getTimeoutMilliseconds(queryParameters); sortBy = queryParser.getSortBy(queryParameters); queryLanguage = queryParser.getQueryLanguage(queryParameters); // default query language is CDR Keyword if (StringUtils.isBlank(queryLanguage) || SearchConstants.CDR_KEYWORD_QUERY_LANGUAGE.equals(queryLanguage)) { LOGGER.debug("Received CDR Keyword-based query."); // keyword parameters textualCriteria = queryParser.getTextualCriteria(queryParameters); if (textualCriteria != null) { boolean fuzzy = textualCriteria.isFuzzy(); LOGGER.debug( "Attempting to create a Contextual filter with params keywords=[{}], isCaseSensitive=[{}], strictMode=[{}], fuzzy=[{}]", textualCriteria.getKeywords(), textualCriteria.isCaseSensitive(), isStrictMode, fuzzy); Filter filter = getContextualFilter(filterBuilder, textualCriteria.getKeywords(), textualCriteria.isCaseSensitive(), isStrictMode, fuzzy); addFilter(filters, filter); if (useDefaultSortIfNotSpecified && sortBy == null) { sortBy = new SortByImpl(Result.RELEVANCE, SortOrder.DESCENDING); } } // Geospatial query parameters geoCriteria = queryParser.getGeospatialCriteria(queryParameters); if (geoCriteria != null) { LOGGER.debug( "Attempting to create a Geospatial filter with params radius=[{}], latitude=[{}], longitude=[{}], bbox=[{}] and geometry=[{}]", geoCriteria.getRadius(), geoCriteria.getLatitude(), geoCriteria.getLongitude(), geoCriteria.getBBoxWKT(), geoCriteria.getGeometryWKT()); Filter filter = getGeoFilter(filterBuilder, geoCriteria.getRadius(), geoCriteria.getLatitude(), geoCriteria.getLongitude(), geoCriteria.getBBoxWKT(), geoCriteria.getGeometryWKT(), geoCriteria.getSpatialOperator()); addFilter(filters, filter); if (useDefaultSortIfNotSpecified && sortBy == null) { sortBy = new SortByImpl(Result.DISTANCE, SortOrder.ASCENDING); } } // Temporal Criteria temporalCriteria = queryParser.getTemporalCriteria(queryParameters); 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(filterBuilder, temporalCriteria.getStartDate(), temporalCriteria.getEndDate(), temporalCriteria.getDateType()); addFilter(filters, filter); } // Property Criteria propertyCriteriaList = queryParser.getPropertyCriteria(queryParameters); 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(filterBuilder, propCriteria.getProperty(), propCriteria.getValue(), propCriteria.getOperator()); addFilter(filters, filter); } } } else if (SearchConstants.CDR_CQL_QUERY_LANGUAGE.equals(queryLanguage)) { LOGGER.debug("Received CQL-based query."); String cqlStr = queryParser.getTextualCriteria(queryParameters).getKeywords(); try { Filter filter = CQL.toFilter(cqlStr); addFilter(filters, filter); } catch (CQLException cqlException) { throw new UnsupportedQueryException("Invalid CQL predicate provided.", cqlException); } } if (filters.isEmpty()) { throw new UnsupportedQueryException( "Could not create any valid filters from the provided query parameters"); } // Default to Effective time based sorting if desired to default and no sort order specified if (useDefaultSortIfNotSpecified && sortBy == null) { sortBy = new SortByImpl(Result.TEMPORAL, SortOrder.DESCENDING); } if (sortBy != null && sortBy.getPropertyName() != null && sortBy.getSortOrder() != null) { humanReadableQueryBuilder.append(" " + SearchConstants.SORTKEYS_PARAMETER + "=[" + sortBy.getPropertyName().getPropertyName() + " " + sortBy.getSortOrder().name() + "]"); } queryFilter = filterBuilder.allOf(filters); } protected void addFilter(List<Filter> filters, Filter filter) { if (filter != null) { LOGGER.debug("Filter was not null, and will be added to the list of valid filters"); filters.add(filter); } else { LOGGER.debug("Filter was null, not adding to the Filter list"); } } protected Filter getGeoFilter(FilterBuilder filterBuilder, Double radius, Double latitude, Double longitude, String boxWKT, String geometry, SpatialOperator operator) { 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); humanReadableQueryBuilder .append(" lat=[" + latitude + "] lon=[" + longitude + "] radius=[" + radius + "]"); } else if (boxWKT != null) { if (SpatialOperator.Contains.equals(operator)) { filter = filterBuilder.attribute(Metacard.ANY_GEO).within().wkt(boxWKT); humanReadableQueryBuilder.append(" box=[" + boxWKT + "] spatialOp=[contains]"); } else { filter = filterBuilder.attribute(Metacard.ANY_GEO).intersecting().wkt(boxWKT); humanReadableQueryBuilder.append(" box=[" + boxWKT + "]"); } } else if (geometry != null) { if (SpatialOperator.Contains.equals(operator)) { filter = filterBuilder.attribute(Metacard.ANY_GEO).within().wkt(geometry); humanReadableQueryBuilder.append( " " + SearchConstants.GEOMETRY_PARAMETER + "=[" + geometry + "] spatialOp=[contains]"); } else { filter = filterBuilder.attribute(Metacard.ANY_GEO).intersecting().wkt(geometry); humanReadableQueryBuilder.append(" " + SearchConstants.GEOMETRY_PARAMETER + "=[" + geometry + "]"); } } return filter; } protected Filter getTemporalFilter(FilterBuilder filterBuilder, Date startDate, Date endDate, String type) 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 Filter getPropertyFilter(FilterBuilder filterBuilder, String property, String value, Operator operator) { Filter filter = null; if (property != null && operator != null) { if (property.equals(Metacard.CONTENT_TYPE)) { filter = getContentTypeFilter(filterBuilder, 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; } private Filter getContentTypeFilter(FilterBuilder filterBuilder, 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); } protected Filter getContextualFilter(FilterBuilder filterBuilder, String keywords, boolean caseSensitive, boolean isStrcitMode, boolean fuzzy) throws UnsupportedQueryException { Filter filter = null; if (keywords != null) { KeywordTextParser keywordParser = Parboiled.createParser(KeywordTextParser.class); ParseRunner<ASTNode> runner = isStrictMode ? new ReportingParseRunner<ASTNode>(keywordParser.inputPhrase()) : new RecoveringParseRunner<ASTNode>(keywordParser.inputPhrase()); ParsingResult<ASTNode> parsingResult = runner.run(keywords); if (!parsingResult.hasErrors()) { try { filter = getFilterFromASTNode(filterBuilder, 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()); } humanReadableQueryBuilder.append(" " + SearchConstants.KEYWORD_PARAMETER + "=[" + keywords + "] " + SearchConstants.CASESENSITIVE_PARAMETER + "=[" + caseSensitive + "] " + SearchConstants.FUZZY_PARAMETER + "=[" + fuzzy + "]"); } return filter; } protected Filter getFilterFromASTNode(FilterBuilder filterBuilder, 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(filterBuilder, astNode.left(), caseSensitive, fuzzy), getFilterFromASTNode(filterBuilder, astNode.right(), caseSensitive, fuzzy)); case OR: return filterBuilder.anyOf( getFilterFromASTNode(filterBuilder, astNode.left(), caseSensitive, fuzzy), getFilterFromASTNode(filterBuilder, astNode.right(), caseSensitive, fuzzy)); case NOT: // since NOT really means AND NOT return filterBuilder.allOf( getFilterFromASTNode(filterBuilder, astNode.left(), caseSensitive, fuzzy), filterBuilder .not(getFilterFromASTNode(filterBuilder, 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"); } }