spring-bootserializationinfinispan

Implementing Infinispan (Data Grid) onto existing Spring Boot application and face problems with encoding


I have a existing Spring Boot 3 application that runs single node with Caffeine cache and the @Cacheable annotations.

The app runs fine so far, as Caffeine doesn't care about the POJO structure or format, it will just store the keys and the values in memory. Remember that, in the Spring Cache world, keys are often objects, e.g. a cached Person getPerson(String firstName, String lastName) uses a compound key.

The application is business-critical with regards to performance. It operates in payment industry so I'll dedicate a few neurons to security considerations.

Okay, now it's time to implement Red Hat Data Grid on Openshift. So I fired up a local instance of Infinispan 14 and hoped that the auto-configuration did some magic.

Here is the infinispan.yaml

infinispan:
  local-cache-configuration:
    name: base-template
    expiration:
      lifespan: 1800000
    encoding:
      media-type: application/x-java-object #We'll talk about this
  cache-container:
    name: default
    caches:
      ipe.my-ipe-cache: # Consider it is referenced in @Cacheable(value = CACHE_KEY)
        local-cache:
          configuration: base-template
      # Consider 24 identical nodes
  server:
    interfaces:
      - name: public
        inet-address: 
         value: 127.0.0.1
    socket-bindings:
      default-interface: public
      port-offset: 0
      socket-binding:
        - name: default
          port: 11222
        - name: memcached
          port: 11221
    endpoints:
      endpoint:
        socket-binding: default
        security-realm: default
    security:
      security-realms:
        - name: default
          properties-realm:
            groups-attribute: Roles
            user-properties:
              path: users.properties
            group-properties:
              path: groups.properties

And application.yaml

infinispan:
  embedded:
    enabled: false
  remote:
    enabled: true
    server-list: "localhost:11222"
    auth-username: admin
    auth-password: admin
#    marshaller: org.infinispan.commons.marshall.UTF8StringMarshaller
#    marshaller: org.infinispan.commons.marshall.ProtoStreamMarshaller
    marshaller: org.infinispan.commons.marshall.JavaSerializationMarshaller
    java-serial-allow-list: "com\\.acme\\.ipe\\.(.*)\\.dto\\.\\w+"

Now the problem lies in configuring the correct marshalling, and I am stuck at this

Anyway it didn't work

org.infinispan.client.hotrod.exceptions.HotRodClientException: org.infinispan.commons.CacheException: ISPN000936: Class 'com.acme.ipe.core.data.dto.ChannelDto' blocked by deserialization allow list. Adjust the configuration serialization allow list regular expression to include this class.
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
    at org.infinispan.client.hotrod.impl.Util.await(Util.java:52)
    at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:196)
    at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:186)
    at org.infinispan.spring.common.provider.SpringCache.put(SpringCache.java:145)
    at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87)
java.lang.IllegalArgumentException: No marshaller registered for object of Java type com.acme.ipe.core.data.dto.ChannelDto : ChannelDto(id=1, creationDate=2017-11-06T06:40:06, ...)
    at org.infinispan.protostream.impl.SerializationContextImpl.getMarshallerDelegate(SerializationContextImpl.java:503)
    at org.infinispan.protostream.WrappedMessage.writeMessage(WrappedMessage.java:281)
    at org.infinispan.protostream.WrappedMessage.write(WrappedMessage.java:242)
    at org.infinispan.protostream.ProtobufUtil.toWrappedByteBuffer(ProtobufUtil.java:153)
    at org.infinispan.commons.marshall.ImmutableProtoStreamMarshaller.objectToBuffer(ImmutableProtoStreamMarshaller.java:55)
    at org.infinispan.commons.marshall.AbstractMarshaller.objectToByteBuffer(AbstractMarshaller.java:70)
    at org.infinispan.client.hotrod.marshall.MarshallerUtil.obj2bytes(MarshallerUtil.java:117)
    at org.infinispan.client.hotrod.DataFormat$DataFormatImpl.valueToBytes(DataFormat.java:92)
    at org.infinispan.client.hotrod.DataFormat.valueToBytes(DataFormat.java:211)
    at org.infinispan.client.hotrod.impl.RemoteCacheImpl.valueToBytes(RemoteCacheImpl.java:610)
    at org.infinispan.client.hotrod.impl.RemoteCacheImpl.putAsync(RemoteCacheImpl.java:306)
    at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:196)
    at org.infinispan.client.hotrod.impl.RemoteCacheSupport.put(RemoteCacheSupport.java:186)
    at org.infinispan.spring.common.provider.SpringCache.put(SpringCache.java:145)
    at org.springframework.cache.interceptor.AbstractCacheInvoker.doPut(AbstractCacheInvoker.java:87)

I can set the media-type of the cache to application/json but I can't find any marshaller in my classpath that supports

Wrapping it up

Is it possible to configure Infinispan to marshal/unmarshal objects to JSON which would solve all possible security and compatibility issues?

Othwerise, is it possible not to manually annotate all existing DTOs with Protobuf annotations, e.g. by using some IDE or Maven plugin that will likely generate a mapping for you?

And as last resort for today, how can I overcome the error ISPN000936 shown a few lines ago? I did add the allow list to my Spring application!


Solution

  • The problem you are having with Java Serialization is that you are telling the server to use encoding: application/x-java-object. This means that the server will want to have the bytecode of your classes to be able to interpret the data and possibly transcode it. This is also the reason for the org.infinispan.commons.CacheException: ISPN000936: Class 'com.acme.ipe.core.data.dto.ChannelDto' blocked by deserialization allow list. error you are getting: it's coming from the server, not the client. Use application/octet-stream on the server to avoid this. Also, JBoss Marshalling will offer better performance than plain Java serialization.

    Protocol Buffers are obviously the ideal choice, but they do require a lot of legwork if adapting a lot of existing code.

    As for JSON, you could implement a client JSONMarshaller using Jackson that does the marshalling/unmarshalling. This is something that could also be contributed upstream.