/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt * * 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 se.streamsource.infrastructure.index.elasticsearch; import org.elasticsearch.index.query.AndFilterBuilder; import org.elasticsearch.index.query.FilterBuilder; import org.elasticsearch.index.query.OrFilterBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.json.JSONException; import org.qi4j.api.Qi4j; import org.qi4j.api.entity.Entity; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.query.grammar.*; import org.qi4j.api.value.ValueComposite; import org.qi4j.spi.Qi4jSPI; import org.qi4j.spi.value.ValueDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.*; import static java.lang.String.format; import static org.elasticsearch.index.query.FilterBuilders.*; import static org.elasticsearch.index.query.FilterBuilders.termFilter; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; /** * ES query parser implementation */ public class ElasticSearchQueryParserImpl implements ElasticSearchQueryParser { @Structure Qi4jSPI qi4jSPI; private Logger LOGGER = LoggerFactory.getLogger(getClass().getName()); private static ThreadLocal<DateFormat> ISO8601_UTC = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" ); dateFormat.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); return dateFormat; } }; public QueryBuilder getQueryBuilder(AndFilterBuilder baseFilter, BooleanExpression whereClause) { if( whereClause == null ) { return matchAllQuery(); } else { processFilter( baseFilter, whereClause ); return matchAllQuery(); } } private void processFilter( FilterBuilder filterBuilder, final BooleanExpression expression ) { if( expression instanceof Conjunction ) { final Conjunction conjunction = (Conjunction) expression; AndFilterBuilder andFilterBuilder = new AndFilterBuilder(); processFilter(andFilterBuilder, conjunction.leftSideExpression()); processFilter( andFilterBuilder, conjunction.rightSideExpression() ); addFilter(andFilterBuilder, filterBuilder); } else if( expression instanceof Disjunction ) { final Disjunction disjunction = (Disjunction) expression; OrFilterBuilder orFilterBuilder = new OrFilterBuilder(); processFilter(orFilterBuilder, disjunction.leftSideExpression()); processFilter(orFilterBuilder, disjunction.rightSideExpression()); addFilter(orFilterBuilder, filterBuilder); } else if( expression instanceof Negation ) { processNegation(filterBuilder, ((Negation) expression).expression()) ; } else if( expression instanceof MatchesPredicate ) { processMatchesPredicate(filterBuilder, (MatchesPredicate) expression); } else if( expression instanceof ComparisonPredicate ) { processComparisonPredicate(filterBuilder, (ComparisonPredicate) expression); } else if( expression instanceof ManyAssociationContainsPredicate ) { processManyAssociationContainsPredicate(filterBuilder, (ManyAssociationContainsPredicate) expression); } else if( expression instanceof PropertyIsNullPredicate ) { processIsNullPredicate(filterBuilder, (PropertyIsNullPredicate) expression); } else if( expression instanceof PropertyIsNotNullPredicate ) { processIsNotNullPredicate(filterBuilder, (PropertyIsNotNullPredicate) expression); } else if( expression instanceof AssociationIsNullPredicate ) { processIsNullPredicate(filterBuilder, (AssociationIsNullPredicate) expression); } else if( expression instanceof AssociationIsNotNullPredicate ) { processIsNotNullPredicate(filterBuilder, (AssociationIsNotNullPredicate) expression); } else if( expression instanceof ContainsPredicate<?, ?> ) { processContainsPredicate( filterBuilder, (ContainsPredicate<?, ?>) expression ); } else if( expression instanceof ContainsAllPredicate<?, ?> ) { processContainsAllPredicate( filterBuilder, (ContainsAllPredicate<?, ?>) expression ); } else { throw new UnsupportedOperationException( "Expression " + expression + " is not supported" ); } } private void processNegation(FilterBuilder filterBuilder, BooleanExpression expression ) { if( expression instanceof PropertyIsNotNullPredicate) { LOGGER.trace( "Processing PropertyNotNullSpecification {}", expression ); addFilter( existsFilter( ((PropertyIsNotNullPredicate)expression).propertyReference().propertyName() ), filterBuilder ); } else if ( expression instanceof AssociationIsNotNullPredicate ) { LOGGER.trace( "Processing AssociationNotNullSpecification {}", expression ); addFilter( existsFilter( ((AssociationIsNotNullPredicate)expression).associationReference().associationName() + ".identity" ), filterBuilder ); } else { LOGGER.trace( "Processing NotSpecification {}", expression ); AndFilterBuilder operandFilter = new AndFilterBuilder(); processFilter(operandFilter, expression); addFilter( notFilter( operandFilter ), filterBuilder ); } } private void processContainsAllPredicate( FilterBuilder filterBuilder, final ContainsAllPredicate<?, ?> predicate ) { LOGGER.trace( "Processing ContainsAllSpecification {}", predicate ); String name = predicate.propertyReference().propertyName(); AndFilterBuilder contAllFilter = new AndFilterBuilder(); for ( Object value : (Collection<?>)((SingleValueExpression)predicate.valueExpression()).value() ) { if ( value instanceof ValueComposite ) { // Query by complex property "example value" ValueComposite valueComposite = ( ValueComposite ) value; ValueDescriptor valueDescriptor = qi4jSPI.getValueDescriptor( valueComposite ); throw new UnsupportedOperationException( "ElasticSearch Index/Query does not support complex " + "queries, ie. queries by 'example value'." ); } else { contAllFilter.add( termFilter( name, toString( value ) ) ); } } addFilter( contAllFilter, filterBuilder ); } private void processContainsPredicate( FilterBuilder filterBuilder, final ContainsPredicate<?, ?> predicate ) { LOGGER.trace( "Processing ContainsSpecification {}", predicate ); String name = predicate.propertyReference().propertyName(); if ( ((SingleValueExpression)predicate.valueExpression()).value() instanceof ValueComposite) { // Query by complex property "example value" ValueComposite value = ( ValueComposite )((SingleValueExpression)predicate.valueExpression()).value(); ValueDescriptor valueDescriptor = qi4jSPI.getValueDescriptor( value ); throw new UnsupportedOperationException( "ElasticSearch Index/Query does not support complex " + "queries, ie. queries by 'example value'." ); } else { String value = toString( ((SingleValueExpression)predicate.valueExpression()).value() ); addFilter( termFilter( name, value ), filterBuilder ); } } private void processMatchesPredicate( FilterBuilder filterBuilder, final MatchesPredicate predicate ) { LOGGER.trace( "Processing MatchesSpecification {}", predicate ); String name = ""; if( predicate.propertyReference().traversedAssociation() != null ) { name = predicate.propertyReference().traversedAssociation().associationName() + ".identity"; } else if( predicate.propertyReference().traversedProperty() != null ) { name = predicate.propertyReference().traversedProperty().propertyName() + "." + predicate.propertyReference().propertyName(); } else { name = predicate.propertyReference().propertyName(); } String value = toString( ((SingleValueExpression)predicate.valueExpression()).value() ).replace( '^', '.' ); addFilter( regexpFilter( name, value), filterBuilder ); } private void processComparisonPredicate( FilterBuilder filterBuilder, final ComparisonPredicate predicate ) { LOGGER.trace( "Processing ComparisonPredicate {}", predicate ); String name = ""; if( predicate.propertyReference().traversedAssociation() != null ) { name = predicate.propertyReference().traversedAssociation().associationName() + ".identity"; } else if( predicate.propertyReference().traversedProperty() != null ) { name = predicate.propertyReference().traversedProperty().propertyName() + "." + predicate.propertyReference().propertyName(); } else { name = predicate.propertyReference().propertyName(); } String value = toString( ((SingleValueExpression)predicate.valueExpression()).value() ); if( predicate instanceof EqualsPredicate ) { addFilter( termFilter(name, value ), filterBuilder ); }else if ( predicate instanceof NotEqualsPredicate ) { addFilter( notFilter( termFilter(name, value) ), filterBuilder ); }else if ( predicate instanceof GreaterOrEqualPredicate ) { addFilter( rangeFilter( name ).from(value).includeLower(true), filterBuilder ); } else if ( predicate instanceof GreaterThanPredicate ) { addFilter( rangeFilter( name ).from(value).includeLower(false), filterBuilder ); } else if ( predicate instanceof LessOrEqualPredicate ) { addFilter( rangeFilter( name ).to( value ).includeUpper( true ), filterBuilder ); } else if ( predicate instanceof LessThanPredicate ) { addFilter( rangeFilter( name ).to( value ).includeUpper( false ), filterBuilder ); } else { throw new UnsupportedOperationException( "Query specification unsupported by Elastic Search: " + predicate.getClass() + ": " + predicate ); } } private void processManyAssociationContainsPredicate( FilterBuilder filterBuilder, ManyAssociationContainsPredicate predicate) { LOGGER.trace( "Processing ManyAssociationContainsSpecification {}", predicate ); String name = predicate.associationReference().associationName() + ".identity"; String value = toString( ((SingleValueExpression)predicate.valueExpression()).value() ); addFilter( termFilter( name, value ), filterBuilder ); } private void processIsNullPredicate( FilterBuilder filterBuilder, final PropertyIsNullPredicate predicate ) { LOGGER.trace( "Processing PropertyNullSpecification {}", predicate ); addFilter( missingFilter( predicate.propertyReference().propertyName() ), filterBuilder ); } private void processIsNullPredicate( FilterBuilder filterBuilder, final AssociationIsNullPredicate predicate ) { LOGGER.trace( "Processing AssociationNullSpecification {}", predicate ); addFilter( missingFilter( predicate.associationReference().associationName() + ".identity" ), filterBuilder ); } private void processIsNotNullPredicate( FilterBuilder filterBuilder, final PropertyIsNotNullPredicate predicate ) { LOGGER.trace( "Processing PropertyNullSpecification {}", predicate ); addFilter( existsFilter( predicate.propertyReference().propertyName() ), filterBuilder ); } private void processIsNotNullPredicate( FilterBuilder filterBuilder, final AssociationIsNotNullPredicate predicate ) { LOGGER.trace( "Processing AssociationNullSpecification {}", predicate ); addFilter( existsFilter( predicate.associationReference().associationName() + ".identity" ), filterBuilder ); } private void addFilter( FilterBuilder filter, FilterBuilder into ) { if ( into instanceof AndFilterBuilder ) { ( ( AndFilterBuilder ) into ).add( filter ); } else if ( into instanceof OrFilterBuilder) { ( ( OrFilterBuilder ) into ).add( filter ); } else { throw new UnsupportedOperationException( "FilterBuilder is nor an AndFB nor an OrFB, cannot continue." ); } } private String toString( Object value ) { if( value == null ) { return null; } if( value instanceof Date ) { return ISO8601_UTC.get().format( (Date) value ); } else { return value.toString(); } } }