/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.nifi.processors.kite;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Collections;
import java.util.Map;
import org.apache.avro.Schema;
import org.apache.avro.SchemaBuilder;
import org.apache.avro.generic.GenericData.Record;
import org.apache.commons.lang.LocaleUtils;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
public class TestAvroRecordConverter {
final static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
final static Map<String, String> EMPTY_MAPPING = ImmutableMap.of();
final static String NESTED_RECORD_SCHEMA_STRING = "{\n"
+ " \"type\": \"record\",\n"
+ " \"name\": \"NestedInput\",\n"
+ " \"namespace\": \"org.apache.example\",\n"
+ " \"fields\": [\n" + " {\n"
+ " \"name\": \"l1\",\n"
+ " \"type\": \"long\"\n"
+ " },\n"
+ " {\n" + " \"name\": \"s1\",\n"
+ " \"type\": \"string\"\n"
+ " },\n"
+ " {\n"
+ " \"name\": \"parent\",\n"
+ " \"type\": [\"null\", {\n"
+ " \"type\": \"record\",\n"
+ " \"name\": \"parent\",\n"
+ " \"fields\": [\n"
+ " { \"name\": \"id\", \"type\": \"long\" },\n"
+ " { \"name\": \"name\", \"type\": \"string\" }\n"
+ " ]"
+ " } ]"
+ " }"
+ " ] }";
final static Schema NESTED_RECORD_SCHEMA = new Schema.Parser()
.parse(NESTED_RECORD_SCHEMA_STRING);
final static Schema NESTED_PARENT_SCHEMA = AvroRecordConverter
.getNonNullSchema(NESTED_RECORD_SCHEMA.getField("parent").schema());
final static Schema UNNESTED_OUTPUT_SCHEMA = SchemaBuilder.record("Output")
.namespace("org.apache.example").fields().requiredLong("l1")
.requiredLong("s1").optionalLong("parentId").endRecord();
/**
* Tests the case where we don't use a mapping file and just map records by
* name.
*/
@Test
public void testDefaultConversion() throws Exception {
// We will convert s1 from string to long (or leave it null), ignore s2,
// convert s3 to from string to double, convert l1 from long to string,
// and leave l2 the same.
Schema input = SchemaBuilder.record("Input")
.namespace("com.cloudera.edh").fields()
.nullableString("s1", "").requiredString("s2")
.requiredString("s3").optionalLong("l1").requiredLong("l2")
.endRecord();
Schema output = SchemaBuilder.record("Output")
.namespace("com.cloudera.edh").fields().optionalLong("s1")
.optionalString("l1").requiredLong("l2").requiredDouble("s3")
.endRecord();
AvroRecordConverter converter = new AvroRecordConverter(input, output,
EMPTY_MAPPING, LocaleUtils.toLocale("en_US"));
Record inputRecord = new Record(input);
inputRecord.put("s1", null);
inputRecord.put("s2", "blah");
inputRecord.put("s3", "5.5");
inputRecord.put("l1", null);
inputRecord.put("l2", 5L);
Record outputRecord = converter.convert(inputRecord);
assertNull(outputRecord.get("s1"));
assertNull(outputRecord.get("l1"));
assertEquals(5L, outputRecord.get("l2"));
assertEquals(5.5, outputRecord.get("s3"));
inputRecord.put("s1", "500");
inputRecord.put("s2", "blah");
inputRecord.put("s3", "5.5e-5");
inputRecord.put("l1", 100L);
inputRecord.put("l2", 2L);
outputRecord = converter.convert(inputRecord);
assertEquals(500L, outputRecord.get("s1"));
assertEquals("100", outputRecord.get("l1"));
assertEquals(2L, outputRecord.get("l2"));
assertEquals(5.5e-5, outputRecord.get("s3"));
}
/**
* Tests the case where we want to default map one field and explicitly map
* another.
*/
@Test
public void testExplicitMapping() throws Exception {
// We will convert s1 from string to long (or leave it null), ignore s2,
// convert l1 from long to string, and leave l2 the same.
Schema input = NESTED_RECORD_SCHEMA;
Schema parent = NESTED_PARENT_SCHEMA;
Schema output = UNNESTED_OUTPUT_SCHEMA;
Map<String, String> mapping = ImmutableMap.of("parent.id", "parentId");
AvroRecordConverter converter = new AvroRecordConverter(input, output,
mapping);
Record inputRecord = new Record(input);
inputRecord.put("l1", 5L);
inputRecord.put("s1", "1000");
Record parentRecord = new Record(parent);
parentRecord.put("id", 200L);
parentRecord.put("name", "parent");
inputRecord.put("parent", parentRecord);
Record outputRecord = converter.convert(inputRecord);
assertEquals(5L, outputRecord.get("l1"));
assertEquals(1000L, outputRecord.get("s1"));
assertEquals(200L, outputRecord.get("parentId"));
}
/**
* Tests the case where we try to convert a string to a long incorrectly.
*/
@Test(expected = org.apache.nifi.processors.kite.AvroRecordConverter.AvroConversionException.class)
public void testIllegalConversion() throws Exception {
// We will convert s1 from string to long (or leave it null), ignore s2,
// convert l1 from long to string, and leave l2 the same.
Schema input = SchemaBuilder.record("Input")
.namespace("com.cloudera.edh").fields()
.nullableString("s1", "").requiredString("s2")
.optionalLong("l1").requiredLong("l2").endRecord();
Schema output = SchemaBuilder.record("Output")
.namespace("com.cloudera.edh").fields().optionalLong("s1")
.optionalString("l1").requiredLong("l2").endRecord();
AvroRecordConverter converter = new AvroRecordConverter(input, output,
EMPTY_MAPPING);
Record inputRecord = new Record(input);
inputRecord.put("s1", "blah");
inputRecord.put("s2", "blah");
inputRecord.put("l1", null);
inputRecord.put("l2", 5L);
converter.convert(inputRecord);
}
@Test
public void testGetUnmappedFields() throws Exception {
Schema input = SchemaBuilder.record("Input")
.namespace("com.cloudera.edh").fields()
.nullableString("s1", "").requiredString("s2")
.optionalLong("l1").requiredLong("l2").endRecord();
Schema output = SchemaBuilder.record("Output")
.namespace("com.cloudera.edh").fields().optionalLong("field")
.endRecord();
// Test the case where the field isn't mapped at all.
AvroRecordConverter converter = new AvroRecordConverter(input, output,
EMPTY_MAPPING);
assertEquals(ImmutableList.of("field"), converter.getUnmappedFields());
// Test the case where we tried to map from a non-existent field.
converter = new AvroRecordConverter(input, output, ImmutableMap.of(
"nonExistentField", "field"));
assertEquals(ImmutableList.of("field"), converter.getUnmappedFields());
// Test the case where we tried to map from a non-existent record.
converter = new AvroRecordConverter(input, output, ImmutableMap.of(
"parent.nonExistentField", "field"));
assertEquals(ImmutableList.of("field"), converter.getUnmappedFields());
// Test a valid case
converter = new AvroRecordConverter(input, output, ImmutableMap.of(
"l2", "field"));
assertEquals(Collections.EMPTY_LIST, converter.getUnmappedFields());
}
}