Интеграционный слой

Интеграционный слой #

Интеграционный слой предоставляет высокоуровневый API, позволяющий реализовать редакторы и панели Eclipse, используя в качестве основного хранилища данных объектное хранилище BM. В частности, интеграционный слой предоставляет следующую функциональность:

  • Разделение контекстов редактирования,
  • Отслеживание истории изменений и выполнения операций отмены / повтора,
  • Отслеживание изменений, которые не отражены в ресурсах рабочей области,
  • Сохранение изменений в ресурсах рабочей области,
  • Рассылка событий об изменениях в объектной модели.

BmModel #

Компонент BmModel (см. интерфейс com._1c.g5.v8.bm.integration.IBmModel) представляет собой фасад, посредством которого вы получаете доступ к основной функциональности интеграционного слоя BM.

Создание BmModel #

Создать экземпляр IBmModel можно только посредством вызова BmModels.create.

Обзор основных методов #

IBmEditingContext createLocalContext(String name) Создает локальный контекст редактирования с заданным именем. Что такое контекст редактирования, будет написано далее в этом документе.

IBmEditingContext getGlobalContext() Получает экземпляр глобального контекста редактирования.

void addSyncEventListener(IBmSyncEventListener listener) Производит подписку на синхронную рассылку событий.

void removeSyncEventListener(IBmSyncEventListener listener) Производит отписку от синхронной рассылки событий.

void addAsyncEventListener(IBmAsyncEventListener listener, BmEventFilter... filters) Производит подписку на асинхронную рассылку событий.

void removeAsyncEventListener(IBmAsyncEventListener listener) Производит отписку от асинхронной рассылки событий.

<T> T execute(IBmTask<T> task) Выполняет задачу BM без добавления записи в историю изменений. Что такое задача BM, будет написано далее в этом документе. При успешном выполнении задачи (если в процессе выполнения задачи не было выброшено исключение) производится рассылка событий BM.

<T> T executeReadonlyTask(IBmTask<T> task) Выполняет задачу BM, выполняющую доступ к данным BM только на чтение.

<T> T executeAndRollback(IBmTask<T> task) Выполняет задачу BM, после выполнения сразу же откатывает изменения (без рассылки событий).

Понятие задачи #

Задача BM — unit of work, объединяющий некоторый набор операций по редактированию данных BM, выполнение которых должно приводить модель в консистентное с точки зрения прикладного решения состояние.

Для выполнения задачи BM необходимо создать класс, реализующий интерфейс com._1c.g5.v8.bm.integration.IBmTask и содержащий логику по редактированию данных, и передать экземпляр этого класса в качестве параметра при вызове одного из следующих методов:

  • IBmModel.execute,
  • IBmModel.executeReadonlyTask,
  • IBmModel.executeAndRollback,
  • IBmEditingContext.execute,
  • IBmGlobalEditingContext.executeImportTask.

Интерфейс IBmTask определяет следующие методы:

  • getName возвращает имя задачи, которое будет отображаться в логах и в пользовательском интерфейсе (например, в пункте меню Edit - Undo)
  • execute содержит логику, отвечающую непосредственно за редактирование данных BM. Редактирование должно производиться в рамках транзакции, которая передается в данный метод в качестве параметра. При этом в данном методе не должны выполняться фиксация или откат транзакции, за это отвечает компонент, выполняющий задачу.
1
2
3
4
5
6
public interface IBmTask<T>
{
    String getName();

    T execute(IBmTransaction transaction, IProgressMonitor progressMonitor);
}

Понятие контекста редактирования #

Контексты редактирования предоставляют возможность отделения модификаций произведенных в разных редакторах. Таким образом предоставляется возможность применения и отката задач, выполненных в данном контексте, независимо от других контекстов (естественно, с ограничениями, связанными с особенностями редактирования конфигураций 1С).

Основной метод интерфейса com._1c.g5.v8.bm.integration.IBmEditingContext - execute. Он выполняет задачу BM в данном контексте. При успешном выполнении задачи (если в процессе выполнения задачи не было выброшено исключение) данная задача добавляется в историю изменений данного контекста.

Контекст помечается как dirty. Это означает, что он содержит изменения, не сохраненные в ресурсах рабочей области. При этом стоит заметить, что модификации уже отражены в основном хранилище BM и, таким образом, видны всем остальным пользователям BM.

Также при успешном выполнении данного метода производится рассылка событий BM.

1
2
3
4
public interface IBmEditingContext
{
    <T> T execute(IBmTask<T> task);
}

Локальный контекст редактирования #

Локальные контексты предназначены для использования при реализации редакторов Eclipse. Задачи, выполненные в локальном контексте, могут отменяться и повторяться (в случае, если нет конфликтующих изменений в других контекстах), сохранение изменений в ресурсах рабочей области происходит только при явном вызове метода save. При вызове метода dispose все несохраненные изменения отменяются.

Локальный контекст представлен интерфейсом com._1c.g5.v8.bm.integration.IBmLocalEditingContext:

  • canUndo, canRedo проверяют возможность выполнения операций отмены и повтора, соответственно,
  • undo, redo производят операции отмены и повтора, соответственно,
  • isDirty проверяет наличие изменений, не отраженных в ресурсах рабочей области,
  • save при наличии изменений, не отраженных в ресурсах рабочей области, производит сохранение изменений в ресурсах рабочей области,
  • dispose уничтожает данный контекст редактирования.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public interface IBmLocalEditingContext
    extends IBmEditingContext
{
	boolean canUndo();
    boolean canRedo();

    void undo();
    void redo();

    boolean isDirty();
    void save();

    void dispose();
}

Глобальный контекст редактирования #

Глобальный контекст редактирования предназначен для выполнения всех задач, не привязанных к определенному редактору, т.е. манипуляции в различных панелях, рефакторинг и т.п. После успешного выполнения задачи в глобальном контексте сохранение производится автоматически. Задачи, выполненные в глобальном контексте, в текущей версии не могут быть отменены или повторены. В будущих версиях такая возможность может быть добавлена.

Глобальный контекст представлен интерфейсом com._1c.g5.v8.bm.integration.IBmGlobalEditingContext и предоставляет один дополнительный метод по сравнению с базовым интерфейсом контекста executeImportTask. Он выполняет задачу импорта в глобальном контексте, при этом происходит рассылка событий, сохранение всех несохраненных локальных контекстов и уничтожение их истории.

1
2
3
4
5
public interface IBmGlobalEditingContext
    extends IBmEditingContext
{
    <T> T executeImportTask(IBmTask<T> task);
}

Обработка отмена / повтора задачи #

Для обработки undo / redo какой-либо задачи используется интерфейс com._1c.g5.v8.bm.integration.IBmPostUndoRedoHandler, который эта задача должна реализовывать. onUndo, onRedo выполняются по факту отмены и повтора задачи, соответственно. Эти методы предназначены для расположения в них нетранзакционной логики, не затрагивающей данные BM. Например, для рассылки бизнес-событий.

1
2
3
4
5
public interface IBmPostUndoRedoHandler
{
    void onUndo();
    void onRedo();
}

Редактирование вне контекста #

Иногда возникает необходимость выполнения задач, которые не должны попадать в историю изменений, а модификации модели, которые они производят, не должны отражаться в ресурсах рабочей области. Например, к такому классу задач относится расчет производных данных объектов.

Специально для этих целей на интерфейсе IBmModel был введен метод execute. При успешном выполнении данного метода производится сохранение произведенных изменений в объектном хранилище BM, производится рассылка событий, но не производится добавление задачи в историю изменений и не производится сохранение изменений в ресурсах рабочей области.

События #

Интерфейс IBmModel предоставляет методы для подписки на синхронную и асинхронную рассылку событий. Асинхронная рассылка событий производится в фоновом потоке. Синхронная рассылка производится в потоке выполнения задачи, сразу после ее успешного завершения. Поскольку синхронная рассылка событий удерживает рабочий поток, не следует подписываться на синхронную рассылку событий без крайней на то необходимости.

В обработчик события BM (и в синхронный, и в асинхронный) в качестве параметра передается экземпляр класса BmEvent, содержащий информацию обо всех изменениях произведенных в рамках задачи BM. В частности, класс BmEvent предоставляет два метода:

  • BmAssociationEvent getAssociationEvent(). Данный метод возвращает экземпляр BmAssociationEvent в том случае, если в рамках выполнения задачи производились операции по добавлению или удалению объектов BM. В противном случае возвращается null. Объект BmAssociationEvent содержит информацию обо всех добавленных и удаленных объектах.
  • BmLongHashMap<BmChangeEvent> getChangeEvents(). Данный метод возвращает map, в котором в роли ключа выступает числовой идентификатор модифицированного объекта, а в роли значения — экземпляр BmChangeEvent, содержащий информацию о том, какие features, BM-свойства и BM-бинарные вложения были изменены. Если в рамках выполненной задачи BM не было модификаций, то возвращается null.

Примеры #

Чтение данных справочника #

Для чтения данных справочника вам необходимо выполнить следующие действия:

  • Получить собственно справочник. В зависимости от ситуации, это может быть получение объекта по его FQN, либо получение объекта из Selection со стороны пользовательского интерфейса, и т.п.,
  • Сформировать задачу (IBmTask), осуществляющую получение необходимых данных из объекта метаданных типа Catalog. В этой задаче:
    • Полученный справочник актуализируется, подключаясь к транзакции, либо получается непосредственно из транзакции,
    • Производится чтение данных справочника,
    • Возвращается результат (опционально),
  • Выполнить эту задачу в режиме “только чтение”

Теперь реализуйте подготовленный план в виде программного кода:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
 * Пример получения данных из объекта метаданных типа Справочник.
 *
 * @param model Модель данных проекта EDT
 */
public void processCatalogData(IBmModel model) {
    // В нашем примере мы получим справочник по его FQN. Поэтому его можно получить непосредственно в самом
    // теле IBmTask

    IBmTask<Integer> getCodeLengthTask = new AbstractBmTask<Integer>("Получение длины кода справочника")
    {
        @Override
        public Integer execute(IBmTransaction transaction, IProgressMonitor progressMonitor)
        {
            // Шаг 1: Получаем справочник по его FQN
            Catalog catalog = (Catalog)transaction.getTopObjectByFqn("Catalog.Клиенты");
 
            // Шаг 2: Получаем искомые данные из справочника
            Integer codeLength = catalog.getCodeLength();
 
            // Шаг 3: И возвращаем их в качестве результата работы IBmTask
            return codeLength;
        }
    };

    // Задача сформирована, остается ее выполнить. Поскольку мы знаем, что данная задача не модифицирует
    // данные - разумно выполнить ее в режиме "только чтение".
    // Результат, возвращаемый задачей после выполнения, будет получен в качестве результата выполнения
    // метода IBmTask#executeReadonlyTask
    Integer codeLength = model.executeReadonlyTask(getCodeLengthTask);

    // Убеждаемся, что получили искомые данные
    System.out.println(codeLength);
}

Обратите внимание на следующие моменты:

  • IBmTask может возвращать значение в качестве результата выполнения. Тип данного значения задается параметром класса IBmTask. В данном примере вы использовали результат типа IntegerIBmTask<Integer> ,
  • Вы использовали базовую реализацию задач AbstractBmTask, содержащую необходимый минимум функциональности для выполнения задачи. Как вы видите, она также параметризуема типом возвращаемого результата — AbstractBmTask<Integer>,
  • Само тело задачи задается в методе execute, который возвращает параметр типа, указанного вами ранее через параметризацию задачи — public Integer execute(IBmTransaction transaction, IProgressMonitor progressMonitor),
  • Все данные модели, используемые внутри задачи, должны быть присоединены к транзакции. Для этого все данные должны быть получены через объект IBmTransaction, передаваемый в качестве параметра метода execute: public Integer execute(IBmTransaction transaction, IProgressMonitor progressMonitor). Также данные могут быть получены путем прохода по свойствам объекта, уже присоединенного к данной транзакции (собственно, получение значения поля и есть такая операция),
  • В случае, если вам не нужно возвращать значение из вашей задачи, можно использовать тип Void, задача в этом случае создается так: IBmTask<Void> someTask = new AbstractBmTask<Void>("Some task"), с соответствующим типом в методе execute: public Void execute(IBmTransaction transaction, IProgressMonitor progressMonitor),
  • Для Void в конце задачи необходимо вернуть null в качестве результата — return null