Over a million developers have joined DZone.

Functional Map API: Working With Single Entries

Learn more about the experimental Functional Map API in Infinispan.

· Integration Zone

Visually compose APIs with easy-to-use tooling. Learn how IBM API Connect provides near-universal access to data and services both on-premises and in the cloud, brought to you in partnership with IBM.

In this blog post we'll continue with the introduction of the experimental Functional Map API, which was released as part of Infinispan 8.0.0.Final, focusing on how to manipulate data using single-key operations.

As mentioned in the Functional Map API introduction, there are three types of operations that can be executed against a functional map: read-only operations (executed via ReadOnlyMap), write-only operations (executed via WriteOnlyMap), and read-write operations (executed via ReadWriteMap) and .

Firstly, we need construct instances of ReadOnlyMap, WriteOnlyMap and ReadWriteMap to be able to work with them:

import org.infinispan.AdvancedCache;
import org.infinispan.commons.api.functional.FunctionalMap.*;
import org.infinispan.functional.impl.*;
import org.infinispan.manager.DefaultCacheManager;

DefaultCacheManager cacheManager = new DefaultCacheManager();
AdvancedCache<String, String> cache = cacheManager.<String, String>getCache().getAdvancedCache();
FunctionalMapImpl<String, String> functionalMap = FunctionalMapImpl.create(cache);

ReadOnlyMap<String, String> readOnlyMap = ReadOnlyMapImpl.create(functionalMap);
WriteOnlyMap<String, String> writeOnlyMap = WriteOnlyMapImpl.create(functionalMap);
ReadWriteMap<String, String> readWriteMap = ReadWriteMapImpl.create(functionalMap);

Next, let's see all three types of operations in action, chaining them to store a single key/value pair along with some metadata, then read it and finally delete a returning the previously stored data:

import org.infinispan.commons.api.functional.EntryView.ReadEntryView;
import org.infinispan.commons.api.functional.FunctionalMap.ReadOnlyMap;
import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap;
import org.infinispan.commons.api.functional.FunctionalMap.WriteOnlyMap;
import org.infinispan.commons.api.functional.MetaParam.MetaLifespan;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;

ReadOnlyMap<String, String> readOnlyMap = ...
WriteOnlyMap<String, String> writeOnlyMap = ...
ReadWriteMap<String, String> readWriteMap = ...

// Write a value and metadata to be associated with a key
CompletableFuture<Void> writeFuture = writeOnlyMap.eval("key1", "value1",
   (v, writeView) -> writeView.set(v, new MetaLifespan(Duration.ofHours(1).toMillis())));

// Chain a read operation to happen after the write operation has completed
CompletableFuture<ReadEntryView<String, String>> readFuture = writeFuture.thenCompose(
   ignore -> readOnlyMap.eval("key1", readView -> readView));

// Chain an operation to log the value read
CompletableFuture<Void> logReadFuture = readFuture.thenAccept(
   view -> System.out.printf("Read entry view: %s%n", view));

// Chain an operation to remove and return previously stored value
CompletableFuture<String> removeFuture = logReadFuture.thenCompose(
   ignore -> readWriteMap.eval("key1", readWriteView -> {
      String previousValue = readWriteView.get();
      return previousValue;

// Finally, log the previously stored value returned by the remove
CompletableFuture<Void> logRemoveFuture = removeFuture.thenAccept(
   previousValue -> System.out.printf("Removed value: %s%n", previousValue));

// Wait for the chain of operations to complete

This example demonstrates some of the key aspects of working with single entries using the Functional Map API:

  • Single entry methods are asynchronous returning CompletableFuture instances which provide methods to compose and chain operations so that it can feel is they're being executed sequentially. Unfortunately Java does not have Haskell's do notation or Scala's for comprehensions to make it more palatable, but it's great news that Java finally offers mechanisms to work with CompletableFutures in a non-blocking way, even if they're a bit more verbose than what's proposed in other languages.
  • All data-handling methods for WriteOnlyMap return CompletableFuture<Void>, meaning that the user can find out when the operation has completed but nothing else, because there's nothing the function can provide that could not be computed in advance or outside the function.
  • The return type for most of the data handling methods in ReadOnlyMap (and ReadWriteMap) are quite flexible. So, a function can decide to return value information, or metadata, or for convenience, it can also return the ReadEntryView it receives as parameter. This can be useful for users wanting to return both value and metadata parameter information.
  • The read-write operation demonstrated above showed how to remove an entry and return the previously associated value. In this particular case, we know there's a value associated with the entry and hence we called ReadEntryView.get() directly, but if we were not sure if the value is present or not, ReadEntryView.find() should be called and return the Optional instance instead.
  • In the example, Lifespan metadata parameter is constructed using the new Java Time API available in Java 8, but it could have been done equally with java.util.concurrent.TimeUnit as long as the conversion was done to number of milliseconds during which the entry should be accessible.
  • Lifespan-based expiration works just as it does with other Infinispan APIs, so you can easily modify the example to lower the lifespan, wait for duration to pass and then verify that the value is not present any more.

If storing a constant value, WriteOnlyMap.eval(K, Consumer) could be used instead of WriteOnlyMap.eval(K, V, Consumer), making the code clearer, but if the value is variable, WriteOnlyMap.eval(K, V, Consumer) should be used to avoid, as much as possible, functions capturing external variables. Clearly, operations exposed by functional map can't cover all scenarios and there might be situations where external variables are captured by functions, but these should in general, should be a minority. Here is as example showing how to implement ConcurrentMap.replace(K, V, V) where external variable capturing is required:

import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

ReadWriteMap<String, String> readWriteMap = ...

// First, do a put-if-absent like operation
CompletableFuture<Boolean> putIfAbsentFuture = readWriteMap.eval("key1", readWriteView -> {
   Optional<String> opt = readWriteView.find();
   boolean isAbsent = !opt.isPresent();
   if (isAbsent) readWriteView.set("value1");
   return isAbsent;

// Chain the put if absent with an operation to log whether the put-if-absent was successful
CompletableFuture<Void> logPutIfAbsentFuture = putIfAbsentFuture.thenAccept(
   success -> System.out.printf("Put if absent successful? %s%n", success));

// Next, chain a replace operation comparing a captured value via equals
String oldValue = "value1";
CompletableFuture<Boolean> replaceFuture = logPutIfAbsentFuture.thenCompose(x ->
   readWriteMap.eval("key1", "value2", (v, readWriteView) ->
      readWriteView.find().map(prev -> {
         if (prev.equals(oldValue)) {
            return true; // Old value matches so set new value and return success
         return false; // Old value does not match
      }).orElse(false) // No value found in the map

// Finally, log the result of replace
CompletableFuture<Void> logReplaceFuture = replaceFuture.thenAccept(
   replaced -> System.out.printf("Replace successful? %s%n", replaced));

// Wait for the chain of operations to complete

The reason we didn't add a WriteOnly.eval(K, V, V, Consumer) to the API is because value-equality-based replace comparisons are just one type of replace operations that could be executed. In other cases, metadata parameter based comparison might be more suitable, e.g. Hot Rod replace operation where version (a type of metadata parameter) equality is the deciding factor to determine whether the replace should happen or not.

In the next blog post, we'll be looking at how to work with multiple entries using the Functional Map API.

Visually compose APIs with easy-to-use tooling. Learn how IBM API Connect provides near-universal access to data and services both on-premises and in the cloud, brought to you in partnership with IBM.


Published at DZone with permission of Manik Surtani, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}