15
15
*/
16
16
package org .springframework .data .couchbase .core .convert ;
17
17
18
- import java .lang .reflect .Constructor ;
19
- import java .lang .reflect .InvocationTargetException ;
20
18
import java .nio .charset .StandardCharsets ;
21
19
import java .util .LinkedList ;
22
20
import java .util .List ;
21
+ import java .util .Locale ;
23
22
import java .util .Map ;
23
+ import java .util .Optional ;
24
24
25
+ import org .springframework .core .convert .ConversionFailedException ;
25
26
import org .springframework .core .convert .ConversionService ;
27
+ import org .springframework .core .convert .ConverterNotFoundException ;
28
+ import org .springframework .data .convert .CustomConversions ;
26
29
import org .springframework .data .convert .PropertyValueConverter ;
27
30
import org .springframework .data .convert .ValueConversionContext ;
28
31
import org .springframework .data .couchbase .core .mapping .CouchbaseDocument ;
29
32
import org .springframework .data .couchbase .core .mapping .CouchbasePersistentEntity ;
30
33
import org .springframework .data .couchbase .core .mapping .CouchbasePersistentProperty ;
31
34
import org .springframework .data .mapping .PersistentProperty ;
35
+ import org .springframework .data .mapping .model .ConvertingPropertyAccessor ;
32
36
import org .springframework .util .Assert ;
33
37
34
38
import com .couchbase .client .core .encryption .CryptoManager ;
39
+ import com .couchbase .client .core .error .InvalidArgumentException ;
40
+ import com .couchbase .client .java .json .JsonArray ;
35
41
import com .couchbase .client .java .json .JsonObject ;
42
+ import com .couchbase .client .java .json .JsonValue ;
36
43
37
44
/**
38
45
* Encrypt/Decrypted properties annotated with
@@ -54,114 +61,216 @@ public Object read(CouchbaseDocument value, ValueConversionContext<? extends Per
54
61
if (decrypted == null ) {
55
62
return null ;
56
63
}
57
- // it's decrypted into a String. Now figure out how to convert to the property type.
64
+ // it's decrypted to byte[]. Now figure out how to convert to the property type.
65
+ return coerceToValueRead (decrypted , (CouchbaseConversionContext ) context );
66
+ }
67
+
68
+ @ Override
69
+ public CouchbaseDocument write (Object value , ValueConversionContext <? extends PersistentProperty <?>> context ) {
58
70
CouchbaseConversionContext ctx = (CouchbaseConversionContext ) context ;
59
71
CouchbasePersistentProperty property = ctx .getProperty ();
60
- org .springframework .data .convert .CustomConversions conversions = ctx .getConverter ().getConversions ();
61
- ConversionService svc = ctx .getConverter ().conversionService ;
72
+ byte [] plainText = coerceToBytesWrite (property , ctx .getAccessor (), ctx );
73
+ Map <String , Object > encrypted = cryptoManager ().encrypt (plainText , CryptoManager .DEFAULT_ENCRYPTER_ALIAS );
74
+ return new CouchbaseDocument ().setContent (encrypted );
75
+ }
76
+
77
+ private Object coerceToValueRead (byte [] decrypted , CouchbaseConversionContext context ) {
78
+ CouchbasePersistentProperty property = context .getProperty ();
79
+
80
+ CustomConversions cnvs = context .getConverter ().getConversions ();
81
+ ConversionService svc = context .getConverter ().getConversionService ();
82
+ Class <?> type = property .getType ();
62
83
63
- boolean wasString = false ;
64
- List <Exception > exceptionList = new LinkedList <>();
65
84
String decryptedString = new String (decrypted );
66
- if (conversions .isSimpleType (property .getType ())
67
- || conversions .hasCustomReadTarget (String .class , context .getProperty ().getType ())) {
68
- if (decryptedString .startsWith ("\" " ) && decryptedString .endsWith ("\" " )) {
69
- decryptedString = decryptedString .substring (1 , decryptedString .length () - 1 );
70
- decryptedString = decryptedString .replaceAll ("\\ \" " , "\" " );
71
- wasString = true ;
72
- }
85
+ if ("null" .equals (decryptedString )) {
86
+ return null ;
73
87
}
88
+ /* this what we would do if we could use a JsonParser with a beanPropertyTypeRef
89
+ final JsonParser plaintextParser = p.getCodec().getFactory().createParser(plaintext);
90
+ plaintextParser.setCodec(p.getCodec());
91
+
92
+ return plaintextParser.readValueAs(beanPropertyTypeRef);
93
+ */
74
94
75
- if (conversions .isSimpleType (property .getType ())) {
76
- if (wasString && conversions .hasCustomReadTarget (String .class , context .getProperty ().getType ())) {
77
- try {
78
- return svc .convert (decryptedString , context .getProperty ().getType ());
79
- } catch (Exception e ) {
80
- exceptionList .add (e );
81
- }
82
- }
83
- if (conversions .hasCustomReadTarget (Long .class , context .getProperty ().getType ())) {
84
- try {
85
- return svc .convert (Long .valueOf (decryptedString ), property .getType ());
86
- } catch (Exception e ) {
87
- exceptionList .add (e );
88
- }
89
- }
90
- if (conversions .hasCustomReadTarget (Double .class , context .getProperty ().getType ())) {
91
- try {
92
- return svc .convert (Double .valueOf (decryptedString ), property .getType ());
93
- } catch (Exception e ) {
94
- exceptionList .add (e );
95
- }
96
- }
97
- if (conversions .hasCustomReadTarget (Boolean .class , context .getProperty ().getType ())) {
98
- try {
99
- Object booleanResult = svc .convert (Boolean .valueOf (decryptedString ), property .getType ());
100
- if (booleanResult == null ) {
101
- throw new Exception ("value " + decryptedString + " would not convert to boolean" );
102
- }
103
- } catch (Exception e ) {
104
- exceptionList .add (e );
105
- }
106
- }
107
- // let's try to find a constructor...
108
- try {
109
- Constructor <?> constructor = context .getProperty ().getType ().getConstructor (String .class );
110
- return constructor .newInstance (decryptedString );
111
- } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e ) {
112
- exceptionList .add (new Exception ("tried to instantiate from constructor taking string arg but got " + e ));
113
- }
114
- // last chance...
95
+ if (!cnvs .isSimpleType (type ) && !type .isArray ()) {
96
+ JsonObject jo = JsonObject .fromJson (decryptedString );
97
+ CouchbaseDocument source = new CouchbaseDocument ().setContent (jo );
98
+ return context .getConverter ().read (property .getTypeInformation (), source );
99
+ } else {
100
+ String jsonString = "{\" " + property .getFieldName () + "\" :" + decryptedString + "}" ;
115
101
try {
116
- return ctx .getConverter ().getPotentiallyConvertedSimpleRead (decryptedString , property );
117
- } catch (Exception e ) {
118
- exceptionList .add (e );
119
- RuntimeException ee = new RuntimeException (
120
- "failed to convert " + decryptedString + " due to the following suppressed reasons(s): " );
121
- exceptionList .stream ().forEach (ee ::addSuppressed );
122
- throw ee ;
102
+ CouchbaseDocument decryptedDoc = new CouchbaseDocument ().setContent (JsonObject .fromJson (jsonString ));
103
+ return context .getConverter ().getPotentiallyConvertedSimpleRead (decryptedDoc .get (property .getFieldName ()),
104
+ property );
105
+ } catch (InvalidArgumentException | ConverterNotFoundException | ConversionFailedException e ) {
106
+ throw new RuntimeException (decryptedString , e );
123
107
}
124
-
125
- } else {
126
- CouchbaseDocument decryptedDoc = new CouchbaseDocument ().setContent (JsonObject .fromJson (decrypted ));
127
- CouchbasePersistentEntity <?> entity = ctx .getConverter ().getMappingContext ()
128
- .getRequiredPersistentEntity (property .getType ());
129
- return ctx .getConverter ().read (entity , decryptedDoc , null );
130
108
}
131
109
}
132
110
133
- @ Override
134
- public CouchbaseDocument write ( Object value , ValueConversionContext <? extends PersistentProperty <?>> context ) {
111
+ private byte [] coerceToBytesWrite ( CouchbasePersistentProperty property , ConvertingPropertyAccessor accessor ,
112
+ CouchbaseConversionContext context ) {
135
113
byte [] plainText ;
136
- CouchbaseConversionContext ctx = (CouchbaseConversionContext ) context ;
137
- CouchbasePersistentProperty property = ctx .getProperty ();
138
- org .springframework .data .convert .CustomConversions conversions = ctx .getConverter ().getConversions ();
139
-
140
- Class <?> sourceType = context .getProperty ().getType ();
141
- Class <?> targetType = conversions .getCustomWriteTarget (context .getProperty ().getType ()).orElse (null );
114
+ CustomConversions cnvs = context .getConverter ().getConversions ();
142
115
143
- value = ctx .getConverter ().getPotentiallyConvertedSimpleWrite (property , ctx .getAccessor (), false );
144
- if (conversions .isSimpleType (sourceType )) {
145
- String plainString ;
146
- plainString = (String ) value ;
147
- if (sourceType == String .class || targetType == String .class ) {
116
+ Class <?> sourceType = property .getType ();
117
+ Class <?> targetType = cnvs .getCustomWriteTarget (property .getType ()).orElse (null );
118
+ Object value = context .getConverter ().getPotentiallyConvertedSimpleWrite (property , accessor , false );
119
+ if (value == null ) { // null
120
+ plainText = "null" .getBytes (StandardCharsets .UTF_8 );
121
+ } else if (value .getClass ().isArray ()) { // array
122
+ JsonArray ja ;
123
+ if (value .getClass ().getComponentType ().isPrimitive ()) {
124
+ ja = jaFromPrimitiveArray (value );
125
+ } else {
126
+ ja = jaFromObjectArray (value , context .getConverter ());
127
+ }
128
+ plainText = ja .toBytes ();
129
+ } else if (cnvs .isSimpleType (sourceType )) { // simpleType
130
+ String plainString = value != null ? value .toString () : null ;
131
+ if ((sourceType == String .class || targetType == String .class ) || sourceType == Character .class
132
+ || sourceType == char .class || Enum .class .isAssignableFrom (sourceType )
133
+ || Locale .class .isAssignableFrom (sourceType )) {
134
+ // TODO use jackson serializer here
148
135
plainString = "\" " + plainString .replaceAll ("\" " , "\\ \" " ) + "\" " ;
149
136
}
150
137
plainText = plainString .getBytes (StandardCharsets .UTF_8 );
151
- } else {
138
+ } else { // an entity
152
139
plainText = JsonObject .fromJson (context .read (value ).toString ().getBytes (StandardCharsets .UTF_8 )).toBytes ();
153
140
}
154
- Map <String , Object > encrypted = cryptoManager ().encrypt (plainText , CryptoManager .DEFAULT_ENCRYPTER_ALIAS );
155
- CouchbaseDocument encryptedDoc = new CouchbaseDocument ();
156
- for (Map .Entry <String , Object > entry : encrypted .entrySet ()) {
157
- encryptedDoc .put (entry .getKey (), entry .getValue ());
158
- }
159
- return encryptedDoc ;
141
+ return plainText ;
160
142
}
161
143
162
144
CryptoManager cryptoManager () {
163
- Assert .notNull (cryptoManager , "cryptoManager is null" );
145
+ Assert .notNull (cryptoManager ,
146
+ "cryptoManager needed to encrypt/decrypt but it is null. Override needed for cryptoManager() method of "
147
+ + AbstractCouchbaseConverter .class .getName ());
164
148
return cryptoManager ;
165
149
}
166
150
151
+ JsonArray jaFromObjectArray (Object value , MappingCouchbaseConverter converter ) {
152
+ CustomConversions cnvs = converter .getConversions ();
153
+ ConversionService svc = converter .getConversionService ();
154
+ JsonArray ja = JsonArray .ja ();
155
+ for (Object o : (Object []) value ) {
156
+ ja .add (coerceToJson (o , cnvs , svc ));
157
+ }
158
+ return ja ;
159
+ }
160
+
161
+ JsonArray jaFromPrimitiveArray (Object value ) {
162
+ Class <?> component = value .getClass ().getComponentType ();
163
+ JsonArray jArray ;
164
+ if (Long .TYPE .isAssignableFrom (component )) {
165
+ jArray = ja_long ((long []) value );
166
+ } else if (Integer .TYPE .isAssignableFrom (component )) {
167
+ jArray = ja_int ((int []) value );
168
+ } else if (Double .TYPE .isAssignableFrom (component )) {
169
+ jArray = ja_double ((double []) value );
170
+ } else if (Float .TYPE .isAssignableFrom (component )) {
171
+ jArray = ja_float ((float []) value );
172
+ } else if (Boolean .TYPE .isAssignableFrom (component )) {
173
+ jArray = ja_boolean ((boolean []) value );
174
+ } else if (Short .TYPE .isAssignableFrom (component )) {
175
+ jArray = ja_short ((short []) value );
176
+ } else if (Byte .TYPE .isAssignableFrom (component )) {
177
+ jArray = ja_byte ((byte []) value );
178
+ } else if (Character .TYPE .isAssignableFrom (component )) {
179
+ jArray = ja_char ((char []) value );
180
+ } else {
181
+ throw new RuntimeException ("unhandled primitive array: " + component .getName ());
182
+ }
183
+ return jArray ;
184
+ }
185
+
186
+ JsonArray ja_long (long [] array ) {
187
+ JsonArray ja = JsonArray .ja ();
188
+ for (long t : array ) {
189
+ ja .add (t );
190
+ }
191
+ return ja ;
192
+ }
193
+
194
+ JsonArray ja_int (int [] array ) {
195
+ JsonArray ja = JsonArray .ja ();
196
+ for (int t : array ) {
197
+ ja .add (t );
198
+ }
199
+ return ja ;
200
+ }
201
+
202
+ JsonArray ja_double (double [] array ) {
203
+ JsonArray ja = JsonArray .ja ();
204
+ for (double t : array ) {
205
+ ja .add (t );
206
+ }
207
+ return ja ;
208
+ }
209
+
210
+ JsonArray ja_float (float [] array ) {
211
+ JsonArray ja = JsonArray .ja ();
212
+ for (float t : array ) {
213
+ ja .add (t );
214
+ }
215
+ return ja ;
216
+ }
217
+
218
+ JsonArray ja_boolean (boolean [] array ) {
219
+ JsonArray ja = JsonArray .ja ();
220
+ for (boolean t : array ) {
221
+ ja .add (t );
222
+ }
223
+ return ja ;
224
+ }
225
+
226
+ JsonArray ja_short (short [] array ) {
227
+ JsonArray ja = JsonArray .ja ();
228
+ for (short t : array ) {
229
+ ja .add (t );
230
+ }
231
+ return ja ;
232
+ }
233
+
234
+ JsonArray ja_byte (byte [] array ) {
235
+ JsonArray ja = JsonArray .ja ();
236
+ for (byte t : array ) {
237
+ ja .add (t );
238
+ }
239
+ return ja ;
240
+ }
241
+
242
+ JsonArray ja_char (char [] array ) {
243
+ JsonArray ja = JsonArray .ja ();
244
+ for (char t : array ) {
245
+ ja .add (String .valueOf (t ));
246
+ }
247
+ return ja ;
248
+ }
249
+
250
+ Object coerceToJson (Object o , CustomConversions cnvs , ConversionService svc ) {
251
+ if (o != null && o .getClass () == Optional .class ) {
252
+ o = ((Optional <?>) o ).isEmpty () ? null : ((Optional ) o ).get ();
253
+ }
254
+ Optional <Class <?>> clazz ;
255
+ if (o == null ) {
256
+ o = JsonValue .NULL ;
257
+ } else if ((clazz = cnvs .getCustomWriteTarget (o .getClass ())).isPresent ()) {
258
+ o = svc .convert (o , clazz .get ());
259
+ } else if (JsonObject .checkType (o )) {
260
+ // The object is of an acceptable type
261
+ } else if (Number .class .isAssignableFrom (o .getClass ())) {
262
+ if (o .toString ().contains ("." )) {
263
+ o = ((Number ) o ).doubleValue ();
264
+ } else {
265
+ o = ((Number ) o ).longValue ();
266
+ }
267
+ } else if (Character .class .isAssignableFrom (o .getClass ())) {
268
+ o = ((Character ) o ).toString ();
269
+ } else if (Enum .class .isAssignableFrom (o .getClass ())) {
270
+ o = ((Enum ) o ).name ();
271
+ } else { // punt
272
+ o = o .toString ();
273
+ }
274
+ return o ;
275
+ }
167
276
}
0 commit comments