diff --git a/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java index b33319b6c..e7d35eaa6 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java @@ -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); } } diff --git a/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java b/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java index 7f26f1967..f74b4be62 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java @@ -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()); diff --git a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java index 11916598f..3c4636aec 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java @@ -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.
- * Hint: you can use {@link org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper} as - * an alternative to the {@link DefaultEntityMapper}. - * - *
-	 * ElasticsearchEntityMapper entityMapper = new ElasticsearchEntityMapper(elasticsearchMappingContext(),
-	 * 		new DefaultConversionService());
-	 * entityMapper.setConversions(elasticsearchCustomConversions());
-	 * 
- * - * @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)); } /** diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultEntityMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultEntityMapper.java deleted file mode 100644 index 1806c59e5..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultEntityMapper.java +++ /dev/null @@ -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, 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 mapToObject(String source, Class 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 readObject(Document source, Class 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, 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, ElasticsearchPersistentProperty> context; - - public SpringDataSerializerModifier( - MappingContext, 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 changeProperties(SerializationConfig config, BeanDescription description, - List properties) { - - Class type = description.getBeanClass(); - ElasticsearchPersistentEntity entity = context.getPersistentEntity(type); - - if (entity == null) { - return super.changeProperties(config, description, properties); - } - - List 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; - } - } - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java index 576812924..41c94c474 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java @@ -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, ElasticsearchPersistentProperty> mappingContext) { - this(mappingContext, initEntityMapper(mappingContext)); + this(mappingContext, null); } public DefaultResultMapper(EntityMapper entityMapper) { @@ -84,7 +85,9 @@ public class DefaultResultMapper extends AbstractResultMapper { MappingContext, 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 diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapper.java deleted file mode 100644 index 924dd6adb..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapper.java +++ /dev/null @@ -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, ElasticsearchPersistentProperty, Object, Document>, - EntityWriter, EntityReader, InitializingBean, EntityMapper { - - private final MappingContext, 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, 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, ElasticsearchPersistentProperty> getMappingContext() { - return mappingContext; - } - - @Override - public ConversionService getConversionService() { - return conversionService; - } - - /** - * Set the {@link CustomConversions} to be applied during the mapping process.
- * 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 readObject(Document source, Class targetType) { - return read(targetType, source); - } - - @SuppressWarnings("unchecked") - @Override - @Nullable - public R read(Class type, Document source) { - return doRead(source, ClassTypeInformation.from((Class) ClassUtils.getUserClass(type))); - } - - @SuppressWarnings("unchecked") - @Nullable - protected R doRead(Map source, TypeInformation typeHint) { - - if (source == null) { - return null; - } - - typeHint = (TypeInformation) 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 readEntity(ElasticsearchPersistentEntity entity, Map 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 readProperties(ElasticsearchPersistentEntity entity, R instance, - ElasticsearchPropertyValueProvider valueProvider) { - - PersistentPropertyAccessor 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 readValue(@Nullable Object source, ElasticsearchPersistentProperty property, - TypeInformation targetType) { - - if (source == null) { - return null; - } - - Class 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) source, property, targetType); - } - - return (R) readSimpleValue(source, targetType); - } - - @SuppressWarnings("unchecked") - private R readMapValue(@Nullable Map source, ElasticsearchPersistentProperty property, - TypeInformation 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 target = new LinkedHashMap<>(); - for (Entry 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 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 readCollectionValue(@Nullable List source, ElasticsearchPersistentProperty property, - TypeInformation targetType) { - - if (source == null) { - return null; - } - - Collection 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) 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) 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 typeHint) { - - if (source == null) { - return; - } - - Class entityType = source.getClass(); - Optional> 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> 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> 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) 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 value, ElasticsearchPersistentProperty property, - TypeInformation typeHint) { - - Map target = new LinkedHashMap<>(); - Streamable> 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 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 mapToObject(String source, Class 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 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 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 { - - final MapValueAccessor mapValueAccessor; - - @SuppressWarnings("unchecked") - @Override - public T getPropertyValue(ElasticsearchPersistentProperty property) { - return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation()); - } - - } - - static class MapValueAccessor { - - final Map target; - - MapValueAccessor(Map 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 parts = Arrays.asList(fieldName.split("\\.")).iterator(); - Map 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 getAsMap(Object result) { - - if (result instanceof Map) { - return (Map) result; - } - - throw new IllegalArgumentException(String.format("%s is not a Map.", result)); - } - } - -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java index 2f428e4e2..beb4b579f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -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 ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class clazz) { SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery); return resultsMapper.mapResults(response, clazz, null); } + @Override public ScrolledPage startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class clazz) { SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery); return resultsMapper.mapResults(response, clazz, null); } + @Override public ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class clazz, SearchResultMapper mapper) { SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery); return mapper.mapResults(response, clazz, null); } + @Override public ScrolledPage startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class clazz, SearchResultMapper mapper) { SearchResponse response = doScroll(prepareScroll(criteriaQuery, scrollTimeInMillis, clazz), criteriaQuery); return mapper.mapResults(response, clazz, null); } + @Override public ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class 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 ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class clazz, SearchResultMapper mapper) { SearchScrollRequest request = new SearchScrollRequest(scrollId); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java index 1657477ea..fef44f559 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -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!"); diff --git a/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java index f94dd705b..a02ddb050 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java @@ -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 mapToObject(String source, Class 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}. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java index 272a3c281..617e172c7 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchOperations.java @@ -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. diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java index 2668c47a3..f1b40a171 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/ElasticsearchConverter.java @@ -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, 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, 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(); } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java index f96b39876..802033e58 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverter.java @@ -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, 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, ElasticsearchPersistentProperty> mappingContext) { - - Assert.notNull(mappingContext, "MappingContext must not be null!"); - - this.mappingContext = mappingContext; - this.conversionService = new DefaultConversionService(); + this(mappingContext, null); } + public MappingElasticsearchConverter( + MappingContext, 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, 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.
+ * 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 read(Class type, Document source) { + return doRead(source, ClassTypeInformation.from((Class) ClassUtils.getUserClass(type))); + } + + @SuppressWarnings("unchecked") + @Nullable + protected R doRead(Map source, TypeInformation typeHint) { + + if (source == null) { + return null; + } + + typeHint = (TypeInformation) 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 readEntity(ElasticsearchPersistentEntity entity, Map 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 readProperties(ElasticsearchPersistentEntity entity, R instance, + ElasticsearchPropertyValueProvider valueProvider) { + + PersistentPropertyAccessor 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 readValue(@Nullable Object source, ElasticsearchPersistentProperty property, + TypeInformation targetType) { + + if (source == null) { + return null; + } + + Class 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) source, property, targetType); + } + + return (R) readSimpleValue(source, targetType); + } + + @SuppressWarnings("unchecked") + private R readMapValue(@Nullable Map source, ElasticsearchPersistentProperty property, + TypeInformation 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 target = new LinkedHashMap<>(); + for (Entry 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 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 readCollectionValue(@Nullable List source, ElasticsearchPersistentProperty property, + TypeInformation targetType) { + + if (source == null) { + return null; + } + + Collection 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) 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) 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 typeHint) { + + if (source == null) { + return; + } + + Class entityType = source.getClass(); + Optional> 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> 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> 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) 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 value, ElasticsearchPersistentProperty property, + TypeInformation typeHint) { + + Map target = new LinkedHashMap<>(); + Streamable> 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 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 mapToObject(String source, Class 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 readObject(Document source, Class 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 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 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 target; + + MapValueAccessor(Map 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 parts = Arrays.asList(fieldName.split("\\.")).iterator(); + Map 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 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 { + + final MapValueAccessor mapValueAccessor; + + @SuppressWarnings("unchecked") + @Override + public T getPropertyValue(ElasticsearchPersistentProperty property) { + return (T) readValue(mapValueAccessor.get(property), property, property.getTypeInformation()); + } + + } + } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/query/ConvertingParameterAccessor.java b/src/main/java/org/springframework/data/elasticsearch/repository/query/ConvertingParameterAccessor.java index 071ec26df..e56d4c036 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/query/ConvertingParameterAccessor.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/query/ConvertingParameterAccessor.java @@ -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)); } diff --git a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryBean.java b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryBean.java index 84ea954ab..ea709b225 100644 --- a/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryBean.java +++ b/src/main/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryBean.java @@ -38,7 +38,7 @@ public class ElasticsearchRepositoryFactoryBean, S, /** * Creates a new {@link ElasticsearchRepositoryFactoryBean} for the given repository interface. - * + * * @param repositoryInterface must not be {@literal null}. */ public ElasticsearchRepositoryFactoryBean(Class repositoryInterface) { @@ -51,9 +51,9 @@ public class ElasticsearchRepositoryFactoryBean, 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; } diff --git a/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java index 0e192f964..8a307f204 100644 --- a/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java +++ b/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java @@ -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; - } } diff --git a/src/test/java/org/springframework/data/elasticsearch/RestElasticsearchTestConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/RestElasticsearchTestConfiguration.java index 83b6d308c..2f9ab0343 100644 --- a/src/test/java/org/springframework/data/elasticsearch/RestElasticsearchTestConfiguration.java +++ b/src/test/java/org/springframework/data/elasticsearch/RestElasticsearchTestConfiguration.java @@ -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(); + } } diff --git a/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java index d7b13a059..1812731ed 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java @@ -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 {} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CustomEntityMapper.java b/src/test/java/org/springframework/data/elasticsearch/core/CustomEntityMapper.java deleted file mode 100644 index 1db5057bf..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/CustomEntityMapper.java +++ /dev/null @@ -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 mapToObject(String source, Class clazz) throws IOException { - // mapping text to Object - return null; - } - - @Override - public Document mapObject(Object source) { - return null; - } - - @Override - public T readObject(Document source, Class targetType) { - return null; - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DefaultEntityMapperTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DefaultEntityMapperTests.java deleted file mode 100644 index 055469561..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/DefaultEntityMapperTests.java +++ /dev/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; - } - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java index 90f83c0d1..246799045 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java @@ -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 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() { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateCustomMapperTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateCustomMapperTests.java deleted file mode 100644 index 7f53f6a1e..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateCustomMapperTests.java +++ /dev/null @@ -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); - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java index 1c8301213..e5045fd3e 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -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; diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java index 6c35da936..82f197a82 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplateTests.java @@ -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()))); } diff --git a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterTests.java deleted file mode 100644 index f3984e449..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterTests.java +++ /dev/null @@ -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(); - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapperUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java similarity index 83% rename from src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapperUnitTests.java rename to src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java index 201517c23..bba66e2e4 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchEntityMapperUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/convert/MappingElasticsearchConverterUnitTests.java @@ -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) bigBunsCafeAsMap.get("location")).put("lat", 34.0945637D); ((HashMap) 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 writeToMap(Object source) { Document sink = Document.create(); - entityMapper.write(source, sink); + mappingElasticsearchConverter.write(source, sink); return sink; } diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java index be4b20692..aa8fca351 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java @@ -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 { + + @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 { - - @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 diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java index 351df0a9a..deea9e119 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java @@ -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) diff --git a/src/test/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryTests.java b/src/test/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryTests.java index ef98d7257..a5f90eb8f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/repository/support/ElasticsearchRepositoryFactoryTests.java @@ -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, 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); } diff --git a/src/test/resources/elasticsearch-template-custom-mapper.xml b/src/test/resources/elasticsearch-template-custom-mapper.xml deleted file mode 100644 index b3a4533b1..000000000 --- a/src/test/resources/elasticsearch-template-custom-mapper.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file