/* 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
}
}