1515 */
1616package org .springframework .data .couchbase .core .convert ;
1717
18- import java .lang .reflect .Constructor ;
19- import java .lang .reflect .InvocationTargetException ;
2018import java .nio .charset .StandardCharsets ;
2119import java .util .LinkedList ;
2220import java .util .List ;
21+ import java .util .Locale ;
2322import java .util .Map ;
23+ import java .util .Optional ;
2424
25+ import org .springframework .core .convert .ConversionFailedException ;
2526import org .springframework .core .convert .ConversionService ;
27+ import org .springframework .core .convert .ConverterNotFoundException ;
28+ import org .springframework .data .convert .CustomConversions ;
2629import org .springframework .data .convert .PropertyValueConverter ;
2730import org .springframework .data .convert .ValueConversionContext ;
2831import org .springframework .data .couchbase .core .mapping .CouchbaseDocument ;
2932import org .springframework .data .couchbase .core .mapping .CouchbasePersistentEntity ;
3033import org .springframework .data .couchbase .core .mapping .CouchbasePersistentProperty ;
3134import org .springframework .data .mapping .PersistentProperty ;
35+ import org .springframework .data .mapping .model .ConvertingPropertyAccessor ;
3236import org .springframework .util .Assert ;
3337
3438import com .couchbase .client .core .encryption .CryptoManager ;
39+ import com .couchbase .client .core .error .InvalidArgumentException ;
40+ import com .couchbase .client .java .json .JsonArray ;
3541import com .couchbase .client .java .json .JsonObject ;
42+ import com .couchbase .client .java .json .JsonValue ;
3643
3744/**
3845 * Encrypt/Decrypted properties annotated with
@@ -54,114 +61,216 @@ public Object read(CouchbaseDocument value, ValueConversionContext<? extends Per
5461 if (decrypted == null ) {
5562 return null ;
5663 }
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 ) {
5870 CouchbaseConversionContext ctx = (CouchbaseConversionContext ) context ;
5971 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 ();
6283
63- boolean wasString = false ;
64- List <Exception > exceptionList = new LinkedList <>();
6584 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 ;
7387 }
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+ */
7494
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 + "}" ;
115101 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 );
123107 }
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 );
130108 }
131109 }
132110
133- @ Override
134- public CouchbaseDocument write ( Object value , ValueConversionContext <? extends PersistentProperty <?>> context ) {
111+ private byte [] coerceToBytesWrite ( CouchbasePersistentProperty property , ConvertingPropertyAccessor accessor ,
112+ CouchbaseConversionContext context ) {
135113 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 ();
142115
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
148135 plainString = "\" " + plainString .replaceAll ("\" " , "\\ \" " ) + "\" " ;
149136 }
150137 plainText = plainString .getBytes (StandardCharsets .UTF_8 );
151- } else {
138+ } else { // an entity
152139 plainText = JsonObject .fromJson (context .read (value ).toString ().getBytes (StandardCharsets .UTF_8 )).toBytes ();
153140 }
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 ;
160142 }
161143
162144 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 ());
164148 return cryptoManager ;
165149 }
166150
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+ }
167276}
0 commit comments