Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Amazon Ion type #130

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,22 @@
<version>0.6</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.amazon.ion</groupId>
<artifactId>ion-java</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-ion</artifactId>
<version>2.11.1</version>
</dependency>
<!-- Required for getting all subclasses of IonValue -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.12</version>
</dependency>

<!-- Testing -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package de.javakaffee.kryoserializers.ion;

import com.amazon.ion.IonDatagram;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonValue;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import org.reflections.Reflections;

import java.util.Set;

/**
* A kryo serializer implementation for Amazon Ion types.
*
* The specs for data type: https://amzn.github.io/ion-docs/
* Github repo for java binding of amzn ion: https://github.com/amzn/ion-java
*/
public class IonValueSerializer extends Serializer<IonValue> {
private final IonSystem _system;

public IonValueSerializer(IonSystem system) {
this._system = system;
setImmutable(true);
}

@Override
public void write(Kryo kryo, Output output, IonValue value) {
IonDatagram dg = _system.newDatagram(value);
int size = dg.byteSize();
byte[] bytes = dg.getBytes();
output.write(size);
output.write(bytes);
output.flush();
}

@Override
public IonValue read(Kryo kryo, Input input, Class<? extends IonValue> type) {
int size = input.read();
byte[] bytes = input.readBytes(size);
return _system.singleValue(bytes);
}

/**
* A utility method to get all subclasses of IonValue. The concrete subclasses
* are not publicly exposed, only the interfaces are. So, need this to register
* the classes.
*
* @return Set of all subclasses of IonValue
*/
public static Set<Class<? extends IonValue>> getAllSubclasses() {
Reflections reflections = new Reflections(IonValue.class.getPackage().getName());
return reflections.getSubTypesOf(IonValue.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package de.javakaffee.kryoserializers.ion;

import com.amazon.ion.IonBlob;
import com.amazon.ion.IonBool;
import com.amazon.ion.IonClob;
import com.amazon.ion.IonDecimal;
import com.amazon.ion.IonInt;
import com.amazon.ion.IonList;
import com.amazon.ion.IonSexp;
import com.amazon.ion.IonString;
import com.amazon.ion.IonStruct;
import com.amazon.ion.IonSymbol;
import com.amazon.ion.IonSystem;
import com.amazon.ion.IonTimestamp;
import com.amazon.ion.IonValue;
import com.amazon.ion.Timestamp;
import com.amazon.ion.system.IonSystemBuilder;
import com.esotericsoftware.kryo.Kryo;
import org.testng.annotations.Test;

import java.util.Objects;

import static de.javakaffee.kryoserializers.KryoTest.deserialize;
import static de.javakaffee.kryoserializers.KryoTest.serialize;
import static org.testng.AssertJUnit.assertEquals;

public class IonValueSerializerTest {
private static IonSystem ION = IonSystemBuilder.standard().build();

@Test
public void testUnregistered() {
Kryo kryo = new Kryo();
kryo.addDefaultSerializer(IonValue.class, new IonValueSerializer(ION));
kryo.setRegistrationRequired(false);

testIonNullPrimitives(kryo);
testIonPrimitives(kryo);
testIonNullContainers(kryo);
testIonEmptyContainers(kryo);
testIonContainers(kryo);
testIonLobs(kryo);
testComposite(kryo);
}

@Test
public void testRegisteredIonPrimitives() {
final Kryo kryo = new Kryo();
kryo.addDefaultSerializer(IonValue.class, new IonValueSerializer(ION));
for (Class<? extends IonValue> cls : IonValueSerializer.getAllSubclasses()) {
kryo.register(cls);
}
kryo.register(CompositeClass.class);
kryo.setRegistrationRequired(true);

testIonNullPrimitives(kryo);
testIonPrimitives(kryo);
testIonNullContainers(kryo);
testIonEmptyContainers(kryo);
testIonContainers(kryo);
testIonLobs(kryo);
testComposite(kryo);
}

private void testIonNullPrimitives(Kryo kryo) {
roundTrip(kryo, ION.newNullInt(), IonInt.class);
roundTrip(kryo, ION.newNullDecimal(), IonDecimal.class);
roundTrip(kryo, ION.newNullBool(), IonBool.class);
roundTrip(kryo, ION.newNullSymbol(), IonSymbol.class);
roundTrip(kryo, ION.newNullString(), IonString.class);
roundTrip(kryo, ION.newNullTimestamp(), IonTimestamp.class);
}

private void testIonPrimitives(Kryo kryo) {
roundTrip(kryo, ION.newInt(10), IonInt.class);
roundTrip(kryo, ION.newDecimal(10.09001), IonDecimal.class);
roundTrip(kryo, ION.newBool(false), IonBool.class);
roundTrip(kryo, ION.newSymbol("symbol"), IonSymbol.class);
roundTrip(kryo, ION.newString("blah"), IonString.class);
roundTrip(kryo, ION.newTimestamp(Timestamp.valueOf("2020-12-31T23:59:59.000Z")), IonTimestamp.class);
}

private void testIonNullContainers(Kryo kryo) {
roundTrip(kryo, ION.newNullStruct(), IonStruct.class);
roundTrip(kryo, ION.newNullList(), IonList.class);
roundTrip(kryo, ION.newNullSexp(), IonSexp.class);
}

private void testIonEmptyContainers(Kryo kryo) {
roundTrip(kryo, ION.newEmptyStruct(), IonStruct.class);
roundTrip(kryo, ION.newEmptyList(), IonList.class);
roundTrip(kryo, ION.newEmptySexp(), IonSexp.class);
}

private void testIonContainers(Kryo kryo) {
IonStruct struct1 = ION.newEmptyStruct();
struct1.add("i", ION.newInt(10));
struct1.add("s", ION.newString("str"));
struct1.add("ss", ION.newSexp(ION.newBool(true)));
roundTrip(kryo, struct1, IonStruct.class);

IonList list1 = ION.newEmptyList();
list1.add(struct1.clone());
roundTrip(kryo, list1, IonList.class);
}

private void testIonLobs(Kryo kryo) {
byte[] bytes = "som random string".getBytes();
IonBlob blob = ION.newBlob(bytes);
roundTrip(kryo, blob, IonBlob.class);

IonClob clob = ION.newClob(bytes);
roundTrip(kryo, clob, IonClob.class);
}

private void testComposite(Kryo kryo) {
CompositeClass composite = new CompositeClass();
composite.name = "my name";
composite.age = 13;
composite.details = (IonStruct) ION.singleValue("{address: \"\", city: \"Seattle\", zip: 11111}");

byte[] bytes = serialize(kryo, composite);
CompositeClass result = deserialize(kryo, bytes, CompositeClass.class);
assertEquals(composite, result);
}

private void roundTrip(Kryo kryo, IonValue value, Class<? extends IonValue> type) {
byte[] bytes = serialize(kryo, value);
IonValue result = deserialize(kryo, bytes, type);
assertIonValue(value, result);
}

private void assertIonValue(IonValue expected, IonValue actual) {
assertEquals(expected, actual);
}

static class CompositeClass {
String name;
int age;
IonStruct details;

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompositeClass that = (CompositeClass) o;
return age == that.age &&
Objects.equals(name, that.name) &&
Objects.equals(details, that.details);
}

@Override
public int hashCode() {
return Objects.hash(name, age, details);
}
}
}