Skip to content

Commit 62a03a8

Browse files
committed
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.
1 parent ac62aaf commit 62a03a8

File tree

3 files changed

+157
-34
lines changed

3 files changed

+157
-34
lines changed
Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014 the original author or authors.
2+
* Copyright 2014-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,52 +16,142 @@
1616
package org.springframework.data.elasticsearch.core;
1717

1818
import java.io.IOException;
19+
import java.util.ArrayList;
1920
import java.util.List;
2021

21-
import com.fasterxml.jackson.annotation.JsonIgnore;
22-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
23-
import com.fasterxml.jackson.annotation.JsonProperty;
24-
import com.fasterxml.jackson.core.*;
25-
import com.fasterxml.jackson.databind.*;
26-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
27-
import com.fasterxml.jackson.databind.module.SimpleModule;
28-
import org.springframework.data.elasticsearch.annotations.Document;
22+
import org.springframework.data.annotation.ReadOnlyProperty;
2923
import org.springframework.data.elasticsearch.core.geo.CustomGeoModule;
30-
import org.springframework.data.geo.*;
31-
import java.util.List;
24+
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
25+
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
26+
import org.springframework.data.mapping.context.MappingContext;
27+
import org.springframework.util.Assert;
3228

33-
import com.fasterxml.jackson.annotation.JsonIgnore;
34-
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
35-
import com.fasterxml.jackson.annotation.JsonProperty;
36-
import com.fasterxml.jackson.core.Version;
37-
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
29+
import com.fasterxml.jackson.databind.BeanDescription;
30+
import com.fasterxml.jackson.databind.DeserializationFeature;
31+
import com.fasterxml.jackson.databind.ObjectMapper;
32+
import com.fasterxml.jackson.databind.SerializationConfig;
3833
import com.fasterxml.jackson.databind.module.SimpleModule;
39-
34+
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
35+
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
4036

4137
/**
42-
* DocumentMapper using jackson
38+
* EntityMapper based on a Jackson {@link ObjectMapper}.
4339
*
4440
* @author Artur Konczak
4541
* @author Petar Tahchiev
42+
* @author Oliver Gierke
4643
*/
4744
public class DefaultEntityMapper implements EntityMapper {
4845

4946
private ObjectMapper objectMapper;
5047

51-
public DefaultEntityMapper() {
48+
/**
49+
* Creates a new {@link DefaultEntityMapper} using the given {@link MappingContext}.
50+
*
51+
* @param context must not be {@literal null}.
52+
*/
53+
public DefaultEntityMapper(
54+
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
55+
56+
Assert.notNull(context, "MappingContext must not be null!");
57+
5258
objectMapper = new ObjectMapper();
59+
60+
objectMapper.registerModule(new SpringDataElasticsearchModule(context));
61+
objectMapper.registerModule(new CustomGeoModule());
62+
5363
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
5464
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
55-
objectMapper.registerModule(new CustomGeoModule());
5665
}
5766

67+
/*
68+
* (non-Javadoc)
69+
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToString(java.lang.Object)
70+
*/
5871
@Override
5972
public String mapToString(Object object) throws IOException {
6073
return objectMapper.writeValueAsString(object);
6174
}
6275

76+
/*
77+
* (non-Javadoc)
78+
* @see org.springframework.data.elasticsearch.core.EntityMapper#mapToObject(java.lang.String, java.lang.Class)
79+
*/
6380
@Override
6481
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
6582
return objectMapper.readValue(source, clazz);
6683
}
84+
85+
/**
86+
* A simple Jackson module to register the {@link SpringDataSerializerModifier}.
87+
*
88+
* @author Oliver Gierke
89+
* @since 3.1
90+
*/
91+
private static class SpringDataElasticsearchModule extends SimpleModule {
92+
93+
private static final long serialVersionUID = -9168968092458058966L;
94+
95+
/**
96+
* Creates a new {@link SpringDataElasticsearchModule} using the given {@link MappingContext}.
97+
*
98+
* @param context must not be {@literal null}.
99+
*/
100+
public SpringDataElasticsearchModule(
101+
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
102+
103+
Assert.notNull(context, "MappingContext must not be null!");
104+
105+
setSerializerModifier(new SpringDataSerializerModifier(context));
106+
}
107+
108+
/**
109+
* A {@link BeanSerializerModifier} that will drop properties annotated with {@link ReadOnlyProperty} for
110+
* serialization.
111+
*
112+
* @author Oliver Gierke
113+
* @since 3.1
114+
*/
115+
private static class SpringDataSerializerModifier extends BeanSerializerModifier {
116+
117+
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context;
118+
119+
public SpringDataSerializerModifier(
120+
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> context) {
121+
122+
Assert.notNull(context, "MappingContext must not be null!");
123+
124+
this.context = context;
125+
}
126+
127+
/*
128+
* (non-Javadoc)
129+
* @see com.fasterxml.jackson.databind.ser.BeanSerializerModifier#changeProperties(com.fasterxml.jackson.databind.SerializationConfig, com.fasterxml.jackson.databind.BeanDescription, java.util.List)
130+
*/
131+
@Override
132+
public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription description,
133+
List<BeanPropertyWriter> properties) {
134+
135+
Class<?> type = description.getBeanClass();
136+
ElasticsearchPersistentEntity<?> entity = context.getPersistentEntity(type);
137+
138+
if (entity == null) {
139+
return super.changeProperties(config, description, properties);
140+
}
141+
142+
List<BeanPropertyWriter> result = new ArrayList<>(properties.size());
143+
144+
for (BeanPropertyWriter beanPropertyWriter : properties) {
145+
146+
ElasticsearchPersistentProperty property = entity.getPersistentProperty(beanPropertyWriter.getName());
147+
148+
if (property != null && property.isWritable()) {
149+
result.add(beanPropertyWriter);
150+
}
151+
}
152+
153+
return result;
154+
}
155+
}
156+
}
67157
}

src/main/java/org/springframework/data/elasticsearch/core/DefaultResultMapper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
3939
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
4040
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
41+
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
4142
import org.springframework.data.mapping.context.MappingContext;
4243
import org.springframework.util.Assert;
4344

@@ -59,11 +60,11 @@ public class DefaultResultMapper extends AbstractResultMapper {
5960
private MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
6061

6162
public DefaultResultMapper() {
62-
super(new DefaultEntityMapper());
63+
this(new SimpleElasticsearchMappingContext());
6364
}
6465

6566
public DefaultResultMapper(MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
66-
super(new DefaultEntityMapper());
67+
super(new DefaultEntityMapper(mappingContext));
6768
this.mappingContext = mappingContext;
6869
}
6970

src/test/java/org/springframework/data/elasticsearch/core/DefaultEntityMapperTests.java

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2014 the original author or authors.
2+
* Copyright 2013-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,22 +15,25 @@
1515
*/
1616
package org.springframework.data.elasticsearch.core;
1717

18-
import static org.hamcrest.Matchers.*;
19-
import static org.junit.Assert.*;
18+
import static org.assertj.core.api.Assertions.*;
2019

2120
import java.io.IOException;
2221
import java.util.Locale;
2322

2423
import org.junit.Before;
2524
import org.junit.Test;
25+
import org.springframework.data.annotation.ReadOnlyProperty;
26+
import org.springframework.data.annotation.Transient;
2627
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
28+
import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
2729
import org.springframework.data.elasticsearch.entities.Car;
2830
import org.springframework.data.elasticsearch.entities.GeoEntity;
2931
import org.springframework.data.geo.Point;
3032

3133
/**
3234
* @author Artur Konczak
3335
* @author Mohsin Husen
36+
* @author Oliver Gierke
3437
*/
3538
public class DefaultEntityMapperTests {
3639

@@ -41,7 +44,7 @@ public class DefaultEntityMapperTests {
4144

4245
@Before
4346
public void init() {
44-
entityMapper = new DefaultEntityMapper();
47+
entityMapper = new DefaultEntityMapper(new SimpleElasticsearchMappingContext());
4548
}
4649

4750
@Test
@@ -52,7 +55,7 @@ public void shouldMapObjectToJsonString() throws IOException {
5255
String jsonResult = entityMapper.mapToString(Car.builder().model(CAR_MODEL).name(CAR_NAME).build());
5356

5457
//Then
55-
assertThat(jsonResult, is(JSON_STRING));
58+
assertThat(jsonResult).isEqualTo(JSON_STRING);
5659
}
5760

5861
@Test
@@ -63,15 +66,14 @@ public void shouldMapJsonStringToObject() throws IOException {
6366
Car result = entityMapper.mapToObject(JSON_STRING, Car.class);
6467

6568
//Then
66-
assertThat(result.getName(), is(CAR_NAME));
67-
assertThat(result.getModel(), is(CAR_MODEL));
69+
assertThat(result.getName()).isEqualTo(CAR_NAME);
70+
assertThat(result.getModel()).isEqualTo(CAR_MODEL);
6871
}
6972

7073
@Test
7174
public void shouldMapGeoPointElasticsearchNames() throws IOException {
7275
//given
7376
final Point point = new Point(10, 20);
74-
final int radius = 10;
7577
final String pointAsString = point.getX() + "," + point.getY();
7678
final double[] pointAsArray = {point.getX(), point.getY()};
7779
final GeoEntity geoEntity = GeoEntity.builder()
@@ -81,13 +83,43 @@ public void shouldMapGeoPointElasticsearchNames() throws IOException {
8183
String jsonResult = entityMapper.mapToString(geoEntity);
8284

8385
//then
84-
assertThat(jsonResult, containsString(pointTemplate("pointA", point)));
85-
assertThat(jsonResult, containsString(pointTemplate("pointB", point)));
86-
assertThat(jsonResult, containsString(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString)));
87-
assertThat(jsonResult, containsString(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1])));
86+
assertThat(jsonResult).contains(pointTemplate("pointA", point));
87+
assertThat(jsonResult).contains(pointTemplate("pointB", point));
88+
assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":\"%s\"", "pointC", pointAsString));
89+
assertThat(jsonResult).contains(String.format(Locale.ENGLISH, "\"%s\":[%.1f,%.1f]", "pointD", pointAsArray[0], pointAsArray[1]));
90+
}
91+
92+
@Test // DATAES-464
93+
public void ignoresReadOnlyProperties() throws IOException {
94+
95+
// given
96+
Sample sample = new Sample();
97+
sample.readOnly = "readOnly";
98+
sample.property = "property";
99+
sample.transientProperty = "transient";
100+
sample.annotatedTransientProperty = "transient";
101+
102+
// when
103+
String result = entityMapper.mapToString(sample);
104+
105+
// then
106+
assertThat(result).contains("\"property\"");
107+
108+
assertThat(result).doesNotContain("readOnly");
109+
assertThat(result).doesNotContain("transientProperty");
110+
assertThat(result).doesNotContain("annotatedTransientProperty");
88111
}
89112

90113
private String pointTemplate(String name, Point point) {
91114
return String.format(Locale.ENGLISH, "\"%s\":{\"lat\":%.1f,\"lon\":%.1f}", name, point.getX(), point.getY());
92115
}
116+
117+
public static class Sample {
118+
119+
120+
public @ReadOnlyProperty String readOnly;
121+
public @Transient String annotatedTransientProperty;
122+
public transient String transientProperty;
123+
public String property;
124+
}
93125
}

0 commit comments

Comments
 (0)