Библиотека для маршалинга POJO в бинарное сообщение, а так же восстановления объекта из такого сообщения. Размер параметров сообщения и их расположение задаются типом сообщения.
В библиотеке используется Java Reflection API. В купе с аннотациями это позволяет использовать POJO для представления параметров бинарного сообщения, вместо применения модели наследования.
Некоторые API протоколов прикладного уровня, которые используются в телекоме, позволяют передать дополнительные параметры сообщений в виде массива байт, где они следуют один за другим без пропусков. Расположение и размер параметров задается типом сообщения.
Так, например, в API, которое предоставляет Dialogic для работы с SIGTRAN, основные поля сообщения: тип, необходимость ответа, флаг ошибки и пр, передается явно. А набор дополнительных параметров, которые соответствуют типу сообщения - в виде массива байт.
Например, допустим у нас определено информативное сообщение с типом 0x00f0, которым подсистема с определенным номером subsystem информирует о длине своей очереди len заданий и общем количестве выполненных задач total:
Offse | Size | Name |
---|---|---|
0 | 1 | subsystem |
1 | 4 | total |
5 | 2 | len |
Передаваемый массив байт имеет следующий вид:
subsystem
->|--|<------
| |
0xf10x000000640x001a
| || |
| total || len|
|<------>||<-->|
Чтобы получить значения параметров из бинарного сообщения можно,
например, воспользоваться функционалом класса ByteBuffer
.
Для приведенного выше примера придется извлечь номер подсистемы инструкцией buff.get()
и сохранить в локальной
переменной, затем вызовом buff.getInt()
получить общее количество выполненных заданий,
и так же сохранить в локальной переменной и т.д.
public void prepare(byte[] data){
ByteBuffer buff = ByteBuffer.wrap(data);
byte subsystem = buff.get();
int total = buff.getInt();
short len = buff.getShort();
...
}
Подобная ситуация и с записью параметров в буфер для последующей отправки.
Приходится постоянно держать в памяти, какие параметры извлечены, а какие еще нет, какой из них в очереди на извлечение и т.д. Кроме того необходимо вручную проверять все ли данные извлечены из массива.
Можно пойти дальше и создать для сообщения POJO объект, который в конструкторе будет получать массив байт и сам его раскладывать в свои члены:
class SubsystemQueue{
private final byte subsystem;
private final int total;
private final short len;
public SubsystemQueue(byte[] data){
ByteBuffer buff = ByteBuffer.wrap(data);
subsystem = buff.get();
total = buff.getInt();
len = buff.getShort();
if(buff.hasRemaining()){
throw new RuntimeError("Invalid buffer len: " + String.valueOf(data.length));
}
}
}
Что же, выглядит намного лучше. Однако код, который извлекает параметры из буфера, придется переписывать от класса к классу. Более того, если вдруг, в сообщении передается не 2-3 параметра, 10-20, то придется постараться, чтобы не запутаться какой параметр мы извлекаем сейчас.
Чтобы было возможно использовать POJO объекты для представления параметров бинарного сообщения, библиотека предоставляет пару аннотаций:
BinaryParams
- помечает класс, который представляет параметры сообщения, задает код сообщения, общую длину всех параметров, а так же начало не используемой области сообщения;BinaryParam
- помечает поле класса как параметр сообщения, задает смещение, а так же длину параметра.
В некоторых обстоятельствах в составе сообщения передаются не используемые байты, которые располагаются в самом конце,
именно для таких случаев аннотация BinaryParams
позволяет задать начало не используемой области.
Если произошла ошибка в указании смещения и длины параметра в BinaryParam
, такая, что между двумя параметрами
образовался пробел, или один параметр "наезжает" на другой, будет выброшено исключение во время инициализации системы.
Чтобы библиотека могла установить и считать свойства, отмеченные BinaryParam
, необходимо в класс добавить get и
set методы вида byte getSubsystem()
и void setSubsystem(byte subs)
.
Можно задать либо одни set-методы, либо только get-методы. Если методы созданы, но не для всех переменных
будет инициировано исключение на этапе инициализации системы.
Конструктор класса не должен принимать параметров.
Пример POJO для нашего сообщения, описанного выше:
@BinaryParams(type = 0x00f0, size = 7)
class SubsystemQueue{
@BinaryParam(begin = 0, length = 1)
private byte subsystem;
@BinaryParam(begin = 1, length = 4)
private int total;
@BinaryParam(begin = 5, length = 2)
private short len;
public void setSubsystem(byte subs){
this.subsystem = subs;
}
public void setTotal(int tot){
this.total = tot;
}
public void setLen(short len){
this.len = len;
}
public byte getSubsystem(){
return this.subsystem;
}
public int getTotal(){
return this.total;
}
public short getLen(){
return this.len;
}
}
Для начала работы с библиотекой, необходимо создать экземпляр класса ClassResolver
.
Он занимается тем, что сканирует указанный пакет на наличие классов с аннотацией BinaryParams
, создает обертку, которая позволяет построить объект из массива байт,
или выполнить маршализацию объекта в бинарное сообщение. Сохраняет ее и код сообщения.
Для упрощения работы, можно воспользоваться ResolverInitialisator
, который производит предварительную настройку окружения,
а так же предоставляет метод ResolverInitialisator.init
, который возвращает ClassResolver
. В качестве параметра ResolverInitialisator.init
принимает пакет для сканирования, в котором находятся все POJO объекты с аннотацией BinaryParams
.
Пример кода инициализации:
ClassResolver resolver = (new ResolverInitialisator()).init("org.binarymarshaller.testutils.research.valid");
Построить объект из массива байт:
public void prepare(byte[] data)
{
ByteBuffer buff = ByteBuffer.wrap(data);
PojoBuilder<SubsystemQueue> builder = resolver.getWrapper(0x00f0);
SubsystemQueue obj = builder.make(buff);
...
}
Маршалинг объекта в бинарное сообщение:
puplic bute[] send(SubsystemQueue que){
PojoBuilder<SubsystemQueue> builder = resolver.getWrapper(0x00f0);
ByteBuffer buff = builder.pack(que);
return buff.array();
}
Сборка проекта осуществляет Maven:
> cd BinaryMarshaller
> mvn clean package
Запуск тестов:
> cd BinaryMarshaller
> mvn clean test