DATACOUCH-140 - Use Converter to match query params and stored values.
When deriving a query, use the CouchbaseConverter to transform the parameters for fields that would be converted upon storage. Reworked the DateConverters so that reading conversion is done from any Number (since N1QL may return longs in Double scientific notation for example).
This commit is contained in:
@@ -88,4 +88,22 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
conversions.registerConvertersIn(conversionService);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertForWriteIfNeeded(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> targetType = this.conversions.getCustomWriteTarget(value.getClass());
|
||||
if (targetType != null) {
|
||||
return this.conversionService.convert(value, targetType);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getWriteClassFor(Class<?> clazz) {
|
||||
Class<?> targetType = this.conversions.getCustomWriteTarget(clazz);
|
||||
return targetType != null ? targetType : clazz;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,10 +26,29 @@ import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProper
|
||||
* Marker interface for the converter, identifying the types to and from that can be converted.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
*/
|
||||
public interface CouchbaseConverter
|
||||
extends EntityConverter<CouchbasePersistentEntity<?>,
|
||||
CouchbasePersistentProperty, Object, CouchbaseDocument>,
|
||||
CouchbaseWriter<Object, CouchbaseDocument>,
|
||||
EntityReader<Object, CouchbaseDocument> {
|
||||
|
||||
/**
|
||||
* Convert the value if necessary to the class that would actually be stored,
|
||||
* or leave it as is if no conversion needed.
|
||||
*
|
||||
* @param value the value to be converted to the class that would actually be stored.
|
||||
* @return the converted value (or the same value if no conversion necessary).
|
||||
*/
|
||||
Object convertForWriteIfNeeded(Object value);
|
||||
|
||||
/**
|
||||
* Return the Class that would actually be stored for a given Class.
|
||||
*
|
||||
* @param clazz the source class.
|
||||
* @return the target class that would actually be stored.
|
||||
* @see #convertForWriteIfNeeded(Object)
|
||||
*/
|
||||
Class<?> getWriteClassFor(Class<?> clazz);
|
||||
}
|
||||
|
||||
@@ -54,18 +54,18 @@ public final class DateConverters {
|
||||
|
||||
converters.add(DateToLongConverter.INSTANCE);
|
||||
converters.add(CalendarToLongConverter.INSTANCE);
|
||||
converters.add(LongToDateConverter.INSTANCE);
|
||||
converters.add(LongToCalendarConverter.INSTANCE);
|
||||
converters.add(NumberToDateConverter.INSTANCE);
|
||||
converters.add(NumberToCalendarConverter.INSTANCE);
|
||||
|
||||
if (JODA_TIME_IS_PRESENT) {
|
||||
converters.add(LocalDateToLongConverter.INSTANCE);
|
||||
converters.add(LocalDateTimeToLongConverter.INSTANCE);
|
||||
converters.add(DateTimeToLongConverter.INSTANCE);
|
||||
converters.add(DateMidnightToLongConverter.INSTANCE);
|
||||
converters.add(LongToLocalDateConverter.INSTANCE);
|
||||
converters.add(LongToLocalDateTimeConverter.INSTANCE);
|
||||
converters.add(LongToDateTimeConverter.INSTANCE);
|
||||
converters.add(LongToDateMidnightConverter.INSTANCE);
|
||||
converters.add(NumberToLocalDateConverter.INSTANCE);
|
||||
converters.add(NumberToLocalDateTimeConverter.INSTANCE);
|
||||
converters.add(NumberToDateTimeConverter.INSTANCE);
|
||||
converters.add(NumberToDateMidnightConverter.INSTANCE);
|
||||
}
|
||||
|
||||
return converters;
|
||||
@@ -92,33 +92,33 @@ public final class DateConverters {
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToDateConverter implements Converter<Long, Date> {
|
||||
public enum NumberToDateConverter implements Converter<Number, Date> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Date convert(Long source) {
|
||||
public Date convert(Number source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Date date = new Date();
|
||||
date.setTime(source);
|
||||
date.setTime(source.longValue());
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToCalendarConverter implements Converter<Long, Calendar> {
|
||||
public enum NumberToCalendarConverter implements Converter<Number, Calendar> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Calendar convert(Long source) {
|
||||
public Calendar convert(Number source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.setTimeInMillis(source * 1000);
|
||||
calendar.setTimeInMillis(source.longValue() * 1000);
|
||||
return calendar;
|
||||
}
|
||||
}
|
||||
@@ -164,42 +164,42 @@ public final class DateConverters {
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToLocalDateConverter implements Converter<Long, LocalDate> {
|
||||
public enum NumberToLocalDateConverter implements Converter<Number, LocalDate> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(Long source) {
|
||||
return source == null ? null : new LocalDate(source);
|
||||
public LocalDate convert(Number source) {
|
||||
return source == null ? null : new LocalDate(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToLocalDateTimeConverter implements Converter<Long, LocalDateTime> {
|
||||
public enum NumberToLocalDateTimeConverter implements Converter<Number, LocalDateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(Long source) {
|
||||
return source == null ? null : new LocalDateTime(source);
|
||||
public LocalDateTime convert(Number source) {
|
||||
return source == null ? null : new LocalDateTime(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToDateTimeConverter implements Converter<Long, DateTime> {
|
||||
public enum NumberToDateTimeConverter implements Converter<Number, DateTime> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public DateTime convert(Long source) {
|
||||
return source == null ? null : new DateTime(source);
|
||||
public DateTime convert(Number source) {
|
||||
return source == null ? null : new DateTime(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
public enum LongToDateMidnightConverter implements Converter<Long, DateMidnight> {
|
||||
public enum NumberToDateMidnightConverter implements Converter<Number, DateMidnight> {
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public DateMidnight convert(Long source) {
|
||||
return source == null ? null : new DateMidnight(source);
|
||||
public DateMidnight convert(Number source) {
|
||||
return source == null ? null : new DateMidnight(source.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ public abstract class CouchbaseSimpleTypes {
|
||||
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
|
||||
simpleTypes.add(RawJsonDocument.class);
|
||||
simpleTypes.add(JsonArray.class);
|
||||
simpleTypes.add(Number.class);
|
||||
COUCHBASE_SIMPLE_TYPES = Collections.unmodifiableSet(simpleTypes);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.couchbase.repository.query;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
|
||||
/**
|
||||
* An {@link Iterator Iterator<Object>} that {@link CouchbaseConverter#convertForWriteIfNeeded(Object) converts}
|
||||
* values to their stored Class if warranted.
|
||||
*/
|
||||
class ConvertingIterator implements Iterator<Object> {
|
||||
private final Iterator<Object> delegate;
|
||||
private final CouchbaseConverter converter;
|
||||
|
||||
public ConvertingIterator(Iterator<Object> delegate, CouchbaseConverter converter) {
|
||||
this.delegate = delegate;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return delegate.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
delegate.remove();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object next() {
|
||||
Object next = delegate.next();
|
||||
return converter.convertForWriteIfNeeded(next);
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,8 @@ import com.couchbase.client.java.query.dsl.path.OrderByPath;
|
||||
import com.couchbase.client.java.query.dsl.path.WherePath;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentPropertyPath;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
@@ -91,22 +89,19 @@ import org.springframework.data.repository.query.parser.PartTree;
|
||||
*/
|
||||
public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression> {
|
||||
|
||||
private final ParameterAccessor accessor;
|
||||
private final WherePath selectFrom;
|
||||
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> context;
|
||||
private final CouchbaseConverter converter;
|
||||
|
||||
public N1qlQueryCreator(PartTree tree, ParameterAccessor parameters, WherePath selectFrom,
|
||||
CouchbaseConverter converter) {
|
||||
super(tree, parameters);
|
||||
this.accessor = parameters;
|
||||
this.selectFrom = selectFrom;
|
||||
this.context = converter.getMappingContext();
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Expression create(Part part, Iterator<Object> iterator) {
|
||||
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||
return prepareExpression(part, path, iterator);
|
||||
return prepareExpression(part, iterator);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,15 +139,17 @@ public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression
|
||||
return selectFrom.where(criteria);
|
||||
}
|
||||
|
||||
protected static Expression prepareExpression(Part part, PersistentPropertyPath<CouchbasePersistentProperty> path,
|
||||
Iterator<Object> parameterValues) {
|
||||
//FIXME use conversions for the parameters and types
|
||||
protected Expression prepareExpression(Part part, Iterator<Object> iterator) {
|
||||
PersistentPropertyPath<CouchbasePersistentProperty> path = converter.getMappingContext()
|
||||
.getPersistentPropertyPath(part.getProperty());
|
||||
ConvertingIterator parameterValues = new ConvertingIterator(iterator, converter);
|
||||
|
||||
//get the whole doted path with fieldNames instead of potentially wrong propNames
|
||||
String fieldNamePath = path.toDotPath(CouchbasePersistentProperty.FIELD_NAME);
|
||||
|
||||
//deal with ignore case
|
||||
boolean ignoreCase = false;
|
||||
Class<?> leafType = path.getLeafProperty().getType();
|
||||
Class<?> leafType = converter.getWriteClassFor(path.getLeafProperty().getType());
|
||||
boolean isString = leafType == String.class;
|
||||
if (part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE) {
|
||||
ignoreCase = isString;
|
||||
@@ -225,6 +222,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression
|
||||
protected static Expression regexp(String left, Iterator<Object> parameterValues) {
|
||||
//TODO migrate to using the Functions util class when 2.0-dp2 / 2.0 GA
|
||||
Object next = parameterValues.next();
|
||||
|
||||
String pattern;
|
||||
if (next == null) {
|
||||
pattern = "";
|
||||
@@ -298,4 +296,5 @@ public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression
|
||||
}
|
||||
return JsonArray.from(values);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public class ViewBasedCouchbaseQuery implements RepositoryQuery {
|
||||
|
||||
ViewQueryCreator creator = new ViewQueryCreator(tree,
|
||||
new ParametersParameterAccessor(method.getParameters(), runtimeParams),
|
||||
baseQuery);
|
||||
baseQuery, operations.getConverter());
|
||||
|
||||
ViewQuery query = creator.createQuery();
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.couchbase.client.java.document.json.JsonArray;
|
||||
import com.couchbase.client.java.document.json.JsonObject;
|
||||
import com.couchbase.client.java.view.ViewQuery;
|
||||
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||
@@ -64,11 +65,13 @@ public class ViewQueryCreator extends AbstractQueryCreator<ViewQuery, ViewQuery>
|
||||
private ViewQuery query;
|
||||
private final PartTree tree;
|
||||
private final int treeCount;
|
||||
private final CouchbaseConverter converter;
|
||||
|
||||
public ViewQueryCreator(PartTree tree, ParameterAccessor parameters, ViewQuery query) {
|
||||
public ViewQueryCreator(PartTree tree, ParameterAccessor parameters, ViewQuery query, CouchbaseConverter converter) {
|
||||
super(tree, parameters);
|
||||
this.query = query;
|
||||
this.tree = tree;
|
||||
this.converter = converter;
|
||||
|
||||
//sanity check the partTree since we have strong restrictions on what's supported:
|
||||
int i = 0;
|
||||
@@ -86,7 +89,9 @@ public class ViewQueryCreator extends AbstractQueryCreator<ViewQuery, ViewQuery>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewQuery create(Part part, Iterator<Object> iterator) {
|
||||
protected ViewQuery create(Part part, Iterator<Object> objectIterator) {
|
||||
ConvertingIterator iterator = new ConvertingIterator(objectIterator, converter);
|
||||
|
||||
switch (part.getType()) {
|
||||
case GREATER_THAN_EQUAL:
|
||||
startKey(iterator);
|
||||
|
||||
Reference in New Issue
Block a user