/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.converter.http; import org.apache.avro.Schema; import org.apache.avro.generic.GenericRecord; import org.apache.commons.lang3.StringUtils; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigParseOptions; import com.typesafe.config.ConfigRenderOptions; import com.typesafe.config.ConfigSyntax; import gobblin.configuration.WorkUnitState; import gobblin.converter.Converter; import gobblin.converter.DataConversionException; import gobblin.converter.SchemaConversionException; import gobblin.converter.SingleRecordIterable; /** * Converts Avro to RestJsonEntry. * This converter won't provide converted Schema mainly because: * 1. The purpose of this converter is to convert DataRecord to fit JSON REST API writer. This converter is * intended to be end of the converter chain and expect to be followed by JSON REST API writer. * 2. JSON schema is still under development and there is no widely accepted JSON Schema. */ public class AvroToRestJsonEntryConverter extends Converter<Schema, Void, GenericRecord, RestEntry<JsonObject>> { //Resource template ( e.g: /sobject/account/${account_id} ) static final String CONVERTER_AVRO_REST_ENTRY_RESOURCE_KEY = "converter.avro.rest.resource_key"; //JSON conversion template @see convertRecord static final String CONVERTER_AVRO_REST_JSON_ENTRY_TEMPLATE = "converter.avro.rest.json_hocon_template"; private final JsonParser parser = new JsonParser(); @Override public Void convertSchema(Schema inputSchema, WorkUnitState workUnit) throws SchemaConversionException { return null; } /** * Use resource key(Optional) and rest json entry as a template and fill in template using Avro as a reference. * e.g: * Rest JSON entry HOCON template: * AccountId=${sf_account_id},Member_Id__c=${member_id} * Avro: * {"sf_account_id":{"string":"0016000000UiCYHAA3"},"member_id":{"long":296458833}} * * Converted Json: * {"AccountId":"0016000000UiCYHAA3","Member_Id__c":296458833} * * As it's template based approach, it can produce nested JSON structure even Avro is flat (or vice versa). * * e.g: * Rest resource template: * /sobject/account/memberId/${member_id} * Avro: * {"sf_account_id":{"string":"0016000000UiCYHAA3"},"member_id":{"long":296458833}} * Converted resource: * /sobject/account/memberId/296458833 * * Converted resource will be used to form end point. * http://www.server.com:9090/sobject/account/memberId/296458833 * * {@inheritDoc} * @see gobblin.converter.Converter#convertRecord(java.lang.Object, java.lang.Object, gobblin.configuration.WorkUnitState) */ @Override public Iterable<RestEntry<JsonObject>> convertRecord(Void outputSchema, GenericRecord inputRecord, WorkUnitState workUnit) throws DataConversionException { Config srcConfig = ConfigFactory.parseString(inputRecord.toString(), ConfigParseOptions.defaults().setSyntax(ConfigSyntax.JSON)); String resourceKey = workUnit.getProp(CONVERTER_AVRO_REST_ENTRY_RESOURCE_KEY, ""); if(!StringUtils.isEmpty(resourceKey)) { final String dummyKey = "DUMMY"; Config tmpConfig = ConfigFactory.parseString(dummyKey + "=" + resourceKey).resolveWith(srcConfig); resourceKey = tmpConfig.getString(dummyKey); } String hoconInput = workUnit.getProp(CONVERTER_AVRO_REST_JSON_ENTRY_TEMPLATE); if(StringUtils.isEmpty(hoconInput)) { return new SingleRecordIterable<>(new RestEntry<>(resourceKey, parser.parse(inputRecord.toString()).getAsJsonObject())); } Config destConfig = ConfigFactory.parseString(hoconInput).resolveWith(srcConfig); JsonObject json = parser.parse(destConfig.root().render(ConfigRenderOptions.concise())).getAsJsonObject(); return new SingleRecordIterable<>(new RestEntry<>(resourceKey, json)); } }