DATAES-530 - Polishing.

Fix generics in ElasticsearchEntityMapper. Extract methods. Move enum conversion handling to simple type handling for symmetric implementation. Swap comparison of nullable types to avoid potential null dereference.

Rename ElasticsearchDefaultTypeMapper to DefaultElasticsearchTypeMapper. Move MapTypeAliasAccessor to DefaultElasticsearchTypeMapper.

Introduce SearchResultMapperAdapter to avoid empty method implementations in anonymous classes. Javadoc, reference docs, formatting.

Original Pull Request: #237
This commit is contained in:
Mark Paluch 2019-02-07 13:34:31 +01:00 committed by Christoph Strobl
parent a64af54e26
commit c3e7056d7b
16 changed files with 373 additions and 302 deletions

View File

@ -66,13 +66,26 @@ for `Converter` registration.
<3> Optionally set `CustomConversions` if applicable. <3> Optionally set `CustomConversions` if applicable.
==== ====
[[elasticsearch.mapping.meta-model.annotations]]
=== Mapping Annotation Overview
The `ElasticsearchEntityMapper` can use metadata to drive the mapping of objects to documents. The following annotations are available:
* `@Id`: Applied at the field level to mark the field used for identity purpose.
* `@Document`: Applied at the class level to indicate this class is a candidate for mapping to the database. You can specify the index name and index type where the document will be stored.
* `@Transient`: By default all private fields are mapped to the document, this annotation excludes the field where it is applied from being stored in the database
* `@PersistenceConstructor`: Marks a given constructor - even a package protected one - to use when instantiating the object from the database. Constructor arguments are mapped by name to the key values in the retrieved Document.
* `@Field`: Applied at the field level and described the name of the field as it will be represented in the Elasticsearch document thus allowing the name to be different than the fieldname of the class.
The mapping metadata infrastructure is defined in a separate spring-data-commons project that is technology agnostic.
[[elasticsearch.mapping.meta-model.rules]] [[elasticsearch.mapping.meta-model.rules]]
=== Mapping Rules === Mapping Rules
==== Type Hints ==== Type Hints
Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping. Those type hints are Mapping uses _type hints_ embedded in the document sent to the server to allow generic type mapping.
represented as `_class` attributes within the document on the server and will be written for each aggregate root. Those type hints are represented as `_class` attributes within the document and are written for each aggregate root.
.Type Hints .Type Hints
==== ====
@ -123,8 +136,7 @@ public class Person {
<1> The configured alias is used when writing the entity. <1> The configured alias is used when writing the entity.
==== ====
NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the NOTE: Type hints will not be written for nested Objects unless the properties type is `Object`, an interface or the actual value type does not match the properties declaration.
actual value type does not match the properties declaration.
==== Geospatial Types ==== Geospatial Types
@ -152,8 +164,7 @@ public class Address {
==== Collections ==== Collections
For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ For values inside Collections apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
and <<elasticsearch.mapping.meta-model.conversions>>.
.Collections .Collections
==== ====
@ -179,8 +190,7 @@ public class Person {
==== Maps ==== Maps
For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ For values inside Maps apply the same mapping rules as for aggregate roots when it comes to _type hints_ and <<elasticsearch.mapping.meta-model.conversions>>.
and <<elasticsearch.mapping.meta-model.conversions>>.
However the Map key needs to a String to be processed by Elasticsearch. However the Map key needs to a String to be processed by Elasticsearch.
.Collections .Collections
@ -214,8 +224,7 @@ public class Person {
[[elasticsearch.mapping.meta-model.conversions]] [[elasticsearch.mapping.meta-model.conversions]]
=== Custom Conversions === Custom Conversions
Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, previous section>> `ElasticsearchCustomConversions` Looking at the `Configuration` from the <<elasticsearch.mapping.meta-model, previous section>> `ElasticsearchCustomConversions` allows registering specific rules for mapping domain and simple types.
allows to register specific rules for mapping domain and simple types.
.Meta Model Object Mapping Configuration .Meta Model Object Mapping Configuration
==== ====

View File

@ -72,15 +72,14 @@ public class ElasticsearchConfigurationSupport {
} }
/** /**
* Returns the {@link EntityMapper} used for mapping source &lt;&gt; DomainType. <br /> * Returns the {@link EntityMapper} used for mapping between the source and domain type. <br />
* <strong>Hint</strong>: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as * <strong>Hint</strong>: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as
* an alternative to the {@link DefaultEntityMapper}. * an alternative to the {@link DefaultEntityMapper}.
* *
* <pre>{@code * <pre class="code">
* ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(), * ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
* new DefaultConversionService()); * new DefaultConversionService());
* entityMapper.setConversions(elasticsearchCustomConversions()); * entityMapper.setConversions(elasticsearchCustomConversions());
* }
* </pre> * </pre>
* *
* @return never {@literal null}. * @return never {@literal null}.

View File

@ -28,14 +28,20 @@ public abstract class AbstractResultMapper implements ResultsMapper {
private final EntityMapper entityMapper; private final EntityMapper entityMapper;
private final ProjectionFactory projectionFactory; private final ProjectionFactory projectionFactory;
/**
* Create a new {@link AbstractResultMapper}.
*
* @param entityMapper must not be {@literal null}.
*/
public AbstractResultMapper(EntityMapper entityMapper) { public AbstractResultMapper(EntityMapper entityMapper) {
this(entityMapper, new SpelAwareProxyProjectionFactory()); this(entityMapper, new SpelAwareProxyProjectionFactory());
} }
/** /**
* Create a new {@link AbstractResultMapper}.
* *
* @param entityMapper * @param entityMapper must not be {@literal null}.
* @param projectionFactory * @param projectionFactory must not be {@literal null}.
* @since 3.2 * @since 3.2
*/ */
public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) { public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) {
@ -47,6 +53,10 @@ public abstract class AbstractResultMapper implements ResultsMapper {
this.projectionFactory = projectionFactory; this.projectionFactory = projectionFactory;
} }
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.ResultsMapper#getEntityMapper()
*/
@Override @Override
public EntityMapper getEntityMapper() { public EntityMapper getEntityMapper() {
return this.entityMapper; return this.entityMapper;
@ -58,6 +68,6 @@ public abstract class AbstractResultMapper implements ResultsMapper {
*/ */
@Override @Override
public ProjectionFactory getProjectionFactory() { public ProjectionFactory getProjectionFactory() {
return projectionFactory; return this.projectionFactory;
} }
} }

View File

@ -88,12 +88,11 @@ public class DefaultResultMapper extends AbstractResultMapper {
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
} }
static EntityMapper initEntityMapper( private static EntityMapper initEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) { MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!"); Assert.notNull(mappingContext, "MappingContext must not be null!");
return new DefaultEntityMapper(mappingContext); return new DefaultEntityMapper(mappingContext);
} }
@Override @Override

View File

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import lombok.RequiredArgsConstructor;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -34,7 +36,6 @@ import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomCo
import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper; import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
@ -76,7 +77,49 @@ public class ElasticsearchEntityMapper implements
this.mappingContext = mappingContext; this.mappingContext = mappingContext;
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService(); this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
this.typeMapper = ElasticsearchTypeMapper.defaultTypeMapper(mappingContext); this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
}
// --> GETTERS / SETTERS
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
*
* @param conversions must not be {@literal null}.
*/
public void setConversions(CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the {@link ElasticsearchTypeMapper} to use for reading / writing type hints.
*
* @param typeMapper must not be {@literal null}.
*/
public void setTypeMapper(ElasticsearchTypeMapper typeMapper) {
this.typeMapper = typeMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
DateFormatterRegistrar.addDateConverters(conversionService);
conversions.registerConvertersIn(conversionService);
} }
// --> READ // --> READ
@ -86,12 +129,14 @@ public class ElasticsearchEntityMapper implements
return read(targetType, source); return read(targetType, source);
} }
@SuppressWarnings("unchecked")
@Override @Override
@Nullable @Nullable
public <R> R read(Class<R> type, Map<String, Object> source) { public <R> R read(Class<R> type, Map<String, Object> source) {
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type))); return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
} }
@SuppressWarnings("unchecked")
@Nullable @Nullable
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) { protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
@ -113,6 +158,7 @@ public class ElasticsearchEntityMapper implements
return readEntity(entity, source); return readEntity(entity, source);
} }
@SuppressWarnings("unchecked")
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) { protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source); ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
@ -125,11 +171,8 @@ public class ElasticsearchEntityMapper implements
R instance = (R) instantiator.createInstance(targetEntity, R instance = (R) instantiator.createInstance(targetEntity,
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null)); new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
if (targetEntity.requiresPropertyPopulation()) { return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
return readProperties(targetEntity, instance, propertyValueProvider); : instance;
}
return instance;
} }
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance, protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
@ -153,6 +196,7 @@ public class ElasticsearchEntityMapper implements
return accessor.getBean(); return accessor.getBean();
} }
@SuppressWarnings("unchecked")
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property, protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) { TypeInformation<R> targetType) {
@ -160,20 +204,19 @@ public class ElasticsearchEntityMapper implements
return null; return null;
} }
Class<?> rawType = targetType.getType(); Class<R> rawType = targetType.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) { if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
return (R) conversionService.convert(source, rawType); return rawType.cast(conversionService.convert(source, rawType));
} else if (source instanceof List) { } else if (source instanceof List) {
return readCollectionValue((List) source, property, targetType); return readCollectionValue((List) source, property, targetType);
} else if (source instanceof Map) { } else if (source instanceof Map) {
return readMapValue((Map<String, Object>) source, property, targetType); return readMapValue((Map<String, Object>) source, property, targetType);
} else if (Enum.class.isAssignableFrom(rawType)) {
return (R) Enum.valueOf((Class<Enum>) rawType, source.toString());
} }
return (R) readSimpleValue(source, targetType); return (R) readSimpleValue(source, targetType);
} }
@SuppressWarnings("unchecked")
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property, private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) { TypeInformation<R> targetType) {
@ -186,10 +229,10 @@ public class ElasticsearchEntityMapper implements
return readEntity(targetEntity, source); return readEntity(targetEntity, source);
} }
Map<String, Object> target = new LinkedHashMap(); Map<String, Object> target = new LinkedHashMap<>();
for (Entry<String, Object> entry : source.entrySet()) { for (Entry<String, Object> entry : source.entrySet()) {
if (conversions.isSimpleType(entry.getValue().getClass())) { if (isSimpleType(entry.getValue())) {
target.put(entry.getKey(), target.put(entry.getKey(),
readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType)); readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType));
} else { } else {
@ -211,13 +254,13 @@ public class ElasticsearchEntityMapper implements
} else { } else {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue())); target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
} }
} }
} }
return (R) target; return (R) target;
} }
@SuppressWarnings("unchecked")
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property, private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) { TypeInformation<R> targetType) {
@ -225,12 +268,11 @@ public class ElasticsearchEntityMapper implements
return null; return null;
} }
List<?> sourceList = source; Collection<Object> target = createCollectionForValue(targetType, source.size());
Collection target = createCollectionForValue(targetType, sourceList.size());
for (Object value : sourceList) { for (Object value : source) {
if (conversions.isSimpleType(value.getClass())) { if (isSimpleType(value)) {
target.add( target.add(
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType)); readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
} else { } else {
@ -246,23 +288,24 @@ public class ElasticsearchEntityMapper implements
return (R) target; return (R) target;
} }
protected Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) { @SuppressWarnings("unchecked")
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
if (value == null) { Class<?> target = targetType.getType();
return null;
}
if (ClassTypeInformation.OBJECT.equals(targetType) if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|| (targetType != null && value.getClass().equals(targetType.getActualType()))) {
return value; return value;
} }
if (conversionService.canConvert(value.getClass(), targetType.getType())) { if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, targetType.getType()); return conversionService.convert(value, target);
} }
throw new MappingException( if (Enum.class.isAssignableFrom(target)) {
String.format("Unable to map %s of type %s to %s", value, value.getClass(), targetType.getType())); return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
} }
// --> WRITE // --> WRITE
@ -275,6 +318,7 @@ public class ElasticsearchEntityMapper implements
return target; return target;
} }
@SuppressWarnings("unchecked")
@Override @Override
public void write(@Nullable Object source, Map<String, Object> sink) { public void write(@Nullable Object source, Map<String, Object> sink) {
@ -284,12 +328,12 @@ public class ElasticsearchEntityMapper implements
if (source instanceof Map) { if (source instanceof Map) {
sink.putAll((Map) source); sink.putAll((Map<String, Object>) source);
return; return;
} }
Class<?> entityType = ClassUtils.getUserClass(source.getClass()); Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<? extends Object> type = ClassTypeInformation.from(entityType); TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) { if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink); typeMapper.writeType(source.getClass(), sink);
@ -337,6 +381,7 @@ public class ElasticsearchEntityMapper implements
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) { if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
typeMapper.writeType(source.getClass(), sink); typeMapper.writeType(source.getClass(), sink);
} }
writeProperties(entity, accessor, sink); writeProperties(entity, accessor, sink);
} }
@ -355,7 +400,7 @@ public class ElasticsearchEntityMapper implements
continue; continue;
} }
if (!conversions.isSimpleType(value.getClass())) { if (!isSimpleType(value)) {
writeProperty(property, value, sink); writeProperty(property, value, sink);
} else { } else {
sink.put(property.getFieldName(), getWriteSimpleValue(value)); sink.put(property.getFieldName(), getWriteSimpleValue(value));
@ -393,15 +438,20 @@ public class ElasticsearchEntityMapper implements
protected Object getWriteSimpleValue(Object value) { protected Object getWriteSimpleValue(Object value) {
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass()); if (value == null) {
return null;
if (!customWriteTarget.isPresent()) {
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
} }
return conversionService.convert(value, customWriteTarget.get()); Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(value.getClass());
if (customTarget.isPresent()) {
return conversionService.convert(value, customTarget.get());
}
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
} }
@SuppressWarnings("unchecked")
protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint, protected Object getWriteComplexValue(ElasticsearchPersistentProperty property, TypeInformation<?> typeHint,
Object value) { Object value) {
@ -412,7 +462,7 @@ public class ElasticsearchEntityMapper implements
return writeMapValue((Map<String, Object>) value, property, typeHint); return writeMapValue((Map<String, Object>) value, property, typeHint);
} }
if (property.isEntity() || !conversions.isSimpleType(value.getClass())) { if (property.isEntity() || !isSimpleType(value)) {
return writeEntity(value, property, typeHint); return writeEntity(value, property, typeHint);
} }
@ -433,7 +483,7 @@ public class ElasticsearchEntityMapper implements
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet()); Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
if (!typeHint.getActualType().getType().equals(Object.class) if (!typeHint.getActualType().getType().equals(Object.class)
&& conversions.isSimpleType(typeHint.getMapValueType().getType())) { && isSimpleType(typeHint.getMapValueType().getType())) {
mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue()))); mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue())));
} else { } else {
@ -442,7 +492,7 @@ public class ElasticsearchEntityMapper implements
Object converted = null; Object converted = null;
if (it.getValue() != null) { if (it.getValue() != null) {
if (conversions.isSimpleType(it.getValue().getClass())) { if (isSimpleType(it.getValue())) {
converted = getWriteSimpleValue(it.getValue()); converted = getWriteSimpleValue(it.getValue());
} else { } else {
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()), converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
@ -451,7 +501,6 @@ public class ElasticsearchEntityMapper implements
} }
target.put(it.getKey(), converted); target.put(it.getKey(), converted);
}); });
} }
@ -461,23 +510,21 @@ public class ElasticsearchEntityMapper implements
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property, private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) { TypeInformation<?> typeHint) {
Streamable collectionSource = value instanceof Iterable ? Streamable.of((Iterable) value) Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
: Streamable.of(ObjectUtils.toObjectArray(value)); : Streamable.of(ObjectUtils.toObjectArray(value));
List<Object> target = new ArrayList<>(); List<Object> target = new ArrayList<>();
if (!typeHint.getActualType().getType().equals(Object.class) if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
&& conversions.isSimpleType(typeHint.getActualType().getType())) {
collectionSource.map(this::getWriteSimpleValue).forEach(target::add); collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
} else { } else {
Streamable.of((Iterable) value).map(it -> { collectionSource.map(it -> {
if (it == null) { if (it == null) {
return null; return null;
} }
if (conversions.isSimpleType(it.getClass())) { if (isSimpleType(it)) {
return getWriteSimpleValue(it); return getWriteSimpleValue(it);
} }
@ -488,48 +535,6 @@ public class ElasticsearchEntityMapper implements
return target; return target;
} }
// --> GETTERS / SETTERS
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
}
@Override
public ConversionService getConversionService() {
return conversionService;
}
/**
* Set the {@link CustomConversions} to be applied during the mapping process. <br />
* Conversions are registered after {@link #afterPropertiesSet() bean initialization}.
*
* @param conversions must not be {@literal null}.
*/
public void setConversions(CustomConversions conversions) {
this.conversions = conversions;
}
/**
* Set the {@link ElasticsearchTypeMapper} to use for reading / writing type hints.
*
* @param typeMapper must not be {@literal null}.
*/
public void setTypeMapper(ElasticsearchTypeMapper typeMapper) {
this.typeMapper = typeMapper;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() {
DateFormatterRegistrar.addDateConverters(conversionService);
conversions.registerConvertersIn(conversionService);
}
// --> LEGACY // --> LEGACY
@Override @Override
@ -549,21 +554,23 @@ public class ElasticsearchEntityMapper implements
// --> PRIVATE HELPERS // --> PRIVATE HELPERS
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType, private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
@Nullable TypeInformation<?> containingStructure) { @Nullable TypeInformation<?> container) {
if (containingStructure != null) { if (container != null) {
if (containingStructure.isCollectionLike()) { if (container.isCollectionLike()) {
if (containingStructure.getActualType().equals(type) && type.getType().equals(actualType)) { if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
return false; return false;
} }
} }
if (containingStructure.isMap()) {
if (containingStructure.getMapValueType().equals(type) && type.getType().equals(actualType)) { if (container.isMap()) {
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
return false; return false;
} }
} }
if (containingStructure.equals(type) && type.getType().equals(actualType)) {
if (container.equals(type) && type.getType().equals(actualType)) {
return false; return false;
} }
} }
@ -574,7 +581,7 @@ public class ElasticsearchEntityMapper implements
/** /**
* Compute the type to use by checking the given entity against the store type; * Compute the type to use by checking the given entity against the store type;
* *
* @param entity * @param entity
* @param source * @param source
* @return * @return
@ -600,12 +607,12 @@ public class ElasticsearchEntityMapper implements
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property, private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
Object value) { Object value) {
return property.getTypeInformation().getActualType().equals(ClassTypeInformation.OBJECT) return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
? mappingContext.getRequiredPersistentEntity(value.getClass()) ? mappingContext.getRequiredPersistentEntity(value.getClass())
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType()); : mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
} }
private Collection<?> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) { private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) // Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
? collectionTypeInformation.getType() // ? collectionTypeInformation.getType() //
@ -618,19 +625,24 @@ public class ElasticsearchEntityMapper implements
return collectionTypeInformation.getType().isArray() // return collectionTypeInformation.getType().isArray() //
? new ArrayList<>(size) // ? new ArrayList<>(size) //
: CollectionFactory.createCollection(collectionType, componentType.getType(), size); : CollectionFactory.createCollection(collectionType, componentType.getType(), size);
}
private boolean isSimpleType(Object value) {
return isSimpleType(value.getClass());
}
private boolean isSimpleType(Class<?> type) {
return conversions.isSimpleType(type);
} }
// --> OHTER STUFF // --> OHTER STUFF
@RequiredArgsConstructor
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> { class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor mapValueAccessor; final MapValueAccessor mapValueAccessor;
public ElasticsearchPropertyValueProvider(MapValueAccessor mapValueAccessor) { @SuppressWarnings("unchecked")
this.mapValueAccessor = mapValueAccessor;
}
@Override @Override
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) { public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation()); return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
@ -670,13 +682,14 @@ public class ElasticsearchEntityMapper implements
return result; return result;
} }
@SuppressWarnings("unchecked")
private Map<String, Object> getAsMap(Object result) { private Map<String, Object> getAsMap(Object result) {
if (result instanceof Map) { if (result instanceof Map) {
return (Map) result; return (Map) result;
} }
throw new IllegalArgumentException(String.format("%s is no Map.", result)); throw new IllegalArgumentException(String.format("%s is not a Map.", result));
} }
} }

View File

@ -24,16 +24,7 @@ import static org.springframework.util.StringUtils.*;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.ActionFuture;
@ -877,7 +868,7 @@ public class ElasticsearchRestTemplate
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName) SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName)
.withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build(); .withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build();
SearchResultMapper onlyIdResultMapper = new SearchResultMapper() { SearchResultMapper onlyIdResultMapper = new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<String> result = new ArrayList<String>(); List<String> result = new ArrayList<String>();
@ -886,14 +877,9 @@ public class ElasticsearchRestTemplate
result.add(id); result.add(id);
} }
if (result.size() > 0) { if (result.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId()); return new AggregatedPageImpl<>((List<T>) result, response.getScrollId());
} }
return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId()); return new AggregatedPageImpl<>(Collections.emptyList(), response.getScrollId());
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
} }
}; };
@ -1458,7 +1444,7 @@ public class ElasticsearchRestTemplate
/** /**
* It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field * It takes two steps to create a List<AliasMetadata> from the elasticsearch http response because the aliases field
* is actually a Map by alias name, but the alias name is on the AliasMetadata. * is actually a Map by alias name, but the alias name is on the AliasMetadata.
* *
* @param aliasResponse * @param aliasResponse
* @return * @return
*/ */

View File

@ -755,7 +755,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName) SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(deleteQuery.getQuery()).withIndices(indexName)
.withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build(); .withTypes(typeName).withPageable(PageRequest.of(0, pageSize)).build();
SearchResultMapper onlyIdResultMapper = new SearchResultMapper() { SearchResultMapper onlyIdResultMapper = new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<String> result = new ArrayList<String>(); List<String> result = new ArrayList<String>();
@ -766,12 +766,7 @@ public class ElasticsearchTemplate implements ElasticsearchOperations, EsClient<
if (result.size() > 0) { if (result.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId()); return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId());
} }
return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId()); return new AggregatedPageImpl<T>(Collections.emptyList(), response.getScrollId());
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
} }
}; };

View File

@ -86,14 +86,14 @@ public interface ResultsMapper extends SearchResultMapper, GetResultMapper, Mult
Object mappedResult = getEntityMapper().readObject(source, type); Object mappedResult = getEntityMapper().readObject(source, type);
if (mappedResult == null) { if (mappedResult == null) {
return (T) mappedResult; return (T) null;
} }
if (type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)) { if (type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)) {
return getProjectionFactory().createProjection(type, mappedResult); return getProjectionFactory().createProjection(type, mappedResult);
} }
return (T) mappedResult; return type.cast(mappedResult);
} }
/** /**
@ -120,13 +120,13 @@ public interface ResultsMapper extends SearchResultMapper, GetResultMapper, Mult
Object mappedResult = getEntityMapper().readObject(source, type); Object mappedResult = getEntityMapper().readObject(source, type);
if (mappedResult == null) { if (mappedResult == null) {
return (T) mappedResult; return null;
} }
if (type.isInterface()) { if (type.isInterface()) {
return getProjectionFactory().createProjection(type, mappedResult); return getProjectionFactory().createProjection(type, mappedResult);
} }
return (T) mappedResult; return type.cast(mappedResult);
} }
} }

View File

@ -0,0 +1,49 @@
/*
* Copyright 2019 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.elasticsearch.core;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.search.SearchHit;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
/**
* Adapter utility for {@link SearchResultMapper} that wish to implement a subset of mapping methods. Default
* implementations throw {@link UnsupportedOperationException}.
*
* @author Mark Paluch
* @since 3.2
*/
abstract class SearchResultMapperAdapter implements SearchResultMapper {
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapResults(org.elasticsearch.action.search.SearchResponse, java.lang.Class, org.springframework.data.domain.Pageable)
*/
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
throw new UnsupportedOperationException();
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapSearchHit(org.elasticsearch.search.SearchHit, java.lang.Class)
*/
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
throw new UnsupportedOperationException();
}
}

View File

@ -30,7 +30,6 @@ import org.springframework.core.convert.converter.Converter;
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
*/ */
public final class DateTimeConverters { public final class DateTimeConverters {
private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC); private static DateTimeFormatter formatter = ISODateTimeFormat.dateTime().withZone(DateTimeZone.UTC);

View File

@ -0,0 +1,104 @@
/*
* Copyright 2019 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.elasticsearch.core.convert;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.convert.TypeAliasAccessor;
import org.springframework.data.convert.TypeInformationMapper;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
/**
* Elasticsearch specific {@link org.springframework.data.convert.TypeMapper} implementation.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 3.2
*/
public class DefaultElasticsearchTypeMapper extends DefaultTypeMapper<Map<String, Object>>
implements ElasticsearchTypeMapper {
private final @Nullable String typeKey;
public DefaultElasticsearchTypeMapper(@Nullable String typeKey) {
this(typeKey, Collections.singletonList(new SimpleTypeInformationMapper()));
}
public DefaultElasticsearchTypeMapper(@Nullable String typeKey,
MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext) {
this(typeKey, new MapTypeAliasAccessor(typeKey), mappingContext,
Collections.singletonList(new SimpleTypeInformationMapper()));
}
public DefaultElasticsearchTypeMapper(@Nullable String typeKey, List<? extends TypeInformationMapper> mappers) {
this(typeKey, new MapTypeAliasAccessor(typeKey), null, mappers);
}
public DefaultElasticsearchTypeMapper(@Nullable String typeKey, TypeAliasAccessor<Map<String, Object>> accessor,
@Nullable MappingContext<? extends PersistentEntity<?, ?>, ?> mappingContext,
List<? extends TypeInformationMapper> mappers) {
super(accessor, mappingContext, mappers);
this.typeKey = typeKey;
}
@Override
public boolean isTypeKey(String key) {
return typeKey != null && typeKey.equals(key);
}
/**
* {@link TypeAliasAccessor} to store aliases in a {@link Map}.
*
* @author Christoph Strobl
*/
public static class MapTypeAliasAccessor implements TypeAliasAccessor<Map<String, Object>> {
private final @Nullable String typeKey;
public MapTypeAliasAccessor(@Nullable String typeKey) {
this.typeKey = typeKey;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeAliasAccessor#readAliasFrom(java.lang.Object)
*/
public Alias readAliasFrom(Map<String, Object> source) {
return Alias.ofNullable(source.get(typeKey));
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeAliasAccessor#writeTypeTo(java.lang.Object, java.lang.Object)
*/
public void writeTypeTo(Map<String, Object> sink, Object alias) {
if (typeKey == null) {
return;
}
sink.put(typeKey, alias);
}
}
}

View File

@ -43,6 +43,7 @@ public class ElasticsearchCustomConversions extends CustomConversions {
static { static {
List<Object> converters = new ArrayList<>(); List<Object> converters = new ArrayList<>();
converters.addAll(GeoConverters.getConvertersToRegister()); converters.addAll(GeoConverters.getConvertersToRegister());
converters.add(StringToUUIDConverter.INSTANCE); converters.add(StringToUUIDConverter.INSTANCE);
converters.add(UUIDToStringConverter.INSTANCE); converters.add(UUIDToStringConverter.INSTANCE);
@ -62,6 +63,9 @@ public class ElasticsearchCustomConversions extends CustomConversions {
super(STORE_CONVERSIONS, converters); super(STORE_CONVERSIONS, converters);
} }
/**
* {@link Converter} to read a {@link UUID} from its {@link String} representation.
*/
@ReadingConverter @ReadingConverter
enum StringToUUIDConverter implements Converter<String, UUID> { enum StringToUUIDConverter implements Converter<String, UUID> {
@ -73,6 +77,9 @@ public class ElasticsearchCustomConversions extends CustomConversions {
} }
} }
/**
* {@link Converter} to write a {@link UUID} to its {@link String} representation.
*/
@WritingConverter @WritingConverter
enum UUIDToStringConverter implements Converter<UUID, String> { enum UUIDToStringConverter implements Converter<UUID, String> {
@ -84,6 +91,9 @@ public class ElasticsearchCustomConversions extends CustomConversions {
} }
} }
/**
* {@link Converter} to read a {@link BigDecimal} from a {@link Double} value.
*/
@ReadingConverter @ReadingConverter
enum DoubleToBigDecimalConverter implements Converter<Double, BigDecimal> { enum DoubleToBigDecimalConverter implements Converter<Double, BigDecimal> {
@ -95,6 +105,9 @@ public class ElasticsearchCustomConversions extends CustomConversions {
} }
} }
/**
* {@link Converter} to write a {@link BigDecimal} to a {@link Double} value.
*/
@WritingConverter @WritingConverter
enum BigDecimalToDoubleConverter implements Converter<BigDecimal, Double> { enum BigDecimalToDoubleConverter implements Converter<BigDecimal, Double> {

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019 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.elasticsearch.core.convert;
import java.util.List;
import java.util.Map;
import org.springframework.data.convert.DefaultTypeMapper;
import org.springframework.data.convert.TypeAliasAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
/**
* Elasticsearch specific {@link org.springframework.data.convert.TypeMapper} implementation.
*
* @author Christoph Strobl
* @since 3.2
*/
class ElasticsearchDefaultTypeMapper extends DefaultTypeMapper<Map<String, Object>> implements ElasticsearchTypeMapper {
private final @Nullable String typeKey;
ElasticsearchDefaultTypeMapper(@Nullable String typeKey, TypeAliasAccessor accessor,
@Nullable MappingContext mappingContext, List additionalMappers) {
super(accessor, mappingContext, additionalMappers);
this.typeKey = typeKey;
}
@Override
public boolean isTypeKey(String key) {
return typeKey != null && typeKey.equals(key);
}
}

View File

@ -15,17 +15,12 @@
*/ */
package org.springframework.data.elasticsearch.core.convert; package org.springframework.data.elasticsearch.core.convert;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import org.springframework.data.convert.SimpleTypeInformationMapper;
import org.springframework.data.convert.TypeAliasAccessor;
import org.springframework.data.convert.TypeMapper; import org.springframework.data.convert.TypeMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.Alias;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.lang.Nullable;
/** /**
* Elasticsearch specific {@link TypeMapper} definition. * Elasticsearch specific {@link TypeMapper} definition.
@ -48,45 +43,15 @@ public interface ElasticsearchTypeMapper extends TypeMapper<Map<String, Object>>
return readType(source) != null; return readType(source) != null;
} }
static ElasticsearchTypeMapper defaultTypeMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
return new ElasticsearchDefaultTypeMapper(DEFAULT_TYPE_KEY, new MapTypeAliasAccessor(DEFAULT_TYPE_KEY),
mappingContext, Collections.singletonList(new SimpleTypeInformationMapper()));
}
/** /**
* {@link TypeAliasAccessor} to store aliases in a {@link Map}. * Creates a new default {@link ElasticsearchTypeMapper}.
* *
* @author Christoph Strobl * @param mappingContext the mapping context.
* @return a new default {@link ElasticsearchTypeMapper}.
* @see DefaultElasticsearchTypeMapper
*/ */
final class MapTypeAliasAccessor implements TypeAliasAccessor<Map<String, Object>> { static ElasticsearchTypeMapper create(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
private final @Nullable String typeKey; return new DefaultElasticsearchTypeMapper(DEFAULT_TYPE_KEY, mappingContext);
public MapTypeAliasAccessor(@Nullable String typeKey) {
this.typeKey = typeKey;
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeAliasAccessor#readAliasFrom(java.lang.Object)
*/
public Alias readAliasFrom(Map<String, Object> source) {
return Alias.ofNullable(source.get(typeKey));
}
/*
* (non-Javadoc)
* @see org.springframework.data.convert.TypeAliasAccessor#writeTypeTo(java.lang.Object, java.lang.Object)
*/
public void writeTypeTo(Map<String, Object> sink, Object alias) {
if (typeKey == null) {
return;
}
sink.put(typeKey, alias);
}
} }
} }

View File

@ -35,14 +35,17 @@ import org.springframework.util.NumberUtils;
*/ */
class GeoConverters { class GeoConverters {
static Collection<? extends Object> getConvertersToRegister() { static Collection<Object> getConvertersToRegister() {
return Arrays.asList(PointToMapConverter.INSTANCE, MapToPointConverter.INSTANCE, GeoPointToMapConverter.INSTANCE, return Arrays.asList(PointToMapConverter.INSTANCE, MapToPointConverter.INSTANCE, GeoPointToMapConverter.INSTANCE,
MapToGeoPointConverter.INSTANCE); MapToGeoPointConverter.INSTANCE);
} }
/**
* {@link Converter} to write a {@link Point} to {@link Map} using {@code lat/long} properties.
*/
@WritingConverter @WritingConverter
enum PointToMapConverter implements Converter<Point, Map> { enum PointToMapConverter implements Converter<Point, Map<String, Object>> {
INSTANCE; INSTANCE;
@ -56,8 +59,11 @@ class GeoConverters {
} }
} }
/**
* {@link Converter} to write a {@link GeoPoint} to {@link Map} using {@code lat/long} properties.
*/
@WritingConverter @WritingConverter
enum GeoPointToMapConverter implements Converter<GeoPoint, Map> { enum GeoPointToMapConverter implements Converter<GeoPoint, Map<String, Object>> {
INSTANCE; INSTANCE;
@ -70,8 +76,11 @@ class GeoConverters {
} }
} }
/**
* {@link Converter} to read a {@link Point} from {@link Map} using {@code lat/long} properties.
*/
@ReadingConverter @ReadingConverter
enum MapToPointConverter implements Converter<Map, Point> { enum MapToPointConverter implements Converter<Map<String, Object>, Point> {
INSTANCE; INSTANCE;
@ -84,8 +93,11 @@ class GeoConverters {
} }
} }
/**
* {@link Converter} to read a {@link GeoPoint} from {@link Map} using {@code lat/long} properties.
*/
@ReadingConverter @ReadingConverter
enum MapToGeoPointConverter implements Converter<Map, GeoPoint> { enum MapToGeoPointConverter implements Converter<Map<String, Object>, GeoPoint> {
INSTANCE; INSTANCE;

View File

@ -15,6 +15,12 @@
*/ */
package org.springframework.data.elasticsearch.core; package org.springframework.data.elasticsearch.core;
import static org.apache.commons.lang.RandomStringUtils.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.elasticsearch.utils.IndexBuilder.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -62,11 +68,6 @@ import org.springframework.data.elasticsearch.entities.SampleEntity;
import org.springframework.data.elasticsearch.entities.SampleMappingEntity; import org.springframework.data.elasticsearch.entities.SampleMappingEntity;
import org.springframework.data.elasticsearch.entities.UseServerConfigurationEntity; import org.springframework.data.elasticsearch.entities.UseServerConfigurationEntity;
import org.springframework.data.util.CloseableIterator; import org.springframework.data.util.CloseableIterator;
import static org.apache.commons.lang.RandomStringUtils.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.elasticsearch.utils.IndexBuilder.*;
/** /**
* Base for testing rest/transport templates * Base for testing rest/transport templates
@ -808,7 +809,7 @@ public class ElasticsearchTemplateTests {
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withIndices(INDEX_NAME) SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withIndices(INDEX_NAME)
.withTypes(TYPE_NAME).withFields("message").build(); .withTypes(TYPE_NAME).withFields("message").build();
// when // when
Page<String> page = elasticsearchTemplate.queryForPage(searchQuery, String.class, new SearchResultMapper() { Page<String> page = elasticsearchTemplate.queryForPage(searchQuery, String.class, new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<String> values = new ArrayList<>(); List<String> values = new ArrayList<>();
@ -817,11 +818,6 @@ public class ElasticsearchTemplateTests {
} }
return new AggregatedPageImpl<>((List<T>) values); return new AggregatedPageImpl<>((List<T>) values);
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
// then // then
assertThat(page, is(notNullValue())); assertThat(page, is(notNullValue()));
@ -941,10 +937,10 @@ public class ElasticsearchTemplateTests {
} }
final SearchResultMapper searchResultMapper = new SearchResultMapper() { final SearchResultMapper searchResultMapper = new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<SampleEntity> result = new ArrayList<SampleEntity>(); List<SampleEntity> result = new ArrayList<>();
for (SearchHit searchHit : response.getHits()) { for (SearchHit searchHit : response.getHits()) {
if (response.getHits().getHits().length <= 0) { if (response.getHits().getHits().length <= 0) {
return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId()); return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId());
@ -959,12 +955,7 @@ public class ElasticsearchTemplateTests {
if (result.size() > 0) { if (result.size() > 0) {
return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId()); return new AggregatedPageImpl<T>((List<T>) result, response.getScrollId());
} }
return new AggregatedPageImpl<T>(Collections.EMPTY_LIST, response.getScrollId()); return new AggregatedPageImpl<T>(Collections.emptyList(), response.getScrollId());
}
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
} }
}; };
@ -1347,7 +1338,8 @@ public class ElasticsearchTemplateTests {
.withHighlightFields(message.toArray(new HighlightBuilder.Field[message.size()])) .withHighlightFields(message.toArray(new HighlightBuilder.Field[message.size()]))
.build(); .build();
Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapper() { Page<SampleEntity> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class,
new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<SampleEntity> chunk = new ArrayList<>(); List<SampleEntity> chunk = new ArrayList<>();
@ -1366,11 +1358,6 @@ public class ElasticsearchTemplateTests {
} }
return null; return null;
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
assertThat(sampleEntities.getContent().get(0).getHighlightedMessage(), is(highlightedMessage)); assertThat(sampleEntities.getContent().get(0).getHighlightedMessage(), is(highlightedMessage));
@ -1409,7 +1396,7 @@ public class ElasticsearchTemplateTests {
.build(); .build();
// when // when
elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapper() { elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
for (SearchHit searchHit : response.getHits()) { for (SearchHit searchHit : response.getHits()) {
@ -1425,11 +1412,6 @@ public class ElasticsearchTemplateTests {
} }
return null; return null;
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
} }
@ -1458,7 +1440,7 @@ public class ElasticsearchTemplateTests {
.withHighlightFields(new HighlightBuilder.Field("message")) .withHighlightFields(new HighlightBuilder.Field("message"))
.build(); .build();
// when // when
elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapper() { elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
for (SearchHit searchHit : response.getHits()) { for (SearchHit searchHit : response.getHits()) {
@ -1471,11 +1453,6 @@ public class ElasticsearchTemplateTests {
} }
return null; return null;
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
} }
@ -1621,7 +1598,8 @@ public class ElasticsearchTemplateTests {
.withTypes(TYPE_NAME) .withTypes(TYPE_NAME)
.build(); .build();
// then // then
Page<SampleEntity> page = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapper() { Page<SampleEntity> page = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class,
new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<SampleEntity> values = new ArrayList<>(); List<SampleEntity> values = new ArrayList<>();
@ -1633,11 +1611,6 @@ public class ElasticsearchTemplateTests {
} }
return new AggregatedPageImpl<>((List<T>) values); return new AggregatedPageImpl<>((List<T>) values);
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
assertThat(page, is(notNullValue())); assertThat(page, is(notNullValue()));
assertThat(page.getContent().size(), is(1)); assertThat(page.getContent().size(), is(1));
@ -1826,7 +1799,8 @@ public class ElasticsearchTemplateTests {
// then // then
SearchQuery searchQuery = new NativeSearchQueryBuilder().withIndices(INDEX_NAME) SearchQuery searchQuery = new NativeSearchQueryBuilder().withIndices(INDEX_NAME)
.withTypes(TYPE_NAME).withQuery(matchAllQuery()).build(); .withTypes(TYPE_NAME).withQuery(matchAllQuery()).build();
Page<Map> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, Map.class, new SearchResultMapper() { Page<Map> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, Map.class,
new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<Map> chunk = new ArrayList<>(); List<Map> chunk = new ArrayList<>();
@ -1847,11 +1821,6 @@ public class ElasticsearchTemplateTests {
} }
return null; return null;
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
assertThat(sampleEntities.getTotalElements(), is(equalTo(2L))); assertThat(sampleEntities.getTotalElements(), is(equalTo(2L)));
assertThat(sampleEntities.getContent().get(0).get("userId"), is(person1.get("userId"))); assertThat(sampleEntities.getContent().get(0).get("userId"), is(person1.get("userId")));
@ -2400,7 +2369,8 @@ public class ElasticsearchTemplateTests {
// When // When
SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTypes("hetro").withIndices(INDEX_1_NAME, INDEX_2_NAME).build(); SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).withTypes("hetro").withIndices(INDEX_1_NAME, INDEX_2_NAME).build();
Page<ResultAggregator> page = elasticsearchTemplate.queryForPage(searchQuery, ResultAggregator.class, new SearchResultMapper() { Page<ResultAggregator> page = elasticsearchTemplate.queryForPage(searchQuery, ResultAggregator.class,
new SearchResultMapperAdapter() {
@Override @Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<ResultAggregator> values = new ArrayList<>(); List<ResultAggregator> values = new ArrayList<>();
@ -2412,11 +2382,6 @@ public class ElasticsearchTemplateTests {
} }
return new AggregatedPageImpl<>((List<T>) values); return new AggregatedPageImpl<>((List<T>) values);
} }
@Override
public <T> T mapSearchHit(SearchHit searchHit, Class<T> type) {
return null;
}
}); });
assertThat(page.getTotalElements(), is(2l)); assertThat(page.getTotalElements(), is(2l));