2015-07-03 11 views
9

Sto cercando un modo per convertire un POJO in un oggetto avro in un modo generico. L'implementazione dovrebbe essere solida per qualsiasi modifica della classe POJO. L'ho raggiunto ma riempiendo esplicitamente il record avro (vedi esempio sotto).Conversione generica da POJO a Avro Record

C'è un modo per sbarazzarsi dei nomi dei campi codificati e basta riempire il disco avro dall'oggetto? La riflessione è l'unico modo, o avro fornisce questa funzionalità fuori dalla scatola?

import java.util.Date; 
import java.util.HashMap; 
import java.util.Map; 

import org.apache.avro.Schema; 
import org.apache.avro.generic.GenericData.Record; 
import org.apache.avro.reflect.ReflectData; 

public class PojoToAvroExample { 

    static class PojoParent { 
     public final Map<String, String> aMap = new HashMap<String, String>(); 
     public final Map<String, Integer> anotherMap = new HashMap<String, Integer>(); 
    } 

    static class Pojo extends PojoParent { 
     public String uid; 
     public Date eventTime; 
    } 

    static Pojo createPojo() { 
     Pojo foo = new Pojo(); 
     foo.uid = "123"; 
     foo.eventTime = new Date(); 
     foo.aMap.put("key", "val"); 
     foo.anotherMap.put("key", 42); 
     return foo; 
    } 

    public static void main(String[] args) { 
     // extract the avro schema corresponding to Pojo class 
     Schema schema = ReflectData.get().getSchema(Pojo.class); 
     System.out.println("extracted avro schema: " + schema); 
     // create avro record corresponding to schema 
     Record avroRecord = new Record(schema); 
     System.out.println("corresponding empty avro record: " + avroRecord); 

     Pojo foo = createPojo(); 
     // TODO: to be replaced by generic variant: 
     // something like avroRecord.importValuesFrom(foo); 
     avroRecord.put("uid", foo.uid); 
     avroRecord.put("eventTime", foo.eventTime); 
     avroRecord.put("aMap", foo.aMap); 
     avroRecord.put("anotherMap", foo.anotherMap); 
     System.out.println("expected avro record: " + avroRecord); 
    } 
} 
+1

Perché non utilizzare [di Avro ReflectDatumWriter] (http: // StackOverflow .com/questions/11866466/using-apache-avro-reflect) per serializzare il POJO? –

+0

Sto usando avro in contesto hadoop. Per la serializzazione vorrei utilizzare AvroParquetOutputFormat – fab

+1

Un approccio inefficiente avrebbe [ReflectDatumWriter scrivere un POJO in byte, quindi GenericDatumReader leggerà i byte in GenericRecord] (http://stackoverflow.com/questions/26435299/write-pojos-to-parquet -file-con-riflessione). –

risposta

-1

Mi serviva esattamente una cosa del genere. La libreria che ti serve è in file JAR avro, ma stranamente, non sembra avere un modo per richiamarla dalla riga di comando di avro-tools.

invocarlo come: java GenerateSchemaFromPOJO com.example.pojo.Person Person.java

import java.io.FileWriter; 
import java.io.IOException; 
import java.io.Writer; 

import org.apache.avro.Schema; 

import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.dataformat.avro.AvroFactory; 
import com.fasterxml.jackson.dataformat.avro.AvroSchema; 
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; 
import com.fasterxml.jackson.dataformat.avro.schema.VisitorFormatWrapperImpl; 

public class GenerateSchemaFromPOJO { 

    public static void main(String[] args) { 
     String className = null; 
     String outputFile = null; 
     Writer outputWriter = null; 
     try { 
      if(args.length != 2) { 
       System.out.println("Usage: java " + GenerateSchemaFromPOJO.class.getCanonicalName() + " classname output-schema-file.json"); 
       System.exit(1); 
      } 
      className = args[0]; 
      outputFile = args[1]; 

      Class<?> clazz = Class.forName(className); 

      AvroFactory avroFactory = new AvroFactory(); 
      ObjectMapper mapper = new ObjectMapper(avroFactory); 

      AvroSchemaGenerator gen = new AvroSchemaGenerator(); 
      mapper.acceptJsonFormatVisitor(clazz, gen); 
      AvroSchema schemaWrapper = gen.getGeneratedSchema(); 

      Schema avroSchema = schemaWrapper.getAvroSchema(); 
      String asJson = avroSchema.toString(true); 

      outputWriter = new FileWriter(outputFile); 
      outputWriter.write(asJson); 
     } catch (Exception ex) { 
      System.err.println("caught " + ex); 
      ex.printStackTrace(); 
      System.exit(1); 
     } finally { 
      if(outputWriter != null) { 
       try { 
        outputWriter.close(); 
       } catch (IOException e) { 
        System.err.println("Caught " + e + " while trying to close outputWriter to " + outputFile);; 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 
} 
+0

Come capisco dalla tua risposta, il tuo codice genera lo schema avro per il dato 'clazz'. Questo non è, quello che stavo chiedendo nella domanda. Faccio lo stesso in linea 'ReflectData.get(). GetSchema (Pojo.class);'. Stavo cercando un modo per sostituire 'avroRecord.put (..., ...);' con una variante generica – fab

3

Qui è modo generico per convertire

public static <V> byte[] toBytesGeneric(final V v, final Class<V> cls) { 
     final ByteArrayOutputStream bout = new ByteArrayOutputStream(); 
     final Schema schema = ReflectData.get().getSchema(cls); 
     final DatumWriter<V> writer = new ReflectDatumWriter<V>(schema); 
     final BinaryEncoder binEncoder = EncoderFactory.get().binaryEncoder(bout, null); 
     try { 
      writer.write(v, binEncoder); 
      binEncoder.flush(); 
     } catch (final Exception e) { 
      throw new RuntimeException(e); 
     } 


     return bout.toByteArray(); 
    } 

public static void main(String[] args) { 
    PojoClass pojoObject = new PojoClass(); 
    toBytesGeneric(pojoObject, PojoClass.class); 
} 
5

Si sta utilizzando Primavera? Costruisco un mappatore per questo con una funzione Spring. Ma è anche possibile costruire che mapping attraverso la riflessione cruda utils troppo:

import org.apache.avro.Schema; 
import org.apache.avro.generic.GenericData; 
import org.apache.avro.reflect.ReflectData; 
import org.springframework.beans.PropertyAccessorFactory; 
import org.springframework.util.Assert; 

public class GenericRecordMapper { 

    public static GenericData.Record mapObjectToRecord(Object object) { 
     Assert.notNull(object, "object must not be null"); 
     final Schema schema = ReflectData.get().getSchema(object.getClass()); 
     final GenericData.Record record = new GenericData.Record(schema); 
     schema.getFields().forEach(r -> record.put(r.name(), PropertyAccessorFactory.forDirectFieldAccess(object).getPropertyValue(r.name()))); 
     return record; 
    } 

    public static <T> T mapRecordToObject(GenericData.Record record, T object) { 
     Assert.notNull(record, "record must not be null"); 
     Assert.notNull(object, "object must not be null"); 
     final Schema schema = ReflectData.get().getSchema(object.getClass()); 
     Assert.isTrue(schema.getFields().equals(record.getSchema().getFields()), "Schema fields didn't match"); 
     record.getSchema().getFields().forEach(d -> PropertyAccessorFactory.forDirectFieldAccess(object).setPropertyValue(d.name(), record.get(d.name()) == null ? record.get(d.name()) : record.get(d.name()).toString())); 
     return object; 
    } 

} 

Con questo mapper è possibile generare un GenericData.Record che può essere facilmente serializzato a Avro. Quando si deserializzare un Avro ByteArray è possibile utilizzarlo per ricostruire un POJO da record di deserializzati:

Serialize

byte[] serialized = avroSerializer.serialize("topic", GenericRecordMapper.mapObjectToRecord(yourPojo)); 

Deserialize

GenericData.Record deserialized = (GenericData.Record) avroDeserializer.deserialize("topic", serialized); 

YourPojo yourPojo = GenericRecordMapper.mapRecordToObject(deserialized, new YourPojo());