/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You 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 com.esri.gpt.catalog.lucene; import com.esri.gpt.catalog.discovery.Discoverable; import com.esri.gpt.catalog.discovery.DiscoveryException; import com.esri.gpt.catalog.discovery.LogicalClause; import com.esri.gpt.catalog.discovery.PropertyClause; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsBetween; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsEqualTo; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsGreaterThan; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsGreaterThanOrEqualTo; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsLessThan; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsLessThanOrEqualTo; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsNotEqualTo; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsNull; import com.esri.gpt.catalog.discovery.PropertyClause.PropertyIsLike; import com.esri.gpt.catalog.discovery.PropertyMeanings; import com.esri.gpt.framework.context.RequestContext; import java.util.Map; import java.util.logging.Logger; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.queryParser.ParseException; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.BooleanClause; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermRangeQuery; /** * Adapts a catalog discovery PropertyClause to the Lucene model. */ public class PropertyClauseAdapter extends DiscoveryClauseAdapter { /** class variables ========================================================= */ /** The Logger. */ private static Logger LOGGER = Logger.getLogger(PropertyClauseAdapter.class.getName()); /** instance variables ====================================================== */ private DatastoreField comparisonField; private Discoverable discoverable; private String expressionToQuery; private Storeable storeable; private DatastoreField termsField; /** constructors ============================================================ */ /** * Constructs with an associated query adapter. * @param queryAdapter the query adapter */ protected PropertyClauseAdapter(LuceneQueryAdapter queryAdapter) { super(queryAdapter); } /** methods ================================================================= */ /** * Adapts a catalog discovery PropertyClause to the Lucene model. * @param activeBooleanQuery the active Lucene boolean query * @param activeLogicalClause the active discovery logical clause * @param propertyClause the property clause to adapt * @throws DiscoveryException if an invalid clause is encountered * @throws ParseException if a Lucene query parsing exception occurs */ protected void adaptPropertyClause(BooleanQuery activeBooleanQuery, LogicalClause activeLogicalClause, PropertyClause propertyClause) throws DiscoveryException, ParseException { LOGGER.finer("Adapting PropertyClause...\n"+propertyClause); // determine the discoverable target, set the underlying storable discoverable = propertyClause.getTarget(); if (discoverable == null) { String sErr = "The PropertyClause.target is null."; throw new DiscoveryException(sErr); } if (discoverable.getStorable() == null) { String sErr = "The PropertyClause.target.storeable is null."; throw new DiscoveryException(sErr); } else { storeable = (Storeable)discoverable.getStorable(); } // execute the appropriate operation, if (propertyClause instanceof PropertyIsLike) { PropertyIsLike like = (PropertyIsLike)propertyClause; prepareTermsField(like); handleTermsClause(activeBooleanQuery,activeLogicalClause,like); } else { prepareComparisonField(propertyClause); handleComparisonClause(activeBooleanQuery,activeLogicalClause,propertyClause); } } /** * Appends a range query to the active boolean query. * @param activeBooleanQuery the active Lucene boolean query * @param activeLogicalClause the active discovery logical clause * @param propertyClause the active property clause * @param lowerBoundary the lower boundary * @param upperBoundary the upper boundary * @param lowerBoundaryIsInclusive (>= versus >) * @param upperBoundaryIsInclusive (<= versus <) * @throws DiscoveryException if an invalid clause is encountered */ private void appendRange(BooleanQuery activeBooleanQuery, LogicalClause activeLogicalClause, PropertyClause propertyClause, String lowerBoundary, String upperBoundary, boolean lowerBoundaryIsInclusive, boolean upperBoundaryIsInclusive) throws DiscoveryException { boolean standard = true; // there is a circumstance where a query for data valid within a range is split across 2 fields String fieldName = this.comparisonField.getName(); if ((fieldName != null) && fieldName.equals("dateValidStart") && (upperBoundary != null)) { if (lowerBoundary == null) { standard = false; TimestampField tsEnd = new TimestampField("dateValidEnd"); Query query = tsEnd.makeRangeQuery( lowerBoundary,upperBoundary,lowerBoundaryIsInclusive,upperBoundaryIsInclusive); appendQuery(activeBooleanQuery,activeLogicalClause,query); } else if (!lowerBoundary.equals(upperBoundary)) { standard = false; TimestampField tsEnd = new TimestampField("dateValidEnd"); Query q1 = this.comparisonField.makeRangeQuery( lowerBoundary,null,lowerBoundaryIsInclusive,false); Query q2 = tsEnd.makeRangeQuery( null,upperBoundary,false,upperBoundaryIsInclusive); BooleanQuery bq = new BooleanQuery(); bq.add(q1,BooleanClause.Occur.MUST); bq.add(q2,BooleanClause.Occur.MUST); this.appendQuery(activeBooleanQuery,activeLogicalClause,bq); return; } } // standard methodology if (standard) { Query query = this.comparisonField.makeRangeQuery( lowerBoundary,upperBoundary,lowerBoundaryIsInclusive,upperBoundaryIsInclusive); appendQuery(activeBooleanQuery,activeLogicalClause,query); } } /** * Adapts a property clause requiring an comparison field expression * to the Lucene model. * @param activeBooleanQuery the active Lucene boolean query * @param activeLogicalClause the active discovery logical clause * @param propertyClause the property clause to adapt * @throws DiscoveryException if an invalid clause is encountered */ private void handleComparisonClause(BooleanQuery activeBooleanQuery, LogicalClause activeLogicalClause, PropertyClause propertyClause) throws DiscoveryException { String fieldName = this.comparisonField.getName(); String literal = propertyClause.getLiteral(); // handle each operation if (propertyClause instanceof PropertyIsBetween) { PropertyIsBetween between = (PropertyIsBetween)propertyClause; String lower = between.getLowerBoundary(); String upper = between.getUpperBoundary(); appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,lower,upper,true,true); } else if (propertyClause instanceof PropertyIsEqualTo) { boolean checkFID = fieldName.equalsIgnoreCase(Storeables.FIELD_UUID) && (literal != null) && (literal.length() > 0); if (checkFID) { String id = literal; Query q1 = new TermRangeQuery(fieldName,id,id,true,true); Query q2 = new TermRangeQuery(Storeables.FIELD_FID,id,id,true,true); BooleanQuery bq = new BooleanQuery(); bq.add(q1,BooleanClause.Occur.SHOULD); bq.add(q2,BooleanClause.Occur.SHOULD); appendQuery(activeBooleanQuery,activeLogicalClause,bq); } else { appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,literal,literal,true,true); } } else if (propertyClause instanceof PropertyIsGreaterThan) { appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,literal,null,false,false); } else if (propertyClause instanceof PropertyIsGreaterThanOrEqualTo) { appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,literal,null,true,false); } else if (propertyClause instanceof PropertyIsLessThan) { appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,null,literal,false,false); } else if (propertyClause instanceof PropertyIsLessThanOrEqualTo) { appendRange(activeBooleanQuery,activeLogicalClause, propertyClause,null,literal,false,true); } else if (propertyClause instanceof PropertyIsNotEqualTo) { appendRange(activeBooleanQuery,new LogicalClause.LogicalNot(), propertyClause,literal,literal,true,true); } else if (propertyClause instanceof PropertyIsNull) { appendNullCheck(activeBooleanQuery,fieldName); } else { String sErr = "Unrecognized property clause type: "; throw new DiscoveryException(sErr+propertyClause.getClass().getName()); } } /** * Adapts a property clause requiring the parsing of an expression * to the Lucene model. * @param activeBooleanQuery the active Lucene boolean query * @param activeLogicalClause the active discovery logical clause * @param propertyClause the property clause to adapt * @throws DiscoveryException if an invalid clause is encountered * @throws ParseException if a Lucene query parsing exception occurs */ private void handleTermsClause(BooleanQuery activeBooleanQuery, LogicalClause activeLogicalClause, PropertyIsLike propertyClause) throws DiscoveryException, ParseException { Analyzer analyzer = getQueryAdapter().getIndexAdapter().newAnalyzer(); String[] fields = null; if (storeable instanceof AnyTextProperty) { AnyTextProperty anyText = (AnyTextProperty)storeable; fields = anyText.getFieldNames(); } else { String fieldName; if (termsField != null) { fieldName = termsField.getName(); } else { fieldName = comparisonField.getName(); } fields = new String[]{fieldName}; } // make the parser LuceneConfig lcfg = getQueryAdapter().getIndexAdapter().getLuceneConfig(); Map<String, IParserProxy> proxies = lcfg.getParserProxies(); final TermResolver streamer = new TermResolver(proxies); final QueryProvider queryProvider = new QueryProvider(fields, lcfg.getUseConstantScoreQuery(), getQueryAdapter(), getMeanings()); QueryParser parser = createQueryParser(fields, analyzer, streamer, queryProvider); if (discoverable.getMeaning().getAllowLeadingWildcard()) { parser.setAllowLeadingWildcard(true); } String sMsg = "Applying parser: "+parser.getClass().getName()+ "\n to fields: "+fields+ "\n queryExpression: "+expressionToQuery; LOGGER.finer(sMsg); // parse the query expression (auto-escape if an exception occurs) try { Query query = parser.parse(expressionToQuery); appendQuery(activeBooleanQuery,activeLogicalClause,query); } catch (ParseException pe) { Query query = parser.parse(QueryParser.escape(expressionToQuery)); appendQuery(activeBooleanQuery,activeLogicalClause,query); } this.getQueryAdapter().setHasScoredExpression(true); } /** * Creates query parser. * @param fields array of fields * @param analyzer analyzer * @param streamer streamer * @param queryProvider query provider * @return query parser */ private QueryParser createQueryParser(String [] fields, Analyzer analyzer, TermResolver streamer, QueryProvider queryProvider) { return ((fields != null) && (fields.length == 1))? new ExtQueryParser(fields[0], analyzer, streamer, queryProvider): new ExtMultiFieldQueryParser(fields, analyzer, streamer, queryProvider); } /** * Gets property meanings. * @return property meanings */ private PropertyMeanings getMeanings() throws DiscoveryException { RequestContext context = this.getQueryAdapter().getIndexAdapter().getRequestContext(); return context.getCatalogConfiguration().getConfiguredSchemas().getPropertyMeanings(); } /** * Ensure that there is a not-tokenized field that can be used for * non-term comparisons, then set appropriate query values. * @param propertyClause the active property clause * @throws DiscoveryException if the property cannot be determined */ private void prepareComparisonField(PropertyClause propertyClause) throws DiscoveryException { // TODO what about geometry is null comparison?? comparisonField = storeable.getComparisonField(); if (comparisonField == null) { String sErr = "Storeable.name \""+storeable.getName()+"\" "; sErr += "has no associated comparison field. "; sErr += propertyClause.getClass().getSimpleName()+" cannot be executed."; throw new DiscoveryException(sErr); } } /** * Ensure that there is a tokenized field that can be used for * term comparisons, then sets the query expression. * @param propertyClause the active property clause * @throws DiscoveryException if the property cannot be determined */ private void prepareTermsField(PropertyIsLike propertyClause) throws DiscoveryException { expressionToQuery = propertyClause.getLiteral(); if (!(storeable instanceof AnyTextProperty)) { termsField = storeable.getTermsField(); if (termsField == null) { comparisonField = storeable.getComparisonField(); if (comparisonField == null) { String sErr = "Storeable.name \""+storeable.getName()+"\" "; sErr += "has no associated terms or comparison field. "; sErr += propertyClause.getClass().getSimpleName()+" cannot be executed."; throw new DiscoveryException(sErr); } } } // TODO the expression may need to be escaped, // we also need to check for wild cards } }