diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfoSerializer.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfoSerializer.java new file mode 100644 index 0000000000..c10cb51880 --- /dev/null +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassInfoSerializer.java @@ -0,0 +1,21 @@ +package org.apache.fory.resolver; + +import org.apache.fory.memory.MemoryBuffer; + +/** + * ClassInfoSerializer provides a mechanism to customize the recording of the ClassInfo + * default implementation uses a short value, however when the class registration needs to be customized + * this can be set of the ClassResolver to handle storing the ClassInfo is a customized manner + */ +public interface ClassInfoSerializer { + /** + * Writes the provided ClassInfo into the buffer in a customized manner which is consistent + * with the readClassInfo method + */ + void writeClassInfo(ClassResolver classResolver, MemoryBuffer buffer, ClassInfo classInfo); + + /** + * Reads the ClassInfo from the provided buffer in a manner which is consistent with writeClassInfo + */ + ClassInfo readClassInfo(ClassResolver classResolver, MemoryBuffer buffer); +} diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java index 8b9eca8dc6..0c543d41cf 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java @@ -218,6 +218,9 @@ public class ClassResolver extends TypeResolver { private short innerEndClassId; private final ShimDispatcher shimDispatcher; + // customized ClassId serialization mechanism + private ClassInfoSerializer classInfoSerializer; + public ClassResolver(Fory fory) { super(fory); this.fory = fory; @@ -1361,23 +1364,25 @@ public void writeClassAndUpdateCache(MemoryBuffer buffer, Class cls) { /** Write classname for java serialization. */ @Override public void writeClassInfo(MemoryBuffer buffer, ClassInfo classInfo) { - if (metaContextShareEnabled) { - // FIXME(chaokunyang) Register class but not register serializer can't be used with - // meta share mode, because no class def are sent to peer. - writeClassInfoWithMetaShare(buffer, classInfo); - } else { - if (classInfo.classId == NO_CLASS_ID) { // no class id provided. - // use classname - // if it's null, it's a bug. - assert classInfo.namespaceBytes != null; - metaStringResolver.writeMetaStringBytesWithFlag(buffer, classInfo.namespaceBytes); - assert classInfo.typeNameBytes != null; - metaStringResolver.writeMetaStringBytes(buffer, classInfo.typeNameBytes); + if (classInfoSerializer != null) { + classInfoSerializer.writeClassInfo(this, buffer, classInfo); + } else if (metaContextShareEnabled) { + // FIXME(chaokunyang) Register class but not register serializer can't be used with + // meta share mode, because no class def are sent to peer. + writeClassInfoWithMetaShare(buffer, classInfo); } else { - // use classId - buffer.writeVarUint32(classInfo.classId << 1); + if (classInfo.classId == NO_CLASS_ID) { // no class id provided. + // use classname + // if it's null, it's a bug. + assert classInfo.namespaceBytes != null; + metaStringResolver.writeMetaStringBytesWithFlag(buffer, classInfo.namespaceBytes); + assert classInfo.typeNameBytes != null; + metaStringResolver.writeMetaStringBytes(buffer, classInfo.typeNameBytes); + } else { + // use classId + buffer.writeVarUint32(classInfo.classId << 1); + } } - } } public void writeClassInfoWithMetaShare(MemoryBuffer buffer, ClassInfo classInfo) { @@ -1483,21 +1488,25 @@ public void writeClassInternal(MemoryBuffer buffer, Class cls) { } public void writeClassInternal(MemoryBuffer buffer, ClassInfo classInfo) { - short classId = classInfo.classId; - if (classId == REPLACE_STUB_ID) { - // clear class id to avoid replaced class written as - // ReplaceResolveSerializer.ReplaceStub - classInfo.classId = NO_CLASS_ID; - } - if (classInfo.classId != NO_CLASS_ID) { - buffer.writeVarUint32(classInfo.classId << 1); - } else { - // let the lowermost bit of next byte be set, so the deserialization can know - // whether need to read class by name in advance - metaStringResolver.writeMetaStringBytesWithFlag(buffer, classInfo.namespaceBytes); - metaStringResolver.writeMetaStringBytes(buffer, classInfo.typeNameBytes); - } - classInfo.classId = classId; + short classId = classInfo.classId; + if(classInfoSerializer != null) { + classInfoSerializer.writeClassInfo(this, buffer, classInfo); + } else { + if (classId == REPLACE_STUB_ID) { + // clear class id to avoid replaced class written as + // ReplaceResolveSerializer.ReplaceStub + classInfo.classId = NO_CLASS_ID; + } + if (classInfo.classId != NO_CLASS_ID) { + buffer.writeVarUint32(classInfo.classId << 1); + } else { + // let the lowermost bit of next byte be set, so the deserialization can know + // whether need to read class by name in advance + metaStringResolver.writeMetaStringBytesWithFlag(buffer, classInfo.namespaceBytes); + metaStringResolver.writeMetaStringBytes(buffer, classInfo.typeNameBytes); + } + } + classInfo.classId = classId; } /** @@ -1506,16 +1515,21 @@ public void writeClassInternal(MemoryBuffer buffer, ClassInfo classInfo) { * #readClassInfo(MemoryBuffer, ClassInfoHolder)} should be invoked. */ public Class readClassInternal(MemoryBuffer buffer) { - int header = buffer.readVarUint32Small14(); final ClassInfo classInfo; - if ((header & 0b1) != 0) { - // let the lowermost bit of next byte be set, so the deserialization can know - // whether need to read class by name in advance - MetaStringBytes packageBytes = metaStringResolver.readMetaStringBytesWithFlag(buffer, header); - MetaStringBytes simpleClassNameBytes = metaStringResolver.readMetaStringBytes(buffer); - classInfo = loadBytesToClassInfo(packageBytes, simpleClassNameBytes); + + if(classInfoSerializer != null) { + classInfo = classInfoSerializer.readClassInfo(this, buffer); } else { - classInfo = registeredId2ClassInfo[(short) (header >> 1)]; + int header = buffer.readVarUint32Small14(); + if ((header & 0b1) != 0) { + // let the lowermost bit of next byte be set, so the deserialization can know + // whether need to read class by name in advance + MetaStringBytes packageBytes = metaStringResolver.readMetaStringBytesWithFlag(buffer, header); + MetaStringBytes simpleClassNameBytes = metaStringResolver.readMetaStringBytes(buffer); + classInfo = loadBytesToClassInfo(packageBytes, simpleClassNameBytes); + } else { + classInfo = registeredId2ClassInfo[(short) (header >> 1)]; + } } final Class cls = classInfo.cls; currentReadClass = cls; @@ -1530,16 +1544,24 @@ public ClassInfo readClassInfo(MemoryBuffer buffer) { if (metaContextShareEnabled) { return readSharedClassMeta(buffer, fory.getSerializationContext().getMetaContext()); } - int header = buffer.readVarUint32Small14(); - ClassInfo classInfo; - if ((header & 0b1) != 0) { - classInfo = readClassInfoFromBytes(buffer, classInfoCache, header); - classInfoCache = classInfo; - } else { - classInfo = getOrUpdateClassInfo((short) (header >> 1)); + else { + ClassInfo classInfo; + if(classInfoSerializer != null) { + classInfo = classInfoSerializer.readClassInfo(this, buffer); + classInfoCache = classInfo; + } else { + int header = buffer.readVarUint32Small14(); + if ((header & 0b1) != 0) { + classInfo = readClassInfoFromBytes(buffer, classInfoCache, header); + classInfoCache = classInfo; + } else { + classInfo = getOrUpdateClassInfo((short) (header >> 1)); + } + } + + currentReadClass = classInfo.cls; + return classInfo; } - currentReadClass = classInfo.cls; - return classInfo; } /** @@ -1549,7 +1571,9 @@ public ClassInfo readClassInfo(MemoryBuffer buffer) { @CodegenInvoke @Override public ClassInfo readClassInfo(MemoryBuffer buffer, ClassInfo classInfoCache) { - if (metaContextShareEnabled) { + if(classInfoSerializer != null) { + return classInfoSerializer.readClassInfo(this, buffer); + } else if (metaContextShareEnabled) { return readSharedClassMeta(buffer, fory.getSerializationContext().getMetaContext()); } int header = buffer.readVarUint32Small14(); diff --git a/java/fory-core/src/test/java/org/apache/fory/serializer/ClassInfoSerializerTest.java b/java/fory-core/src/test/java/org/apache/fory/serializer/ClassInfoSerializerTest.java new file mode 100644 index 0000000000..2149a6625e --- /dev/null +++ b/java/fory-core/src/test/java/org/apache/fory/serializer/ClassInfoSerializerTest.java @@ -0,0 +1,116 @@ +package org.apache.fory.serializer; + + +import lombok.Data; +import org.apache.fory.Fory; +import org.apache.fory.ForyTestBase; +import org.apache.fory.config.Language; +import org.apache.fory.memory.MemoryBuffer; +import org.apache.fory.resolver.ClassInfo; +import org.apache.fory.resolver.ClassInfoSerializer; +import org.apache.fory.resolver.ClassResolver; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.io.Serializable; +import java.util.UUID; + +public class ClassInfoSerializerTest extends ForyTestBase { + + @Data + public static class JavaCustomClass implements Serializable { + public String name; + public transient int age; + public static final UUID UUID = new UUID(123L, 456L); + + public JavaCustomClass(String name, int age) { + this.name = name; + this.age = age; + } + + private void writeObject(java.io.ObjectOutputStream s) throws IOException { + s.defaultWriteObject(); + s.writeInt(age); + } + + private void readObject(java.io.ObjectInputStream s) throws Exception { + s.defaultReadObject(); + this.age = s.readInt(); + } + } + + @Data + public static class CustomClass { + public String name; + public static final UUID UUID = new UUID(444L, 555L); + + public CustomClass(String name) { + this.name = name; + } + } + + + static class Mapper implements ClassInfoSerializer { + + @Override + public void writeClassInfo(ClassResolver classResolver, MemoryBuffer buffer, ClassInfo classInfo) { + UUID uuid; + + if(classInfo.getCls().equals(JavaCustomClass.class)) + uuid = JavaCustomClass.UUID; + else if(classInfo.getCls().equals(CustomClass.class)) + uuid = CustomClass.UUID; + else + throw new RuntimeException("unknown class"); + + buffer.writeVarUint64(uuid.getLeastSignificantBits()); + buffer.writeVarUint64(uuid.getMostSignificantBits()); + } + + @Override + public ClassInfo readClassInfo(ClassResolver classResolver, MemoryBuffer buffer) { + long least = buffer.readVarUint64(); + long most = buffer.readVarUint64(); + UUID uuid = new UUID(most, least); + + if(uuid.equals(JavaCustomClass.UUID)) + return classResolver.getClassInfo(JavaCustomClass.class); + else if(uuid.equals(CustomClass.UUID)) + return classResolver.getClassInfo(CustomClass.class); + else + throw new RuntimeException("unknown class"); + } + } + + @Test + public void testJavaObject() { + Fory fory = + Fory.builder() + .withLanguage(Language.JAVA) + .withRefTracking(false) + .requireClassRegistration(false) + .build(); + + fory.getClassResolver().setClassMapper(new Mapper()); + + JavaCustomClass deser = serDe(fory, new JavaCustomClass("bob", 10)); + Assert.assertEquals(deser.name, "bob"); + Assert.assertEquals(deser.age, 10); + } + + + @Test + public void testObject() { + Fory fory = Fory.builder() + .requireClassRegistration(false) + .build(); + + fory.getClassResolver().setClassMapper(new Mapper()); + + CustomClass deser = serDe(fory, new CustomClass("mike")); + Assert.assertEquals(deser.name, "mike"); + } + +} +