Skip to content

Commit b3d8927

Browse files
committed
Add transcoder support on Cache get, putIfAbsent.
Closes #1966.
1 parent 10f71c9 commit b3d8927

File tree

5 files changed

+240
-12
lines changed

5 files changed

+240
-12
lines changed

src/main/java/org/springframework/data/couchbase/cache/CouchbaseCache.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package org.springframework.data.couchbase.cache;
1717

18+
import io.micrometer.common.lang.Nullable;
19+
1820
import java.lang.reflect.Method;
1921
import java.util.Arrays;
2022
import java.util.Collection;
@@ -70,11 +72,18 @@ public CouchbaseCacheWriter getNativeCache() {
7072
return cacheWriter;
7173
}
7274

75+
/**
76+
* same as inherited, but passes clazz for transcoder
77+
*/
78+
protected Object lookup(final Object key, @Nullable Class<?> clazz) {
79+
return cacheWriter.get(cacheConfig.getCollectionName(), createCacheKey(key), cacheConfig.getValueTranscoder(),
80+
clazz);
81+
}
82+
7383
@Override
7484
protected Object lookup(final Object key) {
75-
return cacheWriter.get(cacheConfig.getCollectionName(), createCacheKey(key), cacheConfig.getValueTranscoder());
85+
return lookup(key, Object.class);
7686
}
77-
7887
/**
7988
* Returns the configuration for this {@link CouchbaseCache}.
8089
*/
@@ -96,6 +105,17 @@ public synchronized <T> T get(final Object key, final Callable<T> valueLoader) {
96105
return value;
97106
}
98107

108+
@org.springframework.lang.Nullable
109+
public <T> T get(Object key, @org.springframework.lang.Nullable Class<T> type) {
110+
Object value = this.fromStoreValue(this.lookup(key, type));
111+
if (value != null && type != null && !type.isInstance(value)) {
112+
String var10002 = type.getName();
113+
throw new IllegalStateException("Cached value is not of required type [" + var10002 + "]: " + value);
114+
} else {
115+
return (T) value;
116+
}
117+
}
118+
99119
@Override
100120
public void put(final Object key, final Object value) {
101121
if (!isAllowNullValues() && value == null) {
@@ -126,6 +146,26 @@ public ValueWrapper putIfAbsent(final Object key, final Object value) {
126146
return new SimpleValueWrapper(result);
127147
}
128148

149+
/**
150+
* Not sure why this isn't in AbstractValueAdaptingCache
151+
*
152+
* @param key
153+
* @param value
154+
* @param clazz
155+
* @return
156+
* @param <T>
157+
*/
158+
public <T> T putIfAbsent(final Object key, final Object value, final Class<T> clazz) {
159+
if (!isAllowNullValues() && value == null) {
160+
return get(key, clazz);
161+
}
162+
163+
Object result = cacheWriter.putIfAbsent(cacheConfig.getCollectionName(), createCacheKey(key),
164+
toStoreValue(value), cacheConfig.getExpiry(), cacheConfig.getValueTranscoder(), clazz);
165+
166+
return (T) result;
167+
}
168+
129169
@Override
130170
public void evict(final Object key) {
131171
cacheWriter.remove(cacheConfig.getCollectionName(), createCacheKey(key));

src/main/java/org/springframework/data/couchbase/cache/CouchbaseCacheWriter.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,25 @@ public interface CouchbaseCacheWriter {
4444
* @param expiry Optional expiration time. Can be {@literal null}.
4545
* @param transcoder Optional transcoder to use. Can be {@literal null}.
4646
*/
47+
@Deprecated
4748
@Nullable
4849
Object putIfAbsent(String collectionName, String key, Object value, @Nullable Duration expiry,
4950
@Nullable Transcoder transcoder);
5051

52+
/**
53+
* Write the given value to Couchbase if the key does not already exist.
54+
*
55+
* @param collectionName The cache name must not be {@literal null}.
56+
* @param key The key for the cache entry. Must not be {@literal null}.
57+
* @param value The value stored for the key. Must not be {@literal null}.
58+
* @param expiry Optional expiration time. Can be {@literal null}.
59+
* @param transcoder Optional transcoder to use. Can be {@literal null}.
60+
* @param clazz Optional class for contentAs(clazz)
61+
*/
62+
@Nullable
63+
Object putIfAbsent(String collectionName, String key, Object value, @Nullable Duration expiry,
64+
@Nullable Transcoder transcoder, @Nullable Class<?> clazz);
65+
5166
/**
5267
* Get the binary value representation from Couchbase stored for the given key.
5368
*
@@ -56,9 +71,22 @@ Object putIfAbsent(String collectionName, String key, Object value, @Nullable Du
5671
* @param transcoder Optional transcoder to use. Can be {@literal null}.
5772
* @return {@literal null} if key does not exist.
5873
*/
74+
@Deprecated
5975
@Nullable
6076
Object get(String collectionName, String key, @Nullable Transcoder transcoder);
6177

78+
/**
79+
* Get the binary value representation from Couchbase stored for the given key.
80+
*
81+
* @param collectionName must not be {@literal null}.
82+
* @param key must not be {@literal null}.
83+
* @param transcoder Optional transcoder to use. Can be {@literal null}.
84+
* @param clazz Optional class for contentAs(clazz)
85+
* @return {@literal null} if key does not exist.
86+
*/
87+
@Nullable
88+
Object get(String collectionName, String key, @Nullable Transcoder transcoder, @Nullable Class<?> clazz);
89+
6290
/**
6391
* Remove the given key from Couchbase.
6492
*

src/main/java/org/springframework/data/couchbase/cache/DefaultCouchbaseCacheWriter.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@
1818

1919
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
2020
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_SCOPE;
21-
import static com.couchbase.client.java.kv.GetOptions.*;
22-
import static com.couchbase.client.java.kv.InsertOptions.*;
23-
import static com.couchbase.client.java.kv.UpsertOptions.*;
24-
import static com.couchbase.client.java.query.QueryOptions.*;
21+
import static com.couchbase.client.java.kv.GetOptions.getOptions;
22+
import static com.couchbase.client.java.kv.InsertOptions.insertOptions;
23+
import static com.couchbase.client.java.kv.UpsertOptions.upsertOptions;
24+
import static com.couchbase.client.java.query.QueryOptions.queryOptions;
2525
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
2626

27+
import io.micrometer.common.lang.Nullable;
28+
2729
import java.time.Duration;
2830

2931
import org.springframework.data.couchbase.CouchbaseClientFactory;
@@ -62,9 +64,26 @@ public void put(final String collectionName, final String key, final Object valu
6264
getCollection(collectionName).upsert(key, value, options);
6365
}
6466

67+
@Deprecated
6568
@Override
6669
public Object putIfAbsent(final String collectionName, final String key, final Object value, final Duration expiry,
6770
final Transcoder transcoder) {
71+
return putIfAbsent(collectionName, key, value, expiry, transcoder, Object.class);
72+
}
73+
74+
/**
75+
* same as above, plus clazz
76+
*
77+
* @param collectionName
78+
* @param key
79+
* @param value
80+
* @param expiry
81+
* @param transcoder
82+
* @param clazz
83+
*/
84+
@Override
85+
public Object putIfAbsent(final String collectionName, final String key, final Object value, final Duration expiry,
86+
final Transcoder transcoder, @Nullable final Class<?> clazz) {
6887
InsertOptions options = insertOptions();
6988

7089
if (expiry != null) {
@@ -79,15 +98,21 @@ public Object putIfAbsent(final String collectionName, final String key, final O
7998
return null;
8099
} catch (final DocumentExistsException ex) {
81100
// If the document exists, return the current one per contract
82-
return get(collectionName, key, transcoder);
101+
return get(collectionName, key, transcoder, clazz);
83102
}
84103
}
85104

105+
@Deprecated
86106
@Override
87107
public Object get(final String collectionName, final String key, final Transcoder transcoder) {
88-
// TODO .. the decoding side transcoding needs to be figured out?
108+
return get(collectionName, key, transcoder, Object.class);
109+
}
110+
111+
@Override
112+
public Object get(final String collectionName, final String key, final Transcoder transcoder,
113+
final Class<?> clazz) {
89114
try {
90-
return getCollection(collectionName).get(key, getOptions().transcoder(transcoder)).contentAs(Object.class);
115+
return getCollection(collectionName).get(key, getOptions().transcoder(transcoder)).contentAs(clazz);
91116
} catch (DocumentNotFoundException ex) {
92117
return null;
93118
}

src/test/java/org/springframework/data/couchbase/cache/CacheUser.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
*/
2727
class CacheUser implements Serializable {
2828
// private static final long serialVersionUID = 8817717605659870262L;
29-
String firstname;
30-
String lastname;
31-
String id;
29+
String firstname; // must have getter/setter for Serialize/Deserialize
30+
String lastname; // must have getter/setter for Serialize/Deserialize
31+
String id; // must have getter/setter for Serialize/Deserialize
32+
33+
public CacheUser() {};
3234

3335
public CacheUser(String id, String firstname, String lastname) {
3436
this.id = id;
@@ -40,6 +42,25 @@ public String getId() {
4042
return id;
4143
}
4244

45+
public String getFirstname() {
46+
return firstname;
47+
}
48+
49+
public String getLastname() {
50+
return lastname;
51+
}
52+
53+
public void setId(String id) {
54+
this.id = id;
55+
}
56+
57+
public void setFirstname(String firstname) {
58+
this.firstname = firstname;
59+
}
60+
61+
public void setLastname(String lastname) {
62+
this.lastname = lastname;
63+
}
4364
// define equals for assertEquals()
4465
public boolean equals(Object o) {
4566
if (o == null) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2022-2024 the original author or authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.couchbase.cache;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNull;
21+
22+
import java.util.UUID;
23+
24+
import org.junit.jupiter.api.BeforeEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.springframework.beans.factory.annotation.Autowired;
27+
import org.springframework.data.couchbase.core.CouchbaseTemplate;
28+
import org.springframework.data.couchbase.domain.Config;
29+
import org.springframework.data.couchbase.util.Capabilities;
30+
import org.springframework.data.couchbase.util.ClusterType;
31+
import org.springframework.data.couchbase.util.CollectionAwareIntegrationTests;
32+
import org.springframework.data.couchbase.util.IgnoreWhen;
33+
import org.springframework.test.annotation.DirtiesContext;
34+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
35+
36+
/**
37+
* CouchbaseCache tests Theses tests rely on a cb server running.
38+
*
39+
* @author Michael Reiche
40+
*/
41+
@IgnoreWhen(clusterTypes = ClusterType.MOCKED, missesCapabilities = { Capabilities.COLLECTIONS })
42+
@SpringJUnitConfig(Config.class)
43+
@DirtiesContext
44+
class CouchbaseCacheCollectionTranscoderIntegrationTests extends CollectionAwareIntegrationTests {
45+
46+
volatile CouchbaseCache cache;
47+
48+
@Autowired CouchbaseTemplate couchbaseTemplate;
49+
50+
@BeforeEach
51+
@Override
52+
public void beforeEach() {
53+
super.beforeEach();
54+
cache = CouchbaseCacheManager.create(couchbaseTemplate.getCouchbaseClientFactory()).createCouchbaseCache(
55+
"myCache", CouchbaseCacheConfiguration.defaultCacheConfig().collection("my_collection").valueTranscoder(
56+
couchbaseTemplate.getCouchbaseClientFactory().getCluster().environment().transcoder()));
57+
58+
cache.clear();
59+
}
60+
61+
@Test
62+
void cachePutGet() {
63+
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
64+
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
65+
assertNull(cache.get(user1.getId(), CacheUser.class)); // was not put -> cacheMiss
66+
cache.put(user1.getId(), user1); // put user1
67+
cache.put(user2.getId(), user2); // put user2
68+
assertEquals(user1, cache.get(user1.getId(), CacheUser.class)); // get user1
69+
assertEquals(user2, cache.get(user2.getId(), CacheUser.class)); // get user2
70+
}
71+
72+
@Test
73+
void cacheEvict() {
74+
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
75+
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
76+
cache.put(user1.getId(), user1); // put user1
77+
cache.put(user2.getId(), user2); // put user2
78+
cache.evict(user1.getId()); // evict user1
79+
assertNull(cache.get(user1.getId(), CacheUser.class)); // get user1 -> not present
80+
assertEquals(user2, cache.get(user2.getId(), CacheUser.class)); // get user2 -> present
81+
}
82+
83+
@Test
84+
void cacheClear() {
85+
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
86+
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
87+
cache.put(user1.getId(), user1); // put user1
88+
cache.put(user2.getId(), user2); // put user2
89+
cache.clear();
90+
assertNull(cache.get(user1.getId(), CacheUser.class)); // get user1 -> not present
91+
assertNull(cache.get(user2.getId(), CacheUser.class)); // get user2 -> not present
92+
}
93+
94+
@Test
95+
void cacheHitMiss() {
96+
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
97+
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
98+
assertNull(cache.get(user2.getId(), CacheUser.class)); // get user2 -> cacheMiss
99+
// cache.put(user1.getId(), null); // JsonTranscoder cannot handle null
100+
// assertNotNull(cache.get(user1.getId(),CacheUser.class)); // cacheHit null
101+
// assertNull(cache.get(user1.getId(),CacheUser.class)); // fetch cached null
102+
}
103+
104+
@Test
105+
void cachePutIfAbsent() {
106+
CacheUser user1 = new CacheUser(UUID.randomUUID().toString(), "first1", "last1");
107+
CacheUser user2 = new CacheUser(UUID.randomUUID().toString(), "first2", "last2");
108+
assertNull(cache.putIfAbsent(user1.getId(), user1, CacheUser.class)); // should put user1, return null
109+
assertEquals(user1, cache.putIfAbsent(user1.getId(), user2, CacheUser.class)); // should not put user2, should
110+
// return user1
111+
assertEquals(user1, cache.get(user1.getId(), CacheUser.class)); // user1.getId() is still user1
112+
}
113+
114+
}

0 commit comments

Comments
 (0)