-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
358 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
dependencies { | ||
api(project(":cache-core")) | ||
implementation("com.fasterxml.jackson.core:jackson-databind") { | ||
version { | ||
require("2.16.0") // imposes a lower bound on acceptable versions | ||
} | ||
} | ||
testImplementation(project(":cache-provider-caffeine")) | ||
} | ||
|
||
publishing.publications.withType<MavenPublication> { | ||
pom { | ||
name.set("Xanthic - Jackson") | ||
description.set("Xanthic Cache Jackson Adapter") | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheAdapter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package io.github.xanthic.jackson; | ||
|
||
import com.fasterxml.jackson.databind.util.LookupCache; | ||
import io.github.xanthic.cache.api.Cache; | ||
import io.github.xanthic.cache.core.CacheApi; | ||
import io.github.xanthic.cache.core.CacheApiSpec; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.Value; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.function.BiConsumer; | ||
import java.util.function.Consumer; | ||
|
||
/** | ||
* Wraps a Xanthic {@link Cache} for use as a Jackson {@link LookupCache}. | ||
* <p> | ||
* Most users should utilize {@link XanthicJacksonCacheProvider} rather than directly interact with this class. | ||
* | ||
* @param <K> The type of keys that form the cache | ||
* @param <V> The type of values contained in the cache | ||
*/ | ||
@Value | ||
@RequiredArgsConstructor | ||
public class XanthicJacksonCacheAdapter<K, V> implements LookupCache<K, V> { | ||
|
||
/** | ||
* The Xanthic cache to use as a Jackson {@link LookupCache}. | ||
*/ | ||
Cache<K, V> cache; | ||
|
||
/** | ||
* The specification associated with the constructed cache. | ||
*/ | ||
Consumer<CacheApiSpec<K, V>> spec; | ||
|
||
/** | ||
* Creates a Jackson {@link LookupCache} by wrapping a Xanthic cache with this adapter. | ||
* | ||
* @param spec the cache specification (note: specifying {@link CacheApiSpec#maxSize(Long)} is recommended) | ||
*/ | ||
public XanthicJacksonCacheAdapter(@NotNull Consumer<CacheApiSpec<K, V>> spec) { | ||
this(CacheApi.create(spec), spec); | ||
} | ||
|
||
@Override | ||
public int size() { | ||
return (int) cache.size(); | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("unchecked") | ||
public V get(Object key) { | ||
return cache.get((K) key); | ||
} | ||
|
||
@Override | ||
public V put(K key, V value) { | ||
return cache.put(key, value); | ||
} | ||
|
||
@Override | ||
public V putIfAbsent(K key, V value) { | ||
return cache.putIfAbsent(key, value); | ||
} | ||
|
||
@Override | ||
public void clear() { | ||
cache.clear(); | ||
} | ||
|
||
@Override | ||
public void contents(BiConsumer<K, V> consumer) { | ||
cache.forEach(consumer); | ||
} | ||
|
||
@Override | ||
public XanthicJacksonCacheAdapter<K, V> emptyCopy() { | ||
return new XanthicJacksonCacheAdapter<>(spec); | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
jackson/src/main/java/io/github/xanthic/jackson/XanthicJacksonCacheProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package io.github.xanthic.jackson; | ||
|
||
import com.fasterxml.jackson.databind.DeserializationConfig; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.JsonDeserializer; | ||
import com.fasterxml.jackson.databind.JsonSerializer; | ||
import com.fasterxml.jackson.databind.SerializationConfig; | ||
import com.fasterxml.jackson.databind.cfg.CacheProvider; | ||
import com.fasterxml.jackson.databind.deser.DeserializerCache; | ||
import com.fasterxml.jackson.databind.ser.SerializerCache; | ||
import com.fasterxml.jackson.databind.type.TypeFactory; | ||
import com.fasterxml.jackson.databind.util.LookupCache; | ||
import com.fasterxml.jackson.databind.util.TypeKey; | ||
import io.github.xanthic.cache.core.CacheApiSpec; | ||
import io.github.xanthic.jackson.util.SerializableConsumer; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.Value; | ||
|
||
/** | ||
* Implementation of Jackson's {@link CacheProvider} that yields Xanthic {@link io.github.xanthic.cache.api.Cache} instances, | ||
* which are backed by any cache implementation of your choosing. | ||
* <p> | ||
* Example usage: | ||
* {@code ObjectMapper mapper = JsonMapper.builder().cacheProvider(XanthicJacksonCacheProvider.defaultInstance()).build(); } | ||
*/ | ||
@Value | ||
@Getter(AccessLevel.PRIVATE) | ||
@RequiredArgsConstructor | ||
public class XanthicJacksonCacheProvider implements CacheProvider { | ||
private static final long serialVersionUID = 1L; | ||
private static final XanthicJacksonCacheProvider DEFAULT_INSTANCE = new XanthicJacksonCacheProvider(); | ||
|
||
/** | ||
* Specification for the deserializer cache. | ||
*/ | ||
SerializableConsumer<CacheApiSpec<JavaType, JsonDeserializer<Object>>> deserializationSpec; | ||
|
||
/** | ||
* Specification for the serializer cache. | ||
*/ | ||
SerializableConsumer<CacheApiSpec<TypeKey, JsonSerializer<Object>>> serializationSpec; | ||
|
||
/** | ||
* Specification for the type factory cache. | ||
*/ | ||
SerializableConsumer<CacheApiSpec<Object, JavaType>> typeFactorySpec; | ||
|
||
/** | ||
* Creates a Jackson {@link CacheProvider} backed by Xanthic, using the specified max cache sizes. | ||
* | ||
* @param maxDeserializerCacheSize the maximum size of the deserializer cache | ||
* @param maxSerializerCacheSize the maximum size of the serializer cache | ||
* @param maxTypeFactoryCacheSize the maximum size of the type factory cache | ||
*/ | ||
public XanthicJacksonCacheProvider(long maxDeserializerCacheSize, long maxSerializerCacheSize, long maxTypeFactoryCacheSize) { | ||
this.deserializationSpec = spec -> spec.maxSize(maxDeserializerCacheSize); | ||
this.serializationSpec = spec -> spec.maxSize(maxSerializerCacheSize); | ||
this.typeFactorySpec = spec -> spec.maxSize(maxTypeFactoryCacheSize); | ||
} | ||
|
||
/** | ||
* Creates a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. | ||
*/ | ||
private XanthicJacksonCacheProvider() { | ||
this(DeserializerCache.DEFAULT_MAX_CACHE_SIZE, SerializerCache.DEFAULT_MAX_CACHE_SIZE, TypeFactory.DEFAULT_MAX_CACHE_SIZE); | ||
} | ||
|
||
@Override | ||
public LookupCache<JavaType, JsonDeserializer<Object>> forDeserializerCache(DeserializationConfig config) { | ||
return new XanthicJacksonCacheAdapter<>(deserializationSpec); | ||
} | ||
|
||
@Override | ||
public LookupCache<TypeKey, JsonSerializer<Object>> forSerializerCache(SerializationConfig config) { | ||
return new XanthicJacksonCacheAdapter<>(serializationSpec); | ||
} | ||
|
||
@Override | ||
public LookupCache<Object, JavaType> forTypeFactory() { | ||
return new XanthicJacksonCacheAdapter<>(typeFactorySpec); | ||
} | ||
|
||
/** | ||
* @return a Jackson {@link CacheProvider} backed by Xanthic, using Jackson's recommended default max cache sizes. | ||
*/ | ||
public static XanthicJacksonCacheProvider defaultInstance() { | ||
return DEFAULT_INSTANCE; | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
jackson/src/main/java/io/github/xanthic/jackson/util/SerializableConsumer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package io.github.xanthic.jackson.util; | ||
|
||
import java.io.Serializable; | ||
import java.util.function.Consumer; | ||
|
||
/** | ||
* A serializable {@link Consumer} since {@link com.fasterxml.jackson.databind.cfg.CacheProvider} | ||
* must be {@link Serializable}, as it is stored in {@link com.fasterxml.jackson.databind.ObjectMapper}. | ||
* | ||
* @param <T> the type of the input for the consumer | ||
*/ | ||
@FunctionalInterface | ||
public interface SerializableConsumer<T> extends Consumer<T>, Serializable {} |
132 changes: 132 additions & 0 deletions
132
jackson/src/test/java/io/github/xanthic/jackson/XanthicJacksonCacheProviderTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package io.github.xanthic.jackson; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.cfg.CacheProvider; | ||
import com.fasterxml.jackson.databind.deser.DeserializerCache; | ||
import com.fasterxml.jackson.databind.json.JsonMapper; | ||
import com.fasterxml.jackson.databind.ser.SerializerCache; | ||
import com.fasterxml.jackson.databind.type.TypeFactory; | ||
import io.github.xanthic.jackson.util.TrackedCacheProvider; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.io.ObjectInputStream; | ||
import java.io.ObjectOutputStream; | ||
import java.util.List; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertFalse; | ||
import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
class XanthicJacksonCacheProviderTest { | ||
|
||
@Test | ||
void defaults() throws JsonProcessingException { | ||
ObjectMapper mapper = JsonMapper.builder() | ||
.cacheProvider(XanthicJacksonCacheProvider.defaultInstance()) | ||
.build(); | ||
assertNotNull(mapper.readValue("{\"bar\":\"baz\"}", Foo.class)); | ||
assertNotNull(mapper.writeValueAsString(new Foo("baz"))); | ||
assertNotNull(mapper.getTypeFactory().constructParametricType(List.class, Integer.class)); | ||
} | ||
|
||
@Test | ||
void deserialize() throws JsonProcessingException { | ||
TrackedCacheProvider provider = new TrackedCacheProvider(); | ||
ObjectMapper mapper = JsonMapper.builder() | ||
.cacheProvider(createCacheProvider(provider)) | ||
.build(); | ||
assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
Foo foo = mapper.readValue("{\"bar\":\"baz\"}", Foo.class); | ||
assertNotNull(foo); | ||
assertEquals("baz", foo.getBar()); | ||
assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
} | ||
|
||
@Test | ||
void serialize() throws JsonProcessingException { | ||
TrackedCacheProvider provider = new TrackedCacheProvider(); | ||
ObjectMapper mapper = JsonMapper.builder() | ||
.cacheProvider(createCacheProvider(provider)) | ||
.build(); | ||
assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
String json = mapper.writeValueAsString(new Foo("baz")); | ||
assertEquals("{\"bar\":\"baz\"}", json); | ||
assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
} | ||
|
||
@Test | ||
void constructType() { | ||
TrackedCacheProvider provider = new TrackedCacheProvider(); | ||
ObjectMapper mapper = JsonMapper.builder() | ||
.cacheProvider(createCacheProvider(provider)) | ||
.build(); | ||
assertFalse(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
JavaType type = mapper.getTypeFactory().constructParametricType(List.class, Integer.class); | ||
assertNotNull(type); | ||
assertTrue(provider.getConstructedCaches().stream().anyMatch(c -> c.size() > 0)); | ||
} | ||
|
||
@Test | ||
void constructMultiple() throws JsonProcessingException { | ||
TrackedCacheProvider provider = new TrackedCacheProvider(); | ||
assertEquals(0, provider.getConstructedCaches().size()); | ||
|
||
ObjectMapper m1 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); | ||
m1.readValue("{\"bar\":\"baz\"}", Foo.class); | ||
m1.writeValueAsString(new Foo("baz")); | ||
m1.getTypeFactory().constructParametricType(List.class, Integer.class); | ||
assertEquals(3, provider.getConstructedCaches().size()); | ||
assertTrue(provider.getConstructedCaches().stream().allMatch(c -> c.size() > 0)); | ||
|
||
ObjectMapper m2 = JsonMapper.builder().cacheProvider(createCacheProvider(provider)).build(); | ||
m2.readValue("{\"bar\":\"baz\"}", Foo.class); | ||
m2.writeValueAsString(new Foo("baz")); | ||
m2.getTypeFactory().constructParametricType(List.class, Integer.class); | ||
assertEquals(6, provider.getConstructedCaches().size()); | ||
assertTrue(provider.getConstructedCaches().stream().allMatch(c -> c.size() > 0)); | ||
} | ||
|
||
@Test | ||
void serializable() throws IOException, ClassNotFoundException { | ||
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) { | ||
oos.writeObject(XanthicJacksonCacheProvider.defaultInstance()); | ||
} | ||
|
||
XanthicJacksonCacheProvider provider; | ||
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()))) { | ||
provider = (XanthicJacksonCacheProvider) ois.readObject(); | ||
} | ||
|
||
assertNotNull(provider); | ||
assertNotNull(provider.forTypeFactory()); | ||
} | ||
|
||
private static CacheProvider createCacheProvider(TrackedCacheProvider trackedProvider) { | ||
return new XanthicJacksonCacheProvider( | ||
spec -> spec.provider(trackedProvider).maxSize((long) DeserializerCache.DEFAULT_MAX_CACHE_SIZE), | ||
spec -> spec.provider(trackedProvider).maxSize((long) SerializerCache.DEFAULT_MAX_CACHE_SIZE), | ||
spec -> spec.provider(trackedProvider).maxSize((long) TypeFactory.DEFAULT_MAX_CACHE_SIZE) | ||
); | ||
} | ||
|
||
@Data | ||
@Setter(AccessLevel.PRIVATE) | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
static class Foo { | ||
private String bar; | ||
} | ||
|
||
} |
23 changes: 23 additions & 0 deletions
23
jackson/src/test/java/io/github/xanthic/jackson/util/TrackedCacheProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package io.github.xanthic.jackson.util; | ||
|
||
import io.github.xanthic.cache.api.Cache; | ||
import io.github.xanthic.cache.api.CacheProvider; | ||
import io.github.xanthic.cache.api.ICacheSpec; | ||
import io.github.xanthic.cache.core.CacheApiSettings; | ||
import lombok.Value; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
@Value | ||
public class TrackedCacheProvider implements CacheProvider { | ||
CacheProvider underlyingProvider = CacheApiSettings.getInstance().getDefaultCacheProvider(); | ||
List<Cache<?, ?>> constructedCaches = new ArrayList<>(); | ||
|
||
@Override | ||
public <K, V> Cache<K, V> build(ICacheSpec<K, V> spec) { | ||
Cache<K, V> cache = underlyingProvider.build(spec); | ||
constructedCaches.add(cache); | ||
return cache; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters