blob: 67aa9fd9092ee9fb47a07bc027c670e7e6c45d7f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.solr.schema;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.search.Query;
import org.apache.lucene.spatial.prefix.NumberRangePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.DateRangePrefixTree;
import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.NRShape;
import org.apache.lucene.spatial.prefix.tree.NumberRangePrefixTree.UnitNRShape;
import org.apache.lucene.spatial.query.SpatialArgs;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.request.SolrRequestInfo;
import org.apache.solr.search.QParser;
import org.apache.solr.search.SyntaxError;
import org.apache.solr.util.DateMathParser;
import org.locationtech.spatial4j.shape.Shape;
/**
* A field for indexed dates and date ranges. It's mostly compatible with DatePointField. It has the potential to allow
* efficient faceting, similar to facet.enum.
*
* @see NumberRangePrefixTreeStrategy
* @see DateRangePrefixTree
*/
public class DateRangeField extends AbstractSpatialPrefixTreeFieldType<NumberRangePrefixTreeStrategy>
implements DateValueFieldType { // used by ParseDateFieldUpdateProcessorFactory
private static final String OP_PARAM = "op";//local-param to resolve SpatialOperation
private static final DateRangePrefixTree tree = new DateRangePrefixTree(DateRangePrefixTree.JAVA_UTIL_TIME_COMPAT_CAL);
@Override
protected void init(IndexSchema schema, Map<String, String> args) {
super.init(schema, args);
}
@Override
protected NumberRangePrefixTreeStrategy newPrefixTreeStrategy(String fieldName) {
return new NumberRangePrefixTreeStrategy(tree, fieldName);
}
@Override
public List<IndexableField> createFields(SchemaField field, Object val) {
if (val instanceof Date || val instanceof Calendar)//From URP?
val = tree.toUnitShape(val);
return super.createFields(field, val);
}
@Override
protected String getStoredValue(Shape shape, String shapeStr) {
// even if shapeStr is set, it might have included some dateMath, so see if we can resolve it first:
if (shape instanceof UnitNRShape) {
UnitNRShape unitShape = (UnitNRShape) shape;
if (unitShape.getLevel() == tree.getMaxLevels()) {
//fully precise date. We can be fully compatible with DatePointField (incl. 'Z')
return shape.toString() + 'Z';
}
}
return (shapeStr == null ? shape.toString() : shapeStr);//we don't normalize ranges here; should we?
}
// Won't be called because we override getStoredValue? any way; easy to implement in terms of that
@Override
public String shapeToString(Shape shape) {
return getStoredValue(shape, null);
}
@Override
public NRShape parseShape(String str) {
if (str.contains(" TO ")) {
//TODO parsing range syntax doesn't support DateMath on either side or exclusive/inclusive
try {
return tree.parseShape(str);
} catch (ParseException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Couldn't parse date because: "+ e.getMessage(), e);
}
} else {
return tree.toShape(parseCalendar(str));
}
}
private Calendar parseCalendar(String str) {
if (str.startsWith("NOW") || str.lastIndexOf('Z') >= 0) { // ? but not if Z is last char ? Ehh, whatever.
//use Solr standard date format parsing rules:
//TODO add DMP utility to return ZonedDateTime alternative, then set cal fields manually, which is faster?
Date date = DateMathParser.parseMath(null, str);
Calendar cal = tree.newCal();
cal.setTime(date);
return cal;
} else {
try {
return tree.parseCalendar(str);
} catch (ParseException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Couldn't parse date because: "+ e.getMessage(), e);
}
}
}
/** For easy compatibility with {@link DateMathParser#parseMath(Date, String)}. */
public Date parseMath(Date now, String rawval) {
return DateMathParser.parseMath(now, rawval);
}
@Override
protected SpatialArgs parseSpatialArgs(QParser parser, String externalVal) {
//We avoid SpatialArgsParser entirely because it isn't very Solr-friendly
final Shape shape = parseShape(externalVal);
final SolrParams localParams = parser.getLocalParams();
SpatialOperation op = SpatialOperation.Intersects;
if (localParams != null) {
String opStr = localParams.get(OP_PARAM);
if (opStr != null)
op = SpatialOperation.get(opStr);
}
return new SpatialArgs(op, shape);
}
@Override
protected Query getSpecializedRangeQuery(QParser parser, SchemaField field, String startStr, String endStr, boolean minInclusive, boolean maxInclusive) {
if (parser == null) {//null when invoked by SimpleFacets. But getQueryFromSpatialArgs expects to get localParams.
final SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();
parser = new QParser("", null, requestInfo.getReq().getParams(), requestInfo.getReq()) {
@Override
public Query parse() throws SyntaxError {
throw new IllegalStateException();
}
};
}
Calendar startCal;
if (startStr == null) {
startCal = tree.newCal();
} else {
startCal = parseCalendar(startStr);
if (!minInclusive) {
startCal.add(Calendar.MILLISECOND, 1);
}
}
Calendar endCal;
if (endStr == null) {
endCal = tree.newCal();
} else {
endCal = parseCalendar(endStr);
if (!maxInclusive) {
endCal.add(Calendar.MILLISECOND, -1);
}
}
Shape shape = tree.toRangeShape(tree.toShape(startCal), tree.toShape(endCal));
SpatialArgs spatialArgs = new SpatialArgs(SpatialOperation.Intersects, shape);
return getQueryFromSpatialArgs(parser, field, spatialArgs);
}
}