[
https://issues.jboss.org/browse/IPROTO-56?page=com.atlassian.jira.plugin....
]
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)