Skip to content

Commit dea9b86

Browse files
committed
FLE Implemenation with Property Value Converter.
Closes #763.
1 parent 7570b96 commit dea9b86

16 files changed

+819
-396
lines changed

spring-data-couchbase/src/main/java/org/springframework/data/couchbase/config/AbstractCouchbaseConfiguration.java

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.ArrayList;
2222
import java.util.HashSet;
2323
import java.util.List;
24+
import java.util.Map;
2425
import java.util.Set;
2526

2627
import org.springframework.beans.factory.config.BeanDefinition;
@@ -31,15 +32,21 @@
3132
import org.springframework.core.convert.converter.GenericConverter;
3233
import org.springframework.core.type.filter.AnnotationTypeFilter;
3334
import org.springframework.data.convert.CustomConversions;
35+
import org.springframework.data.convert.PropertyValueConverterFactory;
36+
import org.springframework.data.convert.PropertyValueConverterRegistrar;
37+
import org.springframework.data.convert.SimplePropertyValueConversions;
3438
import org.springframework.data.couchbase.CouchbaseClientFactory;
3539
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
3640
import org.springframework.data.couchbase.core.CouchbaseTemplate;
3741
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
3842
import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions;
43+
import org.springframework.data.couchbase.core.convert.CouchbasePropertyValueConverterFactory;
44+
import org.springframework.data.couchbase.core.convert.CryptoConverter;
3945
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
4046
import org.springframework.data.couchbase.core.convert.OtherConverters;
4147
import org.springframework.data.couchbase.core.convert.translation.JacksonTranslationService;
4248
import org.springframework.data.couchbase.core.convert.translation.TranslationService;
49+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
4350
import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext;
4451
import org.springframework.data.couchbase.core.mapping.Document;
4552
import org.springframework.data.couchbase.repository.config.ReactiveRepositoryOperationsMapping;
@@ -274,6 +281,7 @@ public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingConte
274281
CouchbaseCustomConversions couchbaseCustomConversions) {
275282
MappingCouchbaseConverter converter = new MappingCouchbaseConverter(couchbaseMappingContext, typeKey());
276283
converter.setCustomConversions(couchbaseCustomConversions);
284+
couchbaseMappingContext.setSimpleTypeHolder(couchbaseCustomConversions.getSimpleTypeHolder());
277285
return converter;
278286
}
279287

@@ -397,26 +405,39 @@ protected boolean autoIndexCreation() {
397405
/**
398406
* Register custom Converters in a {@link CustomConversions} object if required. These {@link CustomConversions} will
399407
* be registered with the {@link #mappingCouchbaseConverter(CouchbaseMappingContext, CouchbaseCustomConversions)} )}
400-
* and {@link #couchbaseMappingContext(CustomConversions)}. Returns an empty {@link CustomConversions} instance by
401-
* default.
408+
* and {@link #couchbaseMappingContext(CustomConversions)}.
402409
*
403-
* @param cryptoManagerOptional optional cryptoManager. Make varargs for backwards compatibility.
404410
* @return must not be {@literal null}.
405411
*/
406412
@Bean(name = BeanNames.COUCHBASE_CUSTOM_CONVERSIONS)
407-
public CustomConversions customConversions(CryptoManager... cryptoManagerOptional) {
408-
assert (cryptoManagerOptional == null || cryptoManagerOptional.length <= 1);
409-
CryptoManager cryptoManager = cryptoManagerOptional != null && cryptoManagerOptional.length == 1
410-
? cryptoManagerOptional[0]
411-
: null;
413+
public CustomConversions customConversions() {
414+
return customConversions(cryptoManager());
415+
}
416+
417+
/**
418+
* Register custom Converters in a {@link CustomConversions} object if required. These {@link CustomConversions} will
419+
* be registered with the {@link #mappingCouchbaseConverter(CouchbaseMappingContext, CouchbaseCustomConversions)} )}
420+
* and {@link #couchbaseMappingContext(CustomConversions)}.
421+
*
422+
* @param cryptoManager
423+
* @return must not be {@literal null}.
424+
*/
425+
public CustomConversions customConversions(CryptoManager cryptoManager) {
412426
List<GenericConverter> newConverters = new ArrayList();
413427
// the cryptoConverters take an argument, so they cannot be created in the
414428
// static block of CouchbaseCustomConversions. And they must be set before the super() constructor
415-
// in CouchbaseCustomerConversions
416-
if (cryptoManager != null) {
417-
newConverters.addAll(OtherConverters.getCryptoConverters(cryptoManager));
418-
}
419-
return new CouchbaseCustomConversions(newConverters);
429+
// in CouchbaseCustomConversions
430+
CustomConversions customConversions = CouchbaseCustomConversions.create( configurationAdapter -> {
431+
SimplePropertyValueConversions valueConversions = new SimplePropertyValueConversions();
432+
valueConversions.setConverterFactory(new CouchbasePropertyValueConverterFactory(cryptoManager));
433+
valueConversions.setValueConverterRegistry(new PropertyValueConverterRegistrar()
434+
.registerConverter(CouchbaseDocument.class, "", new CryptoConverter(cryptoManager))// unnecessary?
435+
.buildRegistry());
436+
configurationAdapter.setPropertyValueConversions(valueConversions);
437+
configurationAdapter.registerConverters(newConverters);
438+
});
439+
440+
return customConversions;
420441
}
421442

422443
@Bean

spring-data-couchbase/src/main/java/org/springframework/data/couchbase/core/convert/AbstractCouchbaseConverter.java

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
package org.springframework.data.couchbase.core.convert;
1818

1919
import java.util.Collections;
20+
import java.util.Map;
2021

2122
import org.springframework.beans.factory.InitializingBean;
2223
import org.springframework.core.convert.ConversionService;
2324
import org.springframework.core.convert.TypeDescriptor;
2425
import org.springframework.core.convert.support.GenericConversionService;
2526
import org.springframework.data.convert.CustomConversions;
27+
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
2628
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
2729
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
2830
import org.springframework.data.mapping.model.EntityInstantiators;
@@ -99,7 +101,7 @@ public void afterPropertiesSet() {
99101

100102
/**
101103
* This convertForWriteIfNeeded takes a property and accessor so that the annotations can be accessed (ie. @Encrypted)
102-
*
104+
*
103105
* @param prop the property to be converted to the class that would actually be stored.
104106
* @param accessor the property accessor
105107
* @return
@@ -110,6 +112,63 @@ public Object convertForWriteIfNeeded(CouchbasePersistentProperty prop, Converti
110112
if (value == null) {
111113
return null;
112114
}
115+
if (conversions.hasValueConverter(prop)) {
116+
CouchbaseDocument encrypted = (CouchbaseDocument) conversions.getPropertyValueConversions()
117+
.getValueConverter(prop).write(value, new CouchbaseConversionContext(prop, (MappingCouchbaseConverter)this, accessor));
118+
return encrypted;
119+
}
120+
Class<?> targetClass = Object.class;
121+
122+
if (prop.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class) != null) {
123+
targetClass = Map.class;
124+
}
125+
boolean canConvert = this.conversionService.canConvert(new TypeDescriptor(prop.getField()),
126+
TypeDescriptor.valueOf(targetClass));
127+
if (canConvert) {
128+
return this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
129+
TypeDescriptor.valueOf(targetClass));
130+
}
131+
132+
Object result = this.conversions.getCustomWriteTarget(prop.getType()) //
133+
.map(it -> this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
134+
TypeDescriptor.valueOf(it))) //
135+
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
136+
137+
return result;
138+
139+
}
140+
141+
/**
142+
* This convertForWriteIfNeeded takes a property and accessor so that the annotations can be accessed (ie. @Encrypted)
143+
*
144+
* @param prop the property to be converted to the class that would actually be stored.
145+
* @param accessor the property accessor
146+
* @return
147+
*/
148+
//@Override
149+
public Object convertForWriteIfNeeded2(CouchbasePersistentProperty prop, ConvertingPropertyAccessor<Object> accessor) {
150+
Object value = accessor.getProperty(prop, prop.getType());
151+
if (value == null) {
152+
return null;
153+
}
154+
/*
155+
if (conversions.hasValueConverter(prop)) {
156+
CouchbaseDocument encrypted = (CouchbaseDocument) conversions.getPropertyValueConversions()
157+
.getValueConverter(prop).write(value, new CouchbaseConversionContext(prop, (MappingCouchbaseConverter)this, accessor));
158+
return encrypted;
159+
}
160+
*/
161+
Class<?> targetClass = Object.class;
162+
163+
if (prop.findAnnotation(com.couchbase.client.java.encryption.annotation.Encrypted.class) != null) {
164+
targetClass = Map.class;
165+
}
166+
boolean canConvert = this.conversionService.canConvert(new TypeDescriptor(prop.getField()),
167+
TypeDescriptor.valueOf(targetClass));
168+
if (canConvert) {
169+
return this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
170+
TypeDescriptor.valueOf(targetClass));
171+
}
113172

114173
Object result = this.conversions.getCustomWriteTarget(prop.getType()) //
115174
.map(it -> this.conversionService.convert(value, new TypeDescriptor(prop.getField()),
@@ -123,7 +182,7 @@ public Object convertForWriteIfNeeded(CouchbasePersistentProperty prop, Converti
123182
/**
124183
* This convertForWriteIfNeed takes only the value to convert. It cannot access the annotations of the Field being
125184
* converted.
126-
*
185+
*
127186
* @param value the value to be converted to the class that would actually be stored.
128187
* @return
129188
*/
@@ -145,13 +204,13 @@ public Object convertToCouchbaseType(Object value, TypeInformation<?> typeInfor
145204
if (value == null) {
146205
return null;
147206
}
148-
207+
149208
return this.conversions.getCustomWriteTarget(value.getClass()) //
150209
.map(it -> (Object) this.conversionService.convert(value, it)) //
151210
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
152-
211+
153212
}
154-
213+
155214
@Override
156215
public Object convertToCouchbaseType(String source) {
157216
return source;
@@ -162,4 +221,9 @@ public Object convertToCouchbaseType(String source) {
162221
public Class<?> getWriteClassFor(Class<?> clazz) {
163222
return this.conversions.getCustomWriteTarget(clazz).orElse(clazz);
164223
}
224+
225+
@Override
226+
public CustomConversions getConversions(){
227+
return conversions;
228+
}
165229
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.springframework.data.couchbase.core.convert;
2+
3+
import org.springframework.beans.PropertyAccessor;
4+
import org.springframework.data.convert.ValueConversionContext;
5+
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
6+
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
7+
import org.springframework.data.util.TypeInformation;
8+
import org.springframework.lang.Nullable;
9+
10+
/**
11+
* {@link ValueConversionContext} that allows to delegate read/write to an underlying {@link CouchbaseConverter}.
12+
*
13+
* @author Christoph Strobl
14+
* @since 3.4
15+
*/
16+
public class CouchbaseConversionContext implements ValueConversionContext<CouchbasePersistentProperty> {
17+
18+
private final CouchbasePersistentProperty persistentProperty;
19+
private final MappingCouchbaseConverter couchbaseConverter;
20+
private final ConvertingPropertyAccessor propertyAccessor;
21+
22+
public CouchbaseConversionContext(CouchbasePersistentProperty persistentProperty,
23+
MappingCouchbaseConverter couchbaseConverter, ConvertingPropertyAccessor accessor) {
24+
25+
this.persistentProperty = persistentProperty;
26+
this.couchbaseConverter = couchbaseConverter;
27+
this.propertyAccessor = accessor;
28+
}
29+
30+
@Override
31+
public CouchbasePersistentProperty getProperty() {
32+
return persistentProperty;
33+
}
34+
35+
@Override
36+
public <T> T write(@Nullable Object value, TypeInformation<T> target) {
37+
return (T) ValueConversionContext.super.write(value, target);
38+
}
39+
40+
@Override
41+
public <T> T read(@Nullable Object value, TypeInformation<T> target) {
42+
return ValueConversionContext.super.read(value, target);
43+
}
44+
45+
public MappingCouchbaseConverter getConverter(){
46+
return couchbaseConverter;
47+
}
48+
49+
public ConvertingPropertyAccessor getAccessor(){
50+
return propertyAccessor;
51+
}
52+
}

spring-data-couchbase/src/main/java/org/springframework/data/couchbase/core/convert/CouchbaseConverter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.data.couchbase.core.convert;
1818

19+
import org.springframework.data.convert.CustomConversions;
1920
import org.springframework.data.convert.EntityConverter;
2021
import org.springframework.data.convert.EntityReader;
2122
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
@@ -80,4 +81,10 @@ Object convertForWriteIfNeeded(final CouchbasePersistentProperty source,
8081
// Object convertToCouchbaseType(Object source, TypeInformation<?> typeInformation);
8182
//
8283
// Object convertToCouchbaseType(String source);
84+
85+
/**
86+
* return the conversions
87+
* @return conversions
88+
*/
89+
CustomConversions getConversions();
8390
}

0 commit comments

Comments
 (0)