Java tutorial
/** * Copyright 2015 Confluent Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package io.confluent.kafkarest.unit; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.TextNode; import org.apache.avro.Schema; import org.apache.avro.generic.GenericArray; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericEnumSymbol; import org.apache.avro.generic.GenericRecord; import org.apache.avro.generic.GenericRecordBuilder; import org.apache.avro.util.Utf8; import org.junit.Test; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import io.confluent.kafkarest.TestUtils; import io.confluent.kafkarest.converters.AvroConverter; import io.confluent.kafkarest.converters.ConversionException; import io.confluent.kafkarest.entities.EntityUtils; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class AvroConverterTest { private static final Schema.Parser parser = new Schema.Parser(); private static final Schema recordSchema = new Schema.Parser().parse("{\"namespace\": \"namespace\",\n" + " \"type\": \"record\",\n" + " \"name\": \"test\",\n" + " \"fields\": [\n" + " {\"name\": \"null\", \"type\": \"null\"},\n" + " {\"name\": \"boolean\", \"type\": \"boolean\"},\n" + " {\"name\": \"int\", \"type\": \"int\"},\n" + " {\"name\": \"long\", \"type\": \"long\"},\n" + " {\"name\": \"float\", \"type\": \"float\"},\n" + " {\"name\": \"double\", \"type\": \"double\"},\n" + " {\"name\": \"bytes\", \"type\": \"bytes\"},\n" + " {\"name\": \"string\", \"type\": \"string\", \"aliases\": [\"string_alias\"]},\n" + " {\"name\": \"null_default\", \"type\": \"null\", \"default\": null},\n" + " {\"name\": \"boolean_default\", \"type\": \"boolean\", \"default\": false},\n" + " {\"name\": \"int_default\", \"type\": \"int\", \"default\": 24},\n" + " {\"name\": \"long_default\", \"type\": \"long\", \"default\": 4000000000},\n" + " {\"name\": \"float_default\", \"type\": \"float\", \"default\": 12.3},\n" + " {\"name\": \"double_default\", \"type\": \"double\", \"default\": 23.2},\n" + " {\"name\": \"bytes_default\", \"type\": \"bytes\", \"default\": \"bytes\"},\n" + " {\"name\": \"string_default\", \"type\": \"string\", \"default\": " + "\"default string\"}\n" + "]\n" + "}"); private static final Schema arraySchema = new Schema.Parser().parse("{\"namespace\": \"namespace\",\n" + " \"type\": \"array\",\n" + " \"name\": \"test\",\n" + " \"items\": \"string\"\n" + "}"); private static final Schema mapSchema = new Schema.Parser().parse("{\"namespace\": \"namespace\",\n" + " \"type\": \"map\",\n" + " \"name\": \"test\",\n" + " \"values\": \"string\"\n" + "}"); private static final Schema unionSchema = new Schema.Parser() .parse("{\"type\": \"record\",\n" + " \"name\": \"test\",\n" + " \"fields\": [\n" + " {\"name\": \"union\", \"type\": [\"string\", \"int\"]}\n" + "]}"); private static final Schema enumSchema = new Schema.Parser() .parse("{ \"type\": \"enum\",\n" + " \"name\": \"Suit\",\n" + " \"symbols\" : [\"SPADES\", \"HEARTS\", \"DIAMONDS\", \"CLUBS\"]\n" + "}"); @Test public void testPrimitiveTypesToAvro() { Object result = AvroConverter.toAvro(null, createPrimitiveSchema("null")); assertTrue(result == null); result = AvroConverter.toAvro(TestUtils.jsonTree("true"), createPrimitiveSchema("boolean")); assertEquals(true, result); result = AvroConverter.toAvro(TestUtils.jsonTree("false"), createPrimitiveSchema("boolean")); assertEquals(false, result); result = AvroConverter.toAvro(TestUtils.jsonTree("12"), createPrimitiveSchema("int")); assertTrue(result instanceof Integer); assertEquals(12, result); result = AvroConverter.toAvro(TestUtils.jsonTree("12"), createPrimitiveSchema("long")); assertTrue(result instanceof Long); assertEquals(12L, result); result = AvroConverter.toAvro(TestUtils.jsonTree("5000000000"), createPrimitiveSchema("long")); assertTrue(result instanceof Long); assertEquals(5000000000L, result); result = AvroConverter.toAvro(TestUtils.jsonTree("23.2"), createPrimitiveSchema("float")); assertTrue(result instanceof Float); assertEquals(23.2f, result); result = AvroConverter.toAvro(TestUtils.jsonTree("23"), createPrimitiveSchema("float")); assertTrue(result instanceof Float); assertEquals(23.0f, result); result = AvroConverter.toAvro(TestUtils.jsonTree("23.2"), createPrimitiveSchema("double")); assertTrue(result instanceof Double); assertEquals(23.2, result); result = AvroConverter.toAvro(TestUtils.jsonTree("23"), createPrimitiveSchema("double")); assertTrue(result instanceof Double); assertEquals(23.0, result); // We can test bytes simply using simple ASCII string since the translation is direct in that // case result = AvroConverter.toAvro(new TextNode("hello"), createPrimitiveSchema("bytes")); assertTrue(result instanceof ByteBuffer); assertEquals(EntityUtils.encodeBase64Binary("hello".getBytes()), EntityUtils.encodeBase64Binary(((ByteBuffer) result).array())); result = AvroConverter.toAvro(TestUtils.jsonTree("\"a string\""), createPrimitiveSchema("string")); assertTrue(result instanceof Utf8); assertEquals(new Utf8("a string"), result); } @Test public void testPrimitiveTypeToAvroSchemaMismatches() { expectConversionException(TestUtils.jsonTree("12"), createPrimitiveSchema("null")); expectConversionException(TestUtils.jsonTree("12"), createPrimitiveSchema("boolean")); expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("int")); // Note that we don't test real numbers => int because JsonDecoder permits this and removes // the decimal part expectConversionException(TestUtils.jsonTree("5000000000"), createPrimitiveSchema("int")); expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("long")); // Note that we don't test real numbers => long because JsonDecoder permits this and removes // the decimal part expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("float")); expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("double")); expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("bytes")); expectConversionException(TestUtils.jsonTree("false"), createPrimitiveSchema("string")); } @Test public void testRecordToAvro() { String json = "{\n" + " \"null\": null,\n" + " \"boolean\": true,\n" + " \"int\": 12,\n" + " \"long\": 5000000000,\n" + " \"float\": 23.4,\n" + " \"double\": 800.25,\n" + " \"bytes\": \"hello\",\n" + " \"string\": \"string\",\n" + " \"null_default\": null,\n" + " \"boolean_default\": false,\n" + " \"int_default\": 24,\n" + " \"long_default\": 4000000000,\n" + " \"float_default\": 12.3,\n" + " \"double_default\": 23.2,\n" + " \"bytes_default\": \"bytes\",\n" + " \"string_default\": \"default\"\n" + "}"; Object result = AvroConverter.toAvro(TestUtils.jsonTree(json), recordSchema); assertTrue(result instanceof GenericRecord); GenericRecord resultRecord = (GenericRecord) result; assertEquals(null, resultRecord.get("null")); assertEquals(true, resultRecord.get("boolean")); assertEquals(12, resultRecord.get("int")); assertEquals(5000000000L, resultRecord.get("long")); assertEquals(23.4f, resultRecord.get("float")); assertEquals(800.25, resultRecord.get("double")); assertEquals(EntityUtils.encodeBase64Binary("hello".getBytes()), EntityUtils.encodeBase64Binary(((ByteBuffer) resultRecord.get("bytes")).array())); assertEquals("string", resultRecord.get("string").toString()); // Nothing to check with default values, just want to make sure an exception wasn't thrown // when they values weren't specified for their fields. } @Test public void testArrayToAvro() { String json = "[\"one\", \"two\", \"three\"]"; Object result = AvroConverter.toAvro(TestUtils.jsonTree(json), arraySchema); assertTrue(result instanceof GenericArray); assertArrayEquals(new Utf8[] { new Utf8("one"), new Utf8("two"), new Utf8("three") }, ((GenericArray) result).toArray()); } @Test public void testMapToAvro() { String json = "{\"first\": \"one\", \"second\": \"two\"}"; Object result = AvroConverter.toAvro(TestUtils.jsonTree(json), mapSchema); assertTrue(result instanceof Map); assertEquals(2, ((Map<String, Object>) result).size()); } @Test public void testUnionToAvro() { Object result = AvroConverter.toAvro(TestUtils.jsonTree("{\"union\":{\"string\":\"test string\"}}"), unionSchema); Object foo = ((GenericRecord) result).get("union"); assertTrue(((GenericRecord) result).get("union") instanceof Utf8); result = AvroConverter.toAvro(TestUtils.jsonTree("{\"union\":{\"int\":12}}"), unionSchema); assertTrue(((GenericRecord) result).get("union") instanceof Integer); try { AvroConverter.toAvro(TestUtils.jsonTree("12.4"), unionSchema); fail("Trying to convert floating point number to union(string,int) schema should fail"); } catch (ConversionException e) { // expected } } @Test public void testEnumToAvro() { Object result = AvroConverter.toAvro(TestUtils.jsonTree("\"SPADES\""), enumSchema); assertTrue(result instanceof GenericEnumSymbol); // There's no failure case here because the only failure mode is passing in non-string data. // Even if they put in an invalid symbol name, the exception won't be thrown until // serialization. } @Test public void testPrimitiveTypesToJson() { AvroConverter.JsonNodeAndSize result = AvroConverter.toJson((int) 0); assertTrue(result.json.isNumber()); assertTrue(result.size > 0); result = AvroConverter.toJson((long) 0); assertTrue(result.json.isNumber()); result = AvroConverter.toJson(0.1f); assertTrue(result.json.isNumber()); result = AvroConverter.toJson(0.1); assertTrue(result.json.isNumber()); result = AvroConverter.toJson(true); assertTrue(result.json.isBoolean()); // "Primitive" here refers to Avro primitive types, which are returned as standalone objects, // which can't have attached schemas. This includes, for example, Strings and byte[] even // though they are not Java primitives result = AvroConverter.toJson("abcdefg"); assertTrue(result.json.isTextual()); assertEquals("abcdefg", result.json.textValue()); result = AvroConverter.toJson(ByteBuffer.wrap("hello".getBytes())); assertTrue(result.json.isTextual()); // Was generated from a string, so the Avro encoding should be equivalent to the string assertEquals("hello", result.json.textValue()); } @Test public void testUnsupportedJavaPrimitivesToJson() { expectConversionException((byte) 0); expectConversionException((char) 0); expectConversionException((short) 0); } @Test public void testRecordToJson() { GenericRecord data = new GenericRecordBuilder(recordSchema).set("null", null).set("boolean", true) .set("int", 12).set("long", 5000000000L).set("float", 23.4f).set("double", 800.25) .set("bytes", ByteBuffer.wrap("bytes".getBytes())).set("string", "string").build(); AvroConverter.JsonNodeAndSize result = AvroConverter.toJson(data); assertTrue(result.size > 0); assertTrue(result.json.isObject()); assertTrue(result.json.get("null").isNull()); assertTrue(result.json.get("boolean").isBoolean()); assertEquals(true, result.json.get("boolean").booleanValue()); assertTrue(result.json.get("int").isIntegralNumber()); assertEquals(12, result.json.get("int").intValue()); assertTrue(result.json.get("long").isIntegralNumber()); assertEquals(5000000000L, result.json.get("long").longValue()); assertTrue(result.json.get("float").isFloatingPointNumber()); assertEquals(23.4f, result.json.get("float").floatValue(), 0.1); assertTrue(result.json.get("double").isFloatingPointNumber()); assertEquals(800.25, result.json.get("double").doubleValue(), 0.01); assertTrue(result.json.get("bytes").isTextual()); // The bytes value was created from an ASCII string, so Avro's encoding should just give that // string back to us in the JSON-serialized version assertEquals("bytes", result.json.get("bytes").textValue()); assertTrue(result.json.get("string").isTextual()); assertEquals("string", result.json.get("string").textValue()); } @Test public void testArrayToJson() { GenericData.Array<String> data = new GenericData.Array(arraySchema, Arrays.asList("one", "two", "three")); AvroConverter.JsonNodeAndSize result = AvroConverter.toJson(data); assertTrue(result.size > 0); assertTrue(result.json.isArray()); assertEquals(3, result.json.size()); assertEquals(JsonNodeFactory.instance.textNode("one"), result.json.get(0)); assertEquals(JsonNodeFactory.instance.textNode("two"), result.json.get(1)); assertEquals(JsonNodeFactory.instance.textNode("three"), result.json.get(2)); } @Test public void testMapToJson() { Map<String, Object> data = new HashMap<String, Object>(); data.put("first", "one"); data.put("second", "two"); AvroConverter.JsonNodeAndSize result = AvroConverter.toJson(data); assertTrue(result.size > 0); assertTrue(result.json.isObject()); assertEquals(2, result.json.size()); assertNotNull(result.json.get("first")); assertEquals("one", result.json.get("first").asText()); assertNotNull(result.json.get("second")); assertEquals("two", result.json.get("second").asText()); } @Test public void testEnumToJson() { AvroConverter.JsonNodeAndSize result = AvroConverter .toJson(new GenericData.EnumSymbol(enumSchema, "SPADES")); assertTrue(result.size > 0); assertTrue(result.json.isTextual()); assertEquals("SPADES", result.json.textValue()); } private static void expectConversionException(JsonNode obj, Schema schema) { try { AvroConverter.toAvro(obj, schema); fail("Expected conversion of " + (obj == null ? "null" : obj.toString()) + " to schema " + schema.toString() + " to fail"); } catch (ConversionException e) { // Expected } } private static void expectConversionException(Object obj) { try { AvroConverter.toJson(obj); fail("Expected conversion of " + (obj == null ? "null" : (obj.toString() + " (" + obj.getClass().getName() + ")")) + " to fail"); } catch (ConversionException e) { // Expected } } private static Schema createPrimitiveSchema(String type) { String schemaString = String.format("{\"type\" : \"%s\"}", type); return parser.parse(schemaString); } }