/* * Hibernate Search, full-text search for your domain model * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.search.query.dsl.sort.impl; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; import java.util.Map; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField; import org.apache.lucene.search.SortField.Type; import org.hibernate.search.engine.metadata.impl.BridgeDefinedField; import org.hibernate.search.engine.metadata.impl.DocumentFieldMetadata; import org.hibernate.search.engine.metadata.impl.TypeMetadata; import org.hibernate.search.exception.SearchException; import org.hibernate.search.query.dsl.impl.QueryBuildingContext; import org.hibernate.search.spatial.Coordinates; import org.hibernate.search.spatial.DistanceSortField; /** * Holds the list of @{link SortField}s as well as the state of the one being constructed. * Use {@link #closeSortField()} to add the current {@code SortField} to the list * of created sort fields. * Use {@link #createSort()} to return Lucene's sort object. * * @author Emmanuel Bernard emmanuel@hibernate.org */ public class SortFieldStates { private static final Object MISSING_VALUE_LAST = new Object(); private static final Object MISSING_VALUE_FIRST = new Object(); private static final Map<SortField.Type, Object> SCALAR_MINIMUMS = new EnumMap<>( SortField.Type.class ); private static final Map<SortField.Type, Object> SCALAR_MAXIMUMS = new EnumMap<>( SortField.Type.class ); static { initScalarMinMax( SortField.Type.DOUBLE, Double.MIN_VALUE, Double.MAX_VALUE ); initScalarMinMax( SortField.Type.FLOAT, Float.MIN_VALUE, Float.MAX_VALUE ); initScalarMinMax( SortField.Type.LONG, Long.MIN_VALUE, Long.MAX_VALUE ); initScalarMinMax( SortField.Type.INT, Integer.MIN_VALUE, Integer.MAX_VALUE ); } private static void initScalarMinMax(Type type, Object minValue, Object maxValue) { SCALAR_MINIMUMS.put( type, minValue ); SCALAR_MAXIMUMS.put( type, maxValue ); } private enum SortOrder { ASC, DESC; } private final QueryBuildingContext queryContext; private SortField.Type currentType; private String currentName; private SortOrder currentOrder; private Object currentMissingValue; private SortField currentSortFieldNativeSortDescription; private Coordinates coordinates; private Double currentLatitude; private Double currentLongitude; private String currentStringNativeSortFieldDescription; public SortFieldStates(QueryBuildingContext queryContext) { this.queryContext = queryContext; } public void setCurrentType(SortField.Type currentType) { this.currentType = currentType; } public void setCurrentName(String fieldName) { this.currentName = fieldName; } public void setCurrentMissingValue(Object currentMissingValue) { this.currentMissingValue = currentMissingValue; } public void setCurrentMissingValueLast() { this.currentMissingValue = MISSING_VALUE_LAST; } public void setCurrentMissingValueFirst() { this.currentMissingValue = MISSING_VALUE_FIRST; } public void setAsc() { this.currentOrder = SortOrder.ASC; } public boolean isAsc() { return SortOrder.ASC.equals( currentOrder ); } public void setDesc() { this.currentOrder = SortOrder.DESC; } public boolean isDesc() { return SortOrder.DESC.equals( currentOrder ); } public List<SortField> sortFields = new ArrayList<>( 3 ); public void setCurrentSortFieldNativeSortDescription(SortField currentSortField) { this.currentSortFieldNativeSortDescription = currentSortField; } public void closeSortField() { SortField sortField; if ( currentSortFieldNativeSortDescription != null ) { sortField = currentSortFieldNativeSortDescription; } else if ( currentType == SortField.Type.SCORE ) { sortField = new SortField( null, SortField.Type.SCORE, isAsc() ); } else if ( currentType == SortField.Type.DOC ) { sortField = new SortField( null, SortField.Type.DOC, isDesc() ); } else if ( coordinates != null ) { sortField = new DistanceSortField( coordinates, currentName, isDesc() ); if ( hasMissingValue() ) { throw new SearchException( "Missing values substitutes are not supported for distance sorting yet" ); } } else if ( currentLatitude != null ) { sortField = new DistanceSortField( currentLatitude, currentLongitude, currentName, isDesc() ); if ( hasMissingValue() ) { throw new SearchException( "Missing values substitutes are not supported for distance sorting yet" ); } } else if ( currentStringNativeSortFieldDescription != null ) { sortField = new NativeSortField( currentName, currentStringNativeSortFieldDescription ); } else { sortField = new SortField( currentName, currentType, isDesc() ); } processMissingValue( sortField ); sortFields.add( sortField ); reset(); } public void determineCurrentSortFieldTypeAutomaticaly() { this.currentType = getCurrentSortFieldTypeFromMetamodel(); } private SortField.Type getCurrentSortFieldTypeFromMetamodel() { SortField.Type type = null; TypeMetadata typeMetadata = queryContext.getExtendedSearchIntegrator() .getIndexBinding( queryContext.getEntityType() ) .getDocumentBuilder() .getTypeMetadata(); BridgeDefinedField bridgeDefinedFieldMetadata = typeMetadata.getBridgeDefinedFieldMetadataFor( currentName ); if ( bridgeDefinedFieldMetadata != null ) { type = getSortFieldType( bridgeDefinedFieldMetadata ); } if ( type == null ) { DocumentFieldMetadata documentFieldMetadata = typeMetadata.getDocumentFieldMetadataFor( currentName ); if ( documentFieldMetadata != null ) { type = getSortFieldType( documentFieldMetadata ); } } if ( type != null ) { return type; } else { throw new SearchException( "Cannot automatically determine the field type for field '" + currentName + "'. Use byField(String, Sort.Type) to provide the sort type explicitly." ); } } private SortField.Type getSortFieldType(BridgeDefinedField bridgeDefinedFieldMetadata) { switch ( bridgeDefinedFieldMetadata.getType() ) { case DOUBLE: return SortField.Type.DOUBLE; case FLOAT: return SortField.Type.FLOAT; case LONG: case DATE: return SortField.Type.LONG; case INTEGER: return SortField.Type.INT; case BOOLEAN: case STRING: return SortField.Type.STRING; case OBJECT: default: return null; } } private SortField.Type getSortFieldType(DocumentFieldMetadata documentFieldMetadata) { if ( documentFieldMetadata.isSpatial() ) { throw new SearchException( "Field '" + currentName + "' is a spatial field." + " For spatial fields, use .byDistance() and not .byField()." ); } else if ( documentFieldMetadata.isNumeric() ) { switch ( documentFieldMetadata.getNumericEncodingType() ) { case DOUBLE: return SortField.Type.DOUBLE; case FLOAT: return SortField.Type.FLOAT; case LONG: return SortField.Type.LONG; case INTEGER: return SortField.Type.INT; case UNKNOWN: default: return null; } } else { return SortField.Type.STRING; } } private void processMissingValue(SortField sortField) { if ( currentMissingValue != null && sortField.getType() != null ) { if ( sortField.getType() == SortField.Type.STRING || sortField.getType() == SortField.Type.STRING_VAL ) { if ( currentMissingValue == MISSING_VALUE_LAST ) { sortField.setMissingValue( SortField.STRING_LAST ); } else if ( currentMissingValue == MISSING_VALUE_FIRST ) { sortField.setMissingValue( SortField.STRING_FIRST ); } else { throw new SearchException( "Unsupported 'use(Object)' for the field type: '" + currentType + "'." + " Only 'sortFirst()' and 'sortLast()' are supported." ); } } else { boolean reverse = sortField.getReverse(); if ( currentMissingValue == MISSING_VALUE_FIRST && !reverse || currentMissingValue == MISSING_VALUE_LAST && reverse ) { Object min = SCALAR_MINIMUMS.get( sortField.getType() ); if ( min != null ) { sortField.setMissingValue( min ); } else { throw new SearchException( "Unsupported 'sortFirst()'/'sortLast()' for the field type: '" + currentType + "'." + " Only 'use(Object)' is supported." ); } } else if ( currentMissingValue == MISSING_VALUE_LAST && !reverse || currentMissingValue == MISSING_VALUE_FIRST && reverse ) { Object max = SCALAR_MAXIMUMS.get( sortField.getType() ); if ( max != null ) { sortField.setMissingValue( max ); } else { throw new SearchException( "Unsupported 'sortFirst()'/'sortLast()' for the field type: '" + currentType + "'." + " Only 'use(Object)' is supported." ); } } else { /* * Field bridge cannot be used for non-string sort values, since the field bridge * only provides a String and the SortField only accepts a value of the * actual sort type (Long, Double, ...). * That's why we don't call useFieldBridgeIfNecessary() here. */ sortField.setMissingValue( currentMissingValue ); } } } } public Sort createSort() { return new Sort( sortFields.toArray( new SortField[ sortFields.size() ] ) ); } private boolean hasMissingValue() { return currentMissingValue != null; } private void reset() { this.currentType = null; this.currentName = null; this.currentOrder = null; this.currentMissingValue = null; this.currentSortFieldNativeSortDescription = null; this.coordinates = null; this.currentLatitude = null; this.currentLongitude = null; } public void setCoordinates(Coordinates coordinates) { this.coordinates = coordinates; } public void setCurrentLatitude(double latitude) { this.currentLatitude = latitude; } public void setCurrentLongitude(double longitude) { this.currentLongitude = longitude; } public void setCurrentStringNativeSortFieldDescription(String nativeSortFieldDescription) { this.currentStringNativeSortFieldDescription = nativeSortFieldDescription; } }