Skip to content

Marshalling/unmarshalling java class from/to binary array

Notifications You must be signed in to change notification settings

sqglobe/BinaryMarshaller

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 

Repository files navigation

BinaryMarshaller

Библиотека для маршалинга 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, то придется постараться, чтобы не запутаться какой параметр мы извлекаем сейчас.

Работа с BinaryMarshaller

Аннотации

Чтобы было возможно использовать 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

About

Marshalling/unmarshalling java class from/to binary array

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages