/*
* Copyright 2012 - 2014 the original author or authors.
*
* Licensed 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.springframework.data.solr.repository.query;
import java.util.Collection;
import java.util.Iterator;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.Part.Type;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.solr.core.mapping.SolrPersistentProperty;
import org.springframework.data.solr.core.query.Criteria;
import org.springframework.data.solr.core.query.Query;
import org.springframework.data.solr.core.query.SimpleQuery;
/**
* Solr specific implmentation of an {@link AbstractQueryCreator} that constructs {@link Query}
*
* @author Christoph Strobl
* @author John Dorman
*/
class SolrQueryCreator extends AbstractQueryCreator<Query, Query> {
private final MappingContext<?, SolrPersistentProperty> context;
public SolrQueryCreator(PartTree tree, SolrParameterAccessor parameters,
MappingContext<?, SolrPersistentProperty> context) {
super(tree, parameters);
this.context = context;
}
@Override
protected Query create(Part part, Iterator<Object> iterator) {
PersistentPropertyPath<SolrPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
return new SimpleQuery(from(part.getType(),
new Criteria(path.toDotPath(SolrPersistentProperty.PropertyToFieldNameConverter.INSTANCE)), iterator));
}
@Override
protected Query and(Part part, Query base, Iterator<Object> iterator) {
if (base == null) {
return create(part, iterator);
}
PersistentPropertyPath<SolrPersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
return base.addCriteria(from(part.getType(),
new Criteria(path.toDotPath(SolrPersistentProperty.PropertyToFieldNameConverter.INSTANCE)), iterator));
}
@Override
protected Query or(Query base, Query query) {
Criteria part = query.getCriteria();
part.setPartIsOr(true);
if (part.hasSiblings()) {
boolean first = true;
for (Criteria nested : part.getSiblings()) {
if (first) {
nested.setPartIsOr(true);
first = false;
}
base.addCriteria(nested);
}
} else {
base.addCriteria(part);
}
return base;
}
@Override
protected Query complete(Query query, Sort sort) {
if (query == null) {
return null;
}
return query.addSort(sort);
}
private Criteria from(Type type, Criteria instance, Iterator<?> parameters) {
Criteria criteria = instance;
if (criteria == null) {
criteria = new Criteria();
}
switch (type) {
case TRUE:
return criteria.is(true);
case FALSE:
return criteria.is(false);
case SIMPLE_PROPERTY:
return criteria.is(appendBoostAndGetParameterValue(criteria, parameters));
case NEGATING_SIMPLE_PROPERTY:
return criteria.is(appendBoostAndGetParameterValue(criteria, parameters)).not();
case IS_NULL:
return criteria.isNull();
case IS_NOT_NULL:
return criteria.isNotNull();
case REGEX:
return criteria.expression(appendBoostAndGetParameterValue(criteria, parameters).toString());
case LIKE:
case STARTING_WITH:
return criteria.startsWith(asStringArray(appendBoostAndGetParameterValue(criteria, parameters)));
case NOT_LIKE:
return criteria.startsWith(asStringArray(appendBoostAndGetParameterValue(criteria, parameters))).not();
case ENDING_WITH:
return criteria.endsWith(asStringArray(appendBoostAndGetParameterValue(criteria, parameters)));
case CONTAINING:
return criteria.contains(asStringArray(appendBoostAndGetParameterValue(criteria, parameters)));
case AFTER:
case GREATER_THAN:
return criteria.greaterThan(appendBoostAndGetParameterValue(criteria, parameters));
case GREATER_THAN_EQUAL:
return criteria.greaterThanEqual(appendBoostAndGetParameterValue(criteria, parameters));
case BEFORE:
case LESS_THAN:
return criteria.lessThan(appendBoostAndGetParameterValue(criteria, parameters));
case LESS_THAN_EQUAL:
return criteria.lessThanEqual(appendBoostAndGetParameterValue(criteria, parameters));
case BETWEEN:
return criteria.between(appendBoostAndGetParameterValue(criteria, parameters),
appendBoostAndGetParameterValue(criteria, parameters));
case IN:
return criteria.in(asArray(appendBoostAndGetParameterValue(criteria, parameters)));
case NOT_IN:
return criteria.in(asArray(appendBoostAndGetParameterValue(criteria, parameters))).not();
case NEAR:
return createNearCriteria(parameters, criteria);
case WITHIN:
return criteria.within((Point) getBindableValue((BindableSolrParameter) parameters.next()),
(Distance) getBindableValue((BindableSolrParameter) parameters.next()));
default:
throw new InvalidDataAccessApiUsageException("Illegal criteria found '" + type + "'.");
}
}
private Object appendBoostAndGetParameterValue(Criteria criteria, Iterator<?> iterator) {
Object param = iterator.next();
if (param instanceof BindableSolrParameter) {
BindableSolrParameter bindable = (BindableSolrParameter) param;
appendBoost(criteria, bindable);
return bindable.getValue();
}
return param;
}
private Criteria appendBoost(Criteria criteria, BindableSolrParameter parameter) {
if (!Float.isNaN(parameter.getBoost())) {
criteria.boost(parameter.getBoost());
}
return criteria;
}
private Object getBindableValue(BindableSolrParameter parameter) {
if (parameter == null) {
return null;
}
return parameter.getValue();
}
private Criteria createNearCriteria(Iterator<?> parameters, Criteria criteria) {
Object value = getBindableValue((BindableSolrParameter) parameters.next());
if (value instanceof Box) {
return criteria.near((Box) value);
} else {
return criteria.near((Point) value, (Distance) getBindableValue((BindableSolrParameter) parameters.next()));
}
}
private Object[] asArray(Object o) {
if (o instanceof Collection) {
return ((Collection<?>) o).toArray();
} else if (o.getClass().isArray()) {
return (Object[]) o;
}
return new Object[] { o };
}
@SuppressWarnings("unchecked")
private String[] asStringArray(Object o) {
if (o instanceof Collection) {
Collection<?> col = (Collection<?>) o;
if (col.isEmpty()) {
return new String[0];
} else {
if (!(col.iterator().next() instanceof String)) {
throw new IllegalArgumentException("Parameter has to be a collection of String.");
}
return ((Collection<String>) o).toArray(new String[col.size()]);
}
} else if (o.getClass().isArray()) {
if (!(o instanceof String[])) {
throw new IllegalArgumentException("Parameter has to be an array of String.");
}
return (String[]) o;
}
return new String[] { o.toString() };
}
}