mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-23 20:42:11 +00:00
parent
7a4aebf9f4
commit
b820c9a422
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -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!");
|
||||
|
||||
|
@ -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}.
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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())));
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user