diff --git a/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/MapBasedEntityCacheTest.java b/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/MapBasedEntityCacheTest.java index e1f70be..0f5b503 100644 --- a/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/MapBasedEntityCacheTest.java +++ b/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/MapBasedEntityCacheTest.java @@ -5,26 +5,25 @@ import java.io.IOException; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; -public class MapBasedEntityCacheTest { +class MapBasedEntityCacheTest { @Test - public void shouldInsertElement() { + void shouldInsertElement() { // given - EntityCache cache = new MapBasedEntityCache<>(); + var cache = new MapBasedEntityCache<>(); // and - String key = "key"; - Object testObject = new Object(); + var key = "key"; + var testObject = new Object(); // when - Object previous = cache.put(key, testObject); + var previous = cache.put(key, testObject); // then assertNull(previous); @@ -34,21 +33,21 @@ public void shouldInsertElement() { } @Test - public void shouldReplaceElement() { + void shouldReplaceElement() { // given - String key = "key"; - Object testObject = new Object(); + var key = "key"; + var testObject = new Object(); // and - ConcurrentMap precomputedCache = new ConcurrentHashMap<>(); + var precomputedCache = new ConcurrentHashMap(); precomputedCache.put(key, testObject); - EntityCache cache = new MapBasedEntityCache<>(() -> precomputedCache); + var cache = new MapBasedEntityCache<>(() -> precomputedCache); // and - Object newObject = new Object(); + var newObject = new Object(); // when - Object previous = cache.put(key, newObject); + var previous = cache.put(key, newObject); // then assertSame(previous, testObject); @@ -58,17 +57,16 @@ public void shouldReplaceElement() { } @Test - public void shouldComputeElement() throws Exception { + void shouldComputeElement() { // given - EntityCache cache = new MapBasedEntityCache<>(); + var cache = new MapBasedEntityCache<>(); // and - String key = "key"; - Object testObject = new Object(); - CacheLoader loader = k -> testObject; + var key = "key"; + var testObject = new Object(); // when - Object computed = cache.computeIfAbsent(key, loader); + var computed = cache.computeIfAbsent(key, k -> testObject); // then assertEquals(1L, cache.size()); @@ -76,21 +74,18 @@ public void shouldComputeElement() throws Exception { } @Test - public void shouldNotComputeIfEntryForGivenKeyAlreadyExists() throws Exception { + void shouldNotComputeIfEntryForGivenKeyAlreadyExists() { // given - String key = "key"; - Object testObject = new Object(); + var key = "key"; + var testObject = new Object(); // and - ConcurrentMap precomputedCache = new ConcurrentHashMap<>(); + var precomputedCache = new ConcurrentHashMap(); precomputedCache.put(key, testObject); - EntityCache cache = new MapBasedEntityCache<>(() -> precomputedCache); - - // and - CacheLoader loader = k -> new Object(); + var cache = new MapBasedEntityCache<>(() -> precomputedCache); // when - Object computed = cache.computeIfAbsent(key, loader); + var computed = cache.computeIfAbsent(key, k -> new Object()); // then assertSame(computed, testObject); @@ -98,15 +93,15 @@ public void shouldNotComputeIfEntryForGivenKeyAlreadyExists() throws Exception { } @Test - public void shouldInvalidateCache() { + void shouldInvalidateCache() { // given - String key = "key"; - Object testObject = new Object(); + var key = "key"; + var testObject = new Object(); // and - ConcurrentMap precomputedCache = new ConcurrentHashMap<>(); + var precomputedCache = new ConcurrentHashMap(); precomputedCache.put(key, testObject); - EntityCache cache = new MapBasedEntityCache<>(() -> precomputedCache); + var cache = new MapBasedEntityCache<>(() -> precomputedCache); // when cache.invalidateAll(); @@ -116,18 +111,17 @@ public void shouldInvalidateCache() { } @Test - public void shouldRethrowAnExceptionIfCacheLoaderFails() { + void shouldRethrowAnExceptionIfCacheLoaderFails() { // given - EntityCache cache = new MapBasedEntityCache<>(); + var cache = new MapBasedEntityCache<>(); // and - String key = "key"; - CacheLoader loader = k -> { - throw new IOException(); - }; + var key = "key"; // when - Executable compute = () -> cache.computeIfAbsent(key, loader); + var compute = (Executable) () -> cache.computeIfAbsent(key, k -> { + throw new IOException(); + }); // then assertThrows(IOException.class, compute); diff --git a/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/keybuilder/CacheKeyBuilderFactoryTest.java b/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/keybuilder/CacheKeyBuilderFactoryTest.java index 2a2d68b..50e1c7f 100644 --- a/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/keybuilder/CacheKeyBuilderFactoryTest.java +++ b/json-cache-core/src/test/java/pl/allegro/tech/jsoncache/keybuilder/CacheKeyBuilderFactoryTest.java @@ -6,29 +6,28 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import pl.allegro.tech.jsoncache.CacheableEntity; import pl.allegro.tech.jsoncache.keybuilder.stategy.CacheKeyBuilderStrategy; -import pl.allegro.tech.jsoncache.support.CacheableEntityBuilder; import pl.allegro.tech.jsoncache.keybuilder.support.KeyBuilderStrategies; +import pl.allegro.tech.jsoncache.support.CacheableEntityBuilder; import java.util.List; -import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class CacheKeyBuilderFactoryTest { +class CacheKeyBuilderFactoryTest { @Test - public void givenKeyBuilderFactoryWithoutStrategiesAndEntityMetadataWhenRequestedForKeyBuilderNoneIsReturned() { + void givenKeyBuilderFactoryWithoutStrategiesAndEntityMetadataWhenRequestedForKeyBuilderNoneIsReturned() { // given - CacheKeyBuilderFactory factory = new CacheKeyBuilderFactory(List.of()); + var factory = new CacheKeyBuilderFactory(List.of()); // and - CacheableEntity entityMetadata = new CacheableEntityBuilder() + var entityMetadata = new CacheableEntityBuilder() .withKeyComponents("a", "b", "c") .build(); // when - Optional> keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); + var keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); // then assertTrue(keyBuilder::isEmpty); @@ -36,64 +35,63 @@ public void givenKeyBuilderFactoryWithoutStrategiesAndEntityMetadataWhenRequeste @ParameterizedTest @ArgumentsSource(KeyBuilderStrategies.StrategyMatchingMetadata.class) - public void givenKeyBuilderFactoryWithStrategyAndEntityMetadataWhenRequestedForKeyBuilderMatchingOneIsReturned( + void givenKeyBuilderFactoryWithStrategyAndEntityMetadataWhenRequestedForKeyBuilderMatchingOneIsReturned( CacheKeyBuilderStrategy strategy, CacheableEntity entityMetadata ) { // given - CacheKeyBuilderFactory factory = new CacheKeyBuilderFactory(List.of(strategy)); + var factory = new CacheKeyBuilderFactory(List.of(strategy)); // when - Optional> keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); + var keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); // then assertTrue(keyBuilder::isPresent); } @Test - public void givenKeyBuilderFactoryWithStrategiesAndInvalidEntityMetadataWhenRequestedForKeyBuilderNoneIsReturned() { + void givenKeyBuilderFactoryWithStrategiesAndInvalidEntityMetadataWhenRequestedForKeyBuilderNoneIsReturned() { // given - CacheKeyBuilderFactory factory = new CacheKeyBuilderFactory(List.of( + var factory = new CacheKeyBuilderFactory(List.of( KeyBuilderStrategies.DUMMY_COMPONENT_STRATEGY, KeyBuilderStrategies.DUMMY_TEMPLATE_STRATEGY )); // and - CacheableEntity entityMetadata = new CacheableEntityBuilder().build(); + var entityMetadata = new CacheableEntityBuilder().build(); // when - Optional> keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); + var keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); // then assertTrue(keyBuilder::isEmpty); } @Test - public void givenKeyBuilderFactoryWithStrategiesWhenBothSupportEntityMetadataThenKeyBuilderForFirstOneIsReturned() - throws Exception { + void givenKeyBuilderFactoryWithStrategiesWhenBothSupportEntityMetadataThenKeyBuilderForFirstOneIsReturned() throws Exception { // given - CacheKeyBuilderFactory factory = new CacheKeyBuilderFactory(List.of( + var factory = new CacheKeyBuilderFactory(List.of( KeyBuilderStrategies.DUMMY_COMPONENT_STRATEGY, KeyBuilderStrategies.DUMMY_TEMPLATE_STRATEGY )); // and - CacheableEntity entityMetadata = new CacheableEntityBuilder() + var entityMetadata = new CacheableEntityBuilder() .withKeyComponents("a", "b", "c") .withKeyTemplate("{{a}}-{{b}}-{{c}}") .build(); // when - Optional> keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); + var keyBuilder = factory.findCacheKeyBuilderFor(entityMetadata, Object.class); // then assertTrue(keyBuilder::isPresent); - Object keySource = new Object(); - Object expectedKey = KeyBuilderStrategies.DUMMY_COMPONENT_STRATEGY + var keySource = new Object(); + var expectedKey = KeyBuilderStrategies.DUMMY_COMPONENT_STRATEGY .prepareBuilder(entityMetadata) .buildKey(keySource); - Object actualKey = keyBuilder.orElseGet(Assertions::fail) + var actualKey = keyBuilder.orElseGet(Assertions::fail) .buildKey(new Object()); assertEquals(expectedKey, actualKey); diff --git a/json-cache-jackson/src/main/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategy.java b/json-cache-jackson/src/main/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategy.java index 89a6b87..e3228f7 100644 --- a/json-cache-jackson/src/main/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategy.java +++ b/json-cache-jackson/src/main/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategy.java @@ -1,17 +1,20 @@ package pl.allegro.tech.jsoncache.jackson.keybuilder.strategy; -import tools.jackson.databind.JsonNode; import pl.allegro.tech.jsoncache.keybuilder.KeyPartMissingException; import pl.allegro.tech.jsoncache.keybuilder.stategy.KeyComponentBasedStrategy; +import tools.jackson.databind.JsonNode; + +import java.util.Optional; /** * Strategy for retrieving key parts directly from {@link JsonNode json nodes}. + * Nested components can be extracted using dot notation, e.g. "user.name" will extract "name" from nested "user" object. */ public class JsonComponentExtractingStrategy extends KeyComponentBasedStrategy { @Override protected String extractKeyPart(JsonNode value, String component) throws KeyPartMissingException { - return value.optional(component) + return findComponentNode(value, component) .filter(JsonNode::isValueNode) .map(JsonNode::asString) .orElseThrow(() -> new KeyPartMissingException(value, component)); @@ -22,4 +25,16 @@ protected Class supportedValueType() { return JsonNode.class; } + private Optional findComponentNode(JsonNode value, String component) { + if (component.indexOf('.') == -1) { + // no nesting - just flat component + return value.optional(component); + } + + for (var fragment : component.split("\\.")) { + value = value.path(fragment); + } + return value.asOptional(); + } + } diff --git a/json-cache-jackson/src/test/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategyTest.java b/json-cache-jackson/src/test/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategyTest.java index a6261ed..2d1fab8 100644 --- a/json-cache-jackson/src/test/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategyTest.java +++ b/json-cache-jackson/src/test/java/pl/allegro/tech/jsoncache/jackson/keybuilder/strategy/JsonComponentExtractingStrategyTest.java @@ -1,86 +1,102 @@ package pl.allegro.tech.jsoncache.jackson.keybuilder.strategy; - -import tools.jackson.databind.JsonNode; -import tools.jackson.databind.node.JsonNodeFactory; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.function.Executable; -import pl.allegro.tech.jsoncache.CacheableEntity; import pl.allegro.tech.jsoncache.keybuilder.KeyBuildingException; import pl.allegro.tech.jsoncache.keybuilder.stategy.CacheKeyBuilderStrategy; import pl.allegro.tech.jsoncache.support.CacheableEntityBuilder; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.node.JsonNodeFactory; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public class JsonComponentExtractingStrategyTest { +class JsonComponentExtractingStrategyTest { + + private final CacheKeyBuilderStrategy strategy = new JsonComponentExtractingStrategy(); @Test - public void shouldExtractKeyFromValueIfDescriptorProvidesKeyComponents() throws Exception { + void shouldExtractKeyFromValueIfDescriptorProvidesKeyComponents() throws Exception { // given - JsonNode value = JsonNodeFactory.instance.objectNode() + var value = JsonNodeFactory.instance.objectNode() .put("a", 1) .put("b", 2); // and - CacheableEntity entityDescriptor = new CacheableEntityBuilder() + var entityDescriptor = new CacheableEntityBuilder() .withKeyComponents("a", "b") .withKeySeparator("-") .build(); + // when + var keyBuilder = strategy.prepareBuilder(entityDescriptor); + var key = keyBuilder.buildKey(value); + + // then + assertEquals("1-2", key); + } + + @Test + void shouldExtractKeyFromNestedValueIfDescriptorUsesDotNotation() throws Exception { + // given + var value = JsonNodeFactory.instance.objectNode() + .put("a", 1); + value.putObject("b") + .put("nested", 2); + // and - CacheKeyBuilderStrategy strategy = new JsonComponentExtractingStrategy(); + var entityDescriptor = new CacheableEntityBuilder() + .withKeyComponents("a", "b.nested") + .withKeySeparator("-") + .build(); // when - String key = strategy.prepareBuilder(entityDescriptor).buildKey(value); + var keyBuilder = strategy.prepareBuilder(entityDescriptor); + var key = keyBuilder.buildKey(value); // then assertEquals("1-2", key); } @Test - public void shouldThrowExceptionIfValueHasMissingKeyParts() { + void shouldThrowExceptionIfValueHasMissingKeyParts() throws Exception { // given - JsonNode value = JsonNodeFactory.instance.objectNode() + var value = JsonNodeFactory.instance.objectNode() .put("a", 1); // and - CacheableEntity entityDescriptor = new CacheableEntityBuilder() + var entityDescriptor = new CacheableEntityBuilder() .withKeyComponents("a", "b") .withKeySeparator("-") .build(); - // and - CacheKeyBuilderStrategy strategy = new JsonComponentExtractingStrategy(); - // when - Executable keyBuilding = () -> strategy.prepareBuilder(entityDescriptor).buildKey(value); + var keyBuilder = strategy.prepareBuilder(entityDescriptor); + var keySupplier = (Executable) () -> keyBuilder.buildKey(value); // then - assertThrows(KeyBuildingException.class, keyBuilding); + assertThrows(KeyBuildingException.class, keySupplier); } @Test - public void shouldExtractEmptyKeyFromValueIfDescriptorProvidesNoKeyComponents() throws Exception { + void shouldExtractEmptyKeyFromValueIfDescriptorProvidesNoKeyComponents() throws Exception { // given - JsonNode value = JsonNodeFactory.instance.objectNode() + var value = JsonNodeFactory.instance.objectNode() .put("a", 1) .put("b", 2); // and - CacheableEntity entityDescriptor = new CacheableEntityBuilder() + var entityDescriptor = new CacheableEntityBuilder() .withKeyTemplate("{{a}}-{{b}}") .build(); - // and - CacheKeyBuilderStrategy strategy = new JsonComponentExtractingStrategy(); - // when - String key = strategy.prepareBuilder(entityDescriptor).buildKey(value); + var keyBuilder = strategy.prepareBuilder(entityDescriptor); + var key = keyBuilder.buildKey(value); // then - assertTrue(key::isEmpty); + assertTrue(key.isEmpty()); } }