/* * Copyright 2014-2015 CyberVision, 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 org.kaaproject.avro.ui.converter; import java.io.IOException; import java.nio.ByteBuffer; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import org.apache.avro.Schema; import org.apache.avro.Schema.Field; import org.apache.avro.Schema.Type; import org.apache.avro.generic.GenericContainer; 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.codehaus.jackson.JsonNode; import org.kaaproject.avro.ui.shared.AlertField; import org.kaaproject.avro.ui.shared.ArrayField; import org.kaaproject.avro.ui.shared.ArrayField.OverrideStrategy; import org.kaaproject.avro.ui.shared.Base64Utils; import org.kaaproject.avro.ui.shared.BooleanField; import org.kaaproject.avro.ui.shared.BytesField; import org.kaaproject.avro.ui.shared.DependenciesField; import org.kaaproject.avro.ui.shared.DoubleField; import org.kaaproject.avro.ui.shared.EnumField; import org.kaaproject.avro.ui.shared.FieldType; import org.kaaproject.avro.ui.shared.FixedField; import org.kaaproject.avro.ui.shared.FloatField; import org.kaaproject.avro.ui.shared.FormContext; import org.kaaproject.avro.ui.shared.FormEnum; import org.kaaproject.avro.ui.shared.FormField; import org.kaaproject.avro.ui.shared.FormField.FieldAccess; import org.kaaproject.avro.ui.shared.Fqn; import org.kaaproject.avro.ui.shared.FqnKey; import org.kaaproject.avro.ui.shared.FqnReferenceField; import org.kaaproject.avro.ui.shared.FqnVersion; import org.kaaproject.avro.ui.shared.IntegerField; import org.kaaproject.avro.ui.shared.LongField; import org.kaaproject.avro.ui.shared.RecordField; import org.kaaproject.avro.ui.shared.StringField; import org.kaaproject.avro.ui.shared.StringField.InputType; import org.kaaproject.avro.ui.shared.UnionField; import org.kaaproject.avro.ui.shared.VersionField; /** * The Class FormAvroConverter. */ public class FormAvroConverter implements ConverterConstants { /** * Creates the record field from schema. * * @param schema the schema * @return the record field * @throws IOException Signals that an I/O exception has occurred. */ public static RecordField createRecordFieldFromSchema(Schema schema) throws IOException { return createRecordFieldFromSchema(schema, null); } /** * Creates the record field from schema. * * @param schema the schema * @param ctlSource the ctl source * @return the record field * @throws IOException Signals that an I/O exception has occurred. */ public static RecordField createRecordFieldFromSchema(Schema schema, CtlSource ctlSource) throws IOException { FormContext context = ctlSource != null ? new FormContext(ctlSource.getCtlTypes()) : new FormContext(); FormField formField = createFieldFromSchema(context, schema, null); if (formField instanceof RecordField) { return (RecordField)formField; } else { throw new IllegalArgumentException("Schema " + schema.getFullName() + " is not a record schema!"); } } /** * Creates the generic record from record field. * * @param recordField the record field * @return the generic record */ public static GenericRecord createGenericRecordFromRecordField(RecordField recordField) { Schema schema = new Schema.Parser().parse(recordField.getSchema()); GenericRecordBuilder builder = new GenericRecordBuilder(schema); for (FormField formField : recordField.getValue()) { String fieldName = formField.getFieldName(); Field field = schema.getField(fieldName); Object fieldValue = convertValue(formField, field.schema()); builder.set(field, fieldValue); } GenericRecord record = builder.build(); return record; } /** * Creates the record field from generic record. * * @param record the record * @return the record field * @throws IOException Signals that an I/O exception has occurred. */ public static RecordField createRecordFieldFromGenericRecord(GenericRecord record) throws IOException { return createRecordFieldFromGenericRecord(record, null); } /** * Creates the record field from generic record. * * @param record the record * @param ctlSource the ctl source * @return the record field * @throws IOException Signals that an I/O exception has occurred. */ public static RecordField createRecordFieldFromGenericRecord(GenericRecord record, CtlSource ctlSource) throws IOException { Schema schema = record.getSchema(); RecordField formData = createRecordFieldFromSchema(schema, ctlSource); fillRecordFieldFromGenericRecord(formData.getContext(), formData, record); return formData; } /** * Creates the field from schema. * * @param context the context * @param schema the schema * @param fieldType the field type * @return the form field * @throws IOException Signals that an I/O exception has occurred. */ private static FormField createFieldFromSchema(FormContext context, Schema schema, FieldType fieldType) throws IOException { FormField formField = null; String fieldName = schema.getName(); String displayName = fieldName; JsonNode displayNameVal = schema.getJsonProp(DISPLAY_NAME); if (displayNameVal != null && displayNameVal.isTextual()) { String displayNameString = displayNameVal.asText().trim(); if (displayNameString.length() > 0) { displayName = displayNameString; } } boolean optional = isNullTypeSchema(schema); boolean isOverride = isOverrideTypeSchema(schema); fieldType = fieldType == null ? toFieldType(schema) : fieldType; Schema fieldTypeSchema = getFieldTypeSchema(schema); String fieldTypeSchemaString = SchemaFormAvroConverter.createSchemaString(fieldTypeSchema, false); if (fieldType == FieldType.UNION) { UnionField unionField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); List<FormField> acceptableValues = new ArrayList<>(); List<Schema> acceptableTypes = fieldTypeSchema.getTypes(); for (int i=0;i<acceptableTypes.size();i++) { if (!isOverrideType(acceptableTypes.get(i)) && acceptableTypes.get(i).getType() != Schema.Type.NULL) { FormField acceptableValue = createFieldFromSchema(context, acceptableTypes.get(i), null); acceptableValues.add(acceptableValue); } } unionField.setAcceptableValues(acceptableValues); formField = unionField; } else if (fieldType == FieldType.RECORD) { boolean isRootRecord = false; if (!context.containsRecordMetadata(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName())) { boolean isTypeHolder = false; boolean isTypeConsumer = false; JsonNode isTypeHolderVal = fieldTypeSchema.getJsonProp(IS_TYPE_HOLDER); if (isTypeHolderVal != null && isTypeHolderVal.isBoolean()) { isTypeHolder = isTypeHolderVal.asBoolean(); } JsonNode isTypeConsumerVal = fieldTypeSchema.getJsonProp(IS_TYPE_CONSUMER); if (isTypeConsumerVal != null && isTypeConsumerVal.isBoolean()) { isTypeConsumer = isTypeConsumerVal.asBoolean(); } RecordField newRecordField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); newRecordField.setFqn(new Fqn(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName())); newRecordField.setIsTypeHolder(isTypeHolder); newRecordField.setIsTypeConsumer(isTypeConsumer); context.putRecordMetadata(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName(), newRecordField); if (context.getRootRecord() == null) { context.setRootRecord(newRecordField); isRootRecord = true; } parseFields(context, newRecordField, fieldTypeSchema); } RecordField metadata = context.getRecordMetadata(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName()); RecordField recordField = (RecordField)metadata.clone(); recordField.setFieldName(fieldName); recordField.setDisplayName(displayName); recordField.setOptional(optional); if (isRootRecord) { context.setRootRecord(recordField); recordField.finalizeMetadata(); } formField = recordField; } else if (fieldType == FieldType.ARRAY) { ArrayField arrayField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); FormField elementMetadata = createFieldFromSchema(context, fieldTypeSchema.getElementType(), null); arrayField.setElementMetadata(elementMetadata); formField = arrayField; } else if (fieldType == FieldType.ENUM) { EnumField enumField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); enumField.setFqn(new Fqn(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName())); List<String> enumSymbols = fieldTypeSchema.getEnumSymbols(); List<FormEnum> enumValues = new ArrayList<>(enumSymbols.size()); for (int i=0;i<enumSymbols.size();i++) { String enumSymbol = enumSymbols.get(i); String displayValue = enumSymbol; FormEnum formEnum = new FormEnum(enumSymbol, displayValue); enumValues.add(formEnum); } enumField.setEnumValues(enumValues); formField = enumField; } else if (fieldType == FieldType.BYTES) { BytesField bytesField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = bytesField; } else if (fieldType == FieldType.FIXED) { FixedField fixedField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); fixedField.setFqn(new Fqn(fieldTypeSchema.getNamespace(), fieldTypeSchema.getName())); fixedField.setFixedSize(fieldTypeSchema.getFixedSize()); formField = fixedField; } else if (fieldType == FieldType.BOOLEAN) { BooleanField booleanField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = booleanField; } else if (fieldType == FieldType.STRING){ StringField stringField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = stringField; } else if (fieldType == FieldType.INT) { IntegerField integerField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = integerField; } else if (fieldType == FieldType.LONG) { LongField longField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = longField; } else if (fieldType == FieldType.FLOAT) { FloatField floatField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = floatField; } else if (fieldType == FieldType.DOUBLE) { DoubleField doubleField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = doubleField; } else if (fieldType == FieldType.TYPE_REFERENCE) { FqnReferenceField fqnReferenceField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = fqnReferenceField; } else if (fieldType == FieldType.ALERT) { AlertField alertField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = alertField; } else if (fieldType == FieldType.VERSION) { VersionField versionField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = versionField; } else if (fieldType == FieldType.DEPENDENCIES) { DependenciesField dependenciesField = createField(context, fieldType, fieldName, displayName, fieldTypeSchemaString, optional, isOverride); formField = dependenciesField; } return formField; } /** * Parses the fields. * * @param context the context * @param formData the form data * @param schema the schema * @throws IOException Signals that an I/O exception has occurred. */ private static void parseFields(FormContext context, RecordField formData, Schema schema) throws IOException { List<Field> schemaFields = schema.getFields(); java.util.Set<String> schemaFieldNames = new java.util.HashSet<>(); for (Field field : schemaFields) { String schemaFieldName = field.name().toLowerCase(); if (!schemaFieldNames.contains(schemaFieldName)) { schemaFieldNames.add(schemaFieldName); } else { throw new IllegalArgumentException("Duplicate field name: " + schemaFieldName); } FieldType fieldType; if (isTypeReferenceType(field)) { fieldType = FieldType.TYPE_REFERENCE; } else if (isAlertType(field)) { fieldType = FieldType.ALERT; } else if (isVersionType(field)) { fieldType = FieldType.VERSION; } else if (isDependenciesType(field)) { fieldType = FieldType.DEPENDENCIES; } else { fieldType = toFieldType(field.schema()); } FormField formField = createFieldFromSchema(context, field.schema(), fieldType); String fieldName = field.name(); String displayName = fieldName; JsonNode displayNameVal = field.getJsonProp(DISPLAY_NAME); if (displayNameVal != null && displayNameVal.isTextual()) { String displayNameString = displayNameVal.asText().trim(); if (displayNameString.length() > 0) { displayName = displayNameString; } } formField.setFieldName(fieldName); formField.setDisplayName(displayName); JsonNode displayPromptVal = field.getJsonProp(DISPLAY_PROMPT); if (displayPromptVal != null && displayPromptVal.isTextual()) { formField.setDisplayPrompt(displayPromptVal.asText()); } JsonNode weightVal = field.getJsonProp(WEIGHT); if (weightVal != null && weightVal.isNumber()) { Number weight = weightVal.getNumberValue(); formField.setWeight(weight.floatValue()); } JsonNode keyIndexVal = field.getJsonProp(KEY_INDEX); if (keyIndexVal != null && keyIndexVal.isNumber()) { Number keyIndex = keyIndexVal.getNumberValue(); formField.setKeyIndex(keyIndex.intValue()); } JsonNode fieldAccessVal = field.getJsonProp(FIELD_ACCESS); if (fieldAccessVal != null && fieldAccessVal.isTextual()) { String fieldAccess = fieldAccessVal.asText(); formField.setFieldAccess(FieldAccess.valueOf(fieldAccess.toUpperCase())); } JsonNode defaultValueVal = field.getJsonProp(BY_DEFAULT); if (fieldType == FieldType.UNION) { UnionField unionField = (UnionField)formField; FormField defaultValue = convertUnionDefaultValue(unionField.getAcceptableValues(), defaultValueVal); unionField.setDefaultValue(defaultValue); if (!formField.isOverride()) { unionField.setValue(defaultValue); } } else if (fieldType == FieldType.ARRAY) { ArrayField arrayField = (ArrayField)formField; JsonNode minRowCountVal = field.getJsonProp(MIN_ROW_COUNT); if (minRowCountVal != null && minRowCountVal.isInt()) { arrayField.setMinRowCount(minRowCountVal.asInt()); } else { arrayField.setMinRowCount(0); } FormField metadata = arrayField.getElementMetadata(); if (!metadata.getFieldType().isComplex()) { metadata.setDisplayPrompt(arrayField.getDisplayPrompt()); } for (int i=0; i<arrayField.getMinRowCount();i++) { arrayField.addArrayData(arrayField.getElementMetadata().clone()); } if (arrayField.isOverride()) { JsonNode overrideStrategyVal = field.getJsonProp(OVERRIDE_STRATEGY); if (overrideStrategyVal != null && overrideStrategyVal.isTextual()) { OverrideStrategy overrideStrategy = OverrideStrategy.valueOf(overrideStrategyVal.asText().toUpperCase()); arrayField.setOverrideStrategy(overrideStrategy); } else { arrayField.setOverrideStrategy(OverrideStrategy.REPLACE); } } } else { if (fieldType == FieldType.ENUM) { EnumField enumField = (EnumField)formField; List<FormEnum> enumValues = enumField.getEnumValues(); JsonNode displayNamesNode = field.getJsonProp(DISPLAY_NAMES); if (displayNamesNode != null && displayNamesNode.isArray()) { for (int i=0;i<enumValues.size();i++) { String displayValue = displayNamesNode.get(i).getTextValue(); enumValues.get(i).setDisplayValue(displayValue); } } String defaultValue = convertJsonValue(fieldType, defaultValueVal); enumField.setDefaultValueFromSymbol(defaultValue); if (!formField.isOverride()) { enumField.setValueFromSymbol(defaultValue); } } else if (fieldType == FieldType.BYTES) { BytesField bytesField = (BytesField)formField; String defaultValue = convertJsonValue(fieldType, defaultValueVal); bytesField.setDefaultValue(defaultValue); if (!formField.isOverride()) { bytesField.setValue(defaultValue); } } else if (fieldType == FieldType.FIXED) { FixedField fixedField = (FixedField)formField; String defaultValue = convertJsonValue(fieldType, defaultValueVal); fixedField.setDefaultValue(defaultValue); if (!formField.isOverride()) { fixedField.setValue(defaultValue); } } else if (fieldType == FieldType.BOOLEAN) { BooleanField booleanField = (BooleanField)formField; Boolean defaultValue = convertJsonValue(fieldType, defaultValueVal); booleanField.setDefaultValue(defaultValue); if (!formField.isOverride()) { booleanField.setValue(defaultValue); } } else if (fieldType == FieldType.STRING) { StringField stringField = (StringField) formField; JsonNode maxLengthVal = field.getJsonProp(MAX_LENGTH); if (maxLengthVal != null && maxLengthVal.isInt()) { stringField.setMaxLength(maxLengthVal.asInt()); } String defaultValue = convertJsonValue(fieldType, defaultValueVal); stringField.setDefaultValue(defaultValue); if (!formField.isOverride()) { stringField.setValue(defaultValue); } JsonNode inputTypeNode = field.getJsonProp(INPUT_TYPE); if (inputTypeNode != null && inputTypeNode.isTextual()) { InputType inputType = InputType.valueOf(inputTypeNode.asText().toUpperCase()); stringField.setInputType(inputType); } } else if (fieldType == FieldType.INT) { Integer defaultValue = convertJsonValue(fieldType, defaultValueVal); ((IntegerField) formField).setDefaultValue(defaultValue); if (!formField.isOverride()) { ((IntegerField) formField).setValue(defaultValue); } } else if (fieldType == FieldType.LONG) { Long defaultValue = convertJsonValue(fieldType, defaultValueVal); ((LongField) formField).setDefaultValue(defaultValue); if (!formField.isOverride()) { ((LongField) formField).setValue(defaultValue); } } else if (fieldType == FieldType.FLOAT) { Float defaultValue = convertJsonValue(fieldType, defaultValueVal); ((FloatField) formField).setDefaultValue(defaultValue); if (!formField.isOverride()) { ((FloatField) formField).setValue(defaultValue); } } else if (fieldType == FieldType.DOUBLE) { Double defaultValue = convertJsonValue(fieldType, defaultValueVal); ((DoubleField) formField).setDefaultValue(defaultValue); if (!formField.isOverride()) { ((DoubleField) formField).setValue(defaultValue); } } } formData.addField(formField); } } /** * Creates the field. * * @param <T> the generic type * @param context the context * @param type the type * @param fieldName the field name * @param displayName the display name * @param schema the schema * @param optional the optional * @param isOverride the is override * @return the t */ @SuppressWarnings("unchecked") private static <T extends FormField> T createField(FormContext context, FieldType type, String fieldName, String displayName, String schema, boolean optional, boolean isOverride) { T field = null; switch (type) { case STRING: field = (T) new StringField(context, fieldName, displayName, schema, optional); break; case ARRAY: field = (T) new ArrayField(context, fieldName, displayName, schema, optional); break; case BOOLEAN: field = (T) new BooleanField(context, fieldName, displayName, schema, optional); break; case ENUM: field = (T) new EnumField(context, fieldName, displayName, schema, optional); break; case INT: field = (T) new IntegerField(context, fieldName, displayName, schema, optional); break; case LONG: field = (T) new LongField(context, fieldName, displayName, schema, optional); break; case FLOAT: field = (T) new FloatField(context, fieldName, displayName, schema, optional); break; case DOUBLE: field = (T) new DoubleField(context, fieldName, displayName, schema, optional); break; case BYTES: field = (T) new BytesField(context, fieldName, displayName, schema, optional); break; case RECORD: field = (T) new RecordField(context, fieldName, displayName, schema, optional); break; case FIXED: field = (T) new FixedField(context, fieldName, displayName, schema, optional); break; case UNION: field = (T) new UnionField(context, fieldName, displayName, schema, optional); break; case TYPE_REFERENCE: field = (T) new FqnReferenceField(context, fieldName, displayName, schema, optional); break; case ALERT: field = (T) new AlertField(context, fieldName, displayName, schema, optional); break; case VERSION: field = (T) new VersionField(context, fieldName, displayName, schema, optional); break; case DEPENDENCIES: field = (T) new DependenciesField(context, fieldName, displayName, schema, optional); break; default: break; } field.setOverride(isOverride); return field; } /** * Convert json value. * * @param <T> the generic type * @param type the type * @param jsonValue the json value * @return the t */ @SuppressWarnings("unchecked") private static <T> T convertJsonValue(FieldType type, JsonNode jsonValue) { if (jsonValue == null) { return null; } T value = null; switch (type) { case BOOLEAN: value = (T) new Boolean(jsonValue.asBoolean()); break; case INT: value = (T) new Integer(jsonValue.asInt()); break; case LONG: value = (T) new Long(jsonValue.asLong()); break; case FLOAT: value = (T) new Float(jsonValue.asDouble()); break; case DOUBLE: value = (T) new Double(jsonValue.asDouble()); break; case STRING: case ENUM: value = (T) jsonValue.asText(); break; case FIXED: case BYTES: if (jsonValue.isTextual() || jsonValue.isBinary()) { return (T) jsonValue.asText(); } else if (jsonValue.isArray()) { byte[] data = new byte[jsonValue.size()]; for (int i=0;i<jsonValue.size();i++) { int val = convertJsonValue(FieldType.INT, jsonValue.get(i)); data[i] = (byte) val; } return (T) Base64Utils.toBase64(data); } break; default: break; } return value; } /** * Sets the json value. * * @param field the field * @param jsonValue the json value */ private static void setJsonValue(FormField field, JsonNode jsonValue) { if (jsonValue == null) { return; } FieldType fieldType = field.getFieldType(); switch (field.getFieldType()) { case BOOLEAN: { Boolean value = convertJsonValue(fieldType, jsonValue); ((BooleanField)field).setValue(value); } break; case INT: { Integer value = convertJsonValue(fieldType, jsonValue); ((IntegerField)field).setValue(value); } break; case LONG: { Long value = convertJsonValue(fieldType, jsonValue); ((LongField)field).setValue(value); } break; case FLOAT: { Float value = convertJsonValue(fieldType, jsonValue); ((FloatField)field).setValue(value); } break; case DOUBLE: { Double value = convertJsonValue(fieldType, jsonValue); ((DoubleField)field).setValue(value); } break; case STRING: { String value = convertJsonValue(fieldType, jsonValue); ((StringField)field).setValue(value); } break; case ENUM: { String value = convertJsonValue(fieldType, jsonValue); ((EnumField)field).setValueFromSymbol(value); } break; case FIXED: { String value = convertJsonValue(fieldType, jsonValue); ((FixedField)field).setValue(value); } break; case BYTES: { String value = convertJsonValue(fieldType, jsonValue); ((BytesField)field).setValue(value); } break; default: break; } } /** * Convert union default value. * * @param acceptableValues the acceptable values * @param jsonValue the json value * @return the form field */ private static FormField convertUnionDefaultValue(List<FormField> acceptableValues, JsonNode jsonValue) { if (jsonValue == null) { return null; } FormField value = null; for (FormField field : acceptableValues) { if (!field.getFieldType().isComplex() && matchesType(field.getFieldType(), jsonValue)) { if (field.getFieldType() == FieldType.ENUM) { String val = convertJsonValue(FieldType.ENUM, jsonValue); List<FormEnum> enumValues = ((EnumField)field).getEnumValues(); boolean found = false; for (FormEnum enumVal : enumValues) { if (enumVal.getEnumSymbol().equals(val)) { found = true; break; } } if (!found) { continue; } } value = field.clone(); setJsonValue(value, jsonValue); return value; } } return value; } /** * Matches type. * * @param type the type * @param jsonValue the json value * @return true, if successful */ private static boolean matchesType(FieldType type, JsonNode jsonValue) { switch (type) { case BOOLEAN: return jsonValue.isBoolean(); case INT: return jsonValue.isInt(); case LONG: return jsonValue.isIntegralNumber(); case FLOAT: return jsonValue.isDouble(); case DOUBLE: return jsonValue.isFloatingPointNumber(); case STRING: case ENUM: return jsonValue.isTextual(); case FIXED: case BYTES: return jsonValue.isBinary() || jsonValue.isArray(); default: return false; } } /** * To field type. * * @param schema the schema * @return the field type */ private static FieldType toFieldType(Schema schema) { switch(schema.getType()) { case RECORD: return FieldType.RECORD; case STRING: return FieldType.STRING; case INT: return FieldType.INT; case LONG: return FieldType.LONG; case FLOAT: return FieldType.FLOAT; case DOUBLE: return FieldType.DOUBLE; case BOOLEAN: return FieldType.BOOLEAN; case BYTES: return FieldType.BYTES; case ENUM: return FieldType.ENUM; case ARRAY: return FieldType.ARRAY; case FIXED: return FieldType.FIXED; case UNION: if (isOverrideTypeSchema(schema)) { boolean isNullType = isNullTypeSchema(schema); if (isNullType && schema.getTypes().size() > 3 || !isNullType && schema.getTypes().size() > 2) { return FieldType.UNION; } for (Schema typeSchema : schema.getTypes()) { if (!isOverrideType(typeSchema)) { FieldType type = toFieldType(typeSchema); if (type != null) { return type; } } } throw new UnsupportedOperationException("Unsupported avro field type: " + schema.getType()); } else if (isNullTypeSchema(schema)) { if (schema.getTypes().size() > 2 || (schema.getTypes().size() == 1 && schema.getTypes().get(0).getType() == Schema.Type.NULL)) { return FieldType.UNION; } else { for (Schema typeSchema : schema.getTypes()) { FieldType type = toFieldType(typeSchema); if (type != null) { return type; } } throw new UnsupportedOperationException("Unsupported avro field type: " + schema.getType()); } } else { return FieldType.UNION; } case NULL: return null; default: throw new UnsupportedOperationException("Unsupported avro field type: " + schema.getType()); } } /** * Checks if is type reference type. * * @param field the field * @return true, if is type reference type */ private static boolean isTypeReferenceType(Field field) { JsonNode fqnReferenceVal = field.getJsonProp(TYPE_REFERENCE); if (fqnReferenceVal != null && fqnReferenceVal.isBoolean()) { return fqnReferenceVal.asBoolean(); } return false; } /** * Checks if is alert type. * * @param field the field * @return true, if is alert type */ private static boolean isAlertType(Field field) { JsonNode alertVal = field.getJsonProp(ALERT); if (alertVal != null && alertVal.isBoolean()) { return alertVal.asBoolean(); } return false; } /** * Checks if is version type. * * @param field the field * @return true, if is version type */ private static boolean isVersionType(Field field) { JsonNode typeVersionVal = field.getJsonProp(TYPE_VERSION); if (typeVersionVal != null && typeVersionVal.isBoolean()) { return typeVersionVal.asBoolean(); } return false; } /** * Checks if is dependencies type. * * @param field the field * @return true, if is dependencies type */ private static boolean isDependenciesType(Field field) { JsonNode typeDependenciesVal = field.getJsonProp(TYPE_DEPENDENCIES); if (typeDependenciesVal != null && typeDependenciesVal.isBoolean()) { return typeDependenciesVal.asBoolean(); } return false; } /** * Gets the field type schema. * * @param schema the schema * @return the field type schema */ protected static Schema getFieldTypeSchema(Schema schema) { if (isOverrideTypeSchema(schema)) { boolean isNullType = isNullTypeSchema(schema); if (isNullType && schema.getTypes().size() > 3 || !isNullType && schema.getTypes().size() > 2) { return schema; } for (Schema typeSchema : schema.getTypes()) { if (!isOverrideType(typeSchema)) { FieldType type = toFieldType(typeSchema); if (type != null) { return typeSchema; } } } throw new UnsupportedOperationException("Unsupported avro field type: " + schema.getType()); } else if (isNullTypeSchema(schema)) { if (schema.getTypes().size() > 2 || (schema.getTypes().size() == 1 && schema.getTypes().get(0).getType() == Schema.Type.NULL)) { return schema; } else { for (Schema typeSchema : schema.getTypes()) { FieldType type = toFieldType(typeSchema); if (type != null) { return typeSchema; } } throw new UnsupportedOperationException("Unsupported avro field type: " + schema.getType()); } } else { return schema; } } /** * Fill record field from generic record. * * @param context the context * @param recordField the record field * @param record the record * @throws IOException Signals that an I/O exception has occurred. */ private static void fillRecordFieldFromGenericRecord(FormContext context, RecordField recordField, GenericRecord record) throws IOException { if (recordField.isNull()) { recordField.finalizeMetadata(); } for (FormField field : recordField.getValue()) { Object value = record.get(field.getFieldName()); setFormFieldValue(context, field, value); } if (context.isCtlSchema() && recordField.isRoot()) { Object value = record.get(SchemaFormAvroConverter.DEPENDENCIES); if (value != null) { setDependenciesValue(context, value); } } } @SuppressWarnings("unchecked") private static void setDependenciesValue(FormContext context, Object value) throws IOException { Iterable<Object> dependenciesData = (Iterable<Object>)value; if (dependenciesData != null) { List<FqnVersion> fqnVersions = new ArrayList<>(); for (Object arrayValue : dependenciesData) { GenericRecord record = (GenericRecord)arrayValue; String fqn = record.get(SchemaFormConstants.FQN).toString(); Integer version = (Integer)record.get(SchemaFormConstants.VERSION); fqnVersions.add(new FqnVersion(fqn, version)); } context.setCtlDependenciesList(fqnVersions); context.updateCtlDependencies(); } } /** * Sets the form field value. * * @param context the context * @param field the field * @param value the value * @throws IOException Signals that an I/O exception has occurred. */ @SuppressWarnings("unchecked") private static void setFormFieldValue(FormContext context, FormField field, Object value) throws IOException { if (field.isOverride() && isOverrideUnchangedDatum(value)) { field.setChanged(false); } else { if (field.isOverride()) { field.setChanged(true); } switch(field.getFieldType()) { case RECORD: RecordField recordField = (RecordField)field; if (value != null) { GenericRecord record = (GenericRecord)value; fillRecordFieldFromGenericRecord(context, recordField, record); } else { recordField.setNull(); } break; case TYPE_REFERENCE: FqnReferenceField fqnReferenceField = (FqnReferenceField)field; if (value != null) { String fqnString = value.toString(); Fqn fqn = new Fqn(fqnString); FqnKey fqnKey = context.fqnToFqnKey(fqn); if (fqnKey == null) { throw new IllegalArgumentException("Type with FQN '" + fqnString + "' is not defined."); } fqnReferenceField.setValue(fqnKey); } else { fqnReferenceField.setValue(null); } break; case ALERT: AlertField alertField = (AlertField)field; if (value != null) { alertField.setValue(value.toString()); } else { alertField.setValue(null); } break; case VERSION: VersionField versionField = (VersionField)field; if (value != null) { versionField.setValue((Integer)value); } else { versionField.setValue(null); } break; case UNION: UnionField unionField = (UnionField)field; if (value != null) { Schema unionSchema = new Schema.Parser().parse(unionField.getSchema()); Schema valueSchema = unionSchema.getTypes().get(GenericData.get().resolveUnion(unionSchema, value)); FormField unionValue = createFieldFromSchema(context, valueSchema, null); setFormFieldValue(context, unionValue, value); unionField.setValue(unionValue); } else { unionField.setValue(null); } break; case STRING: StringField stringField = (StringField)field; if (value != null) { stringField.setValue(value.toString()); } else { stringField.setValue(null); } break; case BYTES: BytesField bytesField = (BytesField)field; if (value != null) { bytesField.setBytes(((ByteBuffer)value).array()); } else { bytesField.setValue(null); } break; case INT: ((IntegerField)field).setValue((Integer)value); break; case LONG: ((LongField)field).setValue((Long)value); break; case FLOAT: ((FloatField)field).setValue((Float)value); break; case DOUBLE: ((DoubleField)field).setValue((Double)value); break; case BOOLEAN: ((BooleanField)field).setValue((Boolean)value); break; case FIXED: FixedField fixedField = (FixedField)field; if (value != null) { byte[] bytesData = ((GenericData.Fixed)value).bytes(); fixedField.setBytes(bytesData); } else { fixedField.setValue(null); } break; case ENUM: EnumField enumField = (EnumField)field; if (value != null) { enumField.setValueFromSymbol(value.toString()); } else { enumField.setValue(null); } break; case ARRAY: ArrayField arrayField = (ArrayField)field; arrayField.getValue().clear(); arrayField.finalizeMetadata(); Iterable<Object> arrayData = (Iterable<Object>)value; if (arrayData != null) { for (Object arrayValue : arrayData) { FormField fieldValue = arrayField.createRow(); setFormFieldValue(context, fieldValue, arrayValue); ((ArrayField)field).addArrayData(fieldValue); } } break; default: break; } } } /** * Convert value. * * @param formField the form field * @param fieldSchema the field schema * @return the object */ private static Object convertValue(FormField formField, Schema fieldSchema) { if (formField.isOverride() && !formField.isChanged()) { return unchangedSymbol; } //Schema fieldSchema = new Schema.Parser().parse(formField.getSchema()); if (formField.isNull() && !(hasType(fieldSchema, Schema.Type.NULL))) { throw new UnsupportedOperationException("Avro field doesn't support null values!"); } switch(fieldSchema.getType()) { case RECORD: return createGenericRecordFromRecordField((RecordField)formField); case STRING: if (formField instanceof FqnReferenceField) { Fqn fqn = ((FqnReferenceField)formField).getFqnValue(); return fqn != null ? fqn.getFqnString() : null; } else if (formField instanceof AlertField) { return ((AlertField)formField).getValue(); } return ((StringField)formField).getValue(); case INT: return ((IntegerField)formField).getValue(); case LONG: return ((LongField)formField).getValue(); case FLOAT: return ((FloatField)formField).getValue(); case DOUBLE: return ((DoubleField)formField).getValue(); case BOOLEAN: // Explicitly set if the field is left untouched by the user if (((BooleanField) formField).getValue() == null && !formField.isOptional()) { ((BooleanField) formField).setValue(Boolean.FALSE); } return ((BooleanField)formField).getValue(); case BYTES: BytesField bytesField = (BytesField)formField; byte[] bytesData = null; try { bytesData = bytesField.getBytes(); } catch (ParseException e) {} if (bytesData != null) { return ByteBuffer.wrap(bytesData); } else { return null; } case ENUM: String enumSymbol = ((EnumField)formField).getValue().getEnumSymbol(); return new GenericData.EnumSymbol(fieldSchema, enumSymbol); case FIXED: FixedField fixedField = (FixedField)formField; byte[] fixedData = null; try { fixedData = fixedField.getBytes(); } catch (ParseException e) {} if (fixedData != null) { GenericData.Fixed genericFixed = new GenericData.Fixed(fieldSchema, fixedData); return genericFixed; } else { return null; } case ARRAY: List<FormField> arrayData = ((ArrayField)formField).getValue(); GenericData.Array<Object> genericArrayData = new GenericData.Array<>(arrayData.size(), fieldSchema); for (FormField arrayField : arrayData) { Object data = convertValue(arrayField, fieldSchema.getElementType()); genericArrayData.add(data); } return genericArrayData; case UNION: if (formField.isNull()) { if (hasType(fieldSchema, Schema.Type.NULL)) { return null; } else { throw new UnsupportedOperationException("Avro field doesn't support null values!"); } } else { FormField value; if (formField.getFieldType() == FieldType.UNION) { UnionField unionField = (UnionField)formField; value = unionField.getValue(); } else if (formField.getFieldType() == FieldType.VERSION) { return ((VersionField)formField).getValue(); } else if (formField.getFieldType() == FieldType.DEPENDENCIES) { List<FqnVersion> fqnVersions = ((DependenciesField)formField).getValue(); int index = fieldSchema.getIndexNamed(FieldType.ARRAY.getName()); Schema dependenciesSchema = fieldSchema.getTypes().get(index); genericArrayData = new GenericData.Array<>(fqnVersions.size(), dependenciesSchema); Schema dependencySchema = dependenciesSchema.getElementType(); for (FqnVersion fqnVersion : fqnVersions) { GenericRecordBuilder builder = new GenericRecordBuilder(dependencySchema); Field field = dependencySchema.getField(SchemaFormAvroConverter.FQN); builder.set(field, fqnVersion.getFqnString()); field = dependencySchema.getField(SchemaFormAvroConverter.VERSION); builder.set(field, fqnVersion.getVersion()); GenericRecord record = builder.build(); genericArrayData.add(record); } return genericArrayData; } else { value = formField; } int index = fieldSchema.getIndexNamed(value.getTypeFullname()); Schema schema = fieldSchema.getTypes().get(index); return convertValue(value, schema); } default: throw new UnsupportedOperationException("Unsupported avro field type: " + fieldSchema.getType()); } } /** * Checks for type. * * @param unionSchema the union schema * @param type the type * @return true, if successful */ private static boolean hasType(Schema unionSchema, Schema.Type type) { if (unionSchema.getType()==Type.UNION) { for (Schema typeSchema : unionSchema.getTypes()) { if (typeSchema.getType()==type) { return true; } } } return false; } /** * Checks if is null type schema. * * @param schema the schema * @return true, if is null type schema */ protected static boolean isNullTypeSchema(Schema schema) { if (schema.getType() == Type.UNION) { for (Schema typeSchema : schema.getTypes()) { if (typeSchema.getType() == Schema.Type.NULL) { return true; } } } return false; } /** * Checks if is override type schema. * * @param schema the schema * @return true, if is override type schema */ private static boolean isOverrideTypeSchema(Schema schema) { if (schema.getType() == Type.UNION && schema.getTypes().size() >= 2) { for (Schema typeSchema : schema.getTypes()) { if (isOverrideType(typeSchema)) { return true; } } } return false; } /** * Checks if is override type. * * @param typeSchema the type schema * @return true, if is override type */ private static boolean isOverrideType(Schema typeSchema) { return typeSchema.getType() == Schema.Type.ENUM && typeSchema.getNamespace().equals(DEFAULT_CONFIG_NAMESPACE) && typeSchema.getName().equals(UNCHANGED_NAME); } /** * Checks if is override unchanged datum. * * @param datum the datum * @return true, if is override unchanged datum */ private static boolean isOverrideUnchangedDatum(Object datum) { if (datum != null && datum instanceof GenericEnumSymbol) { Schema schema = ((GenericContainer)datum).getSchema(); return isOverrideType(schema); } else { return false; } } }