Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to integrate ElasticSearch 7.0 version with Spring Boot?

I'm trying to use the latest version of Elastic Search library which is already available in the maven repos.

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.0.0</version>
</dependency>

But not sure how can I use the 7th version with Spring Boot which imports 6.5. My maven dependency:

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
  </dependency>
like image 835
nsv Avatar asked Apr 19 '19 11:04

nsv


1 Answers

UPDATE

Spring Boot 2.3 is integrating spring-data-elasticsearch 4 so it will support ElasticSearch 7.x out of the box. It will be released soon but you can already try it:

plugins {
  id 'org.springframework.boot' version '2.3.0.RC1'
  id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}

I have tested it positively and all of my test scenarios are passing so I would definitely recommend this way. I will keep the answer below for people that for some reasons can't upgrade to 2.3.

OLD WORKAROUND (previous versions original answer)

As we don`t really know when Spring Data Elastic Search 4.x is going to be released I am posting my way of integrating the current Spring Data Elastic Search 4.x and stable Spring Boot 2.1.7. It might work as a temporary workaround for you if you want to work with the Spring Repositories and the newest Elastic Search.

1) Force newest elastic search client in your dependencies (in my case: build.gradle)

dependencies {
    //Force spring-data to use the newest elastic-search client
    //this should removed as soon as spring-data-elasticsearch:4.0.0 is released!
    implementation('org.springframework.data:spring-data-elasticsearch:4.0.0.BUILD-SNAPSHOT') {
        exclude group: 'org.elasticsearch'
        exclude group: 'org.elasticsearch.plugin'
        exclude group: 'org.elasticsearch.client'
    }

    implementation('org.elasticsearch:elasticsearch:7.3.0') { force = true }
    implementation('org.elasticsearch.client:elasticsearch-rest-high-level-client:7.3.0') { force = true }
    implementation('org.elasticsearch.client:elasticsearch-rest-client:7.3.0') { force = true }
}

2) Disable the Elastic Search auto configuration and health check components as they become incompatible (you may later want to implement your own health check).

@SpringBootApplication(exclude = {ElasticsearchAutoConfiguration.class, ElasticSearchRestHealthIndicatorAutoConfiguration.class})
@EnableElasticsearchRepositories
public class SpringBootApp {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootApp.class, args);
    }

}

3) As we disabled the auto configuration we need to initialize the ElasticsearchRestTemplate ourselves. We also need to do it to provide the custom MappingElasticsearchConverter to avoid class incompatibilities.

/**
 * Manual configuration to support the newest ElasticSearch that is currently not supported by {@link org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration}.
 *
 * @author aleksanderlech
 */
@Configuration
@EnableConfigurationProperties(ElasticsearchProperties.class)
public class ElasticSearchConfiguration {

    @Primary
    @Bean
    public ElasticsearchRestTemplate elasticsearchTemplate(ElasticsearchProperties configuration) {
        var nodes =  Stream.of(configuration.getClusterNodes().split(",")).map(HttpHost::create).toArray(HttpHost[]::new);
        var client = new RestHighLevelClient(RestClient.builder(nodes));
        var converter = new CustomElasticSearchConverter(new SimpleElasticsearchMappingContext(), createConversionService());
        return new ElasticsearchRestTemplate(client, converter, new DefaultResultMapper(converter));
    }

    private DefaultConversionService createConversionService() {
        var conversionService = new DefaultConversionService();
        conversionService.addConverter(new StringToLocalDateConverter());
        return conversionService;
    }
}

CustomElasticSearchConverter:

/**
 * Custom version of {@link MappingElasticsearchConverter} to support newest Spring Data Elasticsearch integration that supports ElasticSearch 7. Remove when Spring Data Elasticsearch 4.x is released.
 */
class CustomElasticSearchConverter extends MappingElasticsearchConverter {

    private CustomConversions conversions = new ElasticsearchCustomConversions(Collections.emptyList());

    CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
        super(mappingContext);
        setConversions(conversions);
    }

    CustomElasticSearchConverter(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext, GenericConversionService conversionService) {
        super(mappingContext, conversionService);
        setConversions(conversions);
    }

    @Override
    protected <R> R readValue(@Nullable Object source, ElasticsearchPersistentProperty property,
                              TypeInformation<R> targetType) {

        if (source == null) {
            return null;
        }

        if (source instanceof List) {
            return readCollectionValue((List) source, property, targetType);
        }

        return super.readValue(source, property, targetType);
    }

    private Object readSimpleValue(@Nullable Object value, TypeInformation<?> targetType) {

        Class<?> target = targetType.getType();

        if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
            return value;
        }

        if (conversions.hasCustomReadTarget(value.getClass(), target)) {
            return getConversionService().convert(value, target);
        }

        if (Enum.class.isAssignableFrom(target)) {
            return Enum.valueOf((Class<Enum>) target, value.toString());
        }

        return getConversionService().convert(value, target);
    }


    private <R> R readCollectionValue(@Nullable List<?> source, ElasticsearchPersistentProperty property,
                                      TypeInformation<R> targetType) {

        if (source == null) {
            return null;
        }

        Collection<Object> target = createCollectionForValue(targetType, source.size());

        for (Object value : source) {

            if (isSimpleType(value)) {
                target.add(
                        readSimpleValue(value, targetType.getComponentType() != null ? targetType.getComponentType() : targetType));
            } else {

                if (value instanceof List) {
                    target.add(readValue(value, property, property.getTypeInformation().getActualType()));
                } else {
                    target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map) value));
                }
            }
        }

        return (R) target;
    }

    private Collection<Object> createCollectionForValue(TypeInformation<?> collectionTypeInformation, int size) {

        Class<?> collectionType = collectionTypeInformation.isCollectionLike()//
                ? collectionTypeInformation.getType() //
                : List.class;

        TypeInformation<?> componentType = collectionTypeInformation.getComponentType() != null //
                ? collectionTypeInformation.getComponentType() //
                : ClassTypeInformation.OBJECT;

        return collectionTypeInformation.getType().isArray() //
                ? new ArrayList<>(size) //
                : CollectionFactory.createCollection(collectionType, componentType.getType(), size);
    }

    private ElasticsearchPersistentEntity<?> computeGenericValueTypeForRead(ElasticsearchPersistentProperty property,
                                                                            Object value) {

        return ClassTypeInformation.OBJECT.equals(property.getTypeInformation().getActualType())
                ? getMappingContext().getRequiredPersistentEntity(value.getClass())
                : getMappingContext().getRequiredPersistentEntity(property.getTypeInformation().getActualType());
    }

    private boolean isSimpleType(Object value) {
        return isSimpleType(value.getClass());
    }

    private boolean isSimpleType(Class<?> type) {
        return conversions.isSimpleType(type);
    }

}
like image 191
Aleksander Lech Avatar answered Nov 19 '22 17:11

Aleksander Lech