15
15
*/
16
16
package org .springframework .data .repository .util ;
17
17
18
+ import scala .Option ;
19
+
18
20
import java .util .Collections ;
19
21
import java .util .HashSet ;
20
22
import java .util .Set ;
35
37
36
38
/**
37
39
* Converters to potentially wrap the execution of a repository method into a variety of wrapper types potentially being
38
- * available on the classpath.
40
+ * available on the classpath. Currently supported:
41
+ * <ul>
42
+ * <li>{@code java.util.Optional}</li>
43
+ * <li>{@code com.google.common.base.Optional}</li>
44
+ * <li>{@code scala.Option}</li>
45
+ * <li>{@code java.util.concurrent.Future}</li>
46
+ * <li>{@code java.util.concurrent.CompletableFuture}</li>
47
+ * <li>{@code org.springframework.util.concurrent.ListenableFuture<}</li>
48
+ * </ul>
39
49
*
40
50
* @author Oliver Gierke
41
51
* @since 1.8
@@ -50,6 +60,8 @@ public abstract class QueryExecutionConverters {
50
60
QueryExecutionConverters .class .getClassLoader ());
51
61
private static final boolean JDK_8_PRESENT = ClassUtils .isPresent ("java.util.Optional" ,
52
62
QueryExecutionConverters .class .getClassLoader ());
63
+ private static final boolean SCALA_PRESENT = ClassUtils .isPresent ("scala.Option" ,
64
+ QueryExecutionConverters .class .getClassLoader ());
53
65
54
66
private static final Set <Class <?>> WRAPPER_TYPES = new HashSet <Class <?>>();
55
67
private static final Set <Converter <Object , Object >> UNWRAPPERS = new HashSet <Converter <Object , Object >>();
@@ -72,6 +84,11 @@ public abstract class QueryExecutionConverters {
72
84
if (JDK_8_PRESENT && SPRING_4_2_PRESENT ) {
73
85
WRAPPER_TYPES .add (NullableWrapperToCompletableFutureConverter .getWrapperType ());
74
86
}
87
+
88
+ if (SCALA_PRESENT ) {
89
+ WRAPPER_TYPES .add (NullableWrapperToScalaOptionConverter .getWrapperType ());
90
+ UNWRAPPERS .add (ScalOptionUnwrapper .INSTANCE );
91
+ }
75
92
}
76
93
77
94
private QueryExecutionConverters () {}
@@ -113,6 +130,10 @@ public static void registerConvertersIn(ConfigurableConversionService conversion
113
130
conversionService .addConverter (new NullableWrapperToCompletableFutureConverter (conversionService ));
114
131
}
115
132
133
+ if (SCALA_PRESENT ) {
134
+ conversionService .addConverter (new NullableWrapperToScalaOptionConverter (conversionService ));
135
+ }
136
+
116
137
conversionService .addConverter (new NullableWrapperToFutureConverter (conversionService ));
117
138
}
118
139
@@ -151,20 +172,23 @@ private static abstract class AbstractWrapperTypeConverter implements GenericCon
151
172
@ SuppressWarnings ("unused" ) //
152
173
private final ConversionService conversionService ;
153
174
private final Class <?>[] wrapperTypes ;
175
+ private final Object nullValue ;
154
176
155
177
/**
156
178
* Creates a new {@link AbstractWrapperTypeConverter} using the given {@link ConversionService} and wrapper type.
157
179
*
158
180
* @param conversionService must not be {@literal null}.
159
181
* @param wrapperTypes must not be {@literal null}.
160
182
*/
161
- protected AbstractWrapperTypeConverter (ConversionService conversionService , Class <?>... wrapperTypes ) {
183
+ protected AbstractWrapperTypeConverter (ConversionService conversionService , Object nullValue ,
184
+ Class <?>... wrapperTypes ) {
162
185
163
186
Assert .notNull (conversionService , "ConversionService must not be null!" );
164
187
Assert .notEmpty (wrapperTypes , "Wrapper type must not be empty!" );
165
188
166
189
this .conversionService = conversionService ;
167
190
this .wrapperTypes = wrapperTypes ;
191
+ this .nullValue = nullValue ;
168
192
}
169
193
170
194
/*
@@ -194,16 +218,9 @@ public final Object convert(Object source, TypeDescriptor sourceType, TypeDescri
194
218
Object value = wrapper .getValue ();
195
219
196
220
// TODO: Add Recursive conversion once we move to Spring 4
197
- return value == null ? getNullValue () : wrap (value );
221
+ return value == null ? nullValue : wrap (value );
198
222
}
199
223
200
- /**
201
- * Return the object that shall be used as a replacement for {@literal null}.
202
- *
203
- * @return must not be {@literal null}.
204
- */
205
- protected abstract Object getNullValue ();
206
-
207
224
/**
208
225
* Wrap the given, non-{@literal null} value into the wrapper type.
209
226
*
@@ -226,16 +243,7 @@ private static class NullableWrapperToGuavaOptionalConverter extends AbstractWra
226
243
* @param conversionService must not be {@literal null}.
227
244
*/
228
245
public NullableWrapperToGuavaOptionalConverter (ConversionService conversionService ) {
229
- super (conversionService , Optional .class );
230
- }
231
-
232
- /*
233
- * (non-Javadoc)
234
- * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#getNullValue()
235
- */
236
- @ Override
237
- protected Object getNullValue () {
238
- return Optional .absent ();
246
+ super (conversionService , Optional .absent (), Optional .class );
239
247
}
240
248
241
249
/*
@@ -265,16 +273,7 @@ private static class NullableWrapperToJdk8OptionalConverter extends AbstractWrap
265
273
* @param conversionService must not be {@literal null}.
266
274
*/
267
275
public NullableWrapperToJdk8OptionalConverter (ConversionService conversionService ) {
268
- super (conversionService , java .util .Optional .class );
269
- }
270
-
271
- /*
272
- * (non-Javadoc)
273
- * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#getNullValue()
274
- */
275
- @ Override
276
- protected Object getNullValue () {
277
- return java .util .Optional .empty ();
276
+ super (conversionService , java .util .Optional .empty (), java .util .Optional .class );
278
277
}
279
278
280
279
/*
@@ -298,24 +297,13 @@ public static Class<?> getWrapperType() {
298
297
*/
299
298
private static class NullableWrapperToFutureConverter extends AbstractWrapperTypeConverter {
300
299
301
- private static final AsyncResult <Object > NULL_OBJECT = new AsyncResult <Object >(null );
302
-
303
300
/**
304
301
* Creates a new {@link NullableWrapperToFutureConverter} using the given {@link ConversionService}.
305
302
*
306
303
* @param conversionService must not be {@literal null}.
307
304
*/
308
305
public NullableWrapperToFutureConverter (ConversionService conversionService ) {
309
- super (conversionService , Future .class , ListenableFuture .class );
310
- }
311
-
312
- /*
313
- * (non-Javadoc)
314
- * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#getNullValue()
315
- */
316
- @ Override
317
- protected Object getNullValue () {
318
- return NULL_OBJECT ;
306
+ super (conversionService , new AsyncResult <Object >(null ), Future .class , ListenableFuture .class );
319
307
}
320
308
321
309
/*
@@ -335,24 +323,39 @@ protected Object wrap(Object source) {
335
323
*/
336
324
private static class NullableWrapperToCompletableFutureConverter extends AbstractWrapperTypeConverter {
337
325
338
- private static final CompletableFuture <Object > NULL_OBJECT = CompletableFuture .completedFuture (null );
339
-
340
326
/**
341
327
* Creates a new {@link NullableWrapperToCompletableFutureConverter} using the given {@link ConversionService}.
342
328
*
343
329
* @param conversionService must not be {@literal null}.
344
330
*/
345
331
public NullableWrapperToCompletableFutureConverter (ConversionService conversionService ) {
346
- super (conversionService , CompletableFuture .class );
332
+ super (conversionService , CompletableFuture .completedFuture ( null ), CompletableFuture . class );
347
333
}
348
334
349
335
/*
350
336
* (non-Javadoc)
351
- * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#getNullValue( )
337
+ * @see org.springframework.data.repository.util.QueryExecutionConverters.AbstractWrapperTypeConverter#wrap(java.lang.Object )
352
338
*/
353
339
@ Override
354
- protected Object getNullValue () {
355
- return NULL_OBJECT ;
340
+ protected Object wrap (Object source ) {
341
+ return source instanceof CompletableFuture ? source : CompletableFuture .completedFuture (source );
342
+ }
343
+
344
+ public static Class <?> getWrapperType () {
345
+ return CompletableFuture .class ;
346
+ }
347
+ }
348
+
349
+ /**
350
+ * A Spring {@link Converter} to support Scala's {@link Option}.
351
+ *
352
+ * @author Oliver Gierke
353
+ * @since 1.13
354
+ */
355
+ private static class NullableWrapperToScalaOptionConverter extends AbstractWrapperTypeConverter {
356
+
357
+ public NullableWrapperToScalaOptionConverter (ConversionService conversionService ) {
358
+ super (conversionService , Option .empty (), Option .class );
356
359
}
357
360
358
361
/*
@@ -361,11 +364,11 @@ protected Object getNullValue() {
361
364
*/
362
365
@ Override
363
366
protected Object wrap (Object source ) {
364
- return source instanceof CompletableFuture ? source : CompletableFuture . completedFuture (source );
367
+ return Option . apply (source );
365
368
}
366
369
367
370
public static Class <?> getWrapperType () {
368
- return CompletableFuture .class ;
371
+ return Option .class ;
369
372
}
370
373
}
371
374
@@ -408,4 +411,24 @@ public Object convert(Object source) {
408
411
return source instanceof java .util .Optional ? ((java .util .Optional <?>) source ).orElse (null ) : source ;
409
412
}
410
413
}
414
+
415
+ /**
416
+ * A {@link Converter} to unwrap a Scala {@link Option} instance.
417
+ *
418
+ * @author Oliver Gierke
419
+ * @author 1.13
420
+ */
421
+ private static enum ScalOptionUnwrapper implements Converter <Object , Object > {
422
+
423
+ INSTANCE ;
424
+
425
+ /*
426
+ * (non-Javadoc)
427
+ * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
428
+ */
429
+ @ Override
430
+ public Object convert (Object source ) {
431
+ return source instanceof Option ? ((Option <?>) source ).orNull (null ) : source ;
432
+ }
433
+ }
411
434
}
0 commit comments