[infinispan-issues] [JBoss JIRA] (IPROTO-56) DynamicEntity support in MarshallerProvider

Gustavo Fernandes (JIRA) issues at jboss.org
Fri Jun 15 11:15:00 EDT 2018


     [ https://issues.jboss.org/browse/IPROTO-56?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Gustavo Fernandes updated IPROTO-56:
------------------------------------
    Steps to Reproduce: 
Add the test to the protostream project:

{code:java}
package org.infinispan.protostream.impl;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.infinispan.protostream.BaseMarshaller;
import org.infinispan.protostream.FileDescriptorSource;
import org.infinispan.protostream.MessageMarshaller;
import org.infinispan.protostream.ProtobufUtil;
import org.infinispan.protostream.SerializationContext;
import org.infinispan.protostream.config.Configuration;
import org.infinispan.protostream.descriptors.Descriptor;
import org.infinispan.protostream.descriptors.Type;
import org.junit.Test;

public class DynamicEntityTest {

   private SerializationContextImpl createContext() {
      return (SerializationContextImpl) ProtobufUtil.newSerializationContext(Configuration.builder().build());
   }

   // Inner class, represents the properties of the Dynamic Entity
   class Child<T> {
      String key;
      T property;

      Child(String key, T property) {
         this.key = key;
         this.property = property;
      }
   }

   // Dynamic entity
   class DynamicEntity {
      int id;
      String type;
      List<Child<?>> children;

      DynamicEntity(int id, String type, List<Child<?>> children) {
         this.id = id;
         this.type = type;
         this.children = children;
      }

      private String generateProto() {
         StringBuilder proto = new StringBuilder();
         AtomicInteger fieldNumber = new AtomicInteger();
         proto.append("message ").append(type).append(" {\n");
         proto.append("required int32 ").append("id").append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         proto.append("required string ").append("type").append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         children.forEach(c -> {
            String type = c.property instanceof Integer ? "int32" : "string";
            proto.append("required ").append(type).append(" ").append(c.key).append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         });
         return proto.append(" } ").toString();
      }
   }

   // The Marshaller for the DynamicEntity
   class EntityMarshaller implements MessageMarshaller<DynamicEntity> {
      private String type;

      EntityMarshaller(String type) {
         this.type = type;
      }

      @Override
      public DynamicEntity readFrom(ProtoStreamReader reader) throws IOException {
         Descriptor descriptor = reader.getSerializationContext().getMessageDescriptor(this.getTypeName());
         int id = reader.readInt("id");
         String type = reader.readString("type");
         List<Child<?>> children = descriptor.getFields().stream()
               .filter(fd -> !fd.getName().equals("id") && !fd.getName().equals("type"))
               .map(field -> {
                  try {
                     String name = field.getName();
                     Object value;
                     if (field.getType() == Type.INT32) {
                        value = reader.readInt(name);
                     } else {
                        value = reader.readString(name);
                     }
                     return new Child<>(field.getName(), value);
                  } catch (Exception ignored) {
                     return null;
                  }
               })
               .collect(toList());

         return new DynamicEntity(id, type, children);
      }

      @Override
      public void writeTo(ProtoStreamWriter writer, DynamicEntity topLevel) throws IOException {
         writer.writeInt("id", topLevel.id);
         writer.writeString("type", topLevel.type);
         topLevel.children.forEach(c -> {
            try {
               String name = c.key;
               Object value = c.property;
               if (value instanceof Integer) {
                  writer.writeInt(name, (Integer) value);
               } else {
                  writer.writeString(name, value.toString());
               }
            } catch (Exception ignored) {
            }
         });

      }

      @Override
      public Class<? extends DynamicEntity> getJavaClass() {
         return DynamicEntity.class;
      }

      @Override
      public String getTypeName() {
         return type;
      }
   }

   @Test
   public void testMarshallerProviderDynamicTypes() throws IOException {
      // Create two dynamic types
      DynamicEntity dynamicEntity = new DynamicEntity(1, "type1",
            asList(new Child<>("eyes", "blue"), new Child<>("age", 23)));
      DynamicEntity otherDynamicEntity = new DynamicEntity(2, "type2",
            asList(new Child<>("country", "Jamaica"), new Child<>("currency", "jmd")));

      // Auto generate the proto file from the entities and register them
      SerializationContextImpl ctx = createContext();
      String protoFile1 = dynamicEntity.generateProto();
      String protoFile2 = otherDynamicEntity.generateProto();
      System.out.println(protoFile1);
      System.out.println(protoFile2);
      ctx.registerProtoFiles(new FileDescriptorSource()
            .addProtoFile(dynamicEntity.type + ".proto", protoFile1)
            .addProtoFile(otherDynamicEntity.type + ".proto", protoFile2));

      // Register a marshaller provider
      ctx.registerMarshallerProvider(new SerializationContext.MarshallerProvider() {
         @Override
         public BaseMarshaller<?> getMarshaller(String typeName) {
            return new EntityMarshaller(typeName);
         }

         @Override
         public BaseMarshaller<?> getMarshaller(Class<?> javaClass) {
            // HARDCODED, the marshaller requires a type but it cannot infer from a class, it'd need access to the instance
            return new EntityMarshaller("type1");
         }
      });

      byte[] bytes = ProtobufUtil.toByteArray(ctx, dynamicEntity);
      DynamicEntity input = ProtobufUtil.fromByteArray(ctx, bytes, DynamicEntity.class);

      assertEquals(1, input.id);
      assertEquals("type1", input.type);
      assertEquals(2, input.children.size());

      Child<?> first = input.children.get(0);
      assertEquals("eyes", first.key);
      assertEquals("blue", first.property);

      Child<?> second = input.children.get(1);
      assertEquals("age", second.key);
      assertEquals(23, second.property);

      // Does not work with other types...
//      byte[] anotherBytes = ProtobufUtil.toByteArray(ctx, otherDynamicEntity);
//      DynamicEntity anotherInput = ProtobufUtil.fromByteArray(ctx, bytes, DynamicEntity.class);
   }
}

{code}

  was:
Add the test to the protostream project:

{code:java}
package org.infinispan.protostream.impl;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.infinispan.protostream.BaseMarshaller;
import org.infinispan.protostream.FileDescriptorSource;
import org.infinispan.protostream.MessageMarshaller;
import org.infinispan.protostream.ProtobufUtil;
import org.infinispan.protostream.SerializationContext;
import org.infinispan.protostream.config.Configuration;
import org.infinispan.protostream.descriptors.Descriptor;
import org.infinispan.protostream.descriptors.Type;
import org.junit.Test;

public class DynamicEntityTest {

   private SerializationContextImpl createContext() {
      return (SerializationContextImpl) ProtobufUtil.newSerializationContext(Configuration.builder().build());
   }

   // Inner class, represents the properties of the Dynamic Entity
   class Child<T> {
      String key;
      T property;

      Child(String key, T property) {
         this.key = key;
         this.property = property;
      }
   }

   // Dynamic entity
   class DynamicEntity {
      int id;
      String type;
      List<Child<?>> children;

      DynamicEntity(int id, String type, List<Child<?>> children) {
         this.id = id;
         this.type = type;
         this.children = children;
      }

      private String generateProto() {
         StringBuilder proto = new StringBuilder();
         AtomicInteger fieldNumber = new AtomicInteger();
         proto.append("message ").append(type).append(" {\n");
         proto.append("required int32 ").append("id").append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         proto.append("required string ").append("type").append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         children.forEach(c -> {
            String type = c.property instanceof Integer ? "int32" : "string";
            proto.append("required ").append(type).append(" ").append(c.key).append("=").append(fieldNumber.incrementAndGet()).append(";\n");
         });
         return proto.append(" } ").toString();
      }
   }

   // The Marshaller for the DynamicEntity
   class EntityMarshaller implements MessageMarshaller<DynamicEntity> {
      private String type;

      EntityMarshaller(String type) {
         this.type = type;
      }

      @Override
      public DynamicEntity readFrom(ProtoStreamReader reader) throws IOException {
         Descriptor descriptor = reader.getSerializationContext().getMessageDescriptor(this.getTypeName());
         int id = reader.readInt("id");
         String type = reader.readString("type");
         List<Child<?>> children = descriptor.getFields().stream()
               .filter(fd -> !fd.getName().equals("id") && !fd.getName().equals("type"))
               .map(field -> {
                  try {
                     String name = field.getName();
                     Object value;
                     if (field.getType() == Type.INT32) {
                        value = reader.readInt(name);
                     } else {
                        value = reader.readString(name);
                     }
                     return new Child<>(field.getName(), value);
                  } catch (Exception ignored) {
                     return null;
                  }
               })
               .collect(toList());

         return new DynamicEntity(id, type, children);
      }

      @Override
      public void writeTo(ProtoStreamWriter writer, DynamicEntity topLevel) throws IOException {
         writer.writeInt("id", topLevel.id);
         writer.writeString("type", topLevel.type);
         topLevel.children.forEach(c -> {
            try {
               String name = c.key;
               Object value = c.property;
               if (value instanceof Integer) {
                  writer.writeInt(name, (Integer) value);
               } else {
                  writer.writeString(name, value.toString());
               }
            } catch (Exception ignored) {
            }
         });

      }

      @Override
      public Class<? extends DynamicEntity> getJavaClass() {
         return DynamicEntity.class;
      }

      @Override
      public String getTypeName() {
         return type;
      }
   }

   @Test
   public void testMarshallerProviderDynamicTypes() throws IOException {
      // Create two dynamic types
      DynamicEntity dynamicEntity = new DynamicEntity(1, "type1",
            asList(new Child<>("eyes", "blue"), new Child<>("age", 23)));
      DynamicEntity otherDynamicEntity = new DynamicEntity(2, "type2",
            asList(new Child<>("country", "Jamaica"), new Child<>("currency", "jmd")));

      // Auto generate the proto file from the entities and register them
      SerializationContextImpl ctx = createContext();
      String protoFile1 = dynamicEntity.generateProto();
      String protoFile2 = otherDynamicEntity.generateProto();
      System.out.println(protoFile1);
      System.out.println(protoFile2);
      ctx.registerProtoFiles(new FileDescriptorSource()
            .addProtoFile(dynamicEntity.type + ".proto", protoFile1)
            .addProtoFile(otherDynamicEntity.type + ".proto", protoFile2));

      // Register a marshaller provider
      ctx.registerMarshallerProvider(new SerializationContext.MarshallerProvider() {
         @Override
         public BaseMarshaller<?> getMarshaller(String typeName) {
            return new EntityMarshaller(typeName);
         }

         @Override
         public BaseMarshaller<?> getMarshaller(Class<?> javaClass) {
            // HARDCODED, ideally it should be able to expose to obtain the 'type' from the class
            return new EntityMarshaller("type1");
         }
      });

      byte[] bytes = ProtobufUtil.toByteArray(ctx, dynamicEntity);
      DynamicEntity input = ProtobufUtil.fromByteArray(ctx, bytes, DynamicEntity.class);

      assertEquals(1, input.id);
      assertEquals("type1", input.type);
      assertEquals(2, input.children.size());

      Child<?> first = input.children.get(0);
      assertEquals("eyes", first.key);
      assertEquals("blue", first.property);

      Child<?> second = input.children.get(1);
      assertEquals("age", second.key);
      assertEquals(23, second.property);

      // Does not work with other types...
//      byte[] anotherBytes = ProtobufUtil.toByteArray(ctx, otherDynamicEntity);
//      DynamicEntity anotherInput = ProtobufUtil.fromByteArray(ctx, bytes, DynamicEntity.class);
   }
}

{code}



> DynamicEntity support in MarshallerProvider
> -------------------------------------------
>
>                 Key: IPROTO-56
>                 URL: https://issues.jboss.org/browse/IPROTO-56
>             Project: Infinispan ProtoStream
>          Issue Type: Bug
>            Reporter: Gustavo Fernandes
>
> The use case is a class that describe the entity. This class contains a 'type' and a list of properties. MarshallerProvider is used to associate a type with a marshaller that uses this 'type' to understand the fields and data types to read/write the stream.
> The type of the entity is contained in the entity itself, and when reading from the stream, this type is used to figure out the fields to read. During writes, though, the type is not involved so it's not possible to use the same strategy. 
> Attached is a unit test to illustrate the situation. This is not necessarily a bug, it may be possible to achieve the usage of Dynamic Entities under other circumstances, or maybe the API needs to be extended.



--
This message was sent by Atlassian JIRA
(v7.5.0#75005)


More information about the infinispan-issues mailing list