/*
* Copyright 2014-2016 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.kaa.server.admin.services.schema;
import org.apache.avro.Schema;
import org.apache.avro.Schema.Field;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericData.Record;
import org.apache.avro.generic.GenericRecord;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
import org.codehaus.jackson.node.TextNode;
import org.kaaproject.avro.ui.shared.ArrayField;
import org.kaaproject.avro.ui.shared.FormContext;
import org.kaaproject.avro.ui.shared.FormField;
import org.kaaproject.avro.ui.shared.RecordField;
import org.kaaproject.avro.ui.shared.UnionField;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* The Class EcfSchemaFormAvroConverter.
*/
public class EcfSchemaFormAvroConverter extends SimpleSchemaFormAvroConverter {
/**
* The Constant EVENT.
*/
private static final String EVENT = "EVENT";
/**
* The Constant OBJECT.
*/
private static final String OBJECT = "OBJECT";
/**
* The Constant CLASS_TYPE.
*/
private static final String CLASS_TYPE = "classType";
/**
* The Constant CLASS_TYPE_TYPE_NAME.
*/
private static final String CLASS_TYPE_TYPE_NAME = "ClassType";
/**
* The event class types.
*/
private static Set<String> eventClassTypes = new HashSet<>();
static {
eventClassTypes.add(RECORD_FIELD_TYPE);
eventClassTypes.add(ENUM_FIELD_TYPE);
eventClassTypes.add(FIXED_FIELD_TYPE);
}
/**
* Instantiates a new ecf schema form avro converter.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public EcfSchemaFormAvroConverter() throws IOException {
super();
}
/**
* Creates the class type field.
*
* @return the field
*/
private Field createClassTypeField() {
List<String> classTypeSymbols = Arrays.asList(OBJECT, EVENT);
Schema classTypeEnum = Schema.createEnum(CLASS_TYPE_TYPE_NAME, null,
BASE_SCHEMA_FORM_NAMESPACE, classTypeSymbols);
Field classTypeField = new Field(CLASS_TYPE, classTypeEnum, null, null);
classTypeField.addProp(DISPLAY_NAME, "Class type");
JsonNodeFactory jsonFactory = JsonNodeFactory.instance;
ArrayNode displayNamesNode = jsonFactory.arrayNode();
displayNamesNode.add(TextNode.valueOf("Object"));
displayNamesNode.add(TextNode.valueOf("Event"));
classTypeField.addProp(DISPLAY_NAMES, displayNamesNode);
classTypeField.addProp(DISPLAY_PROMPT, "Select class type");
classTypeField.addProp(BY_DEFAULT, OBJECT);
return classTypeField;
}
/* (non-Javadoc)
*/
@Override
protected Schema createConverterSchema() throws IOException {
Schema initialSchema = getBaseSchemaFormSchema();
Map<String, Schema> recordSchemaMap = new HashMap<>();
copySchema(initialSchema, recordSchemaMap);
return recordSchemaMap.get(BASE_SCHEMA_FORM_NAMESPACE + "." + UNION_FIELD_TYPE);
}
/* (non-Javadoc)
*/
@Override
protected void customizeRecordFields(Schema recordSchema, List<Field> fields) {
super.customizeRecordFields(recordSchema, fields);
if (eventClassTypes.contains(recordSchema.getName())) {
int index = getFieldIndex(fields, RECORD_NAMESPACE);
if (index > -1) {
fields.add(index + 1, createClassTypeField());
}
}
}
/* (non-Javadoc)
*/
@Override
protected void customizeType(Record record, Schema fieldTypeSchema) {
if (record != null && eventClassTypes.contains(record.getSchema().getName())) {
JsonNode classTypeNode = fieldTypeSchema.getJsonProp(CLASS_TYPE);
Schema enumSchema = record.getSchema().getField(CLASS_TYPE).schema();
if (classTypeNode != null && classTypeNode.isTextual()) {
record.put(CLASS_TYPE,
new GenericData.EnumSymbol(enumSchema,
classTypeNode.asText().toUpperCase()));
} else {
record.put(CLASS_TYPE, new GenericData.EnumSymbol(enumSchema, OBJECT));
}
}
}
/* (non-Javadoc)
*/
@Override
protected void customizeFieldSchema(Schema fieldSchema, GenericRecord fieldType) {
if (eventClassTypes.contains(fieldType.getSchema().getName())) {
GenericData.EnumSymbol classType = (GenericData.EnumSymbol) fieldType.get(CLASS_TYPE);
if (classType != null) {
fieldSchema.addProp(CLASS_TYPE, classType.toString().toLowerCase());
} else {
fieldSchema.addProp(CLASS_TYPE, OBJECT.toLowerCase());
}
}
}
/* (non-Javadoc)
* @see org.kaaproject.avro.ui.converter.SchemaFormAvroConverter#customizeUiForm(
* org.kaaproject.avro.ui.shared.RecordField)
*/
@Override
protected RecordField customizeUiForm(RecordField field) {
field.setDisplayName("Event class family schema");
ArrayField acceptableValuesField = (ArrayField) field.getFieldByName(ACCEPTABLE_VALUES);
acceptableValuesField.setDisplayName("Event classes");
UnionField valuesUnion = (UnionField) acceptableValuesField.getElementMetadata();
valuesUnion.finalizeMetadata();
valuesUnion.setOptional(false);
valuesUnion.setDisplayName("Event class");
updatePossibleEventClasses(valuesUnion, true);
List<FormField> acceptableValues = valuesUnion.getAcceptableValues();
for (FormField acceptableValue : acceptableValues) {
RecordField recordValue = (RecordField) acceptableValue;
recordValue.getFieldByName(RECORD_NAME).setKeyIndex(1);
recordValue.getFieldByName(RECORD_NAME).setWeight(0.3f);
recordValue.getFieldByName(RECORD_NAMESPACE).setKeyIndex(2);
recordValue.getFieldByName(RECORD_NAMESPACE).setWeight(0.5f);
recordValue.getFieldByName(CLASS_TYPE).setKeyIndex(3);
recordValue.getFieldByName(CLASS_TYPE).setWeight(0.2f);
}
if (acceptableValuesField.getValue() != null) {
for (FormField row : acceptableValuesField.getValue()) {
row.setOptional(false);
UnionField rowUnion = (UnionField) row;
rowUnion.setDisplayName("Event class");
updatePossibleEventClasses(rowUnion, true);
updatePossibleEventClasses((RecordField) rowUnion.getValue());
}
}
//RecordField rootRecord = field.getRootRecord();
FormContext context = field.getContext();
RecordField rec = context.getRecordMetadata(BASE_SCHEMA_FORM_NAMESPACE, FIELD);
UnionField typeField = (UnionField) rec.getFieldByName(FIELD_TYPE);
updatePossibleEventClasses(typeField, false);
rec = context.getRecordMetadata(BASE_SCHEMA_FORM_NAMESPACE, ARRAY_FIELD_TYPE);
typeField = (UnionField) rec.getFieldByName(ARRAY_ITEM);
updatePossibleEventClasses(typeField, false);
rec = context.getRecordMetadata(BASE_SCHEMA_FORM_NAMESPACE, UNION_FIELD_TYPE);
acceptableValuesField = (ArrayField) rec.getFieldByName(ACCEPTABLE_VALUES);
typeField = (UnionField) acceptableValuesField.getElementMetadata();
updatePossibleEventClasses(typeField, false);
return field;
}
/**
* Update possible event classes.
*
* @param valuesUnion the values union
* @param include the include
*/
private void updatePossibleEventClasses(UnionField valuesUnion, boolean include) {
List<FormField> acceptableValues = valuesUnion.getAcceptableValues();
List<FormField> filteredAcceptableValues = new ArrayList<>();
for (FormField acceptableValue : acceptableValues) {
if (acceptableValue != null && acceptableValue instanceof RecordField) {
RecordField recordValue = (RecordField) acceptableValue;
if (include && eventClassTypes.contains(recordValue.getFqn().getName())) {
filteredAcceptableValues.add(acceptableValue);
} else if (!include && !eventClassTypes.contains(recordValue.getFqn().getName())) {
filteredAcceptableValues.add(acceptableValue);
}
}
}
valuesUnion.setAcceptableValues(filteredAcceptableValues);
}
/**
* Update possible event classes.
*
* @param row the row
*/
private void updatePossibleEventClasses(RecordField row) {
if (row.getTypeFullname().equals(BASE_SCHEMA_FORM_NAMESPACE + "." + RECORD_FIELD_TYPE)) {
ArrayField arrayField = (ArrayField) row.getFieldByName(FIELDS);
for (FormField valueRow : arrayField.getValue()) {
UnionField fieldType = (UnionField) ((RecordField) valueRow).getFieldByName(FIELD_TYPE);
updatePossibleEventClasses(fieldType, false);
}
}
}
}