1515 */
1616package org .springframework .data .couchbase .cache ;
1717
18+
1819import java .lang .reflect .Method ;
1920import java .util .Arrays ;
2021import java .util .Collection ;
2324import java .util .concurrent .Callable ;
2425
2526import org .springframework .cache .support .AbstractValueAdaptingCache ;
26- import org .springframework .cache .support .SimpleValueWrapper ;
2727import org .springframework .core .convert .ConversionFailedException ;
2828import org .springframework .core .convert .ConversionService ;
2929import org .springframework .core .convert .TypeDescriptor ;
3030import org .springframework .util .Assert ;
3131import org .springframework .util .ObjectUtils ;
3232import org .springframework .util .ReflectionUtils ;
3333
34+ /**
35+ * Couchbase-backed Cache Methods that take a Class return non-wrapped objects - cache-miss cannot be distinguished from
36+ * cached null - this is what AbstractValueAdaptingCache does Methods that do not take a Class return wrapped objects -
37+ * the wrapper is null for cache-miss - the exception is T get(final Object key, final Callable<T> valueLoader), which
38+ * does not return a wrapper because if there is a cache-miss, it gets the value from valueLoader (and caches it). There
39+ * are anomalies with get(key, ValueLoader) - which returns non-wrapped object.
40+ */
3441public class CouchbaseCache extends AbstractValueAdaptingCache {
3542
3643 private final String name ;
@@ -70,11 +77,18 @@ public CouchbaseCacheWriter getNativeCache() {
7077 return cacheWriter ;
7178 }
7279
80+ /**
81+ * same as inherited, but passes clazz for transcoder
82+ */
83+ protected Object lookup (final Object key , Class <?> clazz ) {
84+ return cacheWriter .get (cacheConfig .getCollectionName (), createCacheKey (key ), cacheConfig .getValueTranscoder (),
85+ clazz );
86+ }
87+
7388 @ Override
7489 protected Object lookup (final Object key ) {
75- return cacheWriter . get ( cacheConfig . getCollectionName (), createCacheKey ( key ), cacheConfig . getValueTranscoder () );
90+ return lookup ( key , Object . class );
7691 }
77-
7892 /**
7993 * Returns the configuration for this {@link CouchbaseCache}.
8094 */
@@ -97,33 +111,56 @@ public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
97111 }
98112
99113 @ Override
100- public void put (final Object key , final Object value ) {
101- if (!isAllowNullValues () && value == null ) {
114+ @ SuppressWarnings ("unchecked" )
115+ public <T > T get (final Object key , Class <T > type ) {
116+ Object value = this .fromStoreValue (this .lookup (key , type ));
117+ if (value != null && type != null && !type .isInstance (value )) {
118+ throw new IllegalStateException ("Cached value is not of required type [" + type .getName () + "]: " + value );
119+ } else {
120+ return (T ) value ;
121+ }
122+ }
102123
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 ));
124+ public synchronized <T > T get (final Object key , final Callable <T > valueLoader , Class <T > type ) {
125+ T value = get (key , type );
126+ if (value == null ) { // cannot distinguish between cache miss and cached null
127+ value = valueFromLoader (key , valueLoader );
128+ put (key , value );
107129 }
130+ return value ;
131+ }
108132
133+ @ Override
134+ public void put (final Object key , final Object value ) {
109135 cacheWriter .put (cacheConfig .getCollectionName (), createCacheKey (key ), toStoreValue (value ), cacheConfig .getExpiry (),
110136 cacheConfig .getValueTranscoder ());
111137 }
112138
113139 @ Override
114140 public ValueWrapper putIfAbsent (final Object key , final Object value ) {
115- if (!isAllowNullValues () && value == null ) {
116- return get (key );
117- }
118141
119142 Object result = cacheWriter .putIfAbsent (cacheConfig .getCollectionName (), createCacheKey (key ), toStoreValue (value ),
120143 cacheConfig .getExpiry (), cacheConfig .getValueTranscoder ());
121144
122- if (result == null ) {
123- return null ;
124- }
145+ return toValueWrapper (result );
146+ }
125147
126- return new SimpleValueWrapper (result );
148+ /**
149+ * Not sure why this isn't in AbstractValueAdaptingCache
150+ *
151+ * @param key
152+ * @param value
153+ * @param clazz
154+ * @return
155+ * @param <T>
156+ */
157+ @ SuppressWarnings ("unchecked" )
158+ public <T > T putIfAbsent (final Object key , final Object value , final Class <T > clazz ) {
159+
160+ Object result = cacheWriter .putIfAbsent (cacheConfig .getCollectionName (), createCacheKey (key ),
161+ toStoreValue (value ), cacheConfig .getExpiry (), cacheConfig .getValueTranscoder (), clazz );
162+
163+ return (T ) result ;
127164 }
128165
129166 @ Override
@@ -168,6 +205,9 @@ protected String createCacheKey(final Object key) {
168205 * @throws IllegalStateException if {@code key} cannot be converted to {@link String}.
169206 */
170207 protected String convertKey (final Object key ) {
208+ if (key == null ) {
209+ throw new IllegalArgumentException (String .format ("Cache '%s' does not allow 'null' key." , name ));
210+ }
171211 if (key instanceof String ) {
172212 return (String ) key ;
173213 }
0 commit comments