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 e7d35eaa6..47fbbb00b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/AbstractElasticsearchConfiguration.java @@ -19,8 +19,7 @@ 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; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; /** * @author Christoph Strobl @@ -45,8 +44,7 @@ public abstract class AbstractElasticsearchConfiguration extends ElasticsearchCo * @return never {@literal null}. */ @Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" }) - public ElasticsearchOperations elasticsearchOperations(MappingElasticsearchConverter mappingElasticsearchConverter, - ResultsMapper resultsMapper) { - return new ElasticsearchRestTemplate(elasticsearchClient(), mappingElasticsearchConverter, resultsMapper); + public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter) { + return new ElasticsearchRestTemplate(elasticsearchClient(), elasticsearchConverter); } } 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 f74b4be62..02273549e 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/AbstractReactiveElasticsearchConfiguration.java @@ -22,12 +22,12 @@ 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.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.lang.Nullable; /** * @author Christoph Strobl + * @author Peter-Josef Meisch * @since 3.2 * @see ElasticsearchConfigurationSupport */ @@ -49,11 +49,10 @@ public abstract class AbstractReactiveElasticsearchConfiguration extends Elastic * @return never {@literal null}. */ @Bean - public ReactiveElasticsearchOperations reactiveElasticsearchTemplate( - MappingElasticsearchConverter mappingElasticsearchConverter, ResultsMapper resultsMapper) { + public ReactiveElasticsearchOperations reactiveElasticsearchTemplate(ElasticsearchConverter elasticsearchConverter) { ReactiveElasticsearchTemplate template = new ReactiveElasticsearchTemplate(reactiveElasticsearchClient(), - mappingElasticsearchConverter, resultsMapper); + elasticsearchConverter); 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 3c4636aec..3e2419f3b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java +++ b/src/main/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupport.java @@ -27,13 +27,10 @@ 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.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; @@ -49,7 +46,7 @@ public class ElasticsearchConfigurationSupport { @Bean - public MappingElasticsearchConverter elasticsearchEntityMapper( + public ElasticsearchConverter elasticsearchEntityMapper( SimpleElasticsearchMappingContext elasticsearchMappingContext) { return new MappingElasticsearchConverter(elasticsearchMappingContext); } @@ -72,17 +69,6 @@ public SimpleElasticsearchMappingContext elasticsearchMappingContext() { return mappingContext; } - /** - * Returns the {@link ResultsMapper} to be used for search responses. - * - * @see MappingElasticsearchConverter - * @return never {@literal null}. - */ - @Bean - public ResultsMapper resultsMapper(SimpleElasticsearchMappingContext elasticsearchMappingContext) { - return new DefaultResultMapper(elasticsearchMappingContext, elasticsearchEntityMapper(elasticsearchMappingContext)); - } - /** * Register custom {@link Converter}s in a {@link ElasticsearchCustomConversions} object if required. * diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java index 5baa1e320..d11a3e02c 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/AbstractElasticsearchTemplate.java @@ -5,20 +5,30 @@ import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.data.elasticsearch.annotations.Mapping; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; +import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.index.MappingBuilder; +import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.util.StringUtils; /** * AbstractElasticsearchTemplate * * @author Sascha Woo + * @author Peter-Josef Meisch */ -public abstract class AbstractElasticsearchTemplate { +public abstract class AbstractElasticsearchTemplate implements ElasticsearchOperations { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractElasticsearchTemplate.class); protected ElasticsearchConverter elasticsearchConverter; + protected ElasticsearchConverter createElasticsearchConverter() { + MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter( + new SimpleElasticsearchMappingContext()); + mappingElasticsearchConverter.afterPropertiesSet(); + return mappingElasticsearchConverter; + } + protected String buildMapping(Class clazz) { // load mapping specified in Mapping annotation if present @@ -43,4 +53,8 @@ protected String buildMapping(Class clazz) { } } + @Override + public ElasticsearchConverter getElasticsearchConverter() { + return elasticsearchConverter; + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java deleted file mode 100644 index 0146d5d8f..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/AbstractResultMapper.java +++ /dev/null @@ -1,73 +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 org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.util.Assert; - -/** - * @author Artur Konczak - * @author Christoph Strobl - */ -public abstract class AbstractResultMapper implements ResultsMapper { - - private final EntityMapper entityMapper; - private final ProjectionFactory projectionFactory; - - /** - * Create a new {@link AbstractResultMapper}. - * - * @param entityMapper must not be {@literal null}. - */ - public AbstractResultMapper(EntityMapper entityMapper) { - this(entityMapper, new SpelAwareProxyProjectionFactory()); - } - - /** - * Create a new {@link AbstractResultMapper}. - * - * @param entityMapper must not be {@literal null}. - * @param projectionFactory must not be {@literal null}. - * @since 3.2 - */ - public AbstractResultMapper(EntityMapper entityMapper, ProjectionFactory projectionFactory) { - - Assert.notNull(entityMapper, "EntityMapper must not be null!"); - Assert.notNull(projectionFactory, "ProjectionFactory must not be null!"); - - this.entityMapper = entityMapper; - this.projectionFactory = projectionFactory; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.ResultsMapper#getEntityMapper() - */ - @Override - public EntityMapper getEntityMapper() { - return this.entityMapper; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.ResultsMapper#getProjectionFactory() - */ - @Override - public ProjectionFactory getProjectionFactory() { - return this.projectionFactory; - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java deleted file mode 100644 index 41c94c474..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java +++ /dev/null @@ -1,217 +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.util.ArrayList; -import java.util.List; - -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.get.MultiGetItemResponse; -import org.elasticsearch.action.get.MultiGetResponse; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.common.document.DocumentField; -import org.elasticsearch.search.SearchHit; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.ElasticsearchException; -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; -import org.springframework.data.elasticsearch.support.SearchHitsUtil; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.context.MappingContext; -import org.springframework.data.mapping.model.ConvertingPropertyAccessor; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * @author Artur Konczak - * @author Petar Tahchiev - * @author Young Gu - * @author Oliver Gierke - * @author Chris White - * @author Mark Paluch - * @author Ilkang Na - * @author Sascha Woo - * @author Christoph Strobl - * @author Dmitriy Yakovlev - */ -public class DefaultResultMapper extends AbstractResultMapper { - - private final MappingContext, ElasticsearchPersistentProperty> mappingContext; - private final ConversionService conversionService = new DefaultConversionService(); - - public DefaultResultMapper() { - this(new SimpleElasticsearchMappingContext()); - } - - public DefaultResultMapper( - MappingContext, ElasticsearchPersistentProperty> mappingContext) { - this(mappingContext, null); - } - - public DefaultResultMapper(EntityMapper entityMapper) { - this(new SimpleElasticsearchMappingContext(), entityMapper); - } - - public DefaultResultMapper( - MappingContext, ElasticsearchPersistentProperty> mappingContext, - @Nullable EntityMapper entityMapper) { - - super(entityMapper != null ? entityMapper : initEntityMapper(mappingContext)); - this.mappingContext = mappingContext; - } - - private static EntityMapper initEntityMapper( - MappingContext, ElasticsearchPersistentProperty> mappingContext) { - - Assert.notNull(mappingContext, "MappingContext must not be null!"); - MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter(mappingContext, null); - mappingElasticsearchConverter.afterPropertiesSet(); - return mappingElasticsearchConverter; - } - - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - - long totalHits = SearchHitsUtil.getTotalCount(response.getHits()); - float maxScore = response.getHits().getMaxScore(); - - List results = new ArrayList<>(); - for (SearchHit hit : response.getHits()) { - if (hit != null) { - T result = mapSearchHit(hit, clazz); - - setPersistentEntityId(result, hit.getId(), clazz); - setPersistentEntityVersion(result, hit.getVersion(), clazz); - setPersistentEntityScore(result, hit.getScore(), clazz); - - populateScriptFields(result, hit); - results.add(result); - } - } - - return new AggregatedPageImpl(results, pageable, totalHits, response.getAggregations(), response.getScrollId(), - maxScore); - } - - private void populateScriptFields(T result, SearchHit hit) { - if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) { - for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) { - ScriptedField scriptedField = field.getAnnotation(ScriptedField.class); - if (scriptedField != null) { - String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name(); - DocumentField searchHitField = hit.getFields().get(name); - if (searchHitField != null) { - field.setAccessible(true); - try { - field.set(result, searchHitField.getValue()); - } catch (IllegalArgumentException e) { - throw new ElasticsearchException( - "failed to set scripted field: " + name + " with value: " + searchHitField.getValue(), e); - } catch (IllegalAccessException e) { - throw new ElasticsearchException("failed to access scripted field: " + name, e); - } - } - } - } - } - } - - @Override - public T mapResult(GetResponse response, Class clazz) { - - if (!response.isExists()) { - return null; - } - - T result = mapDocument(DocumentAdapters.from(response), clazz); - if (result != null) { - setPersistentEntityId(result, response.getId(), clazz); - setPersistentEntityVersion(result, response.getVersion(), clazz); - } - return result; - } - - @Override - public List mapResults(MultiGetResponse responses, Class clazz) { - List list = new ArrayList<>(); - for (MultiGetItemResponse response : responses.getResponses()) { - if (!response.isFailed() && response.getResponse().isExists()) { - T result = mapResult(response.getResponse(), clazz); - setPersistentEntityId(result, response.getResponse().getId(), clazz); - setPersistentEntityVersion(result, response.getResponse().getVersion(), clazz); - list.add(result); - } - } - return list; - } - - private void setPersistentEntityId(T result, String id, Class clazz) { - - if (clazz.isAnnotationPresent(Document.class)) { - - ElasticsearchPersistentEntity persistentEntity = mappingContext.getRequiredPersistentEntity(clazz); - ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty(); - - PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>( - persistentEntity.getPropertyAccessor(result), conversionService); - - // Only deal with String because ES generated Ids are strings ! - if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { - accessor.setProperty(idProperty, id); - } - } - } - - private void setPersistentEntityVersion(T result, long version, Class clazz) { - - if (clazz.isAnnotationPresent(Document.class)) { - - ElasticsearchPersistentEntity persistentEntity = mappingContext.getPersistentEntity(clazz); - ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty(); - - // Only deal with Long because ES versions are longs ! - if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) { - // check that a version was actually returned in the response, -1 would indicate that - // a search didn't request the version ids in the response, which would be an issue - Assert.isTrue(version != -1, "Version in response is -1"); - persistentEntity.getPropertyAccessor(result).setProperty(versionProperty, version); - } - } - } - - private void setPersistentEntityScore(T result, float score, Class clazz) { - - if (clazz.isAnnotationPresent(Document.class)) { - - ElasticsearchPersistentEntity entity = mappingContext.getRequiredPersistentEntity(clazz); - - if (!entity.hasScoreProperty()) { - return; - } - - entity.getPropertyAccessor(result) // - .setProperty(entity.getScoreProperty(), score); - } - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java index 02c83ebe3..49b37c0ae 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchOperations.java @@ -109,7 +109,7 @@ public interface ElasticsearchOperations { * * @param indexName * @param type - * @param mappings + * @param clazz * @since 3.2 */ boolean putMapping(String indexName, String type, Class clazz); @@ -180,16 +180,6 @@ public interface ElasticsearchOperations { */ T queryForObject(GetQuery query, Class clazz); - /** - * Execute the query against elasticsearch and return the first returned object using custom mapper - * - * @param query - * @param clazz - * @param mapper - * @return the first matching object - */ - T queryForObject(GetQuery query, Class clazz, GetResultMapper mapper); - /** * Execute the query against elasticsearch and return the first returned object * @@ -217,15 +207,6 @@ public interface ElasticsearchOperations { */ Page queryForPage(SearchQuery query, Class clazz); - /** - * Execute the query against elasticsearch and return result as {@link Page} using custom mapper - * - * @param query - * @param clazz - * @return - */ - Page queryForPage(SearchQuery query, Class clazz, SearchResultMapper mapper); - /** * Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} * @@ -235,16 +216,6 @@ public interface ElasticsearchOperations { */ List> queryForPage(List queries, Class clazz); - /** - * Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom - * mapper - * - * @param queries - * @param clazz - * @return - */ - List> queryForPage(List queries, Class clazz, SearchResultMapper mapper); - /** * Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} * @@ -254,16 +225,6 @@ public interface ElasticsearchOperations { */ List> queryForPage(List queries, List> classes); - /** - * Execute the multi-search against elasticsearch and return result as {@link List} of {@link Page} using custom - * mapper - * - * @param queries - * @param classes - * @return - */ - List> queryForPage(List queries, List> classes, SearchResultMapper mapper); - /** * Execute the query against elasticsearch and return result as {@link Page} * @@ -282,15 +243,6 @@ public interface ElasticsearchOperations { */ Page queryForPage(StringQuery query, Class clazz); - /** - * Execute the query against elasticsearch and return result as {@link Page} using custom mapper - * - * @param query - * @param clazz - * @return - */ - Page queryForPage(StringQuery query, Class clazz, SearchResultMapper mapper); - /** * Executes the given {@link CriteriaQuery} against elasticsearch and return result as {@link CloseableIterator}. *

@@ -319,22 +271,6 @@ public interface ElasticsearchOperations { */ CloseableIterator stream(SearchQuery query, Class clazz); - /** - * Executes the given {@link SearchQuery} against elasticsearch and return result as {@link CloseableIterator} using - * custom mapper. - *

- * Returns a {@link CloseableIterator} that wraps an Elasticsearch scroll context that needs to be closed in case of - * error. - * - * @param element return type - * @param query - * @param clazz - * @param mapper - * @return - * @since 1.3 - */ - CloseableIterator stream(SearchQuery query, Class clazz, SearchResultMapper mapper); - /** * Execute the criteria query against elasticsearch and return result as {@link List} * @@ -439,16 +375,6 @@ default List> queryForList(List queries, List> cla */ List multiGet(SearchQuery searchQuery, Class clazz); - /** - * Execute a multiGet against elasticsearch for the given ids with MultiGetResultMapper - * - * @param searchQuery - * @param clazz - * @param multiGetResultMapper - * @return - */ - List multiGet(SearchQuery searchQuery, Class clazz, MultiGetResultMapper multiGetResultMapper); - /** * Index an object. Will do save or update * @@ -611,18 +537,6 @@ default void bulkUpdate(List queries) { */ ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery query, Class clazz); - /** - * Returns scrolled page for given query - * - * @param query The search query. - * @param scrollTimeInMillis The time in millisecond for scroll feature - * {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}. - * @param mapper Custom impl to map result to entities - * @return The scan id for input query. - */ - ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery query, Class clazz, - SearchResultMapper mapper); - /** * Returns scrolled page for given query * @@ -634,23 +548,8 @@ ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery query, Clas */ ScrolledPage startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class clazz); - /** - * Returns scrolled page for given query - * - * @param criteriaQuery The search query. - * @param scrollTimeInMillis The time in millisecond for scroll feature - * {@link org.elasticsearch.action.search.SearchRequestBuilder#setScroll(org.elasticsearch.common.unit.TimeValue)}. - * @param mapper Custom impl to map result to entities - * @return The scan id for input query. - */ - ScrolledPage startScroll(long scrollTimeInMillis, CriteriaQuery criteriaQuery, Class clazz, - SearchResultMapper mapper); - ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class clazz); - ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class clazz, - SearchResultMapper mapper); - /** * Clears the search contexts associated with specified scroll ids. * 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 c92c6e413..687e94905 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplate.java @@ -99,11 +99,11 @@ import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.client.support.AliasData; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.DocumentAdapters; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.facet.FacetRequest; 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.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.support.SearchHitsUtil; import org.springframework.data.util.CloseableIterator; @@ -147,53 +147,28 @@ * @author Gyula Attila Csorogi */ public class ElasticsearchRestTemplate extends AbstractElasticsearchTemplate - implements ElasticsearchOperations, EsClient, ApplicationContextAware { + implements EsClient, ApplicationContextAware { private static final Logger logger = LoggerFactory.getLogger(ElasticsearchRestTemplate.class); private RestHighLevelClient client; - private ResultsMapper resultsMapper; private String searchTimeout; public ElasticsearchRestTemplate(RestHighLevelClient client) { - MappingElasticsearchConverter mappingElasticsearchConverter = createElasticsearchConverter(); - initialize(client, mappingElasticsearchConverter, - new DefaultResultMapper(mappingElasticsearchConverter.getMappingContext())); - } - - public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter, - EntityMapper entityMapper) { - initialize(client, elasticsearchConverter, - new DefaultResultMapper(elasticsearchConverter.getMappingContext(), entityMapper)); - } - - public ElasticsearchRestTemplate(RestHighLevelClient client, ResultsMapper resultsMapper) { - initialize(client, createElasticsearchConverter(), resultsMapper); + initialize(client, createElasticsearchConverter()); } public ElasticsearchRestTemplate(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) { - initialize(client, elasticsearchConverter, new DefaultResultMapper(elasticsearchConverter.getMappingContext())); + initialize(client, 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) { + private void initialize(RestHighLevelClient client, ElasticsearchConverter elasticsearchConverter) { Assert.notNull(client, "Client must not be null!"); - Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null."); - Assert.notNull(resultsMapper, "ResultsMapper must not be null!"); + Assert.notNull(elasticsearchConverter, "ElasticsearchConverter must not be null."); this.client = client; this.elasticsearchConverter = elasticsearchConverter; - this.resultsMapper = resultsMapper; } @Override @@ -342,25 +317,15 @@ private Map convertMappingResponse(String mappingResponse, Strin } - @Override - public ElasticsearchConverter getElasticsearchConverter() { - return elasticsearchConverter; - } - @Override public T queryForObject(GetQuery query, Class clazz) { - return queryForObject(query, clazz, resultsMapper); - } - - @Override - public T queryForObject(GetQuery query, Class clazz, GetResultMapper mapper) { ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz); GetRequest request = new GetRequest(persistentEntity.getIndexName(), persistentEntity.getIndexType(), query.getId()); GetResponse response; try { response = client.get(request, RequestOptions.DEFAULT); - return mapper.mapResult(response, clazz); + return elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz); } catch (IOException e) { throw new ElasticsearchException("Error while getting for request: " + request.toString(), e); } @@ -385,39 +350,29 @@ private T getObjectFromPage(Page page) { @Override public AggregatedPage queryForPage(SearchQuery query, Class clazz) { - return queryForPage(query, clazz, resultsMapper); - } - - @Override - public AggregatedPage queryForPage(SearchQuery query, Class clazz, SearchResultMapper mapper) { SearchResponse response = doSearch(prepareSearch(query, clazz), query); - return mapper.mapResults(response, clazz, query.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, query.getPageable()); } - @Override - public List> queryForPage(List queries, Class clazz) { - return queryForPage(queries, clazz, resultsMapper); - } - - private List> doMultiSearch(List queries, Class clazz, MultiSearchRequest request, - SearchResultMapper resultsMapper) { + private List> doMultiSearch(List queries, Class clazz, MultiSearchRequest request) { MultiSearchResponse.Item[] items = getMultiSearchResult(request); List> res = new ArrayList<>(queries.size()); int c = 0; for (SearchQuery query : queries) { - res.add(resultsMapper.mapResults(items[c++].getResponse(), clazz, query.getPageable())); + res.add(elasticsearchConverter.mapResults(SearchDocumentResponse.from(items[c++].getResponse()), clazz, + query.getPageable())); } return res; } - private List> doMultiSearch(List queries, List> classes, MultiSearchRequest request, - SearchResultMapper resultsMapper) { + private List> doMultiSearch(List queries, List> classes, MultiSearchRequest request) { MultiSearchResponse.Item[] items = getMultiSearchResult(request); List> res = new ArrayList<>(queries.size()); int c = 0; Iterator> it = classes.iterator(); for (SearchQuery query : queries) { - res.add(resultsMapper.mapResults(items[c++].getResponse(), it.next(), query.getPageable())); + res.add(elasticsearchConverter.mapResults(SearchDocumentResponse.from(items[c++].getResponse()), it.next(), + query.getPageable())); } return res; } @@ -435,27 +390,22 @@ private MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest reque } @Override - public List> queryForPage(List queries, Class clazz, SearchResultMapper mapper) { + public List> queryForPage(List queries, Class clazz) { MultiSearchRequest request = new MultiSearchRequest(); for (SearchQuery query : queries) { request.add(prepareSearch(prepareSearch(query, clazz), query)); } - return doMultiSearch(queries, clazz, request, mapper); + return doMultiSearch(queries, clazz, request); } @Override public List> queryForPage(List queries, List> classes) { - return queryForPage(queries, classes, resultsMapper); - } - - @Override - public List> queryForPage(List queries, List> classes, SearchResultMapper mapper) { MultiSearchRequest request = new MultiSearchRequest(); Iterator> it = classes.iterator(); for (SearchQuery query : queries) { request.add(prepareSearch(prepareSearch(query, it.next()), query)); } - return doMultiSearch(queries, classes, request, mapper); + return doMultiSearch(queries, classes, request); } @Override @@ -529,16 +479,11 @@ public Page queryForPage(CriteriaQuery criteriaQuery, Class clazz) { } catch (IOException e) { throw new ElasticsearchException("Error for search request: " + request.toString(), e); } - return resultsMapper.mapResults(response, clazz, criteriaQuery.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, criteriaQuery.getPageable()); } @Override public Page queryForPage(StringQuery query, Class clazz) { - return queryForPage(query, clazz, resultsMapper); - } - - @Override - public Page queryForPage(StringQuery query, Class clazz, SearchResultMapper mapper) { SearchRequest request = prepareSearch(query, clazz); request.source().query((wrapperQuery(query.getSource()))); SearchResponse response; @@ -547,29 +492,23 @@ public Page queryForPage(StringQuery query, Class clazz, SearchResultM } catch (IOException e) { throw new ElasticsearchException("Error for search request: " + request.toString(), e); } - return mapper.mapResults(response, clazz, query.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, query.getPageable()); } @Override public CloseableIterator stream(CriteriaQuery query, Class clazz) { long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis(); - return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz, resultsMapper); + return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz); } @Override public CloseableIterator stream(SearchQuery query, Class clazz) { - return stream(query, clazz, resultsMapper); - } - - @Override - public CloseableIterator stream(SearchQuery query, Class clazz, SearchResultMapper mapper) { long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis(); - return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz, mapper), clazz, mapper); + return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz); } - private CloseableIterator doStream(long scrollTimeInMillis, ScrolledPage page, Class clazz, - SearchResultMapper mapper) { - return StreamQueries.streamResults(page, scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz, mapper), + private CloseableIterator doStream(long scrollTimeInMillis, ScrolledPage page, Class clazz) { + return StreamQueries.streamResults(page, scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz), this::clearScroll); } @@ -661,7 +600,7 @@ private SearchRequest prepareCount(Query query, Class clazz) { @Override public List multiGet(SearchQuery searchQuery, Class clazz) { - return resultsMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz); + return elasticsearchConverter.mapDocuments(DocumentAdapters.from(getMultiResponse(searchQuery, clazz)), clazz); } private MultiGetResponse getMultiResponse(Query searchQuery, Class clazz) { @@ -698,11 +637,6 @@ private MultiGetResponse getMultiResponse(Query searchQuery, Class clazz) } } - @Override - public List multiGet(SearchQuery searchQuery, Class clazz, MultiGetResultMapper getResultMapper) { - return getResultMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz); - } - @Override public String index(IndexQuery query) { String documentId; @@ -1061,27 +995,13 @@ private SearchResponse doScroll(SearchRequest request, SearchQuery searchQuery) @Override public ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class clazz) { SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery); - return resultsMapper.mapResults(response, clazz, null); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(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); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, null); } @Override @@ -1094,21 +1014,7 @@ public ScrolledPage continueScroll(@Nullable String scrollId, long scroll } catch (IOException e) { throw new ElasticsearchException("Error for search request with scroll: " + request.toString(), e); } - 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); - request.scroll(TimeValue.timeValueMillis(scrollTimeInMillis)); - SearchResponse response; - try { - response = client.searchScroll(request, RequestOptions.DEFAULT); - } catch (IOException e) { - throw new ElasticsearchException("Error for search request with scroll: " + request.toString(), e); - } - return mapper.mapResults(response, clazz, Pageable.unpaged()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, Pageable.unpaged()); } @Override @@ -1426,43 +1332,38 @@ private void prepareSort(Query query, SearchSourceBuilder sourceBuilder, } private IndexRequest prepareIndex(IndexQuery query) { - try { - String indexName = StringUtils.isEmpty(query.getIndexName()) - ? retrieveIndexNameFromPersistentEntity(query.getObject().getClass())[0] - : query.getIndexName(); - String type = StringUtils.isEmpty(query.getType()) - ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0] - : query.getType(); - - IndexRequest indexRequest = null; - - if (query.getObject() != null) { - String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); - // If we have a query id and a document id, do not ask ES to generate one. - if (id != null) { - indexRequest = new IndexRequest(indexName, type, id); - } else { - indexRequest = new IndexRequest(indexName, type); - } - indexRequest.source(resultsMapper.getEntityMapper().mapToString(query.getObject()), - Requests.INDEX_CONTENT_TYPE); - } else if (query.getSource() != null) { - indexRequest = new IndexRequest(indexName, type, query.getId()).source(query.getSource(), - Requests.INDEX_CONTENT_TYPE); + String indexName = StringUtils.isEmpty(query.getIndexName()) + ? retrieveIndexNameFromPersistentEntity(query.getObject().getClass())[0] + : query.getIndexName(); + String type = StringUtils.isEmpty(query.getType()) + ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0] + : query.getType(); + + IndexRequest indexRequest = null; + + if (query.getObject() != null) { + String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); + // If we have a query id and a document id, do not ask ES to generate one. + if (id != null) { + indexRequest = new IndexRequest(indexName, type, id); } else { - throw new ElasticsearchException( - "object or source is null, failed to index the document [id: " + query.getId() + "]"); + indexRequest = new IndexRequest(indexName, type); } - if (query.getVersion() != null) { - indexRequest.version(query.getVersion()); - VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); - indexRequest.versionType(versionType); - } - - return indexRequest; - } catch (IOException e) { - throw new ElasticsearchException("failed to index the document [id: " + query.getId() + "]", e); + indexRequest.source(elasticsearchConverter.mapObject(query.getObject()).toJson(), Requests.INDEX_CONTENT_TYPE); + } else if (query.getSource() != null) { + indexRequest = new IndexRequest(indexName, type, query.getId()).source(query.getSource(), + Requests.INDEX_CONTENT_TYPE); + } else { + throw new ElasticsearchException( + "object or source is null, failed to index the document [id: " + query.getId() + "]"); + } + if (query.getVersion() != null) { + indexRequest.version(query.getVersion()); + VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); + indexRequest.versionType(versionType); } + + return indexRequest; } @Override @@ -1626,10 +1527,6 @@ private static MoreLikeThisQueryBuilder.Item[] toArray(MoreLikeThisQueryBuilder. return values; } - protected ResultsMapper getResultsMapper() { - return resultsMapper; - } - @Deprecated public static String readFileFromClasspath(String url) { return ResourceUtil.readFileFromClasspath(url); @@ -1651,4 +1548,5 @@ public SearchResponse suggest(SuggestBuilder suggestion, String... indices) { public SearchResponse suggest(SuggestBuilder suggestion, Class clazz) { return suggest(suggestion, retrieveIndexNameFromPersistentEntity(clazz)); } + } 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 7ecd7e4d1..9911cc184 100755 --- a/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplate.java @@ -19,7 +19,6 @@ import static org.elasticsearch.index.query.QueryBuilders.*; import static org.springframework.util.CollectionUtils.*; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -86,11 +85,11 @@ import org.springframework.data.elasticsearch.annotations.Setting; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.DocumentAdapters; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.facet.FacetRequest; 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.elasticsearch.core.query.*; import org.springframework.data.elasticsearch.support.SearchHitsUtil; import org.springframework.data.util.CloseableIterator; @@ -128,54 +127,29 @@ */ @Deprecated public class ElasticsearchTemplate extends AbstractElasticsearchTemplate - implements ElasticsearchOperations, EsClient, ApplicationContextAware { + implements EsClient, ApplicationContextAware { private static final Logger QUERY_LOGGER = LoggerFactory .getLogger("org.springframework.data.elasticsearch.core.QUERY"); private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchTemplate.class); private Client client; - private ResultsMapper resultsMapper; private String searchTimeout; public ElasticsearchTemplate(Client client) { - MappingElasticsearchConverter mappingElasticsearchConverter = createElasticsearchConverter(); - initialize(client, mappingElasticsearchConverter, - new DefaultResultMapper(mappingElasticsearchConverter.getMappingContext())); - } - - public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter, - EntityMapper entityMapper) { - initialize(client, elasticsearchConverter, - new DefaultResultMapper(elasticsearchConverter.getMappingContext(), entityMapper)); - } - - public ElasticsearchTemplate(Client client, ResultsMapper resultsMapper) { - initialize(client, createElasticsearchConverter(), resultsMapper); + initialize(client, createElasticsearchConverter()); } public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter) { - this(client, elasticsearchConverter, new DefaultResultMapper(elasticsearchConverter.getMappingContext())); + initialize(client, elasticsearchConverter); } - private MappingElasticsearchConverter createElasticsearchConverter() { - return new MappingElasticsearchConverter(new SimpleElasticsearchMappingContext()); - } - - public ElasticsearchTemplate(Client client, ElasticsearchConverter elasticsearchConverter, - ResultsMapper resultsMapper) { - - initialize(client, elasticsearchConverter, resultsMapper); - } - - private void initialize(Client client, ElasticsearchConverter elasticsearchConverter, ResultsMapper resultsMapper) { + private void initialize(Client client, ElasticsearchConverter elasticsearchConverter) { Assert.notNull(client, "Client must not be null!"); Assert.notNull(elasticsearchConverter, "elasticsearchConverter must not be null."); - Assert.notNull(resultsMapper, "ResultsMapper must not be null!"); this.client = client; this.elasticsearchConverter = elasticsearchConverter; - this.resultsMapper = resultsMapper; } @Override @@ -278,24 +252,14 @@ public Map getMapping(Class clazz) { return getMapping(getPersistentEntityFor(clazz).getIndexName(), getPersistentEntityFor(clazz).getIndexType()); } - @Override - public ElasticsearchConverter getElasticsearchConverter() { - return elasticsearchConverter; - } - @Override public T queryForObject(GetQuery query, Class clazz) { - return queryForObject(query, clazz, resultsMapper); - } - - @Override - public T queryForObject(GetQuery query, Class clazz, GetResultMapper mapper) { ElasticsearchPersistentEntity persistentEntity = getPersistentEntityFor(clazz); GetResponse response = client .prepareGet(persistentEntity.getIndexName(), persistentEntity.getIndexType(), query.getId()).execute() .actionGet(); - T entity = mapper.mapResult(response, clazz); + T entity = elasticsearchConverter.mapDocument(DocumentAdapters.from(response), clazz); return entity; } @@ -318,48 +282,38 @@ private T getObjectFromPage(Page page) { @Override public AggregatedPage queryForPage(SearchQuery query, Class clazz) { - return queryForPage(query, clazz, resultsMapper); - } - - @Override - public AggregatedPage queryForPage(SearchQuery query, Class clazz, SearchResultMapper mapper) { SearchResponse response = doSearch(prepareSearch(query, clazz), query); - return mapper.mapResults(response, clazz, query.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, query.getPageable()); } @Override public List> queryForPage(List queries, Class clazz) { - return queryForPage(queries, clazz, resultsMapper); - } - - @Override - public List> queryForPage(List queries, Class clazz, SearchResultMapper mapper) { MultiSearchRequest request = new MultiSearchRequest(); for (SearchQuery query : queries) { request.add(prepareSearch(prepareSearch(query, clazz), query)); } - return doMultiSearch(queries, clazz, request, mapper); + return doMultiSearch(queries, clazz, request); } - private List> doMultiSearch(List queries, Class clazz, MultiSearchRequest request, - SearchResultMapper resultsMapper) { + private List> doMultiSearch(List queries, Class clazz, MultiSearchRequest request) { MultiSearchResponse.Item[] items = getMultiSearchResult(request); List> res = new ArrayList<>(queries.size()); int c = 0; for (SearchQuery query : queries) { - res.add(resultsMapper.mapResults(items[c++].getResponse(), clazz, query.getPageable())); + res.add(elasticsearchConverter.mapResults(SearchDocumentResponse.from(items[c++].getResponse()), clazz, + query.getPageable())); } return res; } - private List> doMultiSearch(List queries, List> classes, MultiSearchRequest request, - SearchResultMapper resultsMapper) { + private List> doMultiSearch(List queries, List> classes, MultiSearchRequest request) { MultiSearchResponse.Item[] items = getMultiSearchResult(request); List> res = new ArrayList<>(queries.size()); int c = 0; Iterator> it = classes.iterator(); for (SearchQuery query : queries) { - res.add(resultsMapper.mapResults(items[c++].getResponse(), it.next(), query.getPageable())); + res.add(elasticsearchConverter.mapResults(SearchDocumentResponse.from(items[c++].getResponse()), it.next(), + query.getPageable())); } return res; } @@ -374,18 +328,13 @@ private MultiSearchResponse.Item[] getMultiSearchResult(MultiSearchRequest reque @Override public List> queryForPage(List queries, List> classes) { - return queryForPage(queries, classes, resultsMapper); - } - - @Override - public List> queryForPage(List queries, List> classes, SearchResultMapper mapper) { Assert.isTrue(queries.size() == classes.size(), "Queries should have same length with classes"); MultiSearchRequest request = new MultiSearchRequest(); Iterator> it = classes.iterator(); for (SearchQuery query : queries) { request.add(prepareSearch(prepareSearch(query, it.next()), query)); } - return doMultiSearch(queries, classes, request, mapper); + return doMultiSearch(queries, classes, request); } @Override @@ -445,40 +394,29 @@ public Page queryForPage(CriteriaQuery criteriaQuery, Class clazz) { searchRequestBuilder.setPostFilter(elasticsearchFilter); SearchResponse response = getSearchResponse(searchRequestBuilder); - return resultsMapper.mapResults(response, clazz, criteriaQuery.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, criteriaQuery.getPageable()); } @Override public Page queryForPage(StringQuery query, Class clazz) { - return queryForPage(query, clazz, resultsMapper); - } - - @Override - public Page queryForPage(StringQuery query, Class clazz, SearchResultMapper mapper) { SearchResponse response = getSearchResponse(prepareSearch(query, clazz).setQuery(wrapperQuery(query.getSource()))); - return mapper.mapResults(response, clazz, query.getPageable()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, query.getPageable()); } @Override public CloseableIterator stream(CriteriaQuery query, Class clazz) { long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis(); - return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz, resultsMapper); + return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz); } @Override public CloseableIterator stream(SearchQuery query, Class clazz) { - return stream(query, clazz, resultsMapper); - } - - @Override - public CloseableIterator stream(SearchQuery query, Class clazz, SearchResultMapper mapper) { long scrollTimeInMillis = TimeValue.timeValueMinutes(1).millis(); - return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz, mapper), clazz, mapper); + return doStream(scrollTimeInMillis, startScroll(scrollTimeInMillis, query, clazz), clazz); } - private CloseableIterator doStream(long scrollTimeInMillis, ScrolledPage page, Class clazz, - SearchResultMapper mapper) { - return StreamQueries.streamResults(page, scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz, mapper), + private CloseableIterator doStream(long scrollTimeInMillis, ScrolledPage page, Class clazz) { + return StreamQueries.streamResults(page, scrollId -> continueScroll(scrollId, scrollTimeInMillis, clazz), this::clearScroll); } @@ -560,7 +498,7 @@ private SearchRequestBuilder prepareCount(Query query, Class clazz) { @Override public List multiGet(SearchQuery searchQuery, Class clazz) { - return resultsMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz); + return elasticsearchConverter.mapDocuments(DocumentAdapters.from(getMultiResponse(searchQuery, clazz)), clazz); } private MultiGetResponse getMultiResponse(Query searchQuery, Class clazz) { @@ -593,11 +531,6 @@ private MultiGetResponse getMultiResponse(Query searchQuery, Class clazz) return builder.execute().actionGet(); } - @Override - public List multiGet(SearchQuery searchQuery, Class clazz, MultiGetResultMapper getResultMapper) { - return getResultMapper.mapResults(getMultiResponse(searchQuery, clazz), clazz); - } - @Override public String index(IndexQuery query) { String documentId = prepareIndex(query).execute().actionGet().getId(); @@ -890,39 +823,23 @@ private SearchResponse doScroll(SearchRequestBuilder requestBuilder, SearchQuery return getSearchResponse(requestBuilder.setQuery(searchQuery.getQuery())); } + @Override public ScrolledPage startScroll(long scrollTimeInMillis, SearchQuery searchQuery, Class clazz) { SearchResponse response = doScroll(prepareScroll(searchQuery, scrollTimeInMillis, clazz), searchQuery); - return resultsMapper.mapResults(response, clazz, null); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(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); - } - - 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); - } - - 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); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, null); } + @Override public ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class clazz) { SearchResponse response = getSearchResponse( client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute()); - return resultsMapper.mapResults(response, clazz, Pageable.unpaged()); - } - - public ScrolledPage continueScroll(@Nullable String scrollId, long scrollTimeInMillis, Class clazz, - SearchResultMapper mapper) { - SearchResponse response = getSearchResponse( - client.prepareSearchScroll(scrollId).setScroll(TimeValue.timeValueMillis(scrollTimeInMillis)).execute()); - return mapper.mapResults(response, clazz, Pageable.unpaged()); + return elasticsearchConverter.mapResults(SearchDocumentResponse.from(response), clazz, Pageable.unpaged()); } @Override @@ -1189,43 +1106,39 @@ private void prepareSort(Query query, SearchRequestBuilder searchRequestBuilder, } private IndexRequestBuilder prepareIndex(IndexQuery query) { - try { - String indexName = StringUtils.isEmpty(query.getIndexName()) - ? retrieveIndexNameFromPersistentEntity(query.getObject().getClass())[0] - : query.getIndexName(); - String type = StringUtils.isEmpty(query.getType()) - ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0] - : query.getType(); - - IndexRequestBuilder indexRequestBuilder = null; - - if (query.getObject() != null) { - String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); - // If we have a query id and a document id, do not ask ES to generate one. - if (id != null) { - indexRequestBuilder = client.prepareIndex(indexName, type, id); - } else { - indexRequestBuilder = client.prepareIndex(indexName, type); - } - indexRequestBuilder.setSource(resultsMapper.getEntityMapper().mapToString(query.getObject()), - Requests.INDEX_CONTENT_TYPE); - } else if (query.getSource() != null) { - indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(), - Requests.INDEX_CONTENT_TYPE); + String indexName = StringUtils.isEmpty(query.getIndexName()) + ? retrieveIndexNameFromPersistentEntity(query.getObject().getClass())[0] + : query.getIndexName(); + String type = StringUtils.isEmpty(query.getType()) + ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0] + : query.getType(); + + IndexRequestBuilder indexRequestBuilder = null; + + if (query.getObject() != null) { + String id = StringUtils.isEmpty(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId(); + // If we have a query id and a document id, do not ask ES to generate one. + if (id != null) { + indexRequestBuilder = client.prepareIndex(indexName, type, id); } else { - throw new ElasticsearchException( - "object or source is null, failed to index the document [id: " + query.getId() + "]"); + indexRequestBuilder = client.prepareIndex(indexName, type); } - if (query.getVersion() != null) { - indexRequestBuilder.setVersion(query.getVersion()); - VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); - indexRequestBuilder.setVersionType(versionType); - } - - return indexRequestBuilder; - } catch (IOException e) { - throw new ElasticsearchException("failed to index the document [id: " + query.getId() + "]", e); + indexRequestBuilder.setSource(elasticsearchConverter.mapObject(query.getObject()).toJson(), + Requests.INDEX_CONTENT_TYPE); + } else if (query.getSource() != null) { + indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource(), + Requests.INDEX_CONTENT_TYPE); + } else { + throw new ElasticsearchException( + "object or source is null, failed to index the document [id: " + query.getId() + "]"); } + if (query.getVersion() != null) { + indexRequestBuilder.setVersion(query.getVersion()); + VersionType versionType = retrieveVersionTypeFromPersistentEntity(query.getObject().getClass()); + indexRequestBuilder.setVersionType(versionType); + } + + return indexRequestBuilder; } @Override @@ -1337,10 +1250,6 @@ private static MoreLikeThisQueryBuilder.Item[] toArray(MoreLikeThisQueryBuilder. return values; } - protected ResultsMapper getResultsMapper() { - return resultsMapper; - } - @Deprecated public static String readFileFromClasspath(String url) { return ResourceUtil.readFileFromClasspath(url); @@ -1353,4 +1262,5 @@ public SearchResponse suggest(SuggestBuilder suggestion, String... indices) { public SearchResponse suggest(SuggestBuilder suggestion, Class clazz) { return suggest(suggestion, retrieveIndexNameFromPersistentEntity(clazz)); } + } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java deleted file mode 100644 index a02ddb050..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/EntityMapper.java +++ /dev/null @@ -1,61 +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.Map; - -import org.springframework.data.elasticsearch.Document; -import org.springframework.lang.Nullable; - -/** - * DocumentMapper interface, it will allow to customize how we mapping object to json - * - * @author Artur Konczak - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Christoph Strobl - * @author Mark Paluch - */ -public interface EntityMapper { - - 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 Document}. - * - * @param source must not be {@literal null}. - * @return never {@literal null} - * @since 3.2 - */ - Document mapObject(Object source); - - /** - * 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}. - * @param - * @return can be {@literal null}. - * @since 3.2 - */ - @Nullable - T readObject(Document source, Class targetType); -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/GetResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/GetResultMapper.java deleted file mode 100644 index 90cf4208e..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/GetResultMapper.java +++ /dev/null @@ -1,42 +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 org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.index.get.GetResult; -import org.springframework.lang.Nullable; - -/** - * @author Artur Konczak - * @author Mohsin Husen - * @author Christoph Strobl - */ -public interface GetResultMapper { - - T mapResult(GetResponse response, Class clazz); - - /** - * Map a single {@link GetResult} to the given {@link Class type}. - * - * @param getResult must not be {@literal null}. - * @param type must not be {@literal null}. - * @param - * @return can be {@literal null}. - * @since 3.2 - */ - @Nullable - T mapGetResult(GetResult getResult, Class type); -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/MultiGetResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/MultiGetResultMapper.java deleted file mode 100644 index 1b2086d36..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/MultiGetResultMapper.java +++ /dev/null @@ -1,29 +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.util.List; - -import org.elasticsearch.action.get.MultiGetResponse; - -/** - * @author Mohsin Husen - */ -public interface MultiGetResultMapper { - - List mapResults(MultiGetResponse responses, Class clazz); -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java index c95a67cd0..cb9dcdcc2 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/ReactiveElasticsearchTemplate.java @@ -21,7 +21,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -59,6 +58,7 @@ import org.springframework.data.elasticsearch.core.EntityOperations.IndexCoordinates; import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.document.DocumentAdapters; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; @@ -89,7 +89,6 @@ public class ReactiveElasticsearchTemplate implements ReactiveElasticsearchOpera private final ReactiveElasticsearchClient client; private final ElasticsearchConverter converter; private final @NonNull MappingContext, ElasticsearchPersistentProperty> mappingContext; - private final ResultsMapper resultMapper; private final ElasticsearchExceptionTranslator exceptionTranslator; private final EntityOperations operations; @@ -101,17 +100,11 @@ public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client) { } public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter) { - this(client, converter, new DefaultResultMapper(converter.getMappingContext())); - } - - public ReactiveElasticsearchTemplate(ReactiveElasticsearchClient client, ElasticsearchConverter converter, - ResultsMapper resultsMapper) { this.client = client; this.converter = converter; this.mappingContext = converter.getMappingContext(); - this.resultMapper = resultsMapper; this.exceptionTranslator = new ElasticsearchExceptionTranslator(); this.operations = new EntityOperations(this.mappingContext); } @@ -155,11 +148,7 @@ private Mono doIndex(Object value, AdaptibleEntity entity, @Nu ? new IndexRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName(), converter.convertId(id)) : new IndexRequest(indexCoordinates.getIndexName(), indexCoordinates.getTypeName()); - try { - request.source(resultMapper.getEntityMapper().mapToString(value), Requests.INDEX_CONTENT_TYPE); - } catch (IOException e) { - throw new RuntimeException(e); - } + request.source(converter.mapObject(value).toJson(), Requests.INDEX_CONTENT_TYPE); if (entity.isVersionedEntity()) { @@ -185,7 +174,7 @@ public Mono findById(String id, Class entityType, @Nullable String ind Assert.notNull(id, "Id must not be null!"); return doFindById(id, getPersistentEntity(entityType), index, type) - .map(it -> resultMapper.mapGetResult(it, entityType)); + .map(it -> converter.mapDocument(DocumentAdapters.from(it), entityType)); } private Mono doFindById(String id, ElasticsearchPersistentEntity entity, @Nullable String index, @@ -232,7 +221,7 @@ public Flux find(Query query, Class entityType, @Nullable String index Class resultType) { return doFind(query, getPersistentEntity(entityType), index, type) - .map(it -> resultMapper.mapSearchHit(it, resultType)); + .map(it -> converter.mapDocument(DocumentAdapters.from(it), resultType)); } private Flux doFind(Query query, ElasticsearchPersistentEntity entity, @Nullable String index, diff --git a/src/main/java/org/springframework/data/elasticsearch/core/ResultsMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/ResultsMapper.java deleted file mode 100644 index 59b0492ba..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/ResultsMapper.java +++ /dev/null @@ -1,120 +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.elasticsearch.index.get.GetResult; -import org.elasticsearch.search.SearchHit; - -import org.springframework.data.elasticsearch.Document; -import org.springframework.data.elasticsearch.ElasticsearchException; -import org.springframework.data.projection.ProjectionFactory; -import org.springframework.data.projection.SpelAwareProxyProjectionFactory; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * ResultsMapper - * - * @author Rizwan Idrees - * @author Mohsin Husen - * @author Artur Konczak - * @author Christoph Strobl - * @author Mark Paluch - */ -public interface ResultsMapper extends SearchResultMapper, GetResultMapper, MultiGetResultMapper { - - EntityMapper getEntityMapper(); - - /** - * Get the configured {@link ProjectionFactory}.
- * NOTE Should be overwritten in implementation to make use of the type cache. - * - * @since 3.2 - */ - default ProjectionFactory getProjectionFactory() { - return new SpelAwareProxyProjectionFactory(); - } - - @Nullable - @Deprecated - default T mapEntity(String source, Class clazz) { - - if (StringUtils.isEmpty(source)) { - return null; - } - try { - return getEntityMapper().mapToObject(source, clazz); - } catch (IOException e) { - throw new ElasticsearchException("failed to map source [ " + source + "] to class " + clazz.getSimpleName(), e); - } - } - - /** - * Map a single {@link Document} to an instance of the given type. - * - * @param document must not be {@literal null}. - * @param type must not be {@literal null}. - * @param - * @return can be {@literal null} if the {@link Document#isEmpty() is empty}. - * @since 4.0 - */ - @Nullable - default T mapDocument(Document document, Class type) { - - Object mappedResult = getEntityMapper().readObject(document, type); - - if (mappedResult == null) { - return (T) null; - } - - if (type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult)) { - return getProjectionFactory().createProjection(type, mappedResult); - } - - return type.cast(mappedResult); - } - - /** - * Map a single {@link GetResult} to an instance of the given type. - * - * @param getResult must not be {@literal null}. - * @param type must not be {@literal null}. - * @param - * @return can be {@literal null} if the {@link GetResult#isSourceEmpty() is empty}. - * @since 3.2 - */ - @Nullable - default T mapGetResult(GetResult getResult, Class type) { - return mapDocument(DocumentAdapters.from(getResult), type); - } - - /** - * Map a single {@link SearchHit} to an instance of the given type. - * - * @param searchHit must not be {@literal null}. - * @param type must not be {@literal null}. - * @param - * @return can be {@literal null} if the {@link SearchHit} does not have {@link SearchHit#hasSource() a source}. - * @since 3.2 - */ - @Nullable - default T mapSearchHit(SearchHit searchHit, Class type) { - return mapDocument(DocumentAdapters.from(searchHit), type); - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapper.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapper.java deleted file mode 100644 index 3d436a735..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapper.java +++ /dev/null @@ -1,44 +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 org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.search.SearchHit; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; -import org.springframework.lang.Nullable; - -/** - * @author Artur Konczak - * @author Petar Tahchiev - * @author Christoph Strobl - */ -public interface SearchResultMapper { - - AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable); - - /** - * Map a single {@link SearchHit} to the given {@link Class type}. - * - * @param searchHit must not be {@literal null}. - * @param type must not be {@literal null}. - * @param - * @return can be {@literal null}. - * @since 3.2 - */ - @Nullable - T mapSearchHit(SearchHit searchHit, Class type); -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapperAdapter.java b/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapperAdapter.java deleted file mode 100644 index 4ad8d9e44..000000000 --- a/src/main/java/org/springframework/data/elasticsearch/core/SearchResultMapperAdapter.java +++ /dev/null @@ -1,49 +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 org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.search.SearchHit; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; - -/** - * Adapter utility for {@link SearchResultMapper} that wish to implement a subset of mapping methods. Default - * implementations throw {@link UnsupportedOperationException}. - * - * @author Mark Paluch - * @since 3.2 - */ -abstract class SearchResultMapperAdapter implements SearchResultMapper { - - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapResults(org.elasticsearch.action.search.SearchResponse, java.lang.Class, org.springframework.data.domain.Pageable) - */ - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - throw new UnsupportedOperationException(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.elasticsearch.core.SearchResultMapper#mapSearchHit(org.elasticsearch.search.SearchHit, java.lang.Class) - */ - @Override - public T mapSearchHit(SearchHit searchHit, Class type) { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/aggregation/impl/AggregatedPageImpl.java b/src/main/java/org/springframework/data/elasticsearch/core/aggregation/impl/AggregatedPageImpl.java index de5fdd159..957e9e4d8 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/aggregation/impl/AggregatedPageImpl.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/aggregation/impl/AggregatedPageImpl.java @@ -22,12 +22,14 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.FacetedPageImpl; import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; /** * @author Petar Tahchiev * @author Artur Konczak * @author Mohsin Husen * @author Sascha Woo + * @author Peter-Josef Meisch */ public class AggregatedPageImpl extends FacetedPageImpl implements AggregatedPage { @@ -95,6 +97,11 @@ public AggregatedPageImpl(List content, Pageable pageable, long total, Aggreg this.maxScore = maxScore; } + public AggregatedPageImpl(List content, Pageable pageable, SearchDocumentResponse response) { + this(content, pageable, response.getTotalHits(), response.getAggregations(), response.getScrollId(), + response.getMaxScore()); + } + @Override public boolean hasAggregations() { return aggregations != null; 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 f1b40a171..9d2074e6a 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,10 +15,18 @@ */ package org.springframework.data.elasticsearch.core.convert; +import java.util.List; + import org.springframework.data.convert.EntityConverter; -import org.springframework.data.elasticsearch.Document; +import org.springframework.data.domain.Pageable; +import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; +import org.springframework.data.projection.ProjectionFactory; +import org.springframework.data.projection.SpelAwareProxyProjectionFactory; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -48,4 +56,47 @@ default String convertId(Object idValue) { return getConversionService().convert(idValue, String.class); } + + AggregatedPage mapResults(SearchDocumentResponse response, Class clazz, Pageable pageable); + + /** + * Get the configured {@link ProjectionFactory}.
+ * NOTE Should be overwritten in implementation to make use of the type cache. + * + * @since 3.2 + */ + default ProjectionFactory getProjectionFactory() { + return new SpelAwareProxyProjectionFactory(); + } + + /** + * Map a single {@link Document} to an instance of the given type. + * + * @param document must not be {@literal null}. + * @param type must not be {@literal null}. + * @param + * @return can be {@literal null} if the {@link Document#isEmpty()} is true. + * @since 4.0 + */ + @Nullable + T mapDocument(Document document, Class type); + + /** + * Map a list of {@link Document}s to alist of instance of the given type. + * + * @param documents must not be {@literal null}. + * @param type must not be {@literal null}. + * @param + * @return a list obtained by calling {@link #mapDocument(Document, Class)} on the elements of the list. + * @since 4.0 + */ + List mapDocuments(List documents, Class type); + + /** + * Map an object to a {@link Document}. + * + * @param source + * @return will not be {@literal null}. + */ + Document mapObject(Object source); } 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 802033e58..715f59d52 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 @@ -17,9 +17,18 @@ import lombok.RequiredArgsConstructor; -import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.springframework.beans.BeansException; import org.springframework.beans.factory.InitializingBean; @@ -32,9 +41,14 @@ 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.domain.Pageable; +import org.springframework.data.elasticsearch.ElasticsearchException; +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.document.Document; +import org.springframework.data.elasticsearch.core.document.SearchDocument; +import org.springframework.data.elasticsearch.core.document.SearchDocumentResponse; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; @@ -51,9 +65,6 @@ import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; - /** * Elasticsearch specific {@link org.springframework.data.convert.EntityConverter} implementation based on domain type * {@link ElasticsearchPersistentEntity metadata}. @@ -66,11 +77,10 @@ * @since 3.2 */ public class MappingElasticsearchConverter - implements ElasticsearchConverter, EntityMapper, ApplicationContextAware, InitializingBean { + implements ElasticsearchConverter, ApplicationContextAware, InitializingBean { private final MappingContext, ElasticsearchPersistentProperty> mappingContext; private final GenericConversionService conversionService; - private final ObjectReader objectReader; private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList()); private EntityInstantiators instantiators = new EntityInstantiators(); @@ -91,9 +101,6 @@ public MappingElasticsearchConverter( 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 @@ -103,8 +110,6 @@ public void setApplicationContext(ApplicationContext applicationContext) throws } } - // --> GETTERS / SETTERS - @Override public MappingContext, ElasticsearchPersistentProperty> getMappingContext() { return mappingContext; @@ -145,8 +150,6 @@ public void afterPropertiesSet() { conversions.registerConvertersIn(conversionService); } - // --> READ - @SuppressWarnings("unchecked") @Override @Nullable @@ -156,7 +159,7 @@ public R read(Class type, Document source) { @SuppressWarnings("unchecked") @Nullable - protected R doRead(Map source, TypeInformation typeHint) { + protected R doRead(Document source, TypeInformation typeHint) { if (source == null) { return null; @@ -189,8 +192,48 @@ protected R readEntity(ElasticsearchPersistentEntity entity, Map(targetEntity, propertyValueProvider, null)); - return targetEntity.requiresPropertyPopulation() ? readProperties(targetEntity, instance, propertyValueProvider) - : instance; + if (!targetEntity.requiresPropertyPopulation()) { + return instance; + } + + R result = readProperties(targetEntity, instance, propertyValueProvider); + + if (source instanceof Document) { + Document document = (Document) source; + if (document.hasId()) { + ElasticsearchPersistentProperty idProperty = targetEntity.getIdProperty(); + PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>( + targetEntity.getPropertyAccessor(result), conversionService); + // Only deal with String because ES generated Ids are strings ! + if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) { + accessor.setProperty(idProperty, document.getId()); + } + } + + if (document.hasVersion()) { + long version = document.getVersion(); + ElasticsearchPersistentProperty versionProperty = targetEntity.getVersionProperty(); + // Only deal with Long because ES versions are longs ! + if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) { + // check that a version was actually returned in the response, -1 would indicate that + // a search didn't request the version ids in the response, which would be an issue + Assert.isTrue(version != -1, "Version in response is -1"); + targetEntity.getPropertyAccessor(result).setProperty(versionProperty, version); + } + } + } + + if (source instanceof SearchDocument) { + SearchDocument searchDocument = (SearchDocument) source; + if (targetEntity.hasScoreProperty()) { + targetEntity.getPropertyAccessor(result) // + .setProperty(targetEntity.getScoreProperty(), searchDocument.getScore()); + } + populateScriptFields(result, searchDocument); + } + + return result; + } protected R readProperties(ElasticsearchPersistentEntity entity, R instance, @@ -350,8 +393,6 @@ public void write(@Nullable Object source, Document sink) { doWrite(source, sink, type); } - // --> WRITE - protected void doWrite(@Nullable Object source, Document sink, @Nullable TypeInformation typeHint) { if (source == null) { @@ -546,11 +587,24 @@ private Object writeCollectionValue(Object value, ElasticsearchPersistentPropert } @Override - public T mapToObject(String source, Class clazz) throws IOException { - return read(clazz, Document.from(objectReader.readValue(source))); + @Nullable + public T mapDocument(Document document, Class type) { + + Object mappedResult = read(type, document); + + if (mappedResult == null) { + return (T) null; + } + + return type.isInterface() || !ClassUtils.isAssignableValue(type, mappedResult) + ? getProjectionFactory().createProjection(type, mappedResult) + : type.cast(mappedResult); } - // --> LEGACY + @Override + public List mapDocuments(List documents, Class type) { + return documents.stream().map(it -> mapDocument(it, type)).collect(Collectors.toList()); + } @Override public Document mapObject(Object source) { @@ -560,13 +614,6 @@ public Document mapObject(Object source) { 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) { @@ -649,7 +696,37 @@ private boolean isSimpleType(Class type) { return conversions.isSimpleType(type); } - // --> OHTER STUFF + @Override + public AggregatedPage mapResults(SearchDocumentResponse response, Class type, Pageable pageable) { + + List results = response.getSearchDocuments().stream() // + .map(searchDocument -> mapDocument(searchDocument, type)).collect(Collectors.toList()); + + return new AggregatedPageImpl(results, pageable, response); + } + + private void populateScriptFields(T result, SearchDocument searchDocument) { + Map> fields = searchDocument.getFields(); + if (!fields.isEmpty()) { + for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) { + ScriptedField scriptedField = field.getAnnotation(ScriptedField.class); + if (scriptedField != null) { + String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name(); + Object value = searchDocument.getFieldValue(name); + if (value != null) { + field.setAccessible(true); + try { + field.set(result, value); + } catch (IllegalArgumentException e) { + throw new ElasticsearchException("failed to set scripted field: " + name + " with value: " + value, e); + } catch (IllegalAccessException e) { + throw new ElasticsearchException("failed to access scripted field: " + name, e); + } + } + } + } + } + } static class MapValueAccessor { diff --git a/src/main/java/org/springframework/data/elasticsearch/Document.java b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java similarity index 99% rename from src/main/java/org/springframework/data/elasticsearch/Document.java rename to src/main/java/org/springframework/data/elasticsearch/core/document/Document.java index 5e68afebd..e1aaa0594 100644 --- a/src/main/java/org/springframework/data/elasticsearch/Document.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/Document.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch; +package org.springframework.data.elasticsearch.core.document; import java.io.IOException; import java.util.LinkedHashMap; @@ -24,6 +24,7 @@ import java.util.function.LongSupplier; import java.util.function.Supplier; +import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.lang.Nullable; import org.springframework.util.Assert; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/DocumentAdapters.java b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java similarity index 81% rename from src/main/java/org/springframework/data/elasticsearch/core/DocumentAdapters.java rename to src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java index be965a8a6..bb2799c7b 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/DocumentAdapters.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java @@ -13,13 +13,15 @@ * 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.document; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -29,13 +31,12 @@ import java.util.stream.Collectors; import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.document.DocumentField; import org.elasticsearch.index.get.GetResult; import org.elasticsearch.search.SearchHit; -import org.springframework.data.elasticsearch.Document; import org.springframework.data.elasticsearch.ElasticsearchException; -import org.springframework.data.elasticsearch.SearchDocument; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -45,13 +46,15 @@ /** * Utility class to adapt {@link org.elasticsearch.action.get.GetResponse}, - * {@link org.elasticsearch.index.get.GetResult}, {@link org.elasticsearch.search.SearchHit}, - * {@link org.elasticsearch.common.document.DocumentField} to {@link org.springframework.data.elasticsearch.Document}. + * {@link org.elasticsearch.index.get.GetResult}, {@link org.elasticsearch.action.get.MultiGetResponse} + * {@link org.elasticsearch.search.SearchHit}, {@link org.elasticsearch.common.document.DocumentField} to + * {@link Document}. * * @author Mark Paluch + * @author Peter-Josef Meisch * @since 4.0 */ -class DocumentAdapters { +public class DocumentAdapters { /** * Create a {@link Document} from {@link GetResponse}. @@ -59,12 +62,17 @@ class DocumentAdapters { * Returns a {@link Document} using the source if available. * * @param source the source {@link GetResponse}. - * @return the adapted {@link Document}. + * @return the adapted {@link Document}, null if source.isExists() returns false. */ + @Nullable public static Document from(GetResponse source) { Assert.notNull(source, "GetResponse must not be null"); + if (!source.isExists()) { + return null; + } + if (source.isSourceEmpty()) { return fromDocumentFields(source, source.getId(), source.getVersion()); } @@ -82,12 +90,17 @@ public static Document from(GetResponse source) { * Returns a {@link Document} using the source if available. * * @param source the source {@link GetResult}. - * @return the adapted {@link Document}. + * @return the adapted {@link Document}, null if source.isExists() returns false. */ + @Nullable public static Document from(GetResult source) { Assert.notNull(source, "GetResult must not be null"); + if (!source.isExists()) { + return null; + } + if (source.isSourceEmpty()) { return fromDocumentFields(source, source.getId(), source.getVersion()); } @@ -99,6 +112,21 @@ public static Document from(GetResult source) { return document; } + /** + * Creates a List of {@link Document}s from {@link MultiGetResponse}. + * + * @param source the source {@link MultiGetResponse}, not {@literal null}. + * @return a possibly empty list of the Documents. + */ + public static List from(MultiGetResponse source) { + + Assert.notNull(source, "MultiGetResponse must not be null"); + + return Arrays.stream(source.getResponses()) // + .map(itemResponse -> itemResponse.isFailed() ? null : DocumentAdapters.from(itemResponse.getResponse())) // + .filter(Objects::nonNull).collect(Collectors.toList()); + } + /** * Create a {@link SearchDocument} from {@link SearchHit}. *

@@ -114,7 +142,7 @@ public static SearchDocument from(SearchHit source) { BytesReference sourceRef = source.getSourceRef(); if (sourceRef == null || sourceRef.length() == 0) { - return new SearchDocumentAdapter(source.getScore(), + return new SearchDocumentAdapter(source.getScore(), source.getFields(), fromDocumentFields(source, source.getId(), source.getVersion())); } @@ -125,7 +153,7 @@ public static SearchDocument from(SearchHit source) { document.setVersion(source.getVersion()); } - return new SearchDocumentAdapter(source.getScore(), document); + return new SearchDocumentAdapter(source.getScore(), source.getFields(), document); } /** @@ -163,7 +191,7 @@ static class DocumentFieldAdapter implements Document { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasId() + * @see org.springframework.data.elasticsearch.core.document.Document#hasId() */ @Override public boolean hasId() { @@ -172,7 +200,7 @@ public boolean hasId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getId() + * @see org.springframework.data.elasticsearch.core.document.Document#getId() */ @Override public String getId() { @@ -181,7 +209,7 @@ public String getId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#hasVersion() */ @Override public boolean hasVersion() { @@ -190,7 +218,7 @@ public boolean hasVersion() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#getVersion() */ @Override public long getVersion() { @@ -351,7 +379,7 @@ public void forEach(BiConsumer action) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#toJson() + * @see org.springframework.data.elasticsearch.core.document.Document#toJson() */ @Override public String toJson() { @@ -410,16 +438,18 @@ private static Object getValue(DocumentField documentField) { static class SearchDocumentAdapter implements SearchDocument { private final float score; + private final Map> fields = new HashMap<>(); private final Document delegate; - SearchDocumentAdapter(float score, Document delegate) { + SearchDocumentAdapter(float score, Map fields, Document delegate) { this.score = score; this.delegate = delegate; + fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues())); } /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#append(java.lang.String, java.lang.Object) + * @see org.springframework.data.elasticsearch.core.document.Document#append(java.lang.String, java.lang.Object) */ @Override public SearchDocument append(String key, Object value) { @@ -430,7 +460,7 @@ public SearchDocument append(String key, Object value) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.SearchDocument#getScore() + * @see org.springframework.data.elasticsearch.core.document.SearchDocument#getScore() */ @Override public float getScore() { @@ -439,7 +469,16 @@ public float getScore() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasId() + * @see org.springframework.data.elasticsearch.core.document.SearchDocument#getFields() + */ + @Override + public Map> getFields() { + return fields; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.elasticsearch.core.document.Document#hasId() */ @Override public boolean hasId() { @@ -448,7 +487,7 @@ public boolean hasId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getId() + * @see org.springframework.data.elasticsearch.core.document.Document#getId() */ @Override public String getId() { @@ -457,7 +496,7 @@ public String getId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#setId(java.lang.String) + * @see org.springframework.data.elasticsearch.core.document.Document#setId(java.lang.String) */ @Override public void setId(String id) { @@ -466,7 +505,7 @@ public void setId(String id) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#hasVersion() */ @Override public boolean hasVersion() { @@ -475,7 +514,7 @@ public boolean hasVersion() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#getVersion() */ @Override public long getVersion() { @@ -484,7 +523,7 @@ public long getVersion() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#setVersion(long) + * @see org.springframework.data.elasticsearch.core.document.Document#setVersion(long) */ @Override public void setVersion(long version) { @@ -493,7 +532,7 @@ public void setVersion(long version) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#get(java.lang.Object, java.lang.Class) + * @see org.springframework.data.elasticsearch.core.document.Document#get(java.lang.Object, java.lang.Class) */ @Override @Nullable @@ -503,7 +542,7 @@ public T get(Object key, Class type) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#toJson() + * @see org.springframework.data.elasticsearch.core.document.Document#toJson() */ @Override public String toJson() { diff --git a/src/main/java/org/springframework/data/elasticsearch/MapDocument.java b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java similarity index 88% rename from src/main/java/org/springframework/data/elasticsearch/MapDocument.java rename to src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java index 317a1894d..78ad9c08f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/MapDocument.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/MapDocument.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch; +package org.springframework.data.elasticsearch.core.document; import java.util.Collection; import java.util.LinkedHashMap; @@ -21,6 +21,7 @@ import java.util.Set; import java.util.function.BiConsumer; +import org.springframework.data.elasticsearch.ElasticsearchException; import org.springframework.lang.Nullable; import com.fasterxml.jackson.core.JsonProcessingException; @@ -51,7 +52,7 @@ class MapDocument implements Document { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasId() + * @see org.springframework.data.elasticsearch.core.document.Document#hasId() */ @Override public boolean hasId() { @@ -60,7 +61,7 @@ public boolean hasId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getId() + * @see org.springframework.data.elasticsearch.core.document.Document#getId() */ @Override public String getId() { @@ -74,7 +75,7 @@ public String getId() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#setId(java.lang.String) + * @see org.springframework.data.elasticsearch.core.document.Document#setId(java.lang.String) */ @Override public void setId(String id) { @@ -83,7 +84,7 @@ public void setId(String id) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#hasVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#hasVersion() */ @Override public boolean hasVersion() { @@ -92,7 +93,7 @@ public boolean hasVersion() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#getVersion() + * @see org.springframework.data.elasticsearch.core.document.Document#getVersion() */ @Override public long getVersion() { @@ -106,7 +107,7 @@ public long getVersion() { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#setVersion(long) + * @see org.springframework.data.elasticsearch.core.document.Document#setVersion(long) */ @Override public void setVersion(long version) { @@ -259,7 +260,7 @@ public void forEach(BiConsumer action) { /* * (non-Javadoc) - * @see org.springframework.data.elasticsearch.Document#toJson() + * @see org.springframework.data.elasticsearch.core.document.Document#toJson() */ @Override public String toJson() { diff --git a/src/main/java/org/springframework/data/elasticsearch/SearchDocument.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java similarity index 63% rename from src/main/java/org/springframework/data/elasticsearch/SearchDocument.java rename to src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java index 3ac4b9eb4..c5fc6928f 100644 --- a/src/main/java/org/springframework/data/elasticsearch/SearchDocument.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocument.java @@ -13,12 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.data.elasticsearch; +package org.springframework.data.elasticsearch.core.document; + +import java.util.List; +import java.util.Map; /** * Extension to {@link Document} exposing a search {@link #getScore() score}. * * @author Mark Paluch + * @author Peter-Josef Meisch * @since 4.0 * @see Document */ @@ -30,4 +34,22 @@ public interface SearchDocument extends Document { * @return the search {@code score}. */ float getScore(); + + /** + * @return the fields for the search result, not {@literal null} + */ + Map> getFields(); + + /** + * The first value of the given field. + * + * @param name the field name + */ + default V getFieldValue(final String name) { + List values = getFields().get(name); + if (values == null || values.isEmpty()) { + return null; + } + return (V) values.get(0); + } } diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java new file mode 100644 index 000000000..fd1b0e714 --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/SearchDocumentResponse.java @@ -0,0 +1,92 @@ +/* + * 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.document; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.search.aggregations.Aggregations; +import org.springframework.data.elasticsearch.support.SearchHitsUtil; +import org.springframework.util.Assert; + +/** + * This represents the complete search response from Elasticsearch, including the returned documents. Instances must be + * created with the {@link #from(org.elasticsearch.action.search.SearchResponse)} method. + * + * @author Peter-Josef Meisch + * @since 4.0 + */ +public class SearchDocumentResponse { + + private long totalHits; + private float maxScore; + private final String scrollId; + private final List searchDocuments; + private final Aggregations aggregations; + + private SearchDocumentResponse(long totalHits, float maxScore, String scrollId, List searchDocuments, + Aggregations aggregations) { + this.totalHits = totalHits; + this.maxScore = maxScore; + this.scrollId = scrollId; + this.searchDocuments = searchDocuments; + this.aggregations = aggregations; + } + + public long getTotalHits() { + return totalHits; + } + + public float getMaxScore() { + return maxScore; + } + + public String getScrollId() { + return scrollId; + } + + public List getSearchDocuments() { + return searchDocuments; + } + + public Aggregations getAggregations() { + return aggregations; + } + + /** + * creates a SearchDocumentResponse from the {@link org.elasticsearch.action.search.SearchResponse} + * + * @param searchResponse must not be {@literal null} + * @return + */ + public static SearchDocumentResponse from(SearchResponse searchResponse) { + Assert.notNull(searchResponse, "searchResponse must not be null"); + + long totalHits = SearchHitsUtil.getTotalCount(searchResponse.getHits()); + float maxScore = searchResponse.getHits().getMaxScore(); + String scrollId = searchResponse.getScrollId(); + List searchDocuments = StreamSupport.stream(searchResponse.getHits().spliterator(), false) // + .filter(Objects::nonNull) // + .map(DocumentAdapters::from) // + .collect(Collectors.toList()); + Aggregations aggregations = searchResponse.getAggregations(); + + return new SearchDocumentResponse(totalHits, maxScore, scrollId, searchDocuments, aggregations); + } +} diff --git a/src/main/java/org/springframework/data/elasticsearch/core/document/package-info.java b/src/main/java/org/springframework/data/elasticsearch/core/document/package-info.java new file mode 100644 index 000000000..80eaa33be --- /dev/null +++ b/src/main/java/org/springframework/data/elasticsearch/core/document/package-info.java @@ -0,0 +1,5 @@ +/** + * interfaces and classes related to the Document representation of Elasticsearch documents. + */ +@org.springframework.lang.NonNullApi +package org.springframework.data.elasticsearch.core.document; diff --git a/src/main/java/org/springframework/data/elasticsearch/core/mapping/package-info.java b/src/main/java/org/springframework/data/elasticsearch/core/mapping/package-info.java index 5a5dac8c0..c8e7cb653 100644 --- a/src/main/java/org/springframework/data/elasticsearch/core/mapping/package-info.java +++ b/src/main/java/org/springframework/data/elasticsearch/core/mapping/package-info.java @@ -1,4 +1,3 @@ - /** * Infrastructure for the Elasticsearch document-to-object mapping subsystem. */ diff --git a/src/test/java/org/springframework/data/elasticsearch/DocumentUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/DocumentUnitTests.java index eb2717a32..8626600d4 100644 --- a/src/test/java/org/springframework/data/elasticsearch/DocumentUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/DocumentUnitTests.java @@ -21,6 +21,7 @@ import java.util.Collections; import org.junit.Test; +import org.springframework.data.elasticsearch.core.document.Document; /** * Unit tests for {@link Document}. diff --git a/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java b/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java index 172c305e3..b9c9529f0 100644 --- a/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java +++ b/src/test/java/org/springframework/data/elasticsearch/ElasticsearchTestConfiguration.java @@ -21,12 +21,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.elasticsearch.config.ElasticsearchConfigurationSupport; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; +import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter; import org.springframework.data.elasticsearch.junit.junit4.TestNodeResource; /** - * configuration class for the classic ElasticsearchTemplate. Needs a {@link TestNodeResource} bean that should be set up in - * the test as ClassRule and exported as bean. + * configuration class for the classic ElasticsearchTemplate. Needs a {@link TestNodeResource} bean that should be set + * up in the test as ClassRule and exported as bean. * * @author Peter-Josef Meisch */ @@ -41,8 +41,9 @@ public Client elasticsearchClient() { } @Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" }) - public ElasticsearchTemplate elasticsearchTemplate(Client elasticsearchClient, MappingElasticsearchConverter entityMapper) { - return new ElasticsearchTemplate(elasticsearchClient, entityMapper); + public ElasticsearchTemplate elasticsearchTemplate(Client elasticsearchClient, + ElasticsearchConverter elasticsearchConverter) { + return new ElasticsearchTemplate(elasticsearchClient, elasticsearchConverter); } } diff --git a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientTests.java b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientTests.java index 74c8c5a26..a35c0095f 100644 --- a/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/client/reactive/ReactiveElasticsearchClientTests.java @@ -18,6 +18,8 @@ import static org.assertj.core.api.Assertions.*; import lombok.SneakyThrows; +import org.junit.ClassRule; +import org.springframework.data.elasticsearch.junit.junit4.TestNodeResource; import reactor.test.StepVerifier; import java.io.IOException; @@ -68,9 +70,11 @@ * @author Peter-Josef Meisch */ @RunWith(SpringRunner.class) -@ContextConfiguration("classpath:infrastructure.xml") public class ReactiveElasticsearchClientTests { + @ClassRule + public static TestNodeResource testNodeResource = new TestNodeResource(); + public @Rule ElasticsearchVersionRule elasticsearchVersion = ElasticsearchVersionRule.any(); static final String INDEX_I = "idx-1-reactive-client-tests"; 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 1812731ed..9494f505d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/config/ElasticsearchConfigurationSupportUnitTests.java @@ -30,10 +30,8 @@ import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient; 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; /** @@ -102,13 +100,6 @@ public void reactiveConfigContainsReactiveElasticsearchTemplate() { assertThat(context.getBean(ReactiveElasticsearchTemplate.class)).isNotNull(); } - @Test // DATAES-530 - public void usesConfiguredEntityMapper() { - - AbstractApplicationContext context = new AnnotationConfigApplicationContext(EntityMapperConfig.class); - assertThat(context.getBean(EntityMapper.class)).isInstanceOf(MappingElasticsearchConverter.class); - } - @Configuration static class StubConfig extends ElasticsearchConfigurationSupport { diff --git a/src/test/java/org/springframework/data/elasticsearch/core/CustomResultMapper.java b/src/test/java/org/springframework/data/elasticsearch/core/CustomResultMapper.java deleted file mode 100644 index 377a557b9..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/CustomResultMapper.java +++ /dev/null @@ -1,58 +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.util.List; - -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.get.MultiGetResponse; -import org.elasticsearch.action.search.SearchResponse; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; - -/** - * @author Artur Konczak - * @author Mohsin Husen - */ -public class CustomResultMapper implements ResultsMapper { - - private EntityMapper entityMapper; - - public CustomResultMapper(EntityMapper entityMapper) { - this.entityMapper = entityMapper; - } - - @Override - public EntityMapper getEntityMapper() { - return entityMapper; - } - - @Override - public T mapResult(GetResponse response, Class clazz) { - return null; // To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - return null; // To change body of implemented methods use File | Settings | File Templates. - } - - @Override - public List mapResults(MultiGetResponse responses, Class clazz) { - return null; - } -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java deleted file mode 100644 index 246799045..000000000 --- a/src/test/java/org/springframework/data/elasticsearch/core/DefaultResultMapperTests.java +++ /dev/null @@ -1,336 +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 java.util.Arrays.*; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; -import static org.springframework.data.elasticsearch.annotations.FieldType.*; - -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.lang.Double; -import java.lang.Long; -import java.lang.Object; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.apache.lucene.search.TotalHits; -import org.elasticsearch.action.get.GetResponse; -import org.elasticsearch.action.get.MultiGetItemResponse; -import org.elasticsearch.action.get.MultiGetResponse; -import org.elasticsearch.action.search.SearchResponse; -import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.document.DocumentField; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.SearchHits; -import org.elasticsearch.search.aggregations.Aggregation; -import org.elasticsearch.search.aggregations.Aggregations; -import org.elasticsearch.search.aggregations.bucket.terms.Terms; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.Version; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.elasticsearch.annotations.Document; -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; - -import com.fasterxml.jackson.databind.util.ArrayIterator; - -/** - * @author Artur Konczak - * @author Mohsin Husen - * @author Chris White - * @author Mark Paluch - * @author Ilkang Na - * @author Christoph Strobl - * @author Peter-Josef Meisch - */ -public class DefaultResultMapperTests { - - private SimpleElasticsearchMappingContext context = new SimpleElasticsearchMappingContext(); - private EntityMapper entityMapper = new MappingElasticsearchConverter(context); - private DefaultResultMapper resultMapper = new DefaultResultMapper(context, entityMapper); - - private SearchResponse response = mock(SearchResponse.class); - - @Test - public void shouldMapAggregationsToPage() { - - // given - SearchHit[] hits = { createCarHit("Ford", "Grat"), createCarHit("BMW", "Arrow") }; - SearchHits searchHits = mock(SearchHits.class); - when(searchHits.getTotalHits()).thenReturn(new TotalHits(2L, TotalHits.Relation.EQUAL_TO)); - when(searchHits.iterator()).thenReturn(new ArrayIterator(hits)); - when(response.getHits()).thenReturn(searchHits); - - Aggregations aggregations = new Aggregations(asList(createCarAggregation())); - when(response.getAggregations()).thenReturn(aggregations); - - // when - AggregatedPage page = resultMapper.mapResults(response, Car.class, Pageable.unpaged()); - - // then - page.hasFacets(); - assertThat(page.hasAggregations()).isTrue(); - assertThat(page.getAggregation("Diesel").getName()).isEqualTo("Diesel"); - } - - @Test - public void shouldMapSearchRequestToPage() { - - // given - SearchHit[] hits = { createCarHit("Ford", "Grat"), createCarHit("BMW", "Arrow") }; - SearchHits searchHits = mock(SearchHits.class); - when(searchHits.getTotalHits()).thenReturn(new TotalHits(2L, TotalHits.Relation.EQUAL_TO)); - when(searchHits.iterator()).thenReturn(new ArrayIterator(hits)); - when(response.getHits()).thenReturn(searchHits); - - // when - Page page = resultMapper.mapResults(response, Car.class, Pageable.unpaged()); - - // then - assertThat(page.hasContent()).isTrue(); - assertThat(page.getTotalElements()).isEqualTo(2); - assertThat(page.getContent().get(0).getName()).isEqualTo("Ford"); - } - - @Test - public void shouldMapPartialSearchRequestToObject() { - - // given - SearchHit[] hits = { createCarPartialHit("Ford", "Grat"), createCarPartialHit("BMW", "Arrow") }; - SearchHits searchHits = mock(SearchHits.class); - when(searchHits.getTotalHits()).thenReturn(new TotalHits(2L, TotalHits.Relation.EQUAL_TO)); - when(searchHits.iterator()).thenReturn(new ArrayIterator(hits)); - when(response.getHits()).thenReturn(searchHits); - - // when - Page page = resultMapper.mapResults(response, Car.class, Pageable.unpaged()); - - // then - assertThat(page.hasContent()).isTrue(); - assertThat(page.getTotalElements()).isEqualTo(2); - assertThat(page.getContent().get(0).getName()).isEqualTo("Ford"); - } - - @Test - public void shouldMapGetRequestToObject() { - - // given - GetResponse response = mock(GetResponse.class); - when(response.isExists()).thenReturn(true); - - Map sourceAsMap = new HashMap<>(); - sourceAsMap.put("name", "Ford"); - sourceAsMap.put("model", "Grat"); - - when(response.getSourceAsMap()).thenReturn(sourceAsMap); - when(response.getSourceAsBytesRef()).thenReturn(new BytesArray(" ")); - - // when - Car result = resultMapper.mapResult(response, Car.class); - - // then - assertThat(result).isNotNull(); - assertThat(result.getModel()).isEqualTo("Grat"); - assertThat(result.getName()).isEqualTo("Ford"); - } - - @Test // DATAES-281 - @Ignore("fix me - UnsupportedOperation") - public void setsIdentifierOnImmutableType() { - - GetResponse response = mock(GetResponse.class); - when(response.isExists()).thenReturn(true); - when(response.getSourceAsString()).thenReturn("{}"); - when(response.getSourceAsBytesRef()).thenReturn(new BytesArray("{}")); - when(response.getId()).thenReturn("identifier"); - - ImmutableEntity result = resultMapper.mapResult(response, ImmutableEntity.class); - - assertThat(result).isNotNull(); - assertThat(result.getId()).isEqualTo("identifier"); - } - - @Test // DATAES-198 - public void setsVersionFromGetResponse() { - - GetResponse response = mock(GetResponse.class); - when(response.isExists()).thenReturn(true); - when(response.getSourceAsString()).thenReturn("{}"); - when(response.getVersion()).thenReturn(1234L); - - MappedEntity result = resultMapper.mapResult(response, MappedEntity.class); - - assertThat(result).isNotNull(); - assertThat(result.getVersion()).isEqualTo(1234); - } - - @Test // DATAES-198 - public void setsVersionFromMultiGetResponse() { - - GetResponse response1 = mock(GetResponse.class); - when(response1.isExists()).thenReturn(true); - when(response1.getSourceAsString()).thenReturn("{}"); - when(response1.getSourceAsBytesRef()).thenReturn(new BytesArray("{}")); - when(response1.isExists()).thenReturn(true); - when(response1.getVersion()).thenReturn(1234L); - - GetResponse response2 = mock(GetResponse.class); - when(response2.isExists()).thenReturn(true); - when(response2.getSourceAsString()).thenReturn("{}"); - when(response2.getSourceAsBytesRef()).thenReturn(new BytesArray("{}")); - when(response2.isExists()).thenReturn(true); - when(response2.getVersion()).thenReturn(5678L); - - MultiGetResponse multiResponse = mock(MultiGetResponse.class); - when(multiResponse.getResponses()).thenReturn(new MultiGetItemResponse[] { - new MultiGetItemResponse(response1, null), new MultiGetItemResponse(response2, null) }); - - List results = resultMapper.mapResults(multiResponse, MappedEntity.class); - - assertThat(results).isNotNull().hasSize(2); - - assertThat(results.get(0).getVersion()).isEqualTo(1234); - assertThat(results.get(1).getVersion()).isEqualTo(5678); - } - - @Test // DATAES-198 - public void setsVersionFromSearchResponse() { - - SearchHit hit1 = mock(SearchHit.class); - when(hit1.getSourceRef()).thenReturn(new BytesArray("{}")); - when(hit1.getVersion()).thenReturn(1234L); - - SearchHit hit2 = mock(SearchHit.class); - when(hit2.getSourceRef()).thenReturn(new BytesArray("{}")); - when(hit2.getVersion()).thenReturn(5678L); - - SearchHits searchHits = mock(SearchHits.class); - when(searchHits.getTotalHits()).thenReturn(new TotalHits(2L, TotalHits.Relation.EQUAL_TO)); - when(searchHits.iterator()).thenReturn(Arrays.asList(hit1, hit2).iterator()); - - SearchResponse searchResponse = mock(SearchResponse.class); - when(searchResponse.getHits()).thenReturn(searchHits); - - AggregatedPage results = resultMapper.mapResults(searchResponse, MappedEntity.class, - mock(Pageable.class)); - - assertThat(results).isNotNull(); - - assertThat(results.getContent().get(0).getVersion()).isEqualTo(1234); - assertThat(results.getContent().get(1).getVersion()).isEqualTo(5678); - } - - private Aggregation createCarAggregation() { - - Aggregation aggregation = mock(Terms.class); - when(aggregation.getName()).thenReturn("Diesel"); - return aggregation; - } - - private SearchHit createCarHit(String name, String model) { - - SearchHit hit = mock(SearchHit.class); - String json = createJsonCar(name, model); - when(hit.getSourceAsString()).thenReturn(json); - when(hit.getSourceRef()).thenReturn(new BytesArray(json)); - Map map = new LinkedHashMap<>(); - map.put("name", name); - map.put("model", model); - when(hit.getSourceAsMap()).thenReturn(map); - return hit; - } - - private SearchHit createCarPartialHit(String name, String model) { - - SearchHit hit = mock(SearchHit.class); - when(hit.getSourceAsString()).thenReturn(null); - when(hit.getFields()).thenReturn(createCarFields(name, model)); - when(hit.iterator()).thenReturn(createCarFields(name, model).values().iterator()); - return hit; - } - - private String createJsonCar(String name, String model) { - - String q = "\""; - StringBuffer sb = new StringBuffer(); - sb.append("{").append(q).append("name").append(q).append(":").append(q).append(name).append(q).append(","); - sb.append(q).append("model").append(q).append(":").append(q).append(model).append(q).append("}"); - return sb.toString(); - } - - private Map createCarFields(String name, String model) { - - Map result = new HashMap<>(); - result.put("name", new DocumentField("name", asList(name))); - result.put("model", new DocumentField("model", asList(model))); - return result; - } - - @Document(indexName = "test-index-immutable-internal") - @NoArgsConstructor(force = true) - @Getter - static class ImmutableEntity { - - private final String id, name; - } - - @Data - static class Car { - - private String name; - private String model; - } - - @Data - @Document(indexName = "test-index-sample-default-result-mapper", type = "test-type") - static class MappedEntity { - - @Id private String id; - @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; - private boolean available; - private String highlightedMessage; - private GeoPoint location; - @Version private Long version; - @Score private float score; - } - -} diff --git a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java index 5417e88cc..0f20ef44d 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/DocumentAdaptersUnitTests.java @@ -29,8 +29,9 @@ import org.elasticsearch.index.get.GetResult; import org.elasticsearch.search.SearchHit; import org.junit.Test; -import org.springframework.data.elasticsearch.Document; -import org.springframework.data.elasticsearch.SearchDocument; +import org.springframework.data.elasticsearch.core.document.Document; +import org.springframework.data.elasticsearch.core.document.DocumentAdapters; +import org.springframework.data.elasticsearch.core.document.SearchDocument; /** * Unit tests for {@link DocumentAdapters}. diff --git a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java index cab2989c0..c4685c602 100644 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchRestTemplateTests.java @@ -23,6 +23,7 @@ import lombok.Data; import java.io.IOException; +import java.lang.Object; import java.util.HashMap; import java.util.Map; 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 5ca0da090..4ccf2e7e3 100755 --- a/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java +++ b/src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java @@ -35,20 +35,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; import org.assertj.core.util.Lists; -import org.elasticsearch.action.get.MultiGetItemResponse; -import org.elasticsearch.action.get.MultiGetResponse; import org.elasticsearch.action.index.IndexRequest; -import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateRequestBuilder; @@ -56,22 +50,17 @@ import org.elasticsearch.index.VersionType; import org.elasticsearch.script.Script; import org.elasticsearch.script.ScriptType; -import org.elasticsearch.search.SearchHit; -import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; -import org.elasticsearch.search.fetch.subphase.highlight.HighlightField; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Version; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; import org.springframework.data.elasticsearch.ElasticsearchException; @@ -83,18 +72,13 @@ 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.aggregation.impl.AggregatedPageImpl; -import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter; import org.springframework.data.elasticsearch.core.geo.GeoPoint; -import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.data.util.CloseableIterator; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.util.ReflectionTestUtils; /** - * Base for testing rest/transport templates + * Base for testing rest/transport templates. Contains the test common to both implementing classes. * * @author Rizwan Idrees * @author Mohsin Husen @@ -116,9 +100,7 @@ * @author Farid Azaza * @author Gyula Attila Csorogi */ -@RunWith(SpringRunner.class) -@ContextConfiguration("classpath:elasticsearch-template-test.xml") -public class ElasticsearchTemplateTests { +public abstract class ElasticsearchTemplateTests { private static final String INDEX_NAME_SAMPLE_ENTITY = "test-index-sample-core-template"; private static final String INDEX_1_NAME = "test-index-1"; @@ -126,32 +108,6 @@ public class ElasticsearchTemplateTests { private static final String INDEX_3_NAME = "test-index-3"; private static final String TYPE_NAME = "test-type"; - private final SearchResultMapper searchResultMapper = new SearchResultMapperAdapter() { - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - - List result = new ArrayList<>(); - for (SearchHit searchHit : response.getHits()) { - - if (response.getHits().getHits().length <= 0) { - return new AggregatedPageImpl(Collections.emptyList(), response.getScrollId()); - } - - String message = (String) searchHit.getSourceAsMap().get("message"); - SampleEntity sampleEntity = new SampleEntity(); - sampleEntity.setId(searchHit.getId()); - sampleEntity.setMessage(message); - result.add(sampleEntity); - } - - if (result.size() > 0) { - return new AggregatedPageImpl((List) result, response.getScrollId()); - } - - return new AggregatedPageImpl(Collections.emptyList(), response.getScrollId()); - } - }; - @Autowired protected ElasticsearchOperations elasticsearchTemplate; @Before @@ -296,21 +252,7 @@ public void shouldReturnObjectsForGivenIdsUsingMultiGetWithFields() { // when SearchQuery query = new NativeSearchQueryBuilder().withIds(Arrays.asList(documentId, documentId2)) .withFields("message", "type").build(); - List sampleEntities = elasticsearchTemplate.multiGet(query, SampleEntity.class, - new MultiGetResultMapper() { - @Override - public LinkedList mapResults(MultiGetResponse responses, Class clazz) { - LinkedList list = new LinkedList<>(); - for (MultiGetItemResponse response : responses.getResponses()) { - SampleEntity entity = new SampleEntity(); - entity.setId(response.getResponse().getId()); - entity.setMessage((String) response.getResponse().getSource().get("message")); - entity.setType((String) response.getResponse().getSource().get("type")); - list.add((T) entity); - } - return list; - } - }); + List sampleEntities = elasticsearchTemplate.multiGet(query, SampleEntity.class); // then assertThat(sampleEntities).hasSize(2); @@ -1012,8 +954,9 @@ public void shouldReturnSpecifiedFields() { // given String documentId = randomNumeric(5); String message = "some test message"; - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(message) - .version(System.currentTimeMillis()).build(); + String type = "some type"; + SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(message).type(type) + .version(System.currentTimeMillis()).location(new GeoPoint(1.2, 3.4)).build(); IndexQuery indexQuery = getIndexQuery(sampleEntity); @@ -1021,22 +964,17 @@ public void shouldReturnSpecifiedFields() { elasticsearchTemplate.refresh(SampleEntity.class); SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withIndices(INDEX_NAME_SAMPLE_ENTITY).withTypes(TYPE_NAME).withFields("message").build(); + // when - Page page = elasticsearchTemplate.queryForPage(searchQuery, String.class, new SearchResultMapperAdapter() { - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - List values = new ArrayList<>(); - for (SearchHit searchHit : response.getHits()) { - values.add((String) searchHit.getSourceAsMap().get("message")); - } - return new AggregatedPageImpl<>((List) values); - } - }); + Page page = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class); // then assertThat(page).isNotNull(); assertThat(page.getTotalElements()).isEqualTo(1); - assertThat(page.getContent().get(0)).isEqualTo(message); + final SampleEntity actual = page.getContent().get(0); + assertThat(actual.message).isEqualTo(message); + assertThat(actual.getType()).isNull(); + assertThat(actual.getLocation()).isNull(); } @Test @@ -1173,14 +1111,13 @@ public void shouldReturnResultsWithScanAndScrollForSpecifiedFieldsForCriteriaQue criteriaQuery.addFields("message"); criteriaQuery.setPageable(PageRequest.of(0, 10)); - ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, criteriaQuery, SampleEntity.class, - searchResultMapper); + ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, criteriaQuery, SampleEntity.class); String scrollId = scroll.getScrollId(); List sampleEntities = new ArrayList<>(); while (scroll.hasContent()) { sampleEntities.addAll(scroll.getContent()); scrollId = scroll.getScrollId(); - scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class, searchResultMapper); + scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class); } elasticsearchTemplate.clearScroll(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1201,14 +1138,13 @@ public void shouldReturnResultsWithScanAndScrollForSpecifiedFieldsForSearchCrite .withIndices(INDEX_NAME_SAMPLE_ENTITY).withTypes(TYPE_NAME).withFields("message").withQuery(matchAllQuery()) .withPageable(PageRequest.of(0, 10)).build(); - ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class, - searchResultMapper); + ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class); String scrollId = scroll.getScrollId(); List sampleEntities = new ArrayList<>(); while (scroll.hasContent()) { sampleEntities.addAll(scroll.getContent()); scrollId = scroll.getScrollId(); - scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class, searchResultMapper); + scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class); } elasticsearchTemplate.clearScroll(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1230,14 +1166,13 @@ public void shouldReturnResultsForScanAndScrollWithCustomResultMapperForGivenCri criteriaQuery.addTypes(TYPE_NAME); criteriaQuery.setPageable(PageRequest.of(0, 10)); - ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, criteriaQuery, SampleEntity.class, - searchResultMapper); + ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, criteriaQuery, SampleEntity.class); String scrollId = scroll.getScrollId(); List sampleEntities = new ArrayList<>(); while (scroll.hasContent()) { sampleEntities.addAll(scroll.getContent()); scrollId = scroll.getScrollId(); - scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class, searchResultMapper); + scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class); } elasticsearchTemplate.clearScroll(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1257,14 +1192,13 @@ public void shouldReturnResultsForScanAndScrollWithCustomResultMapperForGivenSea SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery()) .withIndices(INDEX_NAME_SAMPLE_ENTITY).withTypes(TYPE_NAME).withPageable(PageRequest.of(0, 10)).build(); - ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class, - searchResultMapper); + ScrolledPage scroll = elasticsearchTemplate.startScroll(1000, searchQuery, SampleEntity.class); String scrollId = scroll.getScrollId(); List sampleEntities = new ArrayList<>(); while (scroll.hasContent()) { sampleEntities.addAll(scroll.getContent()); scrollId = scroll.getScrollId(); - scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class, searchResultMapper); + scroll = elasticsearchTemplate.continueScroll(scrollId, 1000, SampleEntity.class); } elasticsearchTemplate.clearScroll(scrollId); assertThat(sampleEntities).hasSize(30); @@ -1555,167 +1489,6 @@ public void shouldDoUpsertIfDocumentDoesNotExist() { assertThat(indexedEntity.getMessage()).isEqualTo(message); } - @Test - public void shouldReturnHighlightedFieldsForGivenQueryAndFields() { - - // given - String documentId = randomNumeric(5); - String actualMessage = "some test message"; - String highlightedMessage = "some test message"; - - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).message(actualMessage) - .version(System.currentTimeMillis()).build(); - - IndexQuery indexQuery = getIndexQuery(sampleEntity); - - // when - elasticsearchTemplate.index(indexQuery); - elasticsearchTemplate.refresh(SampleEntity.class); - - List message = new HighlightBuilder().field("message").fields(); - SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(termQuery("message", "test")) - .withHighlightFields(message.toArray(new HighlightBuilder.Field[message.size()])).build(); - - Page sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, - new SearchResultMapperAdapter() { - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - List chunk = new ArrayList<>(); - for (SearchHit searchHit : response.getHits()) { - if (response.getHits().getHits().length <= 0) { - return null; - } - SampleEntity user = new SampleEntity(); - user.setId(searchHit.getId()); - user.setMessage((String) searchHit.getSourceAsMap().get("message")); - user.setHighlightedMessage(searchHit.getHighlightFields().get("message").fragments()[0].toString()); - chunk.add(user); - } - if (chunk.size() > 0) { - return new AggregatedPageImpl<>((List) chunk); - } - return null; - } - }); - - // then - assertThat(sampleEntities.getContent().get(0).getHighlightedMessage()).isEqualTo(highlightedMessage); - } - - @Test // DATAES-412 - public void shouldReturnMultipleHighlightFields() { - - // given - String documentId = randomNumeric(5); - String actualType = "some test type"; - String actualMessage = "some test message"; - String highlightedType = "some test type"; - String highlightedMessage = "some test message"; - - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).type(actualType).message(actualMessage) - .version(System.currentTimeMillis()).build(); - - IndexQuery indexQuery = getIndexQuery(sampleEntity); - - elasticsearchTemplate.index(indexQuery); - elasticsearchTemplate.refresh(SampleEntity.class); - - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(boolQuery().must(termQuery("type", "test")).must(termQuery("message", "test"))) - .withHighlightFields(new HighlightBuilder.Field("type"), new HighlightBuilder.Field("message")).build(); - - // when - elasticsearchTemplate.queryForPage(searchQuery, SampleEntity.class, new SearchResultMapperAdapter() { - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - for (SearchHit searchHit : response.getHits()) { - Map highlightFields = searchHit.getHighlightFields(); - HighlightField highlightFieldType = highlightFields.get("type"); - HighlightField highlightFieldMessage = highlightFields.get("message"); - - // then - assertThat(highlightFieldType).isNotNull(); - assertThat(highlightFieldMessage).isNotNull(); - assertThat(highlightFieldType.fragments()[0].toString()).isEqualTo(highlightedType); - assertThat(highlightFieldMessage.fragments()[0].toString()).isEqualTo(highlightedMessage); - } - return null; - } - }); - } - - @Test // DATAES-645 - public void shouldReturnHighlightedFieldsInScroll() { - - // given - long scrollTimeInMillis = 3000; - String documentId = randomNumeric(5); - String actualType = "some test type"; - String actualMessage = "some test message"; - String highlightedType = "some test type"; - String highlightedMessage = "some test message"; - - SampleEntity sampleEntity = SampleEntity.builder().id(documentId).type(actualType).message(actualMessage) - .version(System.currentTimeMillis()).build(); - - IndexQuery indexQuery = getIndexQuery(sampleEntity); - - elasticsearchTemplate.index(indexQuery); - elasticsearchTemplate.refresh(SampleEntity.class); - - HighlightBuilder highlightBuilder = new HighlightBuilder().field("type").field("message"); - - SearchQuery searchQuery = new NativeSearchQueryBuilder() - .withQuery(boolQuery().must(termQuery("type", "test")).must(termQuery("message", "test"))) - .withPageable(PageRequest.of(0, 10)).withHighlightBuilder(highlightBuilder).build(); - - SearchResultMapper searchResultMapper = new SearchResultMapper() { - @Override - public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) { - MappingElasticsearchConverter mappingElasticsearchConverter = new MappingElasticsearchConverter( - new SimpleElasticsearchMappingContext()); - ArrayList result = new ArrayList<>(); - - for (SearchHit searchHit : response.getHits()) { - try { - result - .add((T) mappingElasticsearchConverter.mapToObject(searchHit.getSourceAsString(), SampleEntity.class)); - } catch (IOException e) { - e.printStackTrace(); - } - - Map highlightFields = searchHit.getHighlightFields(); - HighlightField highlightFieldType = highlightFields.get("type"); - HighlightField highlightFieldMessage = highlightFields.get("message"); - - // then - assertThat(highlightFieldType).isNotNull(); - assertThat(highlightFieldMessage).isNotNull(); - assertThat(highlightFieldType.fragments()[0].toString()).isEqualTo(highlightedType); - assertThat(highlightFieldMessage.fragments()[0].toString()).isEqualTo(highlightedMessage); - } - - return new AggregatedPageImpl(result, pageable, response.getHits().getTotalHits().value, - response.getAggregations(), response.getScrollId(), response.getHits().getMaxScore()); - } - - @Override - public T mapSearchHit(SearchHit searchHit, Class type) { - return null; - } - }; - - // when - ScrolledPage scroll = elasticsearchTemplate.startScroll(scrollTimeInMillis, searchQuery, - SampleEntity.class, searchResultMapper); - while (scroll.hasContent()) { - scroll = elasticsearchTemplate.continueScroll(scroll.getScrollId(), scrollTimeInMillis, SampleEntity.class, - searchResultMapper); - } - - elasticsearchTemplate.clearScroll(scroll.getScrollId()); - } - @Test // DATAES-671 public void shouldPassIndicesOptionsForGivenSearchScrollQuery() { @@ -1738,13 +1511,12 @@ public void shouldPassIndicesOptionsForGivenSearchScrollQuery() { List entities = new ArrayList<>(); ScrolledPage scroll = elasticsearchTemplate.startScroll(scrollTimeInMillis, searchQuery, - SampleEntity.class, searchResultMapper); + SampleEntity.class); entities.addAll(scroll.getContent()); while (scroll.hasContent()) { - scroll = elasticsearchTemplate.continueScroll(scroll.getScrollId(), scrollTimeInMillis, SampleEntity.class, - searchResultMapper); + scroll = elasticsearchTemplate.continueScroll(scroll.getScrollId(), scrollTimeInMillis, SampleEntity.class); entities.addAll(scroll.getContent()); } @@ -1754,84 +1526,6 @@ public void shouldPassIndicesOptionsForGivenSearchScrollQuery() { assertThat(entities.size()).isGreaterThanOrEqualTo(1); } - @Test // DATAES-479 - public void shouldHonorTheHighlightBuilderOptions() { - - // given - String documentId = randomNumeric(5); - String actualMessage = "some test message with unsafe