package com.linkedin.databus2.schemas.utils; /* * Copyright 2013 LinkedIn Corp. All rights reserved * * 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. */ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import org.apache.avro.Schema; import org.apache.avro.Schema.Field; import org.apache.avro.Schema.Type; import org.jboss.netty.util.internal.ConcurrentHashMap; import com.linkedin.databus2.schemas.VersionedSchema; /** * Static helper methods for common Avro schema-related operations. */ public class SchemaHelper { /** * @return true if the given field is an Avro array type */ public static boolean isArray(Field field) { return containsType(field, Type.ARRAY); } /** * @return true if the given field is nullable */ public static boolean isNullable(Field field) { return containsType(field, Type.NULL); } /** * @return true if the given field schema is nullable */ public static boolean isNullable(Schema fieldSchema) { return containsType(fieldSchema, Type.NULL); } /** * @return the field's type; if the field is a union, the first non-null type from the union is returned. */ public static Type getAnyType(Field field) { return unwindUnionSchema(field).getType(); } /** * @return the field's schema; if the field is a union, the schema for the first non-null type from the * union is returned. */ public static Schema unwindUnionSchema(Field field) { Schema schema = field.schema(); Type fieldType = schema.getType(); // If this is a union, check the child types and return the first non-null schema if (fieldType == Type.UNION) { List<Schema> unionTypes = schema.getTypes(); for (Schema unionSubSchema : unionTypes) { if (unionSubSchema.getType() != Type.NULL) { return unionSubSchema; } } } return schema; } /** * @return true if the field is of type {@code type} or is a union containing {@code type} */ public static boolean containsType(Field field, Type type) { return containsType(field.schema(), type); } /** * @return true if the schema is of type {@code type} or is a union containing {@code type} */ public static boolean containsType(Schema schema, Type type) { Type fieldType = schema.getType(); // If the types are equal, then we're done if (fieldType == type) { return true; } // If this is a union, then check the child types and see if the type exists here if (fieldType == Type.UNION) { List<Schema> unionTypes = schema.getTypes(); for (Schema unionSubSchema : unionTypes) { if (unionSubSchema.getType() == type) { return true; } } } // Not a match, return false. return false; } /** * @return the value of the meta field name contained in this Schema's "meta" attribute */ // TODO: merge this and subsequent method using generics for first arg public static final String getMetaField(Schema schema, String metaFieldName) { if (schema == null) { return null; } String metaValue = schema.getProp(metaFieldName); if (null != metaValue) { return metaValue; } String meta = schema.getProp("meta"); if (meta == null) { return null; } String[] metaSplit = meta.split(";"); for (String s : metaSplit) { int eqIdx = s.indexOf('='); if (eqIdx > 0) { String itemKey = s.substring(0, eqIdx).trim(); String itemValue = s.substring(eqIdx + 1).trim(); if (null == metaValue && metaFieldName.equals(itemKey)) { metaValue = itemValue; } } } return metaValue; } /** * @return the value of the meta field name contained in this Field's "meta" attribute */ public static final String getMetaField(Field field, String metaFieldName) { if (field == null) { return null; } String metaValue = field.getProp(metaFieldName); if (null != metaValue) { return metaValue; } String meta = field.getProp("meta"); if (meta == null) { return null; } String[] metaSplit = meta.split(";"); for (String s : metaSplit) { int eqIdx = s.indexOf('='); if (eqIdx > 0) { String itemKey = s.substring(0, eqIdx).trim(); String itemValue = s.substring(eqIdx + 1).trim(); if (null == metaValue && metaFieldName.equals(itemKey)) { metaValue = itemValue; } } } return metaValue; } /** * @return the schemaId for this schema */ public static final byte[] getSchemaId(String schema) { return Utils.md5(schema.getBytes(Charset.defaultCharset())); } private static Map<String,List<Field>> orderedMap = new ConcurrentHashMap<String, List<Field>>(); /** * Order the fields present in the schema based on the metaFieldName and comparator being passed. * * @param schema Schema containing the fields to be ordered * @param metaFieldName Meta Field for ordering * @param comparator comparator for sorting * @return ordered Field list or null if schema is null */ public static List<Field> getOrderedFieldsByDBFieldPosition(final VersionedSchema vs) { if ( null == vs || null == vs.getSchema()) return null; List<Field> fieldList = orderedMap.get(vs.getId().toString()); if(fieldList != null) { return fieldList; } Schema schema = vs.getSchema(); fieldList = getOrderedFieldsByMetaField(schema, "dbFieldPosition", new Comparator<String>() { @Override public int compare(String o1, String o2) { // TODO Auto-generated method stub int m1 = Integer.parseInt(o1); int m2 = Integer.parseInt(o2); return m1-m2; } }); orderedMap.put(vs.getId().toString(), fieldList); return fieldList; } /** * Order the fields present in the schema based on the metaFieldName and comparator being passed. * * @param schema Schema containing the fields to be ordered * @param metaFieldName Meta Field for ordering * @param comparator comparator for sorting * @return ordered Field list or null if schema is null */ public static List<Field> getOrderedFieldsByMetaField(final Schema schema, final String metaFieldName, final Comparator<String> comparator ) { if ( null == schema) return null; // It is safer to create a copy of fields list, as we want to change the order only on the copy // so as to not confuse AVRO in case it returns the internal fields collection List<Field> fields = new ArrayList<Field>(schema.getFields()); Collections.sort(fields, new Comparator<Field>() { @Override public int compare(Field o1, Field o2) { String m1 = getMetaField(o1, metaFieldName); String m2 = getMetaField(o2, metaFieldName); return comparator.compare(m1, m2); } }); return fields; } }