/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.avro.core; import com.espertech.esper.avro.util.support.SupportAvroUtil; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.EventPropertyDescriptor; import com.espertech.esper.client.EventPropertyGetter; import com.espertech.esper.client.EventType; import com.espertech.esper.client.scopetest.EPAssertionUtil; import junit.framework.TestCase; import org.apache.avro.Schema; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericEnumSymbol; import org.apache.avro.generic.GenericFixed; import org.apache.avro.util.Utf8; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Consumer; import static com.espertech.esper.avro.core.AvroConstant.PROP_JAVA_STRING_KEY; import static com.espertech.esper.avro.core.AvroConstant.PROP_JAVA_STRING_VALUE; import static com.espertech.esper.avro.util.support.SupportAvroUtil.makeAvroSupportEventType; import static org.apache.avro.SchemaBuilder.record; public class TestAvroEventType extends TestCase { public void testGetPropertyType() { Schema lvl2Schema = record("lvl2Schema").fields() .name("nestedValue").type().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().noDefault() .name("nestedIndexed").type().array().items().intBuilder().endInt().arrayDefault(Collections.emptyList()) .name("nestedMapped").type().map().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).values().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().mapDefault(null) .endRecord(); Schema lvl1Schema = record("lvl1Schema").fields() .name("lvl2").type(lvl2Schema).noDefault() .requiredInt("intPrimitive") .name("indexed").type().array().items().intBuilder().endInt().arrayDefault(Collections.emptyList()) .name("mapped").type().map().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).values().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().mapDefault(null) .endRecord(); Schema schema = record("typename").fields() .requiredInt("myInt") .optionalInt("myIntBoxed") .name("myString").type().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().noDefault() .name("lvl1").type(lvl1Schema).noDefault() .name("myNullValue").type().nullType().noDefault() .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(int.class, null, eventType, "myInt"); assertPropertyType(Integer.class, null, eventType, "myIntBoxed"); assertPropertyType(String.class, null, eventType, "myString"); assertPropertyType(null, null, eventType, "myNullValue"); assertPropertyType(GenericData.Record.class, null, eventType, "lvl1"); assertPropertyType(int.class, null, eventType, "lvl1.intPrimitive"); assertPropertyType(String.class, null, eventType, "lvl1.lvl2.nestedValue"); assertPropertyType(int.class, null, eventType, "lvl1.indexed[1]"); assertPropertyType(String.class, null, eventType, "lvl1.mapped('a')"); assertPropertyType(String.class, null, eventType, "lvl1.lvl2.nestedMapped('a')"); assertPropertyType(int.class, null, eventType, "lvl1.lvl2.nestedIndexed[1]"); assertNotAProperty(eventType, "dummy"); assertNotAProperty(eventType, "lvl1.dfgdg"); assertNotAProperty(eventType, "xxx.intPrimitive"); assertNotAProperty(eventType, "lvl1.lvl2.nestedValueXXX"); assertNotAProperty(eventType, "myInt[1]"); assertNotAProperty(eventType, "lvl1.intPrimitive[1]"); assertNotAProperty(eventType, "myInt('a')"); assertNotAProperty(eventType, "lvl1.intPrimitive('a')"); assertNotAProperty(eventType, "lvl1.lvl2.nestedIndexed('a')"); assertNotAProperty(eventType, "lvl1.lvl2.nestedMapped[1]"); GenericData.Record lvl2Rec = new GenericData.Record(lvl2Schema); lvl2Rec.put("nestedValue", 100); lvl2Rec.put("nestedIndexed", Arrays.asList(19, 21)); lvl2Rec.put("nestedMapped", Collections.singletonMap("nestedkey", "nestedvalue")); GenericData.Record lvl1Rec = new GenericData.Record(lvl1Schema); lvl1Rec.put("lvl2", lvl2Rec); lvl1Rec.put("intPrimitive", 10); lvl1Rec.put("indexed", Arrays.asList(1, 2, 3)); lvl1Rec.put("mapped", Collections.singletonMap("key", "value")); GenericData.Record record = new GenericData.Record(schema); record.put("lvl1", lvl1Rec); record.put("myInt", 99); record.put("myIntBoxed", 554); record.put("myString", "hugo"); record.put("myNullValue", null); AvroGenericDataEventBean eventBean = new AvroGenericDataEventBean(record, eventType); assertEquals(99, eventBean.get("myInt")); assertEquals(554, eventBean.get("myIntBoxed")); assertEquals("hugo", eventBean.get("myString")); assertEquals(lvl1Rec, eventBean.get("lvl1")); assertEquals(10, eventBean.get("lvl1.intPrimitive")); assertEquals(100, eventBean.get("lvl1.lvl2.nestedValue")); assertEquals(2, eventBean.get("lvl1.indexed[1]")); assertEquals("value", eventBean.get("lvl1.mapped('key')")); assertEquals(null, eventBean.get("myNullValue")); assertEquals("nestedvalue", eventBean.get("lvl1.lvl2.nestedMapped('nestedkey')")); assertEquals(21, eventBean.get("lvl1.lvl2.nestedIndexed[1]")); } public void testRequiredType() { Schema schema = record("typename").fields() .requiredInt("myInt") .requiredString("myCharSeq") .name("myString").type().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().noDefault() .requiredBoolean("myBoolean") .requiredBytes("myBytes") .requiredDouble("myDouble") .requiredFloat("myFloat") .requiredLong("myLong") .endRecord(); String[] propNames = "myInt,myCharSeq,myString,myBoolean,myBytes,myDouble,myFloat,myLong".split(","); EventType eventType = makeAvroSupportEventType(schema); EPAssertionUtil.assertEqualsExactOrder(eventType.getPropertyNames(), propNames); assertEquals(GenericData.Record.class, eventType.getUnderlyingType()); assertNull(eventType.getSuperTypes()); assertPropertyType(int.class, null, eventType, "myInt"); assertPropertyType(CharSequence.class, null, eventType, "myCharSeq"); assertPropertyType(String.class, null, eventType, "myString"); assertPropertyType(boolean.class, null, eventType, "myBoolean"); assertPropertyType(ByteBuffer.class, null, eventType, "myBytes"); assertPropertyType(double.class, null, eventType, "myDouble"); assertPropertyType(float.class, null, eventType, "myFloat"); assertPropertyType(long.class, null, eventType, "myLong"); for (String propName : propNames) { assertTrue(eventType.isProperty(propName)); } GenericData.Record datum = getRecordWithValues(schema); assertValuesRequired(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'myInt': 10, 'myCharSeq': 'x', 'myString': 'y', 'myBoolean': true, 'myBytes': '\\u00AA\'," + "'myDouble' : 50, 'myFloat':100, 'myLong':20}"; assertValuesRequired(new AvroGenericDataEventBean(SupportAvroUtil.parseQuoted(schema, jsonWValues), eventType)); } public void testOptionalType() { Schema schema = record("typename").fields() .optionalInt("myInt") .optionalString("myCharSeq") .name("myString").type().optional().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString() .optionalBoolean("myBoolean") .optionalBytes("myBytes") .optionalDouble("myDouble") .optionalFloat("myFloat") .optionalLong("myLong") .endRecord(); runAssertionNullableOrOptTypes(schema); } public void testNullableType() { Schema schema = record("typename").fields() .nullableInt("myInt", Integer.MIN_VALUE) .nullableString("myCharSeq", null) .name("myString").type().nullable().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().stringDefault(null) .nullableBoolean("myBoolean", false) .nullableBytes("myBytes", new byte[0]) .nullableDouble("myDouble", Double.MIN_VALUE) .nullableFloat("myFloat", Float.MIN_VALUE) .nullableLong("myLong", Long.MIN_VALUE) .endRecord(); runAssertionNullableOrOptTypes(schema); } public void testNestedSimple() { String schemaText = "{" + " 'type' : 'record'," + " 'name' : 'MyEvent'," + " 'fields' : [ {" + " 'name' : 'innerEvent'," + " 'type' : {" + " 'type' : 'record'," + " 'name' : 'innerEventTypeName'," + " 'fields' : [ {" + " 'name' : 'innerValue'," + " 'type' : {'type':'string','avro.java.string':'String'}" + " } ]" + " }" + " }]" + "}"; Schema schema = new Schema.Parser().parse(schemaText.replace("'", "\"")); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(GenericData.Record.class, null, eventType, "innerEvent"); String[] propNames = "innerEvent".split(","); EPAssertionUtil.assertEqualsExactOrder(eventType.getPropertyNames(), propNames); assertTrue(eventType.isProperty("innerEvent")); GenericData.Record datumInner = new GenericData.Record(schema.getField("innerEvent").schema()); datumInner.put("innerValue", "i1"); GenericData.Record datum = new GenericData.Record(schema); datum.put("innerEvent", datumInner); assertValuesNested(datum, new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'innerEvent': {'innerValue' : 'i1'}}}"; datum = SupportAvroUtil.parseQuoted(schema, jsonWValues); assertValuesNested(datum, new AvroGenericDataEventBean(datum, eventType)); } public void testArrayOfPrimitive() { Schema schema = record("typename").fields() .name("intArray").type().array().items().intBuilder().endInt().arrayDefault(Collections.emptyList()) .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(Collection.class, int.class, eventType, "intArray"); Consumer<EventBean> asserter = eventBean -> { assertEquals(1, eventBean.get("intArray[0]")); assertEquals(2, eventBean.get("intArray[1]")); assertEquals(1, eventType.getGetter("intArray[0]").get(eventBean)); assertEquals(2, eventType.getGetter("intArray[1]").get(eventBean)); }; GenericData.Record datum = new GenericData.Record(schema); datum.put("intArray", Arrays.asList(1, 2)); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'intArray':[1,2]}}"; datum = SupportAvroUtil.parseQuoted(schema, jsonWValues); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); } public void testMapOfString() { Schema schema = record("typename").fields() .name("anMap").type().map().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).values().stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString().mapDefault(null) .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(Map.class, String.class, eventType, "anMap"); Consumer<EventBean> asserter = eventBean -> { assertEquals("myValue", eventBean.get("anMap('myKey')")); assertEquals("myValue", eventType.getGetter("anMap('myKey')").get(eventBean)); }; GenericData.Record datum = new GenericData.Record(schema); datum.put("anMap", Collections.singletonMap("myKey", "myValue")); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'anMap':{'myKey':'myValue'}}"; datum = SupportAvroUtil.parseQuoted(schema, jsonWValues); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); } public void testFixed() { Schema schema = record("typename").fields() .name("aFixed").type().fixed("abc").size(2).fixedDefault(ByteBuffer.wrap(new byte[0])) .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(GenericFixed.class, null, eventType, "aFixed"); Consumer<EventBean> asserter = eventBean -> { GenericData.Fixed fixed = (GenericData.Fixed) eventBean.get("aFixed"); assertTrue(Arrays.equals(fixed.bytes(), new byte[]{1, 2})); }; GenericData.Record datum = new GenericData.Record(schema); datum.put("aFixed", new GenericData.Fixed(schema.getField("aFixed").schema(), new byte[]{1, 2})); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'aFixed': '\\u0001\\u0002\'}"; datum = SupportAvroUtil.parseQuoted(schema, jsonWValues); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); } public void testEnumSymbol() { Schema schema = record("typename").fields() .name("aEnum").type().enumeration("myEnum").symbols("a", "b").enumDefault("x") .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(GenericEnumSymbol.class, null, eventType, "aEnum"); Consumer<EventBean> asserter = eventBean -> { GenericEnumSymbol v = (GenericEnumSymbol) eventBean.get("aEnum"); assertEquals("b", v.toString()); }; GenericData.Record datum = new GenericData.Record(schema); datum.put("aEnum", new GenericData.EnumSymbol(schema.getField("aEnum").schema(), "b")); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'aEnum': 'b'}"; datum = SupportAvroUtil.parseQuoted(schema, jsonWValues); asserter.accept(new AvroGenericDataEventBean(datum, eventType)); } public void testUnionResultingInObject() { Schema schema = record("typename").fields() .name("anUnion").type().unionOf() .intBuilder().endInt() .and() .stringBuilder().prop(PROP_JAVA_STRING_KEY, PROP_JAVA_STRING_VALUE).endString() .and() .nullType() .endUnion().noDefault() .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(Object.class, null, eventType, "anUnion"); Consumer<Object> asserterFromDatum = (value) -> { GenericData.Record datum = new GenericData.Record(schema); datum.put("anUnion", value); assertEquals(value, new AvroGenericDataEventBean(datum, eventType).get("anUnion")); }; asserterFromDatum.accept("a"); asserterFromDatum.accept(1); asserterFromDatum.accept(null); BiConsumer<String, Object> asserterFromJson = (json, value) -> { GenericData.Record datum = SupportAvroUtil.parseQuoted(schema, json); assertEquals(value, new AvroGenericDataEventBean(datum, eventType).get("anUnion")); }; asserterFromJson.accept("{'anUnion':{'int':1}}", 1); asserterFromJson.accept("{'anUnion':{'string':'abc'}}", "abc"); asserterFromJson.accept("{'anUnion':null}", null); } public void testUnionResultingInNumber() { Schema schema = record("typename").fields() .name("anUnion").type().unionOf() .intBuilder().endInt() .and() .floatBuilder().endFloat() .endUnion().noDefault() .endRecord(); EventType eventType = makeAvroSupportEventType(schema); assertPropertyType(Number.class, null, eventType, "anUnion"); Consumer<Object> asserterFromDatum = (value) -> { GenericData.Record datum = new GenericData.Record(schema); datum.put("anUnion", value); assertEquals(value, new AvroGenericDataEventBean(datum, eventType).get("anUnion")); }; asserterFromDatum.accept(1); asserterFromDatum.accept(2f); BiConsumer<String, Object> asserterFromJson = (json, value) -> { GenericData.Record datum = SupportAvroUtil.parseQuoted(schema, json); assertEquals(value, new AvroGenericDataEventBean(datum, eventType).get("anUnion")); }; asserterFromJson.accept("{'anUnion':{'int':1}}", 1); asserterFromJson.accept("{'anUnion':{'float':2}}", 2f); } private void assertValuesNested(GenericData.Record datum, AvroGenericDataEventBean bean) { assertEquals("i1", bean.get("innerEvent.innerValue")); assertEquals("i1", bean.getEventType().getGetter("innerEvent.innerValue").get(bean)); assertSame(datum.get("innerEvent"), bean.get("innerEvent")); assertSame(datum.get("innerEvent"), bean.getEventType().getGetter("innerEvent").get(bean)); } private void runAssertionNullableOrOptTypes(Schema schema) { EventType eventType = makeAvroSupportEventType(schema); assertTypesBoxed(eventType); GenericData.Record datum = getRecordWithValues(schema); assertValuesRequired(new AvroGenericDataEventBean(datum, eventType)); String jsonWValues = "{'myInt': {'int': 10}, 'myCharSeq': {'string': 'x'}, 'myString':{'string': 'y'}," + "'myBoolean': {'boolean': true}, 'myBytes': {'bytes': '\\u00AA\'}, " + "'myDouble': {'double': 50}, 'myFloat': {'float': 100}, 'myLong': {'long': 20}}"; assertValuesRequired(new AvroGenericDataEventBean(SupportAvroUtil.parseQuoted(schema, jsonWValues), eventType)); String jsonWNull = "{'myInt': null, 'myCharSeq': null, 'myString':null," + "'myBoolean': null, 'myBytes': null, " + "'myDouble': null, 'myFloat': null, 'myLong': null}"; assertValuesNull(new AvroGenericDataEventBean(SupportAvroUtil.parseQuoted(schema, jsonWNull), eventType)); } private void assertValuesRequired(AvroGenericDataEventBean bean) { assertValue(10, bean, "myInt"); assertValue(new Utf8("x"), bean, "myCharSeq"); assertValue("y", bean, "myString"); assertValue(true, bean, "myBoolean"); assertValue(ByteBuffer.wrap(new byte[]{(byte) 170}), bean, "myBytes"); assertValue(50d, bean, "myDouble"); assertValue(100f, bean, "myFloat"); assertValue(20L, bean, "myLong"); } private void assertValuesNull(AvroGenericDataEventBean bean) { assertValue(null, bean, "myInt"); assertValue(null, bean, "myCharSeq"); assertValue(null, bean, "myString"); assertValue(null, bean, "myBoolean"); assertValue(null, bean, "myBytes"); assertValue(null, bean, "myDouble"); assertValue(null, bean, "myFloat"); assertValue(null, bean, "myLong"); } private void assertValue(Object expected, AvroGenericDataEventBean bean, String propertyName) { if (expected instanceof ByteBuffer) { assertEqualsByteBuf((ByteBuffer) expected, (ByteBuffer) bean.get(propertyName)); EventPropertyGetter getter = bean.getEventType().getGetter(propertyName); assertEqualsByteBuf((ByteBuffer) expected, (ByteBuffer) getter.get(bean)); } else { assertEquals(expected, bean.get(propertyName)); EventPropertyGetter getter = bean.getEventType().getGetter(propertyName); assertEquals(expected, getter.get(bean)); } } private void assertPropertyType(Class expectedType, Class expectedComponentType, EventType eventType, String propertyName) { assertEquals(expectedType, eventType.getPropertyType(propertyName)); assertTrue(eventType.isProperty(propertyName)); if (!propertyName.contains(".")) { EventPropertyDescriptor descriptor = eventType.getPropertyDescriptor(propertyName); assertEquals(expectedType, descriptor.getPropertyType()); assertEquals(expectedComponentType, descriptor.getPropertyComponentType()); } } private void assertEqualsByteBuf(ByteBuffer expected, ByteBuffer received) { assertTrue(Arrays.equals(expected.array(), received.array())); } private GenericData.Record getRecordWithValues(Schema schema) { GenericData.Record datum = new GenericData.Record(schema); datum.put("myInt", 10); datum.put("myCharSeq", new Utf8("x")); datum.put("myString", "y"); datum.put("myBoolean", true); datum.put("myBytes", ByteBuffer.wrap(new byte[]{(byte) 170})); datum.put("myDouble", 50d); datum.put("myFloat", 100f); datum.put("myLong", 20L); return datum; } private void assertTypesBoxed(EventType eventType) { assertPropertyType(Integer.class, null, eventType, "myInt"); assertPropertyType(CharSequence.class, null, eventType, "myCharSeq"); assertPropertyType(String.class, null, eventType, "myString"); assertPropertyType(Boolean.class, null, eventType, "myBoolean"); assertPropertyType(ByteBuffer.class, null, eventType, "myBytes"); assertPropertyType(Double.class, null, eventType, "myDouble"); assertPropertyType(Float.class, null, eventType, "myFloat"); assertPropertyType(Long.class, null, eventType, "myLong"); } private void assertNotAProperty(EventType type, String propertyName) { assertFalse(type.isProperty(propertyName)); assertNull(type.getPropertyType(propertyName)); assertNull(type.getGetter(propertyName)); assertNull(type.getPropertyDescriptor(propertyName)); } }