15
15
*/
16
16
package org .springframework .data .couchbase .cache ;
17
17
18
+
18
19
import java .lang .reflect .Method ;
19
20
import java .util .Arrays ;
20
21
import java .util .Collection ;
23
24
import java .util .concurrent .Callable ;
24
25
25
26
import org .springframework .cache .support .AbstractValueAdaptingCache ;
26
- import org .springframework .cache .support .SimpleValueWrapper ;
27
27
import org .springframework .core .convert .ConversionFailedException ;
28
28
import org .springframework .core .convert .ConversionService ;
29
29
import org .springframework .core .convert .TypeDescriptor ;
30
+ import org .springframework .lang .NonNull ;
30
31
import org .springframework .util .Assert ;
31
32
import org .springframework .util .ObjectUtils ;
32
33
import org .springframework .util .ReflectionUtils ;
33
34
35
+ /**
36
+ * Couchbase-backed Cache Methods that take a Class return non-wrapped objects - cache-miss cannot be distinguished from
37
+ * cached null - this is what AbstractValueAdaptingCache does Methods that do not take a Class return wrapped objects -
38
+ * the wrapper is null for cache-miss - the exception is T get(final Object key, final Callable<T> valueLoader), which
39
+ * does not return a wrapper because if there is a cache-miss, it gets the value from valueLoader (and caches it). There
40
+ * are anomalies with get(key, ValueLoader) - which returns non-wrapped object.
41
+ */
34
42
public class CouchbaseCache extends AbstractValueAdaptingCache {
35
43
36
44
private final String name ;
@@ -60,21 +68,30 @@ private static <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
60
68
}
61
69
}
62
70
71
+ @ NonNull
63
72
@ Override
64
73
public String getName () {
65
74
return name ;
66
75
}
67
76
77
+ @ NonNull
68
78
@ Override
69
79
public CouchbaseCacheWriter getNativeCache () {
70
80
return cacheWriter ;
71
81
}
72
82
73
- @ Override
74
- protected Object lookup (final Object key ) {
75
- return cacheWriter .get (cacheConfig .getCollectionName (), createCacheKey (key ), cacheConfig .getValueTranscoder ());
83
+ /**
84
+ * same as inherited, but passes clazz for transcoder
85
+ */
86
+ protected Object lookup (final Object key , Class <?> clazz ) {
87
+ return cacheWriter .get (cacheConfig .getCollectionName (), createCacheKey (key ), cacheConfig .getValueTranscoder (),
88
+ clazz );
76
89
}
77
90
91
+ @ Override
92
+ protected Object lookup (@ NonNull final Object key ) {
93
+ return lookup (key , Object .class );
94
+ }
78
95
/**
79
96
* Returns the configuration for this {@link CouchbaseCache}.
80
97
*/
@@ -84,7 +101,7 @@ public CouchbaseCacheConfiguration getCacheConfiguration() {
84
101
85
102
@ Override
86
103
@ SuppressWarnings ("unchecked" )
87
- public synchronized <T > T get (final Object key , final Callable <T > valueLoader ) {
104
+ public synchronized <T > T get (@ NonNull final Object key , @ NonNull final Callable <T > valueLoader ) {
88
105
ValueWrapper result = get (key );
89
106
90
107
if (result != null ) {
@@ -97,42 +114,65 @@ public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
97
114
}
98
115
99
116
@ Override
100
- public void put (final Object key , final Object value ) {
101
- if (!isAllowNullValues () && value == null ) {
117
+ @ SuppressWarnings ("unchecked" )
118
+ public <T > T get (@ NonNull final Object key , Class <T > type ) {
119
+ Object value = this .fromStoreValue (this .lookup (key , type ));
120
+ if (value != null && type != null && !type .isInstance (value )) {
121
+ throw new IllegalStateException ("Cached value is not of required type [" + type .getName () + "]: " + value );
122
+ } else {
123
+ return (T ) value ;
124
+ }
125
+ }
102
126
103
- throw new IllegalArgumentException (String .format (
104
- "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\" #result == null\" )' or "
105
- + "configure CouchbaseCache to allow 'null' via CouchbaseCacheConfiguration." ,
106
- name ));
127
+ public synchronized <T > T get (@ NonNull final Object key , final Callable <T > valueLoader , Class <T > type ) {
128
+ T value = get (key , type );
129
+ if (value == null ) { // cannot distinguish between cache miss and cached null
130
+ value = valueFromLoader (key , valueLoader );
131
+ put (key , value );
107
132
}
133
+ return value ;
134
+ }
108
135
136
+ @ Override
137
+ public void put (@ NonNull final Object key , final Object value ) {
109
138
cacheWriter .put (cacheConfig .getCollectionName (), createCacheKey (key ), toStoreValue (value ), cacheConfig .getExpiry (),
110
139
cacheConfig .getValueTranscoder ());
111
140
}
112
141
113
142
@ Override
114
- public ValueWrapper putIfAbsent (final Object key , final Object value ) {
115
- if (!isAllowNullValues () && value == null ) {
116
- return get (key );
117
- }
143
+ public ValueWrapper putIfAbsent (@ NonNull final Object key , final Object value ) {
118
144
119
145
Object result = cacheWriter .putIfAbsent (cacheConfig .getCollectionName (), createCacheKey (key ), toStoreValue (value ),
120
146
cacheConfig .getExpiry (), cacheConfig .getValueTranscoder ());
121
147
122
- if (result == null ) {
123
- return null ;
124
- }
148
+ return toValueWrapper (result );
149
+ }
125
150
126
- return new SimpleValueWrapper (result );
151
+ /**
152
+ * Not sure why this isn't in AbstractValueAdaptingCache
153
+ *
154
+ * @param key
155
+ * @param value
156
+ * @param clazz
157
+ * @return
158
+ * @param <T>
159
+ */
160
+ @ SuppressWarnings ("unchecked" )
161
+ public <T > T putIfAbsent (@ NonNull final Object key , final Object value , final Class <T > clazz ) {
162
+
163
+ Object result = cacheWriter .putIfAbsent (cacheConfig .getCollectionName (), createCacheKey (key ),
164
+ toStoreValue (value ), cacheConfig .getExpiry (), cacheConfig .getValueTranscoder (), clazz );
165
+
166
+ return (T ) result ;
127
167
}
128
168
129
169
@ Override
130
- public void evict (final Object key ) {
170
+ public void evict (@ NonNull final Object key ) {
131
171
cacheWriter .remove (cacheConfig .getCollectionName (), createCacheKey (key ));
132
172
}
133
173
134
174
@ Override
135
- public boolean evictIfPresent (final Object key ) {
175
+ public boolean evictIfPresent (@ NonNull final Object key ) {
136
176
return cacheWriter .remove (cacheConfig .getCollectionName (), createCacheKey (key ));
137
177
}
138
178
@@ -152,7 +192,7 @@ public void clear() {
152
192
* @param key will never be {@literal null}.
153
193
* @return never {@literal null}.
154
194
*/
155
- protected String createCacheKey (final Object key ) {
195
+ protected String createCacheKey (@ NonNull final Object key ) {
156
196
String convertedKey = convertKey (key );
157
197
if (!cacheConfig .usePrefix ()) {
158
198
return convertedKey ;
@@ -167,7 +207,10 @@ protected String createCacheKey(final Object key) {
167
207
* @return never {@literal null}.
168
208
* @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
169
209
*/
170
- protected String convertKey (final Object key ) {
210
+ protected String convertKey (@ NonNull final Object key ) {
211
+ if (key == null ) {
212
+ throw new IllegalArgumentException (String .format ("Cache '%s' does not allow 'null' key." , name ));
213
+ }
171
214
if (key instanceof String ) {
172
215
return (String ) key ;
173
216
}
0 commit comments