/* * 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.control.service.sdk; import org.apache.avro.Schema; import org.apache.avro.Schema.Type; import org.kaaproject.kaa.server.control.service.sdk.compiler.JavaDynamicBean; import org.kaaproject.kaa.server.control.service.sdk.compiler.JavaDynamicCompiler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; public class SchemaUtil { private static final Logger LOG = LoggerFactory.getLogger(SchemaUtil.class); private SchemaUtil() { } /** * Check if schemas are equals to each other. * * @param s1 the schema one * @param s2 the schema two * @return boolean 'true' if the input schemas are equals */ public static boolean isEqualSchemas(Schema s1, Schema s2) { if (!(s1.getType().equals(s2.getType()) && s1.getFullName().equals(s2.getFullName()))) { return false; } switch (s1.getType()) { case RECORD: return isEqualRecords(s1, s2); case UNION: return isEqualUnions(s1, s2); case ARRAY: return isEqualSchemas(s1.getElementType(), s2.getElementType()); case MAP: return isEqualSchemas(s1.getValueType(), s2.getValueType()); case ENUM: return isEqualEnums(s1, s2); case FIXED: return s1.getFixedSize() == s2.getFixedSize(); default: return true; } } private static boolean isEqualEnums(Schema s1, Schema s2) { List<String> symbols1 = s1.getEnumSymbols(); List<String> symbols2 = s2.getEnumSymbols(); if (symbols1.size() != symbols2.size()) { return false; } else { Collections.sort(symbols1); Collections.sort(symbols2); return symbols1.equals(symbols2); } } private static boolean isEqualUnions(Schema s1, Schema s2) { SortedMap<String, Schema> types1 = new TreeMap<>(); SortedMap<String, Schema> types2 = new TreeMap<>(); for (Schema schema : s1.getTypes()) { types1.put(schema.getName(), schema); } for (Schema schema : s2.getTypes()) { types2.put(schema.getName(), schema); } return isEqualSchemaMaps(types1, types2); } private static boolean isEqualRecords(Schema s1, Schema s2) { if (s1.getFields().size() != s2.getFields().size()) { return false; } SortedMap<String, Schema> fields1 = new TreeMap<>(); SortedMap<String, Schema> fields2 = new TreeMap<>(); for (Schema.Field field : s1.getFields()) { fields1.put(field.name(), field.schema()); } for (Schema.Field field : s2.getFields()) { fields2.put(field.name(), field.schema()); } return isEqualSchemaMaps(fields1, fields2); } private static boolean isEqualSchemaMaps(SortedMap<String, Schema> map1, SortedMap<String, Schema> map2) { if (!map1.keySet().equals(map2.keySet())) { return false; } for (String fieldKey : map1.keySet()) { if (!isEqualSchemas(map1.get(fieldKey), map2.get(fieldKey))) { return false; } } return true; } /** * Get unique schemas. * * @param schemas the collection of schemas * @return the unique schemas. * @throws Exception if schemas full names are identical and schemas are not equals to each other */ public static Map<String, Schema> getUniqueSchemasMap(Collection<Schema> schemas) throws Exception { Map<String, Schema> map = new HashMap<>(); List<Schema> allPossible = new LinkedList<>(); for (Schema schema : schemas) { allPossible.addAll(getChildSchemas(schema)); } for (Schema schema : allPossible) { String key = schema.getFullName(); if (!map.containsKey(key)) { map.put(key, schema); } else { if (!SchemaUtil.isEqualSchemas(schema, map.get(key))) { LOG.debug("Classes {} are not the same: \n{}\n\n{}", key, schema.toString(), map.get(key).toString()); throw new IllegalArgumentException("Multiple occurrences of " + key + " with different fields"); } } } return map; } /** * Get child schemas. * * @param parent the input schema * @return the list of child schemas */ public static List<Schema> getChildSchemas(Schema parent) { Map<String, Schema> namedSchemaMap = new HashMap<>(); parseChildSchemas(parent, namedSchemaMap); return new LinkedList<Schema>(namedSchemaMap.values()); } private static void parseChildSchemas(Schema parent, Map<String, Schema> namedSchemaMap) { switch (parent.getType()) { case RECORD: case ENUM: case FIXED: if (!namedSchemaMap.containsKey(parent.getFullName())) { namedSchemaMap.put(parent.getFullName(), parent); if (parent.getType() == Type.RECORD) { for (Schema.Field field : parent.getFields()) { parseChildSchemas(field.schema(), namedSchemaMap); } } } break; case UNION: for (Schema schema : parent.getTypes()) { parseChildSchemas(schema, namedSchemaMap); } break; case ARRAY: parseChildSchemas(parent.getElementType(), namedSchemaMap); break; case MAP: parseChildSchemas(parent.getValueType(), namedSchemaMap); break; default: break; } } /** * Compile avro schema. * * @param avroSchema the avro schema * @return the collection of java dynamic beans */ public static Collection<JavaDynamicBean> compileAvroSchema(Schema avroSchema) { try { LOG.debug("Compiling {}", avroSchema); Map<String, Schema> uniqueSchemas = SchemaUtil .getUniqueSchemasMap(Collections.singletonList(avroSchema)); List<JavaDynamicBean> javaSources = JavaSdkGenerator .generateSchemaSources(avroSchema, uniqueSchemas); JavaDynamicCompiler compiler = new JavaDynamicCompiler(); compiler.init(); return compiler.compile(javaSources); } catch (Exception cause) { LOG.error("Failed to compile {}", avroSchema, cause); String userMessage = "Failed to compile the schema: " + cause.getMessage(); throw new IllegalArgumentException(userMessage, cause); } } }