use of com.linkedin.data.schema.SchemaParserFactory in project rest.li by linkedin.
the class SchemaTranslator method avroToDataSchema.
/**
* Translate an Avro {@link Schema} to a {@link DataSchema}.
* <p>
* If the translation mode is {@link AvroToDataSchemaTranslationMode#RETURN_EMBEDDED_SCHEMA}
* and a {@link DataSchema} is embedded in the Avro schema, then return the embedded schema.
* An embedded schema is present if the Avro {@link Schema} has a "com.linkedin.data" property and the
* "com.linkedin.data" property contains both "schema" and "optionalDefaultMode" properties.
* The "schema" property provides the embedded {@link DataSchema}.
* The "optionalDefaultMode" property provides how optional default values were translated.
* <p>
* If the translation mode is {@link AvroToDataSchemaTranslationMode#VERIFY_EMBEDDED_SCHEMA}
* and a {@link DataSchema} is embedded in the Avro schema, then verify that the embedded schema
* translates to the input Avro schema. If the translated and embedded schema is the same,
* then return the embedded schema, else throw {@link IllegalArgumentException}.
* <p>
* If the translation mode is {@link com.linkedin.data.avro.AvroToDataSchemaTranslationMode#TRANSLATE}
* or no embedded {@link DataSchema} is present, then this method
* translates the provided Avro {@link Schema} to a {@link DataSchema}
* as described follows:
* <p>
* This method translates union with null record fields in Avro {@link Schema}
* to optional fields in {@link DataSchema}. Record fields
* whose type is a union with null will be translated to a new type, and the field becomes optional.
* If the Avro union has two types (one of them is the null type), then the new type of the
* field is the non-null member type of the union. If the Avro union does not have two types
* (one of them is the null type) then the new type of the field is a union type with the null type
* removed from the original union.
* <p>
* This method also translates default values. If the field's type is a union with null
* and has a default value, then this method also translates the default value of the field
* to comply with the new type of the field. If the default value is null,
* then remove the default value. If new type is not a union and the default value
* is of the non-null member type, then assign the default value to the
* non-null value within the union value (i.e. the value of the only entry within the
* JSON object.) If the new type is a union and the default value is of the
* non-null member type, then assign the default value to a JSON object
* containing a single entry with the key being the member type discriminator of
* the first union member and the value being the actual member value.
* <p>
* Both the schema and default value translation takes into account that default value
* representation for Avro unions does not include the member type discriminator and
* the type of the default value is always the 1st member of the union.
*
* @param avroSchemaInJson provides the JSON representation of the Avro {@link Schema}.
* @param options specifies the {@link AvroToDataSchemaTranslationOptions}.
* @return the translated {@link DataSchema}.
* @throws IllegalArgumentException if the Avro {@link Schema} cannot be translated.
*/
public static DataSchema avroToDataSchema(String avroSchemaInJson, AvroToDataSchemaTranslationOptions options) throws IllegalArgumentException {
ValidationOptions validationOptions = SchemaParser.getDefaultSchemaParserValidationOptions();
validationOptions.setAvroUnionMode(true);
SchemaParserFactory parserFactory = SchemaParserFactory.instance(validationOptions);
DataSchemaResolver resolver = getResolver(parserFactory, options);
PegasusSchemaParser parser = parserFactory.create(resolver);
parser.parse(avroSchemaInJson);
if (parser.hasError()) {
throw new IllegalArgumentException(parser.errorMessage());
}
assert (parser.topLevelDataSchemas().size() == 1);
DataSchema dataSchema = parser.topLevelDataSchemas().get(0);
DataSchema resultDataSchema = null;
AvroToDataSchemaTranslationMode translationMode = options.getTranslationMode();
if (translationMode == AvroToDataSchemaTranslationMode.RETURN_EMBEDDED_SCHEMA || translationMode == AvroToDataSchemaTranslationMode.VERIFY_EMBEDDED_SCHEMA) {
// check for embedded schema
Object dataProperty = dataSchema.getProperties().get(SchemaTranslator.DATA_PROPERTY);
if (dataProperty != null && dataProperty.getClass() == DataMap.class) {
Object schemaProperty = ((DataMap) dataProperty).get(SchemaTranslator.SCHEMA_PROPERTY);
if (schemaProperty.getClass() == DataMap.class) {
SchemaParser embeddedSchemaParser = SchemaParserFactory.instance().create(null);
embeddedSchemaParser.parse(Arrays.asList(schemaProperty));
if (embeddedSchemaParser.hasError()) {
throw new IllegalArgumentException("Embedded schema is invalid\n" + embeddedSchemaParser.errorMessage());
}
assert (embeddedSchemaParser.topLevelDataSchemas().size() == 1);
resultDataSchema = embeddedSchemaParser.topLevelDataSchemas().get(0);
if (translationMode == AvroToDataSchemaTranslationMode.VERIFY_EMBEDDED_SCHEMA) {
// additional verification to make sure that embedded schema translates to Avro schema
DataToAvroSchemaTranslationOptions dataToAvdoSchemaOptions = new DataToAvroSchemaTranslationOptions();
Object optionalDefaultModeProperty = ((DataMap) dataProperty).get(SchemaTranslator.OPTIONAL_DEFAULT_MODE_PROPERTY);
dataToAvdoSchemaOptions.setOptionalDefaultMode(OptionalDefaultMode.valueOf(optionalDefaultModeProperty.toString()));
Schema avroSchemaFromEmbedded = dataToAvroSchema(resultDataSchema, dataToAvdoSchemaOptions);
Schema avroSchemaFromJson = Schema.parse(avroSchemaInJson);
if (avroSchemaFromEmbedded.equals(avroSchemaFromJson) == false) {
throw new IllegalArgumentException("Embedded schema does not translate to input Avro schema: " + avroSchemaInJson);
}
}
}
}
}
if (resultDataSchema == null) {
// translationMode == TRANSLATE or no embedded schema
DataSchemaTraverse traverse = new DataSchemaTraverse();
traverse.traverse(dataSchema, AvroToDataSchemaConvertCallback.INSTANCE);
// convert default values
traverse.traverse(dataSchema, DefaultAvroToDataConvertCallback.INSTANCE);
// make sure it can round-trip
String dataSchemaJson = dataSchema.toString();
resultDataSchema = DataTemplateUtil.parseSchema(dataSchemaJson);
}
return resultDataSchema;
}
Aggregations