Как в Java создать эквивалент файла-контейнера Apache Avro без необходимости использовать файл в качестве носителя?

Это в некотором роде выстрел в темноту, если кто-то, кто разбирается в Java-реализации Apache Avro, читает это.

Моя высокоуровневая задача состоит в том, чтобы иметь некоторый способ передачи некоторой серии данных avro по сети (скажем, для примера, HTTP, но конкретный протокол не так важен для этой цели). В моем контексте у меня есть HttpServletResponse, в который мне нужно как-то записать эти данные.

Сначала я попытался записать данные как виртуальную версию файла-контейнера avro (предположим, что "response" имеет тип HttpServletResponse):

response.setContentType("application/octet-stream");
response.setHeader("Content-transfer-encoding", "binary");
ServletOutputStream outStream = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(outStream);

Schema someSchema = Schema.parse(".....some valid avro schema....");
GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("somefield", someData);
...

GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
DataFileWriter<GenericRecord> fileWriter = new DataFileWriter<GenericRecord>(datumWriter);
fileWriter.create(someSchema, bos);
fileWriter.append(someRecord);
fileWriter.close();
bos.flush();

Все было хорошо и замечательно, но оказалось, что Avro не предоставляет способа чтения файла-контейнера отдельно от реального файла: DataFileReader имеет только два конструктора:

public DataFileReader(File file, DatumReader<D> reader);

и

public DataFileReader(SeekableInput sin, DatumReader<D> reader);

где SeekableInput - это некоторая специфическая для avro настраиваемая форма, создание которой также заканчивается чтением из файла. Теперь, учитывая это, если только не существует способа каким-то образом превратить InputStream в File (http://stackoverflow.com/questions/578305/create-a-java-file-object-or-equivalent-using-a-byte-array-in-memory-without-a предполагает, что нет, и я также пробовал искать в документации Java), этот подход не будет работать, если читатель на другом конце OutputStream получает этот avro контейнерный файл (я не уверен, почему они позволили выводить avro бинарные контейнерные файлы в произвольный OutputStream, не обеспечив способ читать их из соответствующего InputStream на другом конце, но это не имеет значения). Похоже, что реализация читателя контейнерных файлов требует функциональности "seekable", которую предоставляет конкретный File.

Ладно, не похоже, что этот подход сделает то, что я хочу. Как насчет создания JSON-ответа, имитирующего файл-контейнер avro?

public static Schema WRAPPER_SCHEMA = Schema.parse(
  "{\"type\": \"record\", " +
   "\"name\": \"AvroContainer\", " +
   "\"doc\": \"a JSON avro container file\", " +
   "\"namespace\": \"org.bar.foo\", " +
   "\"fields\": [" +
     "{\"name\": \"schema\", \"type\": \"string\", \"doc\": \"schema representing the included data\"}, " +
     "{\"name\": \"data\", \"type\": \"bytes\", \"doc\": \"packet of data represented by the schema\"}]}"
  );

Я не уверен, что это лучший способ, учитывая вышеуказанные ограничения, но похоже, что это может сработать. Я помещу схему (например, "Schema someSchema" из примера выше) как String в поле "schema", а затем помещу avro-binary-serialized форму записи, соответствующей этой схеме (т.е. "GenericRecord someRecord") в поле "data".

На самом деле я хотел узнать о конкретной детали этого, которая описана ниже, но я подумал, что стоит дать и более широкий контекст, так что если есть лучший высокоуровневый подход, который я мог бы использовать (этот подход работает, но просто не кажется оптимальным), пожалуйста, дайте мне знать.

Мой вопрос в том, что если я выберу этот подход на основе JSON, как мне записать двоичное представление моей Записи в поле "data" схемы AvroContainer? Например, я дошел до этого места:

ByteArrayOutputStream baos = new ByteArrayOutputStream();
GenericDatumWriter<GenericRecord> datumWriter = new GenericDatumWriter<GenericRecord>(someSchema);
Encoder e = new BinaryEncoder(baos);
datumWriter.write(resultsRecord, e);
e.flush();

GenericRecord someRecord = new GenericData.Record(someSchema);
someRecord.put("schema", someSchema.toString());
someRecord.put("data", ByteBuffer.wrap(baos.toByteArray()));
datumWriter = new GenericDatumWriter<GenericRecord>(WRAPPER_SCHEMA);
JsonGenerator jsonGenerator = new JsonFactory().createJsonGenerator(baos, JsonEncoding.UTF8);
e = new JsonEncoder(WRAPPER_SCHEMA, jsonGenerator);
datumWriter.write(someRecord, e);
e.flush();

PrintWriter printWriter = response.getWriter(); // recall that response is the HttpServletResponse
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
printWriter.print(baos.toString("UTF-8"));

Сначала я попробовал опустить пункт ByteBuffer.wrap, но затем строка

datumWriter.write(someRecord, e);

выдала исключение, что я не могу привести байтовый массив в ByteBuffer. Справедливо, похоже, что когда класс Encoder (подклассом которого является JsonEncoder) вызывается для записи объекта avro Bytes, он требует, чтобы в качестве аргумента был указан ByteBuffer. Таким образом, я попробовал инкапсулировать байт[] с помощью java.nio.ByteBuffer.wrap, но когда данные были выведены, они были выведены как прямая серия байтов, без прохождения через шестнадцатеричное представление avro:

"data": {"bytes": ".....some gibberish other than the expected format...}

Это кажется неправильным. Согласно документации avro, пример объекта bytes, который они дают, говорит, что мне нужно поместить объект json, пример которого выглядит как "\u00FF", а то, что я туда поместил, явно не в этом формате. Теперь я хочу узнать следующее:

  • Каков пример формата avro байтов? Выглядит ли он примерно как "\uDEADBEEFDEADBEEF..."?
  • Как мне преобразовать мои двоичные данные avro (выводимые BinaryEncoder в массив byte[]) в формат, который я могу засунуть в объект GenericRecord и заставить его правильно печататься в JSON? Например, мне нужен объект DATA, для которого я могу вызвать GenericRecord "someRecord.put("data", DATA);" с моими сериализованными данными avro внутри?
  • Как мне затем прочитать эти данные обратно в массив байтов на другом (consumer) end, когда ему дается текстовое представление JSON и он хочет воссоздать GenericRecord, представленный JSON формата AvroContainer?
  • (повторяя вопрос, заданный ранее) Есть ли лучший способ, которым я мог бы все это сделать?
18
задан omnilinguist 24 September 2011 в 18:54
поделиться