mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-28 14:52:20 +00:00
DATAES-464 - DefaultEntityWriter now considers read-only and transient properties.
We now register a custom Jackson module that filters read-only and transient properties for serialization.
This commit is contained in:
parent
ac62aaf856
commit
62a03a8fb7
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
* Copyright 2014-2018 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.
|
||||
@ -16,52 +16,142 @@
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.*;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
|
||||
import org.springframework.data.geo.*;
|
||||
import java.util.List;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.Version;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
|
||||
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
|
||||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||
|
||||
/**
|
||||
* DocumentMapper using jackson
|
||||
* EntityMapper based on a Jackson {@link ObjectMapper}.
|
||||
*
|
||||
* @author Artur Konczak
|
||||
* @author Petar Tahchiev
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultEntityMapper implements EntityMapper {
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
public DefaultEntityMapper() {
|
||||
/**
|
||||
* Creates a new {@link DefaultEntityMapper} using the given {@link MappingContext}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
public DefaultEntityMapper(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
|
||||
objectMapper = new ObjectMapper();
|
||||
|
||||
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
|
||||
objectMapper.registerModule(new CustomGeoModule());
|
||||
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
|
||||
objectMapper.registerModule(new CustomGeoModule());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToString(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public String mapToString(Object object) throws IOException {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
|
||||
return objectMapper.readValue(source, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 3.1
|
||||
*/
|
||||
private static class SpringDataElasticsearchModule extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = -9168968092458058966L;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringDataElasticsearchModule} using the given {@link MappingContext}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
public SpringDataElasticsearchModule(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
|
||||
setSerializerModifier(new SpringDataSerializerModifier(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BeanSerializerModifier} that will drop properties annotated with {@link ReadOnlyProperty} for
|
||||
* serialization.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 3.1
|
||||
*/
|
||||
private static class SpringDataSerializerModifier extends BeanSerializerModifier {
|
||||
|
||||
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
|
||||
|
||||
public SpringDataSerializerModifier(
|
||||
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
|
||||
|
||||
Assert.notNull(context, "MappingContext must not be null!");
|
||||
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#changeProperties(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, java.util.List)
|
||||
*/
|
||||
@Override
|
||||
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription description,
|
||||
List<BeanPropertyWriter> properties) {
|
||||
|
||||
Class<?> type = description.getBeanClass();
|
||||
ElasticsearchPersistentEntity<?> entity = context.getPersistentEntity(type);
|
||||
|
||||
if (entity == null) {
|
||||
return super.changeProperties(config, description, properties);
|
||||
}
|
||||
|
||||
List<BeanPropertyWriter> result = new ArrayList<>(properties.size());
|
||||
|
||||
for (BeanPropertyWriter beanPropertyWriter : properties) {
|
||||
|
||||
ElasticsearchPersistentProperty property = entity.getPersistentProperty(beanPropertyWriter.getName());
|
||||
|
||||
if (property != null && property.isWritable()) {
|
||||
result.add(beanPropertyWriter);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
|
||||
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
|
||||
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@ -59,11 +60,11 @@ public class DefaultResultMapper extends AbstractResultMapper {
|
||||
private MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
|
||||
|
||||
public DefaultResultMapper() {
|
||||
super(new DefaultEntityMapper());
|
||||
this(new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
|
||||
super(new DefaultEntityMapper());
|
||||
super(new DefaultEntityMapper(mappingContext));
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2014 the original author or authors.
|
||||
* Copyright 2013-2018 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.
|
||||
@ -15,15 +15,17 @@
|
||||
*/
|
||||
package org.springframework.data.elasticsearch.core;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.annotation.ReadOnlyProperty;
|
||||
import org.springframework.data.annotation.Transient;
|
||||
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
|
||||
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
|
||||
import org.springframework.data.elasticsearch.entities.Car;
|
||||
import org.springframework.data.elasticsearch.entities.GeoEntity;
|
||||
import org.springframework.data.geo.Point;
|
||||
@ -31,6 +33,7 @@ import org.springframework.data.geo.Point;
|
||||
/**
|
||||
* @author Artur Konczak
|
||||
* @author Mohsin Husen
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultEntityMapperTests {
|
||||
|
||||
@ -41,7 +44,7 @@ public class DefaultEntityMapperTests {
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
entityMapper = new DefaultEntityMapper();
|
||||
entityMapper = new DefaultEntityMapper(new SimpleElasticsearchMappingContext());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -52,7 +55,7 @@ public class DefaultEntityMapperTests {
|
||||
String jsonResult = entityMapper.mapToString(Car.builder().model(CAR_MODEL).name(CAR_NAME).build());
|
||||
|
||||
//Then
|
||||
assertThat(jsonResult, is(JSON_STRING));
|
||||
assertThat(jsonResult).isEqualTo(JSON_STRING);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -63,15 +66,14 @@ public class DefaultEntityMapperTests {
|
||||
Car result = entityMapper.mapToObject(JSON_STRING, Car.class);
|
||||
|
||||
//Then
|
||||
assertThat(result.getName(), is(CAR_NAME));
|
||||
assertThat(result.getModel(), is(CAR_MODEL));
|
||||
assertThat(result.getName()).isEqualTo(CAR_NAME);
|
||||
assertThat(result.getModel()).isEqualTo(CAR_MODEL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapGeoPointElasticsearchNames() throws IOException {
|
||||
//given
|
||||
final Point point = new Point(10, 20);
|
||||
final int radius = 10;
|
||||
final String pointAsString = point.getX() + "," + point.getY();
|
||||
final double[] pointAsArray = {point.getX(), point.getY()};
|
||||
final GeoEntity geoEntity = GeoEntity.builder()
|
||||
@ -81,13 +83,43 @@ public class DefaultEntityMapperTests {
|
||||
String jsonResult = entityMapper.mapToString(geoEntity);
|
||||
|
||||
//then
|
||||
assertThat(jsonResult, containsString(pointTemplate("pointA", point)));
|
||||
assertThat(jsonResult, containsString(pointTemplate("pointB", point)));
|
||||
assertThat(jsonResult, containsString(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString)));
|
||||
assertThat(jsonResult, containsString(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1])));
|
||||
assertThat(jsonResult).contains(pointTemplate("pointA", point));
|
||||
assertThat(jsonResult).contains(pointTemplate("pointB", point));
|
||||
assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString));
|
||||
assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1]));
|
||||
}
|
||||
|
||||
@Test // DATAES-464
|
||||
public void ignoresReadOnlyProperties() throws IOException {
|
||||
|
||||
// given
|
||||
Sample sample = new Sample();
|
||||
sample.readOnly = "readOnly";
|
||||
sample.property = "property";
|
||||
sample.transientProperty = "transient";
|
||||
sample.annotatedTransientProperty = "transient";
|
||||
|
||||
// when
|
||||
String result = entityMapper.mapToString(sample);
|
||||
|
||||
// then
|
||||
assertThat(result).contains("\"property\"");
|
||||
|
||||
assertThat(result).doesNotContain("readOnly");
|
||||
assertThat(result).doesNotContain("transientProperty");
|
||||
assertThat(result).doesNotContain("annotatedTransientProperty");
|
||||
}
|
||||
|
||||
private String pointTemplate(String name, Point point) {
|
||||
return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY());
|
||||
}
|
||||
|
||||
public static class Sample {
|
||||
|
||||
|
||||
public @ReadOnlyProperty String readOnly;
|
||||
public @Transient String annotatedTransientProperty;
|
||||
public transient String transientProperty;
|
||||
public String property;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user