Это в некотором роде выстрел в темноте, если кто-то подкован с реализацией 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 к файлу (http://stackoverflow.com/info/578305/create-a-java-file-object-or-equivalent-using-a-byte- array-in-memory-without-a предполагает, что этого не происходит, и я также попытался оглянуться на документацию по Java), этот подход не будет работать, если читатель на другом конце OutputStream получит этот файл контейнера avro ( Я не уверен, почему они разрешили выводить двоичные файлы контейнера avro на произвольный OutputStream, не предоставляя способ прочитать их из соответствующего InputStream на другом конце, но это рядом с точкой). Похоже, что реализация файлового чтения контейнера требует "поисковой" функциональности, которую предоставляет конкретный файл.
Хорошо, так что не похоже, что этот подход сделает то, что я хочу. Как насчет создания ответа 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" сверху) в виде строки в поле "схема", а затем поместил в австро-двоичную сериализованную форму записи, соответствующую этой схеме (т.е. "GenericRecord" someRecord ") внутри поля данных.
Я действительно хотел узнать о конкретной детали того, что описано ниже, но я подумал, что было бы целесообразно также дать более широкий контекст, чтобы, если есть более высокий подход на высоком уровне, который я мог бы взять ( этот подход работает, но просто не кажется оптимальным), пожалуйста, дайте мне знать.
Мой вопрос заключается в том, что, исходя из этого подхода, основанного на JSON, как написать двоичное представление Avore моей записи в поле "данные" схемы 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, но когда данные были распечатаны, он был напечатан как прямая серия байтов, без прохождения через австровое шестнадцатеричное представление:
"data": {"bytes": ".....some gibberish other than the expected format...}
Это не кажется правильным. Согласно документации avro, пример байтов, который они дают, говорит о том, что мне нужно поставить объект json, пример которого выглядит как "\ u00FF", и то, что я там вписал, явно не соответствует этому формату. Теперь я хочу знать следующее:
- Каков пример формата avro bytes? Это похоже на "\ uDEADBEEFDEADBEEF..."?
- Как я могу принудить свои двоичные данные avro (как вывод BinaryEncoder в массив byte []) в формат, который я могу вставить в объект GenericRecord и правильно ли напечатать его в JSON? Например, мне нужен объект DATA, для которого я могу вызвать некоторый GenericRecord "someRecord.put(" данные ", DATA); с моими сериализованными данными avro внутри?
- Как бы я затем прочитал эти данные обратно в массив байтов на другом (потребительском) конце, когда ему было предоставлено текстовое представление JSON и он хочет воссоздать GenericRecord, представленный форматом JSON AvroContainer?
- (повторив вопрос раньше) Есть ли лучший способ, которым я мог бы все это сделать?