DATAES-629 - ElasticsearchEntityMapper refactoring.

Original PR: #309
This commit is contained in:
Peter-Josef Meisch 2019-09-16 19:37:51 +02:00
parent 7a4aebf9f4
commit b820c9a422
29 changed files with 876 additions and 1510 deletions

View File

@ -19,6 +19,8 @@ import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
/**
* @author Christoph Strobl
@ -42,8 +44,9 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo
*
* @return never {@literal null}.
*/
@Bean(name = {"elasticsearchOperations", "elasticsearchTemplate"})
public ElasticsearchOperations elasticsearchOperations() {
return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter(), resultsMapper());
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(MappingElasticsearchConverter mappingElasticsearchConverter,
ResultsMapper resultsMapper) {
return new ElasticsearchRestTemplate(elasticsearchClient(), mappingElasticsearchConverter, resultsMapper);
}
}

View File

@ -22,6 +22,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.lang.Nullable;
/**
@ -47,10 +49,11 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic
* @return never {@literal null}.
*/
@Bean
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate() {
public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(
MappingElasticsearchConverter mappingElasticsearchConverter, ResultsMapper resultsMapper) {
ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(),
elasticsearchConverter(), resultsMapper());
mappingElasticsearchConverter, resultsMapper);
template.setIndicesOptions(indicesOptions());
template.setRefreshPolicy(refreshPolicy());

View File

@ -27,14 +27,13 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.data.annotation.Persistent;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.DefaultResultMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ResultsMapper;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
@ -43,14 +42,16 @@ import org.springframework.util.StringUtils;
/**
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
@Configuration
public class ElasticsearchConfigurationSupport {
@Bean
public ElasticsearchConverter elasticsearchConverter() {
return new MappingElasticsearchConverter(elasticsearchMappingContext());
public MappingElasticsearchConverter elasticsearchEntityMapper(
SimpleElasticsearchMappingContext elasticsearchMappingContext) {
return new MappingElasticsearchConverter(elasticsearchMappingContext);
}
/**
@ -71,33 +72,15 @@ public class ElasticsearchConfigurationSupport {
return mappingContext;
}
/**
* 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
* an alternative to the {@link DefaultEntityMapper}.
*
* <pre class="code">
* ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
* new DefaultConversionService());
* entityMapper.setConversions(elasticsearchCustomConversions());
* </pre>
*
* @return never {@literal null}.
*/
@Bean
public EntityMapper entityMapper() {
return new DefaultEntityMapper(elasticsearchMappingContext());
}
/**
* Returns the {@link ResultsMapper} to be used for search responses.
*
* @see #entityMapper()
* @see MappingElasticsearchConverter
* @return never {@literal null}.
*/
@Bean
public ResultsMapper resultsMapper() {
return new DefaultResultMapper(elasticsearchMappingContext(), entityMapper());
public ResultsMapper resultsMapper(SimpleElasticsearchMappingContext elasticsearchMappingContext) {
return new DefaultResultMapper(elasticsearchMappingContext, elasticsearchEntityMapper(elasticsearchMappingContext));
}
/**

View File

@ -1,190 +0,0 @@
/*
* Copyright 2014-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
*
* https://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 java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.elasticsearch.Document;
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
/**
* EntityMapper based on a Jackson {@link ObjectMapper}.
*
* @author Artur Konczak
* @author Petar Tahchiev
* @author Oliver Gierke
* @author Christoph Strobl
* @author Mark Paluch
*/
public class DefaultEntityMapper implements EntityMapper {
private ObjectMapper objectMapper;
/**
* Creates a new {@link DefaultEntityMapper} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public DefaultEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
objectMapper = new ObjectMapper();
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
objectMapper.registerModule(new CustomGeoModule());
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToString(java.lang.Object)
*/
@Override
public String mapToString(Object object) throws IOException {
return objectMapper
.writeValueAsString(object instanceof Document ? new LinkedHashMap<>((Document) object) : object);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapObject(java.lang.Object)
*/
@Override
public Document mapObject(Object source) {
try {
return objectMapper.readValue(mapToString(source), Document.class);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
*/
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
/*
* (non-Javadoc)
* @see org.springframework.data.elasticsearch.core.EntityMapper#readObject(java.util.Map, java.lang.Class)
*/
@Override
public <T> T readObject(Document source, Class<T> targetType) {
try {
return mapToObject(mapToString(source), targetType);
} catch (IOException e) {
throw new MappingException(e.getMessage(), e);
}
}
/**
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataElasticsearchModule extends SimpleModule {
private static final long serialVersionUID = -9168968092458058966L;
/**
* Creates a new {@link SpringDataElasticsearchModule} using the given {@link MappingContext}.
*
* @param context must not be {@literal null}.
*/
public SpringDataElasticsearchModule(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
setSerializerModifier(new SpringDataSerializerModifier(context));
}
/**
* A {@link BeanSerializerModifier} that will drop properties annotated with {@link ReadOnlyProperty} for
* serialization.
*
* @author Oliver Gierke
* @since 3.1
*/
private static class SpringDataSerializerModifier extends BeanSerializerModifier {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
public SpringDataSerializerModifier(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
Assert.notNull(context, "MappingContext must not be null!");
this.context = context;
}
/*
* (non-Javadoc)
* @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#changeProperties(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, java.util.List)
*/
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription description,
List<BeanPropertyWriter> properties) {
Class<?> type = description.getBeanClass();
ElasticsearchPersistentEntity<?> entity = context.getPersistentEntity(type);
if (entity == null) {
return super.changeProperties(config, description, properties);
}
List<BeanPropertyWriter> result = new ArrayList<>(properties.size());
for (BeanPropertyWriter beanPropertyWriter : properties) {
ElasticsearchPersistentProperty property = entity.getPersistentProperty(beanPropertyWriter.getName());
if (property != null && property.isWritable()) {
result.add(beanPropertyWriter);
}
}
return result;
}
}
}
}

View File

@ -32,6 +32,7 @@ import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
@ -65,7 +66,7 @@ public class DefaultResultMapper extends AbstractResultMapper {
public DefaultResultMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this(mappingContext, initEntityMapper(mappingContext));
this(mappingContext, null);
}
public DefaultResultMapper(EntityMapper entityMapper) {
@ -84,7 +85,9 @@ public class DefaultResultMapper extends AbstractResultMapper {
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
return new DefaultEntityMapper(mappingContext);
MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(mappingContext, null);
mappingElasticsearchConverter.afterPropertiesSet();
return mappingElasticsearchConverter;
}
@Override

View File

@ -1,732 +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
*
* https://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 lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.convert.EntityReader;
import org.springframework.data.convert.EntityWriter;
import org.springframework.data.elasticsearch.Document;
import org.springframework.data.elasticsearch.SearchDocument;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchTypeMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
/**
* Elasticsearch specific {@link EntityReader} & {@link EntityWriter} implementation based on domain type
* {@link ElasticsearchPersistentEntity metadata}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Mark Paluch
* @since 3.2
*/
public class ElasticsearchEntityMapper
implements EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Document>,
EntityWriter<Object, Document>, EntityReader<Object, Document>, InitializingBean, EntityMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private final ObjectReader objectReader;
private final ObjectWriter objectWriter;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
private EntityInstantiators instantiators = new EntityInstantiators();
private ElasticsearchTypeMapper typeMapper;
public ElasticsearchEntityMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable GenericConversionService conversionService) {
this.mappingContext = mappingContext;
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
ObjectMapper objectMapper = new ObjectMapper();
objectReader = objectMapper.readerFor(HashMap.class);
objectWriter = objectMapper.writer();
}
// --> 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
@Override
public <T> T readObject(Document source, Class<T> targetType) {
return read(targetType, source);
}
@SuppressWarnings("unchecked")
@Override
@Nullable
public <R> R read(Class<R> type, Document source) {
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
}
@SuppressWarnings("unchecked")
@Nullable
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
if (source == null) {
return null;
}
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
return conversionService.convert(source, typeHint.getType());
}
if (typeHint.isMap() || ClassTypeInformation.OBJECT.equals(typeHint)) {
return (R) source;
}
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeHint);
return readEntity(entity, source);
}
@SuppressWarnings("unchecked")
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ElasticsearchPropertyValueProvider propertyValueProvider = new ElasticsearchPropertyValueProvider(
new MapValueAccessor(source));
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
R instance = (R) instantiator.createInstance(targetEntity,
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
: instance;
}
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
ElasticsearchPropertyValueProvider valueProvider) {
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
continue;
}
Object value = valueProvider.getPropertyValue(prop);
if (value != null) {
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
}
}
return accessor.getBean();
}
@SuppressWarnings("unchecked")
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Class<R> rawType = targetType.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
return rawType.cast(conversionService.convert(source, rawType));
} else if (source instanceof List) {
return readCollectionValue((List) source, property, targetType);
} else if (source instanceof Map) {
return readMapValue((Map<String, Object>) source, property, targetType);
}
return (R) readSimpleValue(source, targetType);
}
@SuppressWarnings("unchecked")
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
TypeInformation information = typeMapper.readType(source);
if (property.isEntity() && !property.isMap() || information != null) {
ElasticsearchPersistentEntity<?> targetEntity = information != null
? mappingContext.getRequiredPersistentEntity(information)
: mappingContext.getRequiredPersistentEntity(property);
return readEntity(targetEntity, source);
}
Map<String, Object> target = new LinkedHashMap<>();
for (Entry<String, Object> entry : source.entrySet()) {
if (isSimpleType(entry.getValue())) {
target.put(entry.getKey(),
readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType));
} else {
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entry.getValue());
if (targetEntity.getTypeInformation().isMap()) {
Map<String, Object> valueMap = (Map) entry.getValue();
if (typeMapper.containsTypeInformation(valueMap)) {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
} else {
target.put(entry.getKey(), readValue(valueMap, property, targetEntity.getTypeInformation()));
}
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
target.put(entry.getKey(),
readValue(entry.getValue(), property, targetEntity.getTypeInformation().getActualType()));
} else {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Collection<Object> target = createCollectionForValue(targetType, source.size());
for (Object value : source) {
if (isSimpleType(value)) {
target.add(
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
} else {
if (value instanceof List) {
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
} else {
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
Class<?> target = targetType.getType();
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
// --> WRITE
@Override
public Document mapObject(Object source) {
Document target = Document.create();
write(source, target);
return target;
}
@SuppressWarnings("unchecked")
@Override
public void write(@Nullable Object source, Document sink) {
if (source == null) {
return;
}
if (source instanceof Map) {
sink.putAll((Map<String, Object>) source);
return;
}
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink);
}
doWrite(source, sink, type);
}
protected void doWrite(@Nullable Object source, Document sink, @Nullable TypeInformation<? extends Object> typeHint) {
if (source == null) {
return;
}
Class<?> entityType = source.getClass();
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
if (customTarget.isPresent()) {
sink.putAll(conversionService.convert(source, Map.class));
return;
}
if (typeHint != null) {
ElasticsearchPersistentEntity<?> entity = typeHint.getType().equals(entityType)
? mappingContext.getRequiredPersistentEntity(typeHint)
: mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
return;
}
// write Entity
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
}
protected void writeEntity(ElasticsearchPersistentEntity<?> entity, Object source, Document sink,
@Nullable TypeInformation containingStructure) {
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
typeMapper.writeType(source.getClass(), sink);
}
writeProperties(entity, accessor, new MapValueAccessor(sink));
}
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
MapValueAccessor sink) {
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = accessor.getProperty(property);
if (value == null) {
continue;
}
if (!isSimpleType(value)) {
writeProperty(property, value, sink);
} else {
sink.set(property, getWriteSimpleValue(value));
}
}
}
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) {
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
if (customWriteTarget.isPresent()) {
Class<?> writeTarget = customWriteTarget.get();
sink.set(property, conversionService.convert(value, writeTarget));
return;
}
TypeInformation<?> typeHint = property.getTypeInformation();
if (typeHint.equals(ClassTypeInformation.OBJECT)) {
if (value instanceof List) {
typeHint = ClassTypeInformation.LIST;
} else if (value instanceof Map) {
typeHint = ClassTypeInformation.MAP;
} else if (value instanceof Set) {
typeHint = ClassTypeInformation.SET;
} else if (value instanceof Collection) {
typeHint = ClassTypeInformation.COLLECTION;
}
}
sink.set(property, getWriteComplexValue(property, typeHint, value));
}
protected Object getWriteSimpleValue(Object value) {
if (value == null) {
return null;
}
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,
Object value) {
if (typeHint.isCollectionLike() || value instanceof Iterable) {
return writeCollectionValue(value, property, typeHint);
}
if (typeHint.isMap()) {
return writeMapValue((Map<String, Object>) value, property, typeHint);
}
if (property.isEntity() || !isSimpleType(value)) {
return writeEntity(value, property, typeHint);
}
return value;
}
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
Document target = Document.create();
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
property.getTypeInformation());
return target;
}
private Object writeMapValue(Map<String, Object> value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Map<Object, Object> target = new LinkedHashMap<>();
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
if (!typeHint.getActualType().getType().equals(Object.class)
&& isSimpleType(typeHint.getMapValueType().getType())) {
mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue())));
} else {
mapSource.forEach(it -> {
Object converted = null;
if (it.getValue() != null) {
if (isSimpleType(it.getValue())) {
converted = getWriteSimpleValue(it.getValue());
} else {
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
it.getValue());
}
}
target.put(it.getKey(), converted);
});
}
return target;
}
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
: Streamable.of(ObjectUtils.toObjectArray(value));
List<Object> target = new ArrayList<>();
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
} else {
collectionSource.map(it -> {
if (it == null) {
return null;
}
if (isSimpleType(it)) {
return getWriteSimpleValue(it);
}
return getWriteComplexValue(property, ClassTypeInformation.from(it.getClass()), it);
}).forEach(target::add);
}
return target;
}
// --> LEGACY
@Override
public String mapToString(Object source) throws IOException {
Document sink = Document.create();
write(source, sink);
return objectWriter.writeValueAsString(new LinkedHashMap<>(sink));
}
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return read(clazz, Document.from(objectReader.readValue(source)));
}
// --> PRIVATE HELPERS
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
@Nullable TypeInformation<?> container) {
if (container != null) {
if (container.isCollectionLike()) {
if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.isMap()) {
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.equals(type) && type.getType().equals(actualType)) {
return false;
}
}
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
&& !conversions.hasCustomWriteTarget(type.getType());
}
/**
* Compute the type to use by checking the given entity against the store type;
*
* @param entity
* @param source
* @return
*/
private ElasticsearchPersistentEntity<?> computeClosestEntity(ElasticsearchPersistentEntity<?> entity,
Map<String, Object> source) {
TypeInformation<?> typeToUse = typeMapper.readType(source);
if (typeToUse == null) {
return entity;
}
if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike()
&& !entity.getTypeInformation().isMap()
&& !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) {
return entity;
}
return mappingContext.getRequiredPersistentEntity(typeToUse);
}
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
Object value) {
return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
? mappingContext.getRequiredPersistentEntity(value.getClass())
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
}
private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
? collectionTypeInformation.getType() //
: List.class;
TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
? collectionTypeInformation.getComponentType() //
: ClassTypeInformation.OBJECT;
return collectionTypeInformation.getType().isArray() //
? new ArrayList<>(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
@RequiredArgsConstructor
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor mapValueAccessor;
@SuppressWarnings("unchecked")
@Override
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
}
}
static class MapValueAccessor {
final Map<String, Object> target;
MapValueAccessor(Map<String, Object> target) {
this.target = target;
}
public Object get(ElasticsearchPersistentProperty property) {
if (property.isIdProperty() && ((Document) target).hasId()) {
return ((Document) target).getId();
}
if (property.isVersionProperty() && ((Document) target).hasVersion()) {
return ((Document) target).getVersion();
}
if (property.isScoreProperty()) {
return ((SearchDocument) target).getScore();
}
String fieldName = property.getFieldName();
if (!fieldName.contains(".")) {
return target.get(fieldName);
}
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
Map<String, Object> source = target;
Object result = null;
while (source != null && parts.hasNext()) {
result = source.get(parts.next());
if (parts.hasNext()) {
source = getAsMap(result);
}
}
return result;
}
public void set(ElasticsearchPersistentProperty property, Object value) {
if (property.isIdProperty()) {
((Document) target).setId((String) value);
}
if (property.isVersionProperty()) {
((Document) target).setVersion((Long) value);
}
target.put(property.getFieldName(), value);
}
@SuppressWarnings("unchecked")
private Map<String, Object> getAsMap(Object result) {
if (result instanceof Map) {
return (Map) result;
}
throw new IllegalArgumentException(String.format("%s is not a Map.", result));
}
}
}

View File

@ -157,28 +157,37 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate
private String searchTimeout;
public ElasticsearchRestTemplate(RestHighLevelClient client) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
MappingElasticsearchConverter mappingElasticsearchConverter = createElasticsearchConverter();
initialize(client, mappingElasticsearchConverter,
new DefaultResultMapper(mappingElasticsearchConverter.getMappingContext()));
}
public ElasticsearchRestTemplate(RestHighLevelClient client, EntityMapper entityMapper) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()), entityMapper);
}
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter,
EntityMapper entityMapper) {
this(client, elasticsearchConverter,
public ElasticsearchRestTemplate(RestHighLevelClient client,
ElasticsearchConverter elasticsearchConverter, EntityMapper entityMapper) {
initialize(client, elasticsearchConverter,
new DefaultResultMapper(elasticsearchConverter.getMappingContext(), entityMapper));
}
public ElasticsearchRestTemplate(RestHighLevelClient client, ResultsMapper resultsMapper) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()), resultsMapper);
initialize(client, createElasticsearchConverter(), resultsMapper);
}
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) {
this(client, elasticsearchConverter, new DefaultResultMapper(elasticsearchConverter.getMappingContext()));
public ElasticsearchRestTemplate(RestHighLevelClient client,
ElasticsearchConverter elasticsearchConverter) {
initialize(client, elasticsearchConverter,
new DefaultResultMapper(elasticsearchConverter.getMappingContext()));
}
public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter,
public ElasticsearchRestTemplate(RestHighLevelClient client,
ElasticsearchConverter elasticsearchConverter, ResultsMapper resultsMapper) {
initialize(client, elasticsearchConverter, resultsMapper);
}
private MappingElasticsearchConverter createElasticsearchConverter() {
return new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
}
private void initialize(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter,
ResultsMapper resultsMapper) {
super(elasticsearchConverter);
@ -1049,28 +1058,33 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate
}
}
@Override
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz) {
SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery);
return resultsMapper.mapResults(response, clazz, null);
}
@Override
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz) {
SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery);
return resultsMapper.mapResults(response, clazz, null);
}
@Override
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class<T> clazz,
SearchResultMapper mapper) {
SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery);
return mapper.mapResults(response, clazz, null);
}
@Override
public <T> ScrolledPage<T> startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class<T> clazz,
SearchResultMapper mapper) {
SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery);
return mapper.mapResults(response, clazz, null);
}
@Override
public <T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz) {
SearchScrollRequest request = new SearchScrollRequest(scrollId);
request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis));
@ -1083,6 +1097,7 @@ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate
return resultsMapper.mapResults(response, clazz, Pageable.unpaged());
}
@Override
public <T> ScrolledPage<T> continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class<T> clazz,
SearchResultMapper mapper) {
SearchScrollRequest request = new SearchScrollRequest(scrollId);

View File

@ -139,25 +139,28 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate impleme
private String searchTimeout;
public ElasticsearchTemplate(Client client) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()));
}
public ElasticsearchTemplate(Client client, EntityMapper entityMapper) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()), entityMapper);
MappingElasticsearchConverter mappingElasticsearchConverter = createElasticsearchConverter();
initialize(client, mappingElasticsearchConverter,
new DefaultResultMapper(mappingElasticsearchConverter.getMappingContext()));
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
EntityMapper entityMapper) {
this(client, elasticsearchConverter,
initialize(client, elasticsearchConverter,
new DefaultResultMapper(elasticsearchConverter.getMappingContext(), entityMapper));
}
public ElasticsearchTemplate(Client client, ResultsMapper resultsMapper) {
this(client, new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()), resultsMapper);
initialize(client, createElasticsearchConverter(), resultsMapper);
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter) {
this(client, elasticsearchConverter, new DefaultResultMapper(elasticsearchConverter.getMappingContext()));
this(client, elasticsearchConverter,
new DefaultResultMapper(elasticsearchConverter.getMappingContext()));
}
private MappingElasticsearchConverter createElasticsearchConverter() {
return new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
}
public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter,
@ -165,6 +168,11 @@ public class ElasticsearchTemplate extends AbstractElasticsearchTemplate impleme
super(elasticsearchConverter);
initialize(client, elasticsearchConverter, resultsMapper);
}
private void initialize(Client client, ElasticsearchConverter elasticsearchConverter,
ResultsMapper resultsMapper) {
Assert.notNull(client, "Client must not be null!");
Assert.notNull(resultsMapper, "ResultsMapper must not be null!");

View File

@ -32,12 +32,14 @@ import org.springframework.lang.Nullable;
*/
public interface EntityMapper {
String mapToString(Object object) throws IOException;
default String mapToString(Object object) throws IOException {
return mapObject(object).toJson();
}
<T> T mapToObject(String source, Class<T> clazz) throws IOException;
/**
* Map the given {@literal source} to {@link Map}.
* Map the given {@literal source} to {@link Document}.
*
* @param source must not be {@literal null}.
* @return never {@literal null}
@ -46,7 +48,7 @@ public interface EntityMapper {
Document mapObject(Object source);
/**
* Map the given {@link Map} into an instance of the {@literal targetType}.
* Map the given {@link Document} into an instance of the {@literal targetType}.
*
* @param source must not be {@literal null}.
* @param targetType must not be {@literal null}.

View File

@ -37,6 +37,7 @@ import org.springframework.util.Assert;
* {@link Publisher}.
*
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @since 3.2
*/
public interface ReactiveElasticsearchOperations {
@ -172,7 +173,7 @@ public interface ReactiveElasticsearchOperations {
/**
* Check if an entity with given {@literal id} exists.
*
*
* @param id the {@literal _id} of the document to look for.
* @param entityType the domain type used.
* @return a {@link Mono} emitting {@literal true} if a matching document exists, {@literal false} otherwise.

View File

@ -15,34 +15,20 @@
*/
package org.springframework.data.elasticsearch.core.convert;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.convert.EntityConverter;
import org.springframework.data.elasticsearch.Document;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.util.Assert;
/**
* ElasticsearchConverter
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Christoph Strobl
* @author Peter-Josef Meisch
*/
public interface ElasticsearchConverter {
/**
* Returns the underlying {@link org.springframework.data.mapping.context.MappingContext} used by the converter.
*
* @return never {@literal null}
*/
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext();
/**
* Returns the underlying {@link org.springframework.core.convert.ConversionService} used by the converter.
*
* @return never {@literal null}.
*/
ConversionService getConversionService();
public interface ElasticsearchConverter
extends EntityConverter<ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty, Object, Document> {
/**
* Convert a given {@literal idValue} to its {@link String} representation taking potentially registered
@ -55,6 +41,7 @@ public interface ElasticsearchConverter {
default String convertId(Object idValue) {
Assert.notNull(idValue, "idValue must not be null!");
if (!getConversionService().canConvert(idValue.getClass(), String.class)) {
return idValue.toString();
}

View File

@ -15,41 +15,96 @@
*/
package org.springframework.data.elasticsearch.core.convert;
import lombok.RequiredArgsConstructor;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.CollectionFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.convert.EntityInstantiator;
import org.springframework.data.convert.EntityInstantiators;
import org.springframework.data.elasticsearch.Document;
import org.springframework.data.elasticsearch.SearchDocument;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
import org.springframework.data.mapping.model.PersistentEntityParameterValueProvider;
import org.springframework.data.mapping.model.PropertyValueProvider;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.Streamable;
import org.springframework.data.util.TypeInformation;
import org.springframework.format.datetime.DateFormatterRegistrar;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
/**
* MappingElasticsearchConverter
* Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type
* {@link ElasticsearchPersistentEntity metadata}.
*
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Christoph Strobl
* @author Peter-Josef Meisch
* @author Mark Paluch
* @since 3.2
*/
public class MappingElasticsearchConverter implements ElasticsearchConverter, ApplicationContextAware {
public class MappingElasticsearchConverter
implements ElasticsearchConverter, EntityMapper, ApplicationContextAware, InitializingBean {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
private final GenericConversionService conversionService;
private final ObjectReader objectReader;
@SuppressWarnings("unused")
private ApplicationContext applicationContext;
private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());
private EntityInstantiators instantiators = new EntityInstantiators();
private ElasticsearchTypeMapper typeMapper;
public MappingElasticsearchConverter(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
this.conversionService = new DefaultConversionService();
this(mappingContext, null);
}
public MappingElasticsearchConverter(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext,
@Nullable GenericConversionService conversionService) {
Assert.notNull(mappingContext, "MappingContext must not be null!");
this.mappingContext = mappingContext;
this.conversionService = conversionService != null ? conversionService : new DefaultConversionService();
this.typeMapper = ElasticsearchTypeMapper.create(mappingContext);
ObjectMapper objectMapper = new ObjectMapper();
objectReader = objectMapper.readerFor(HashMap.class);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (mappingContext instanceof ApplicationContextAware) {
((ApplicationContextAware) mappingContext).setApplicationContext(applicationContext);
}
}
// --> GETTERS / SETTERS
@Override
public MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> getMappingContext() {
return mappingContext;
@ -57,14 +112,631 @@ public class MappingElasticsearchConverter implements ElasticsearchConverter, Ap
@Override
public ConversionService getConversionService() {
return this.conversionService;
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
@SuppressWarnings("unchecked")
@Override
@Nullable
public <R> R read(Class<R> type, Document source) {
return doRead(source, ClassTypeInformation.from((Class<R>) ClassUtils.getUserClass(type)));
}
@SuppressWarnings("unchecked")
@Nullable
protected <R> R doRead(Map<String, Object> source, TypeInformation<R> typeHint) {
if (source == null) {
return null;
}
typeHint = (TypeInformation<R>) typeMapper.readType(source, typeHint);
if (conversions.hasCustomReadTarget(Map.class, typeHint.getType())) {
return conversionService.convert(source, typeHint.getType());
}
if (typeHint.isMap() || ClassTypeInformation.OBJECT.equals(typeHint)) {
return (R) source;
}
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(typeHint);
return readEntity(entity, source);
}
@SuppressWarnings("unchecked")
protected <R> R readEntity(ElasticsearchPersistentEntity<?> entity, Map<String, Object> source) {
ElasticsearchPersistentEntity<?> targetEntity = computeClosestEntity(entity, source);
ElasticsearchPropertyValueProvider propertyValueProvider = new ElasticsearchPropertyValueProvider(
new MapValueAccessor(source));
EntityInstantiator instantiator = instantiators.getInstantiatorFor(targetEntity);
R instance = (R) instantiator.createInstance(targetEntity,
new PersistentEntityParameterValueProvider<>(targetEntity, propertyValueProvider, null));
return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider)
: instance;
}
protected <R> R readProperties(ElasticsearchPersistentEntity<?> entity, R instance,
ElasticsearchPropertyValueProvider valueProvider) {
PersistentPropertyAccessor<R> accessor = new ConvertingPropertyAccessor<>(entity.getPropertyAccessor(instance),
conversionService);
for (ElasticsearchPersistentProperty prop : entity) {
if (entity.isConstructorArgument(prop) || prop.isScoreProperty()) {
continue;
}
Object value = valueProvider.getPropertyValue(prop);
if (value != null) {
accessor.setProperty(prop, valueProvider.getPropertyValue(prop));
}
}
return accessor.getBean();
}
@SuppressWarnings("unchecked")
protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Class<R> rawType = targetType.getType();
if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
return rawType.cast(conversionService.convert(source, rawType));
} else if (source instanceof List) {
return readCollectionValue((List) source, property, targetType);
} else if (source instanceof Map) {
return readMapValue((Map<String, Object>) source, property, targetType);
}
return (R) readSimpleValue(source, targetType);
}
@SuppressWarnings("unchecked")
private <R> R readMapValue(@Nullable Map<String, Object> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
TypeInformation information = typeMapper.readType(source);
if (property.isEntity() && !property.isMap() || information != null) {
ElasticsearchPersistentEntity<?> targetEntity = information != null
? mappingContext.getRequiredPersistentEntity(information)
: mappingContext.getRequiredPersistentEntity(property);
return readEntity(targetEntity, source);
}
Map<String, Object> target = new LinkedHashMap<>();
for (Entry<String, Object> entry : source.entrySet()) {
if (isSimpleType(entry.getValue())) {
target.put(entry.getKey(),
readSimpleValue(entry.getValue(), targetType.isMap() ? targetType.getComponentType() : targetType));
} else {
ElasticsearchPersistentEntity<?> targetEntity = computeGenericValueTypeForRead(property, entry.getValue());
if (targetEntity.getTypeInformation().isMap()) {
Map<String, Object> valueMap = (Map) entry.getValue();
if (typeMapper.containsTypeInformation(valueMap)) {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
} else {
target.put(entry.getKey(), readValue(valueMap, property, targetEntity.getTypeInformation()));
}
} else if (targetEntity.getTypeInformation().isCollectionLike()) {
target.put(entry.getKey(),
readValue(entry.getValue(), property, targetEntity.getTypeInformation().getActualType()));
} else {
target.put(entry.getKey(), readEntity(targetEntity, (Map) entry.getValue()));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
TypeInformation<R> targetType) {
if (source == null) {
return null;
}
Collection<Object> target = createCollectionForValue(targetType, source.size());
for (Object value : source) {
if (isSimpleType(value)) {
target.add(
readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
} else {
if (value instanceof List) {
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
} else {
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
}
}
}
return (R) target;
}
@SuppressWarnings("unchecked")
private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {
Class<?> target = targetType.getType();
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
return value;
}
if (conversions.hasCustomReadTarget(value.getClass(), target)) {
return conversionService.convert(value, target);
}
if (Enum.class.isAssignableFrom(target)) {
return Enum.valueOf((Class<Enum>) target, value.toString());
}
return conversionService.convert(value, target);
}
@SuppressWarnings("unchecked")
@Override
public void write(@Nullable Object source, Document sink) {
if (source == null) {
return;
}
if (source instanceof Map) {
sink.putAll((Map<String, Object>) source);
return;
}
Class<?> entityType = ClassUtils.getUserClass(source.getClass());
TypeInformation<?> type = ClassTypeInformation.from(entityType);
if (requiresTypeHint(type, source.getClass(), null)) {
typeMapper.writeType(source.getClass(), sink);
}
doWrite(source, sink, type);
}
// --> WRITE
protected void doWrite(@Nullable Object source, Document sink, @Nullable TypeInformation<? extends Object> typeHint) {
if (source == null) {
return;
}
Class<?> entityType = source.getClass();
Optional<Class<?>> customTarget = conversions.getCustomWriteTarget(entityType, Map.class);
if (customTarget.isPresent()) {
sink.putAll(conversionService.convert(source, Map.class));
return;
}
if (typeHint != null) {
ElasticsearchPersistentEntity<?> entity = typeHint.getType().equals(entityType)
? mappingContext.getRequiredPersistentEntity(typeHint)
: mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
return;
}
// write Entity
ElasticsearchPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(entityType);
writeEntity(entity, source, sink, null);
}
protected void writeEntity(ElasticsearchPersistentEntity<?> entity, Object source, Document sink,
@Nullable TypeInformation containingStructure) {
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(source);
if (requiresTypeHint(entity.getTypeInformation(), source.getClass(), containingStructure)) {
typeMapper.writeType(source.getClass(), sink);
}
writeProperties(entity, accessor, new MapValueAccessor(sink));
}
protected void writeProperties(ElasticsearchPersistentEntity<?> entity, PersistentPropertyAccessor<?> accessor,
MapValueAccessor sink) {
for (ElasticsearchPersistentProperty property : entity) {
if (!property.isWritable()) {
continue;
}
Object value = accessor.getProperty(property);
if (value == null) {
continue;
}
if (!isSimpleType(value)) {
writeProperty(property, value, sink);
} else {
sink.set(property, getWriteSimpleValue(value));
}
}
}
protected void writeProperty(ElasticsearchPersistentProperty property, Object value, MapValueAccessor sink) {
Optional<Class<?>> customWriteTarget = conversions.getCustomWriteTarget(value.getClass());
if (customWriteTarget.isPresent()) {
Class<?> writeTarget = customWriteTarget.get();
sink.set(property, conversionService.convert(value, writeTarget));
return;
}
TypeInformation<?> typeHint = property.getTypeInformation();
if (typeHint.equals(ClassTypeInformation.OBJECT)) {
if (value instanceof List) {
typeHint = ClassTypeInformation.LIST;
} else if (value instanceof Map) {
typeHint = ClassTypeInformation.MAP;
} else if (value instanceof Set) {
typeHint = ClassTypeInformation.SET;
} else if (value instanceof Collection) {
typeHint = ClassTypeInformation.COLLECTION;
}
}
sink.set(property, getWriteComplexValue(property, typeHint, value));
}
protected Object getWriteSimpleValue(Object value) {
if (value == null) {
return null;
}
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,
Object value) {
if (typeHint.isCollectionLike() || value instanceof Iterable) {
return writeCollectionValue(value, property, typeHint);
}
if (typeHint.isMap()) {
return writeMapValue((Map<String, Object>) value, property, typeHint);
}
if (property.isEntity() || !isSimpleType(value)) {
return writeEntity(value, property, typeHint);
}
return value;
}
private Object writeEntity(Object value, ElasticsearchPersistentProperty property, TypeInformation<?> typeHint) {
Document target = Document.create();
writeEntity(mappingContext.getRequiredPersistentEntity(value.getClass()), value, target,
property.getTypeInformation());
return target;
}
private Object writeMapValue(Map<String, Object> value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Map<Object, Object> target = new LinkedHashMap<>();
Streamable<Entry<String, Object>> mapSource = Streamable.of(value.entrySet());
if (!typeHint.getActualType().getType().equals(Object.class)
&& isSimpleType(typeHint.getMapValueType().getType())) {
mapSource.forEach(it -> target.put(it.getKey(), getWriteSimpleValue(it.getValue())));
} else {
mapSource.forEach(it -> {
Object converted = null;
if (it.getValue() != null) {
if (isSimpleType(it.getValue())) {
converted = getWriteSimpleValue(it.getValue());
} else {
converted = getWriteComplexValue(property, ClassTypeInformation.from(it.getValue().getClass()),
it.getValue());
}
}
target.put(it.getKey(), converted);
});
}
return target;
}
private Object writeCollectionValue(Object value, ElasticsearchPersistentProperty property,
TypeInformation<?> typeHint) {
Streamable<?> collectionSource = value instanceof Iterable ? Streamable.of((Iterable<?>) value)
: Streamable.of(ObjectUtils.toObjectArray(value));
List<Object> target = new ArrayList<>();
if (!typeHint.getActualType().getType().equals(Object.class) && isSimpleType(typeHint.getActualType().getType())) {
collectionSource.map(this::getWriteSimpleValue).forEach(target::add);
} else {
collectionSource.map(it -> {
if (it == null) {
return null;
}
if (isSimpleType(it)) {
return getWriteSimpleValue(it);
}
return getWriteComplexValue(property, ClassTypeInformation.from(it.getClass()), it);
}).forEach(target::add);
}
return target;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
if (mappingContext instanceof ApplicationContextAware) {
((ApplicationContextAware) mappingContext).setApplicationContext(applicationContext);
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return read(clazz, Document.from(objectReader.readValue(source)));
}
// --> LEGACY
@Override
public Document mapObject(Object source) {
Document target = Document.create();
write(source, target);
return target;
}
@Override
public <T> T readObject(Document source, Class<T> targetType) {
return read(targetType, source);
}
// --> PRIVATE HELPERS
private boolean requiresTypeHint(TypeInformation<?> type, Class<?> actualType,
@Nullable TypeInformation<?> container) {
if (container != null) {
if (container.isCollectionLike()) {
if (type.equals(container.getActualType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.isMap()) {
if (type.equals(container.getMapValueType()) && type.getType().equals(actualType)) {
return false;
}
}
if (container.equals(type) && type.getType().equals(actualType)) {
return false;
}
}
return !conversions.isSimpleType(type.getType()) && !type.isCollectionLike()
&& !conversions.hasCustomWriteTarget(type.getType());
}
/**
* Compute the type to use by checking the given entity against the store type;
*
* @param entity
* @param source
* @return
*/
private ElasticsearchPersistentEntity<?> computeClosestEntity(ElasticsearchPersistentEntity<?> entity,
Map<String, Object> source) {
TypeInformation<?> typeToUse = typeMapper.readType(source);
if (typeToUse == null) {
return entity;
}
if (!entity.getTypeInformation().getType().isInterface() && !entity.getTypeInformation().isCollectionLike()
&& !entity.getTypeInformation().isMap()
&& !ClassUtils.isAssignableValue(entity.getType(), typeToUse.getType())) {
return entity;
}
return mappingContext.getRequiredPersistentEntity(typeToUse);
}
private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
Object value) {
return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
? mappingContext.getRequiredPersistentEntity(value.getClass())
: mappingContext.getRequiredPersistentEntity(property.getTypeInformation().getActualType());
}
private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {
Class<?> collectionType = collectionTypeInformation.isSubTypeOf(Collection.class) //
? collectionTypeInformation.getType() //
: List.class;
TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
? collectionTypeInformation.getComponentType() //
: ClassTypeInformation.OBJECT;
return collectionTypeInformation.getType().isArray() //
? new ArrayList<>(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
static class MapValueAccessor {
final Map<String, Object> target;
MapValueAccessor(Map<String, Object> target) {
this.target = target;
}
public Object get(ElasticsearchPersistentProperty property) {
if (target instanceof Document) {
// nested objects may have properties like 'id' which are recognized as isIdProperty() but they are not
// Documents
Document document = (Document) target;
if (property.isIdProperty() && document.hasId()) {
return document.getId();
}
if (property.isVersionProperty() && document.hasVersion()) {
return document.getVersion();
}
}
if (target instanceof SearchDocument && property.isScoreProperty()) {
return ((SearchDocument) target).getScore();
}
String fieldName = property.getFieldName();
if (!fieldName.contains(".")) {
return target.get(fieldName);
}
Iterator<String> parts = Arrays.asList(fieldName.split("\\.")).iterator();
Map<String, Object> source = target;
Object result = null;
while (source != null && parts.hasNext()) {
result = source.get(parts.next());
if (parts.hasNext()) {
source = getAsMap(result);
}
}
return result;
}
public void set(ElasticsearchPersistentProperty property, Object value) {
if (property.isIdProperty()) {
((Document) target).setId(value.toString());
}
if (property.isVersionProperty()) {
((Document) target).setVersion((Long) value);
}
target.put(property.getFieldName(), value);
}
@SuppressWarnings("unchecked")
private Map<String, Object> getAsMap(Object result) {
if (result instanceof Map) {
return (Map) result;
}
throw new IllegalArgumentException(String.format("%s is not a Map.", result));
}
}
@RequiredArgsConstructor
class ElasticsearchPropertyValueProvider implements PropertyValueProvider<ElasticsearchPersistentProperty> {
final MapValueAccessor mapValueAccessor;
@SuppressWarnings("unchecked")
@Override
public <T> T getPropertyValue(ElasticsearchPersistentProperty property) {
return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation());
}
}
}

View File

@ -59,12 +59,12 @@ public class ConvertingParameterAccessor implements ElasticsearchParameterAccess
return delegate.getDynamicProjection();
}
@Override
public Class<?> findDynamicProjection() {
return delegate.findDynamicProjection();
}
@Override
public Class<?> findDynamicProjection() {
return delegate.findDynamicProjection();
}
@Override
@Override
public Object getBindableValue(int index) {
return getConvertedValue(delegate.getBindableValue(index));
}

View File

@ -38,7 +38,7 @@ public class ElasticsearchRepositoryFactoryBean<T extends Repository<S, ID>, S,
/**
* Creates a new {@link ElasticsearchRepositoryFactoryBean} for the given repository interface.
*
*
* @param repositoryInterface must not be {@literal null}.
*/
public ElasticsearchRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
@ -51,9 +51,9 @@ public class ElasticsearchRepositoryFactoryBean<T extends Repository<S, ID>, S,
* @param operations the operations to set
*/
public void setElasticsearchOperations(ElasticsearchOperations operations) {
Assert.notNull(operations, "ElasticsearchOperations must not be null!");
setMappingContext(operations.getElasticsearchConverter().getMappingContext());
this.operations = operations;
}

View File

@ -19,11 +19,9 @@ import org.elasticsearch.client.Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport;
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
/**
* configuration class for the classic ElasticsearchTemplate. Needs a {@link TestNodeResource} bean that should be set up in
@ -42,20 +40,8 @@ public class ElasticsearchTestConfiguration extends ElasticsearchConfigurationSu
}
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchTemplate elasticsearchTemplate(Client elasticsearchClient, EntityMapper entityMapper) {
public ElasticsearchTemplate elasticsearchTemplate(Client elasticsearchClient, MappingElasticsearchConverter entityMapper) {
return new ElasticsearchTemplate(elasticsearchClient, entityMapper);
}
/*
* need the ElasticsearchMapper, because some tests rely on @Field(name) being handled correctly
*/
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
}

View File

@ -18,10 +18,7 @@ package org.springframework.data.elasticsearch;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
/**
* @author Peter-Josef Meisch
@ -29,22 +26,9 @@ import org.springframework.data.elasticsearch.core.EntityMapper;
@Configuration
public class RestElasticsearchTestConfiguration extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
return TestUtils.restHighLevelClient();
}
/*
* need the ElasticsearchMapper, because some tests rely on @Field(name) being handled correctly
*/
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
return TestUtils.restHighLevelClient();
}
}

View File

@ -25,17 +25,15 @@ import org.apache.commons.lang.ClassUtils;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
/**
@ -108,7 +106,7 @@ public class ElasticsearchConfigurationSupportUnitTests {
public void usesConfiguredEntityMapper() {
AbstractApplicationContext context = new AnnotationConfigApplicationContext(EntityMapperConfig.class);
assertThat(context.getBean(EntityMapper.class)).isInstanceOf(ElasticsearchEntityMapper.class);
assertThat(context.getBean(EntityMapper.class)).isInstanceOf(MappingElasticsearchConverter.class);
}
@Configuration
@ -135,19 +133,7 @@ public class ElasticsearchConfigurationSupportUnitTests {
}
@Configuration
static class EntityMapperConfig extends ElasticsearchConfigurationSupport {
@Bean
@Override
public EntityMapper entityMapper() {
ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
new DefaultConversionService());
entityMapper.setConversions(elasticsearchCustomConversions());
return entityMapper;
}
}
static class EntityMapperConfig extends ElasticsearchConfigurationSupport {}
@Document(indexName = "config-support-tests")
static class Entity {}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2013-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
*
* https://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 java.io.IOException;
import org.springframework.data.elasticsearch.Document;
/**
* @author Artur Konczak
* @author Mohsin Husen
* @author Mark Paluch
*/
public class CustomEntityMapper implements EntityMapper {
public CustomEntityMapper() {
// custom configuration/implementation (e.g. FasterXML/jackson)
}
@Override
public String mapToString(Object object) throws IOException {
// mapping Object to text
return null;
}
@Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
// mapping text to Object
return null;
}
@Override
public Document mapObject(Object source) {
return null;
}
@Override
public <T> T readObject(Document source, Class<T> targetType) {
return null;
}
}

View File

@ -1,164 +0,0 @@
/*
* Copyright 2013-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
*
* https://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 static org.assertj.core.api.Assertions.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.Locale;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.geo.Point;
/**
* @author Artur Konczak
* @author Mohsin Husen
* @author Oliver Gierke
* @author Peter-Josef Meisch
*/
public class DefaultEntityMapperTests {
public static final String JSON_STRING = "{\"name\":\"Grat\",\"model\":\"Ford\"}";
public static final String CAR_MODEL = "Ford";
public static final String CAR_NAME = "Grat";
DefaultEntityMapper entityMapper;
@Before
public void init() {
entityMapper = new DefaultEntityMapper(new SimpleElasticsearchMappingContext());
}
@Test
public void shouldMapObjectToJsonString() throws IOException {
// given
// when
String jsonResult = entityMapper.mapToString(new Car(CAR_NAME, CAR_MODEL));
// then
assertThat(jsonResult).isEqualTo(JSON_STRING);
}
@Test
public void shouldMapJsonStringToObject() throws IOException {
// given
// when
Car result = entityMapper.mapToObject(JSON_STRING, Car.class);
// then
assertThat(result.getName()).isEqualTo(CAR_NAME);
assertThat(result.getModel()).isEqualTo(CAR_MODEL);
}
@Test
public void shouldMapGeoPointElasticsearchNames() throws IOException {
// given
Point point = new Point(10, 20);
String pointAsString = point.getX() + "," + point.getY();
double[] pointAsArray = { point.getX(), point.getY() };
GeoEntity geoEntity = GeoEntity.builder().pointA(point).pointB(GeoPoint.fromPoint(point)).pointC(pointAsString)
.pointD(pointAsArray).build();
// when
String jsonResult = entityMapper.mapToString(geoEntity);
// then
assertThat(jsonResult).contains(pointTemplate("pointA", point));
assertThat(jsonResult).contains(pointTemplate("pointB", point));
assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString));
assertThat(jsonResult)
.contains(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1]));
}
@Test // DATAES-464
public void ignoresReadOnlyProperties() throws IOException {
// given
Sample sample = new Sample();
sample.readOnly = "readOnly";
sample.property = "property";
sample.transientProperty = "transient";
sample.annotatedTransientProperty = "transient";
// when
String result = entityMapper.mapToString(sample);
// then
assertThat(result).contains("\"property\"");
assertThat(result).doesNotContain("readOnly");
assertThat(result).doesNotContain("transientProperty");
assertThat(result).doesNotContain("annotatedTransientProperty");
}
private String pointTemplate(String name, Point point) {
return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY());
}
public static class Sample {
public @ReadOnlyProperty String readOnly;
public @Transient String annotatedTransientProperty;
public transient String transientProperty;
public String property;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class Car {
private String name;
private String model;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Document(indexName = "test-index-geo-core-default-entity-mapper", type = "geo-test-index", shards = 1, replicas = 0,
refreshInterval = "-1")
static class GeoEntity {
@Id private String id;
// geo point - Custom implementation + Spring Data
@GeoPointField private Point pointA;
private GeoPoint pointB;
@GeoPointField private String pointC;
@GeoPointField private double[] pointD;
}
}

View File

@ -64,6 +64,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
@ -78,36 +79,13 @@ import com.fasterxml.jackson.databind.util.ArrayIterator;
* @author Christoph Strobl
* @author Peter-Josef Meisch
*/
@RunWith(Parameterized.class)
public class DefaultResultMapperTests {
private DefaultResultMapper resultMapper;
private SimpleElasticsearchMappingContext context;
private EntityMapper entityMapper;
private SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext();
private EntityMapper entityMapper = new MappingElasticsearchConverter(context);
private DefaultResultMapper resultMapper = new DefaultResultMapper(context, entityMapper);
@Mock private SearchResponse response;
public DefaultResultMapperTests(SimpleElasticsearchMappingContext context, EntityMapper entityMapper) {
this.context = context;
this.entityMapper = entityMapper;
}
@Parameters
public static Collection<Object[]> data() {
SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext();
return Arrays.asList(new Object[] { context, new DefaultEntityMapper(context) },
new Object[] { context, new ElasticsearchEntityMapper(context, new DefaultConversionService()) });
}
@Before
public void init() {
MockitoAnnotations.initMocks(this);
resultMapper = new DefaultResultMapper(context, entityMapper);
}
private SearchResponse response = mock(SearchResponse.class);
@Test
public void shouldMapAggregationsToPage() {

View File

@ -1,51 +0,0 @@
/*
* Copyright 2013-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
*
* https://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 static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Artur Konczak
* @author Peter-Josef Meisch
*/
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:elasticsearch-template-custom-mapper.xml")
public class ElasticsearchTemplateCustomMapperTests {
@Autowired private ElasticsearchTemplate elasticsearchTemplate;
@Autowired private EntityMapper entityMapper;
@Autowired private ResultsMapper resultsMapper;
@Test
public void shouldUseCustomMapper() {
// given
// when
// then
assertThat(elasticsearchTemplate.getResultsMapper()).isSameAs(resultsMapper);
assertThat(elasticsearchTemplate.getResultsMapper().getEntityMapper()).isSameAs(entityMapper);
}
}

View File

@ -3164,8 +3164,7 @@ public class ElasticsearchTemplateTests {
static class SampleEntity {
@Id private String id;
@org.springframework.data.elasticsearch.annotations.Field(type = Text, store = true,
fielddata = true) private String type;
@Field(type = Text, store = true, fielddata = true) private String type;
@Field(type = Text, store = true, fielddata = true) private String message;
private int rate;
@ScriptedField private Double scriptedRate;

View File

@ -24,6 +24,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
@ -102,7 +103,7 @@ public class ReactiveElasticsearchTemplateTests {
restTemplate.refresh(SampleEntity.class);
template = new ReactiveElasticsearchTemplate(TestUtils.reactiveClient(), restTemplate.getElasticsearchConverter(),
new DefaultResultMapper(new ElasticsearchEntityMapper(
new DefaultResultMapper(new MappingElasticsearchConverter(
restTemplate.getElasticsearchConverter().getMappingContext(), new DefaultConversionService())));
}

View File

@ -1,64 +0,0 @@
/*
* Copyright 2013-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
*
* https://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 static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.context.MappingContext;
/**
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
public class MappingElasticsearchConverterTests {
@Test(expected = IllegalArgumentException.class)
public void shouldFailToInitializeGivenMappingContextIsNull() {
// given
new MappingElasticsearchConverter(null);
}
@Test
public void shouldReturnMappingContextWithWhichItWasInitialized() {
// given
MappingContext mappingContext = new SimpleElasticsearchMappingContext();
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext);
// then
assertThat(converter.getMappingContext()).isNotNull();
assertThat(converter.getMappingContext()).isSameAs(mappingContext);
}
@Test
public void shouldReturnDefaultConversionService() {
// given
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
// when
ConversionService conversionService = converter.getConversionService();
// then
assertThat(conversionService).isNotNull();
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
package org.springframework.data.elasticsearch.core.convert;
import static org.assertj.core.api.Assertions.*;
@ -39,6 +39,7 @@ import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.annotation.Id;
@ -49,26 +50,27 @@ import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.elasticsearch.Document;
import org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchCustomConversions;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.geo.Box;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
import org.springframework.data.mapping.context.MappingContext;
/**
* Unit tests for {@link ElasticsearchEntityMapper}.
* Unit tests for {@link MappingElasticsearchConverter}.
*
* @author Christoph Strobl
* @author Mark Paluch
* @author Peter-Josef Meisch
*/
public class ElasticsearchEntityMapperUnitTests {
public class MappingElasticsearchConverterUnitTests {
static final String JSON_STRING = "{\"_class\":\"org.springframework.data.elasticsearch.core.ElasticsearchEntityMapperUnitTests$Car\",\"name\":\"Grat\",\"model\":\"Ford\"}";
static final String JSON_STRING = "{\"_class\":\"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$Car\",\"name\":\"Grat\",\"model\":\"Ford\"}";
static final String CAR_MODEL = "Ford";
static final String CAR_NAME = "Grat";
ElasticsearchEntityMapper entityMapper;
MappingElasticsearchConverter mappingElasticsearchConverter;
Person sarahConnor;
Person kyleReese;
@ -100,10 +102,10 @@ public class ElasticsearchEntityMapperUnitTests {
mappingContext.setInitialEntitySet(Collections.singleton(Rifle.class));
mappingContext.afterPropertiesSet();
entityMapper = new ElasticsearchEntityMapper(mappingContext, new GenericConversionService());
entityMapper.setConversions(
mappingElasticsearchConverter = new MappingElasticsearchConverter(mappingContext, new GenericConversionService());
mappingElasticsearchConverter.setConversions(
new ElasticsearchCustomConversions(Arrays.asList(new ShotGunToMapConverter(), new MapToShotGunConverter())));
entityMapper.afterPropertiesSet();
mappingElasticsearchConverter.afterPropertiesSet();
sarahConnor = new Person();
sarahConnor.id = "sarah";
@ -124,7 +126,7 @@ public class ElasticsearchEntityMapperUnitTests {
t800AsMap.put("id", "t800");
t800AsMap.put("name", "T-800");
t800AsMap.put("gender", "MACHINE");
t800AsMap.put("_class", "org.springframework.data.elasticsearch.core.ElasticsearchEntityMapperUnitTests$Person");
t800AsMap.put("_class", "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$Person");
observatoryRoad = new Address();
observatoryRoad.city = "Los Angeles";
@ -141,7 +143,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("id", "sarah");
sarahAsMap.put("name", "Sarah Connor");
sarahAsMap.put("gender", "MAN");
sarahAsMap.put("_class", "org.springframework.data.elasticsearch.core.ElasticsearchEntityMapperUnitTests$Person");
sarahAsMap.put("_class", "org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$Person");
kyleAsMap = Document.create();
kyleAsMap.put("id", "kyle");
@ -165,7 +167,7 @@ public class ElasticsearchEntityMapperUnitTests {
((HashMap<String, Object>) bigBunsCafeAsMap.get("location")).put("lat", 34.0945637D);
((HashMap<String, Object>) bigBunsCafeAsMap.get("location")).put("lon", -118.1545845D);
bigBunsCafeAsMap.put("_class",
"org.springframework.data.elasticsearch.core.ElasticsearchEntityMapperUnitTests$Place");
"org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverterUnitTests$Place");
gunAsMap = Document.create();
gunAsMap.put("label", "Glock 19");
@ -187,12 +189,46 @@ public class ElasticsearchEntityMapperUnitTests {
shotGunAsMap.put("_class", ShotGun.class.getName());
}
@Test // DATAES-530
@Test(expected = IllegalArgumentException.class)
public void shouldFailToInitializeGivenMappingContextIsNull() {
// given
new MappingElasticsearchConverter(null);
}
@Test
public void shouldReturnMappingContextWithWhichItWasInitialized() {
// given
MappingContext mappingContext = new SimpleElasticsearchMappingContext();
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(mappingContext);
// then
assertThat(converter.getMappingContext()).isNotNull();
assertThat(converter.getMappingContext()).isSameAs(mappingContext);
}
@Test
public void shouldReturnDefaultConversionService() {
// given
MappingElasticsearchConverter converter = new MappingElasticsearchConverter(
new SimpleElasticsearchMappingContext());
// when
ConversionService conversionService = converter.getConversionService();
// then
assertThat(conversionService).isNotNull();
}
@Test // DATAES-530
public void shouldMapObjectToJsonString() throws IOException {
// Given
// When
String jsonResult = entityMapper.mapToString(Car.builder().model(CAR_MODEL).name(CAR_NAME).build());
String jsonResult = mappingElasticsearchConverter.mapToString(Car.builder().model(CAR_MODEL).name(CAR_NAME).build());
// Then
assertThat(jsonResult).isEqualTo(JSON_STRING);
@ -203,7 +239,7 @@ public class ElasticsearchEntityMapperUnitTests {
// Given
// When
Car result = entityMapper.mapToObject(JSON_STRING, Car.class);
Car result = mappingElasticsearchConverter.mapToObject(JSON_STRING, Car.class);
// Then
assertThat(result.getName()).isEqualTo(CAR_NAME);
@ -219,7 +255,7 @@ public class ElasticsearchEntityMapperUnitTests {
GeoEntity geoEntity = GeoEntity.builder().pointA(point).pointB(GeoPoint.fromPoint(point)).pointC(pointAsString)
.pointD(pointAsArray).build();
// when
String jsonResult = entityMapper.mapToString(geoEntity);
String jsonResult = mappingElasticsearchConverter.mapToString(geoEntity);
// then
assertThat(jsonResult).contains(pointTemplate("pointA", point));
@ -240,7 +276,7 @@ public class ElasticsearchEntityMapperUnitTests {
sample.annotatedTransientProperty = "transient";
// when
String result = entityMapper.mapToString(sample);
String result = mappingElasticsearchConverter.mapToString(sample);
// then
assertThat(result).contains("\"property\"");
@ -291,7 +327,7 @@ public class ElasticsearchEntityMapperUnitTests {
@Test // DATAES-530
public void readTypeCorrectly() {
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target).isEqualTo(sarahConnor);
}
@ -301,7 +337,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("coWorkers", Arrays.asList(kyleAsMap));
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.getCoWorkers()).contains(kyleReese);
}
@ -311,7 +347,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("inventoryList", Arrays.asList(gunAsMap, grenadeAsMap));
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.getInventoryList()).containsExactly(gun, grenade);
}
@ -345,7 +381,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("shippingAddresses", Collections.singletonMap("home", gratiotAveAsMap));
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.getShippingAddresses()).hasSize(1).containsEntry("home", observatoryRoad);
}
@ -355,7 +391,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("inventoryMap", Collections.singletonMap("glock19", gunAsMap));
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.getInventoryMap()).hasSize(1).containsEntry("glock19", gun);
}
@ -379,7 +415,7 @@ public class ElasticsearchEntityMapperUnitTests {
Document source = Document.create();
source.put("objectList", Arrays.asList(t800AsMap, gunAsMap));
Skynet target = entityMapper.read(Skynet.class, source);
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
assertThat(target.getObjectList()).containsExactly(t800, gun);
}
@ -402,7 +438,7 @@ public class ElasticsearchEntityMapperUnitTests {
Document source = Document.create();
source.put("objectList", Arrays.asList(Arrays.asList(t800AsMap, gunAsMap)));
Skynet target = entityMapper.read(Skynet.class, source);
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
assertThat(target.getObjectList()).containsExactly(Arrays.asList(t800, gun));
}
@ -427,7 +463,7 @@ public class ElasticsearchEntityMapperUnitTests {
Document source = Document.create();
source.put("objectMap", Collections.singletonMap("glock19", gunAsMap));
Skynet target = entityMapper.read(Skynet.class, source);
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
assertThat(target.getObjectMap()).containsEntry("glock19", gun);
}
@ -451,7 +487,7 @@ public class ElasticsearchEntityMapperUnitTests {
Document source = Document.create();
source.put("objectMap", Collections.singletonMap("inventory", Collections.singletonMap("glock19", gunAsMap)));
Skynet target = entityMapper.read(Skynet.class, source);
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
assertThat(target.getObjectMap()).containsEntry("inventory", Collections.singletonMap("glock19", gun));
}
@ -461,7 +497,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("address", gratiotAveAsMap);
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.getAddress()).isEqualTo(observatoryRoad);
}
@ -472,7 +508,7 @@ public class ElasticsearchEntityMapperUnitTests {
Document source = Document.create();
source.put("object", t800AsMap);
Skynet target = entityMapper.read(Skynet.class, source);
Skynet target = mappingElasticsearchConverter.read(Skynet.class, source);
assertThat(target.getObject()).isEqualTo(t800);
}
@ -493,7 +529,7 @@ public class ElasticsearchEntityMapperUnitTests {
@Test // DATAES-530
public void readsAliased() {
assertThat(entityMapper.read(Inventory.class, rifleAsMap)).isEqualTo(rifle);
assertThat(mappingElasticsearchConverter.read(Inventory.class, rifleAsMap)).isEqualTo(rifle);
}
@Test // DATAES-530
@ -501,7 +537,7 @@ public class ElasticsearchEntityMapperUnitTests {
t800AsMap.put("inventoryList", Collections.singletonList(rifleAsMap));
assertThat(entityMapper.read(Person.class, t800AsMap).getInventoryList()).containsExactly(rifle);
assertThat(mappingElasticsearchConverter.read(Person.class, t800AsMap).getInventoryList()).containsExactly(rifle);
}
@Test // DATAES-530
@ -511,7 +547,7 @@ public class ElasticsearchEntityMapperUnitTests {
@Test // DATAES-530
public void appliesCustomConverterForRead() {
assertThat(entityMapper.read(Inventory.class, shotGunAsMap)).isEqualTo(shotGun);
assertThat(mappingElasticsearchConverter.read(Inventory.class, shotGunAsMap)).isEqualTo(shotGun);
}
@Test // DATAES-530
@ -529,7 +565,7 @@ public class ElasticsearchEntityMapperUnitTests {
sarahAsMap.put("address", bigBunsCafeAsMap);
Person target = entityMapper.read(Person.class, sarahAsMap);
Person target = mappingElasticsearchConverter.read(Person.class, sarahAsMap);
assertThat(target.address).isEqualTo(bigBunsCafe);
}
@ -541,7 +577,7 @@ public class ElasticsearchEntityMapperUnitTests {
private Map<String, Object> writeToMap(Object source) {
Document sink = Document.create();
entityMapper.write(source, sink);
mappingElasticsearchConverter.write(source, sink);
return sink;
}

View File

@ -59,7 +59,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
public class ElasticsearchStringQueryUnitTests {
@Mock ElasticsearchOperations operations;
ElasticsearchConverter converter;
ElasticsearchConverter converter;
@Before
public void setUp() {
@ -107,6 +107,16 @@ public class ElasticsearchStringQueryUnitTests {
new SpelAwareProxyProjectionFactory(), converter.getMappingContext());
}
private interface SampleRepository extends Repository<Person, String> {
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
Person findByName(String name);
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
}
/**
* @author Rizwan Idrees
* @author Mohsin Husen
@ -158,16 +168,6 @@ public class ElasticsearchStringQueryUnitTests {
}
}
private interface SampleRepository extends Repository<Person, String> {
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
Person findByName(String name);
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
}
/**
* @author Rizwan Idrees
* @author Mohsin Husen

View File

@ -22,6 +22,7 @@ import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@ -46,7 +47,6 @@ import org.springframework.data.elasticsearch.annotations.InnerField;
import org.springframework.data.elasticsearch.annotations.MultiField;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.elasticsearch.core.query.StringQuery;
@ -58,7 +58,6 @@ import org.springframework.expression.spel.standard.SpelExpressionParser;
/**
* @author Christoph Strobl
* @currentRead Fool's Fate - Robin Hobb
* @author Peter-Josef Meisch
*/
@RunWith(MockitoJUnitRunner.class)

View File

@ -25,10 +25,7 @@ import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
@ -37,19 +34,17 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat
* @author Rizwan Idrees
* @author Mohsin Husen
* @author Mark Paluch
* @author Peter-Josef Meisch
*/
@RunWith(MockitoJUnitRunner.class)
public class ElasticsearchRepositoryFactoryTests {
@Mock private ElasticsearchOperations operations;
private ElasticsearchConverter converter;
@Mock private ElasticsearchOperations operations;
private ElasticsearchRepositoryFactory factory;
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext = new SimpleElasticsearchMappingContext();
@Before
public void before() {
converter = new MappingElasticsearchConverter(mappingContext);
ElasticsearchConverter converter = new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext());
when(operations.getElasticsearchConverter()).thenReturn(converter);
factory = new ElasticsearchRepositoryFactory(operations);
}

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<import resource="infrastructure.xml"/>
<bean name="elasticsearchTemplate"
class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
<constructor-arg name="client" ref="client"/>
<constructor-arg name="resultsMapper" ref="resultMapper"/>
</bean>
<bean name="entityMapper" class="org.springframework.data.elasticsearch.core.CustomEntityMapper"/>
<bean name="resultMapper" class="org.springframework.data.elasticsearch.core.CustomResultMapper">
<constructor-arg ref="entityMapper"/>
</bean>
</beans>