Объектное хранилище

Объектное хранилище #

Объектное хранилище BM предоставляет следующие возможности, отсутствующие в EMF:

  • Транзакционное редактирование,
  • Блокировки,
  • Быстрый поиск объекта по идентификатору.
  • Контроль за расходом памяти.
  • Эффективное использование файловой системы.
  • Парциальная загрузка данных.

Краткий пример использования #

Прежде всего включите в список зависимостей своего проекта пакет com._1c.g5.v8.bm.core из одноименного бандла.

Затем вам необходимо разработать персистентную модель. Для этой цели в BM используется фреймворк EMF. Для декларативного описания EMF-классов используйте язык Xcore (см. https://wiki.eclipse.org/Xcore). Далее показан пример описания персистентной модели с использованием языка Xcore:

 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
@GenModel(
    loadInitialization="false",
    literalsInterface="true",
    nonNLSMarkers="true",
    prefix="SampleModel",
    copyrightText="Copyright (C) 2020, 1C-Soft LLC",
    updateClasspath="false",
    featureDelegation="Reflective",
    rootExtendsClass="com._1c.g5.v8.bm.core.BmObject",
    rootExtendsInterface="com._1c.g5.v8.bm.core.IBmObject")
@Ecore(nsPrefix="bmcoretest", nsURI="http://g5.1c.ru/v8/bm/samplemodel")
package com._1c.g5.v8.bm.samplemodel

class Library
{
    String name
    contains Book[] books
    contains Author[] authors
}

class Book
{
    String title
    int pageCount
    refers Author[] authors
}

class Author
{
    String firstName
    String lastName
}

Здесь вы видите стандартный подход к описанию EMF-модели с использованием Xcore. Единственный момент, на котором стоит заострить внимание, это настройки генератора:

  • BM поддерживает только рефлективное делегирование (стр. 8).
  • Классы модели должны расширять класс com._1c.g5.v8.bm.core.BmObject (стр. 9) и реализовывать интерфейс com._1c.g5.v8.bm.core.IBmObject (стр. 10).

После того, как модель описана и по ней сгенерированы Java-классы, можете приступать к конфигурированию и старту «движка» объектного хранилища. В следующем примере в первой строке производится создание «движка» с заданными параметрами, в последней — его запуск. После завершения работы метода start созданный «движок» готов к работе:

1
2
3
4
5
IBmEngine engine = BmEngines.createEngine(
    "samplemodel",
    FileSystems.getDefault().getPath("samplemodel-data"),
    Arrays.asList(EcorePackage.eINSTANCE, SampleModelPackage.eINSTANCE));
engine.start();

Рассмотрите, как осуществляется процесс редактирования персистентной модели. Во-первых, для осуществления редактирования модели необходимо создать транзакцию. Во-вторых, чтобы сделать объект персистентным, его необходимо подключить к BM.

Подключить объект к BM можно двумя способами:

  • если объект является объектом верхнего уровня (top object в терминах BM или root object в терминах EMF), то его подключение осуществляется посредством вызова метода IBmTransaction.attachTopObject;
  • если объект является вложенным (contained), то его нужно добавить в объект-контейнер, подключенный к модели.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
IBmTransaction transaction = engine.beginReadWriteTransaction();

Library library = SampleModelFactory.eINSTANCE.createLibrary();
transaction.attachTopObject(library, "MyLibrary");
library.setName("My library");

Author rayBradbury = SampleModelFactory.eINSTANCE.createAuthor();
library.getAuthors().add(rayBradbury);
rayBradbury.setFirstName("Ray");
rayBradbury.setLastName("Bradbury");

transaction.commit();

В первой строке вы начинаете транзакцию на редактирование посредством вызова метода beginReadWriteTransaction.

В четвертой строке вы подключате экземпляр класса Library к ВМ как объект верхнего уровня. Вторым параметром в метод attachTopObject передается FQN подключаемого объекта.

В восьмой строке вы подключаете экземпляр класса Author к ВМ как вложенный объект.

В последней строке вы фиксируете транзакцию, сохраняя, тем самым, произведенные изменения в персистентном хранилище.

Далее вы можете в любое время получить добавленные объекты, отредактировать, добавить новые, связать их с уже существующими и так далее.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
IBmTransaction transaction = engine.beginReadWriteTransaction();

Library library = (Library)transaction.getTopObjectByFqn("MyLibrary");
Author rayBradbury = library.getAuthors().get(0);

Book dandelionWine = SampleModelFactory.eINSTANCE.createBook();
library.getBooks().add(dandelionWine);
dandelionWine.setName("Dandelion wine");
dandelionWine.getAuthors().add(rayBradbury);

transaction.commit();

В этом отрывке кода вы видите следующие действия:

  • в отдельной транзакции (стр. 1) производится получение ранее созданных объектов (стр. 3, 4),
  • создается экземпляр объекта Book (стр. 6),
  • он добавляется в объект-контейнер Library (стр. 7),
  • ему устанавливается ссылка на существующий экземпляр класса Author (стр. 9),
  • по завершении всех изменений фиксируется транзакция (стр. 11).

В данном примере были рассмотрены базовые приемы работы с объектным хранилищем. Более подробно работа с ним будет рассмотрена далее в этом документе.

«Движок» #

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

Создание «движка» #

Создать экземпляр IBmEngine можно только посредством вызова одного из методов BmEngines.createEngine. Рассмотрите более подробно методы BmEngines.createEngine.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class BmEngines
{
    public static IBmEngine createEngine(String id, Path root, Collection<EPackage> ePackages)
    {
        return createEngine(id, root, ePackages, new BmEngineSettings());
    }

    public static IBmEngine createEngine(String id, Path root, Collection<EPackage> ePackages,
        BmEngineSettings settings)
    {
        // ...
    }
}

Первый метод создает «движок» со стандартными настройками, второй метод позволяет задать собственные настройки «движка». Первые три параметра у методов одинаковые:

  1. id. Идентификатор хранилища. Он необходим для того, чтобы отличать объекты, принадлежащие данному хранилищу, от объектов, принадлежащих другим хранилищам. Также идентификатор хранилища фигурирует в URI объектов,
  2. root. Путь к каталогу, в котором будут располагаться файлы хранилища,
  3. ePackages. Коллекция EMF-пакетов, с объектами которых может работать «движок».

Как уже было сказано выше, второй метод позволяет вам задать собственные настройки, которые передаются четвертым параметров в объекте BmEngineSettings. Все настройки являются опциональными, таким образом, если вы не укажете какой-либо параметр, то BM будет использовать стандартное значение. Вы можете указать следующие настройки:

  • lockWaitTimeout. Максимальное время ожидания установки блокировки объекта в транзакции в миллисекундах,
  • enableEvents. Включает сбор информации об изменениях, произведенных в транзакции, которые возвращаются вместе с результатом фиксации транзакции ввиде объекта-события,
  • enableMonitoring. Включает мониторинг работы BM. В данный момент мониторинг позволяет отслеживать подвисшие и долгоживущие транзакции, а также ситуации взаимной блокировки транзакций,
  • useMemoryMappedFiles. Включает использование файлов отображаемых в память,
  • executorService. Экземпляр ExecutorService, который будет использоваться для внутренних задач BM,
  • crashListener. Подписчик, который уведомляется при обнаружении сбоя в объектном хранилище,
  • unfinishedCommitProcessor. Подписчик, который уведомляется о коммитах, незавершенных в рамках предыдущей сессии работы BM,
  • uriBuildContributors. Компоненты, посредством поставки которых клиенты BM могут настраивать процесс построения URI объектов BM,
  • crossReferenceFilters. Компоненты, посредством поставки которых клиенты BM могут настраивать процесс фильтрации обратных ссылок,
  • externalUriResolvers. Компоненты, посредством которых клиенты BM могут обеспечивать возможность резолвинга любых EMF-объектов, не принадлежащих данному хранилищу,
  • referencePersistenceContributors. Компоненты, для эффективного хранения и резолвинга ссылок на любые EMF-объекты, не принадлежащие данному хранилищу. Данный механизм является улучшенной альтернативой механизму external URI resolver-ов,
  • attributeSerializers. Сериализаторы примитивных атрибутов. Значение использованных терминов будет приведено далее в этом документе.

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

void start() Производит запуск модели. Сразу после возврата из данного метода «движок» готов к работе.

void close() Производит останов модели. В самом начале этого метода устанавливается признак неактивности «движка», любые обращения к нему, как в этом потоке так и в других не допускаются.

BmState getState() Возвращает состояние «движка».

String getId() Возвращает идентификатор хранилища.

IBmTransaction beginReadWriteTransaction() Начинает транзакцию на редактирование.

IBmTransaction beginReadOnlyTransaction() Начинает транзакцию на чтение.

IBmObject getObjectById(long id) Получает объект по его идентификатору вне транзакции.

IBmObject getTopObjectByFqn(String fqn) Получает объект верхнего уровня по его FQN вне транзакции.

List<IBmCrossReference> getBackReferences(IBmObject object) Получает список обратных ссылок по объекту (всех ссылок, указывающих на данный объект).

Транзакции #

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

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

В текущем решении редактирование данных допустимо только в транзакциях. Далее описаны основные сценарии редактирования данных.

Создание нового объекта #

Создание объекта в хранилище сводится к созданию экземпляра EMF-объекта и подключению ее к модели. Объекты верхнего уровня подключаются посредством вызова метода IBmTransaction.attachTopObject, вложенные объекты подключаются посредством добавления в подключенный к модели объект-контейнер:

1
2
3
4
5
6
7
8
9
IBmTransaction transaction = engine.beginReadWriteTransaction();

ContainerObject containerObject = SomeModelFactory.eINSTANCE.createContainerObject();
transaction.attachTopObject(containerObject, "TestContainer1");

ContainedObject containedObject = SomeModelFactory.eINSTANCE.createContainedObject();
containerObject.setContainedObject(containedObject);

transaction.commit();

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
IBmTransaction transaction = engine.beginReadWriteTransaction(false);

ContainerObject containerObject = SomeModelFactory.eINSTANCE.createContainerObject();

ContainedObject containedObject = SomeModelFactory.eINSTANCE.createContainedObject();
containerObject.setContainedObject(containedObject);

transaction.attachTopObject(containerObject, "TestContainer1");

transaction.commit();

Редактирование существующего объекта #

Для редактирования существующего объекта, необходимо получить этот объект в транзакции и изменить интересующие features, BM-свойства, BM-бинарные вложения. Объект можно получить одним из следующих способов:

  • Объект верхнего уровня можно получить по его FQN с помощью метода BmTransaction.getTopObjectByFqn,
  • Любой объект можно получить по его внутреннему числовому идентификатору с помощью метода IBmTransaction.getObjectById,
  • Объект можно получить путем считывания значения одной из его ссылок (как containment, так и не containment),
  • Путем получения соответствующего транзакционного экземпляра по экземпляру, не привязанному к какой-либо транзакции.

Следующий пример демонстрирует описанные выше приемы:

 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
35
// Редактирование объекта верхнего уровня, полученного по FQN
IBmTransaction transaction1 = engine.beginReadWriteTransaction();

Catalog catalog = (Catalog)transaction1.getTopObjectByFqn("Catalog.Goods");
catalog.setHierarchical(true);
catalog.setHierarchyType(HierarchyType.HIERARCHY_FOLDERS_AND_ITEMS);

transaction1.commit();

// Редактирование объекта, полученного по числовому идентификатору.
IBmTransaction transaction2 = engine.beginReadWriteTransaction();

Catalog catalog = (Catalog)transaction2.getObjectById(123L);
catalog.setCodeLength(5);

transaction2.commit();

// Редактирование объекта, полученного путем прохода по ссылкам
IBmTransaction transaction3 = engine.beginReadWriteTransaction();

Catalog catalog = (Catalog)transaction3.getTopObjectByFqn("Catalog.Goods");
CatalogAttribute attribute = catalog.getAttributes().get(0);
attribute.setIndexing(Indexing.INDEX);

transaction3.commit();

// Получение транзакционного экземпляра объекта по экземпляру, не привязанному к какой-либо транзакции
CatalogAttribute globalInstance = engine.getObjectById(456L);

IBmTransaction transaction4 = engine.beginReadWriteTransaction();

CatalogAttribute transactionalCounterpart = transaction4.toTransactionObject(globalInstance);
transactionalCounterpart.setFullTextSearch(FullTextSearchUsing.DONT_USE);

transaction4.commit();

Удаление существующего объекта #

Как и при создании объектов, способ удаления объекта зависит от того, является ли он объектом верхнего уровня или вложенным объектом. Объекты верхнего уровня удаляются с помощью метода IBmTransaction.detachTopObject, вложенные объекты удаляются путем удаления из контейнера. При удалении объекта (не важно, является ли он объектом верхнего уровня или вложенным объектом) рекурсивно удаляются все его вложенные объекты.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Удаление объекта верхнего уровня
// Вместе со справочником будут удалены также все его реквизиты, табличные части, реквизиты табличных частей и т.д.
IBmTransaction transaction1 = engine.beginReadWriteTransaction();

Catalog catalog = (Catalog)transaction1.getTopObjectByFqn("Catalog.Goods");
transaction1.detachTopObject(catalog);

transaction1.commit();

// Удаление вложенного объекта
// Реквизит удаляется путем удаления из containment-коллекции "attributes"
IBmTransaction transaction2 = engine.beginReadWriteTransaction();

Catalog catalog = (Catalog)transaction2.getTopObjectByFqn("Catalog.Goods");
for (Iterator<CatalogAttribute> it = catalog.getAttributes().iterator(); it.hasNext();)
{
    CatalogAttribute attribute = it.next();
    if (attribute.getName().equals("Attribute5"))
    {
        it.remove();
    }
}

transaction2.commit();

Блокировки #

В обязанности механизма транзакций входит координация конкурентного доступа к данным хранилища. Для предотвращения неконтролируемого редактирования объектов параллельными транзакциями при получении объекта (А) в транзакции (любым из перечисленных ранее способов) автоматически устанавливается блокировка на всё containment-дерево, которому принадлежит объект А. Другими словами блокируется объект верхнего уровня, соответствующий объекту А, и все его прямые и непрямые дочерние объекты. Таким образом ни одна из параллельно выполняющихся транзакций не может помешать работе с полученным в транзакции объектом.

Снятие блокировок осуществляется по завершении транзакции. Транзакция может быть завершена с помощью методов commit или rollback.

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

Транзакции на чтение (read-only) #

Если предполагается, что в транзакции будет производиться только чтение данных, то имеет смысл создавать транзакцию на чтение. Для этого необходимо вызвать один из методов IBmEngine.beginReadOnlyTransaction. Использование транзакций на чтение обладает следующими преимуществами:

  • Во-первых, при попытке выполнения операции записи будет выброшено исключение, что позволяет на ранней стадии выявить ошибки программирования,
  • Во-вторых, работа в транзакции на чтение немного эффективнее, при этом и дальше будет вестись работа по оптимизации на данном направлении.

Доступ к данным без явного создания транзакций #

Для обеспечения работоспособности старых версий 1C:EDT в BM реализована возможность доступа к данным без явного создания транзакций. Чтобы получить объект таким образом можно воспользоваться одним из двух методов:

  • IBmEngine.getObjectById(long) — для получения любого объекта по числовому идентификатору,
  • IBmEngine.getTopObjectByFqn(String) — для получения объекта верхнего уровня по FQN. На самом деле в этих случаях внутри BM создается “неявная” транзакция, в которой производится получение объекта. Она начинается и фиксируется “прозрачно” для вас.

С работой без явного создания транзакций связано несколько ограничений:

  • Редактирование данных объектов, полученных таким образом, запрещено.
  • Экземпляр, полученный таким образом, содержит снимок состояния объекта BM, в котором объект пребывал на момент получения. Состояние включает в себя значения features, ВМ-свойств, ВМ-бинарных вложений, но не состояние contained-объектов и контейнеров.
  • Снимок состояния включает в себя значения ссылок, но не включает объекты, на которые эти ссылки указывают. Таким образом может возникнуть ситуация, когда у объекта установлена ссылка, но к моменту ее резолвинга целевой объект может быть уже удален. Это не проблема в случае кросс-ссылок, т.к. в этом случае будет просто возвращен прокси-объект. В случае же containment-ссылок возврат прокси-объекта не имеет смысла, возврат null тоже неприемлем, т.к. ссылка установлена, соответственно будет произведен выброс исключения.
  • При резолвинге ссылок объекта, полученного таким образом, внутри BM неявно создается транзакция. Таким образом, при обходе больших графов будет создаваться чрезмерное количество транзакций, что негативно скажется на производительности.

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

Идентификация объектов #

Виды идентификаторов объектов BM #

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

Кроме этого, у всех объектов верхнего уровня есть еще один идентификатор — FQN. «FQN» — не самое удачное название, но оно привычно для разработчиков 1C:EDT. FQN представляет собой обычную строку, которую вы задаете при добавлении объекта в модель. В отличие от внутренних идентификаторов, FQN можно менять.

Ниже представлены методы API BM, связанные с идентификацией объектов.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IBmObject
{
    // Возвращает внутренний идентификатор объекта.
    long bmGetId();
 
    // Возвращает FQN объекта верхнего уровня.
    String bmGetFqn();
}
 
public interface IBmTransaction
{
    // Получает объект по внутреннему идентификатору.
    IBmObject getObjectById(long id);
 
    // Получает объект верхнего уровня по FQN.
    IBmObject getTopObjectByFqn(String fqn);
 
    // Добавляет объект верхнего уровня с указанным FQN в модель.
    void attachTopObject(IBmObject object, String fqn);

    // Обновляет FQN объекта верхнего уровня.
    void updateTopObjectFqn(IBmObject object, String fqn);
}

URI #

Помимо числового идентификатора и FQN у объектов BM есть еще URI. URI не являются полноценными идентификаторами и для этого есть две причины:

  • Во-первых, URI не являются полностью самостоятельными, т.к. генерируются на базе FQN соответствующего объекта верхнего уровня,
  • Во-вторых, URI не предназначены для активного использования вами с целью получения объектов, хотя такая возможность есть, см. метод getObjectByUri(URI) на интерфейсе IBmTransaction. В первую очередь URI необходимы для кодирования кросс-ссылок при сохранении объектов в основном хранилище. Кросс-ссылки это все ссылки, не помеченные как containment.

URI объектов BM строятся по общим правилам построения URI в EMF, т.е. URI объекта формируется путем добавления фрагмента к URI ресурса, в котором этот объект расположен.

С ресурсами в BM связана одна особенность. BM не оперирует понятиями ресурсов, а оперирует только объектами. С связи с этим в BM ресурсы являются некоторой синтетической сущностью, которая введена лишь для обеспечения минимально необходимой совместимости с EMF. Ресурс BM связан один-к-одному с соответствующим объектом верхнего уровня. В терминах EMF можно сказать, что при добавлении в модель объекта верхнего уровня BM автоматически создает для этого объекта ресурс и размещает объект в этом ресурсе. При этом вы не можете каким-либо образом повлиять на то, в каком ресурсе будет размещен объект BM.

URI ресурса BM строится по следующему шаблону:

bm://<идентификатор модели>/<FQN соответствующего объекта верхнего уровня>

Фрагменты URI формируются по следующим правилам:

  1. Фрагмент URI объекта верхнего уровня всегда содержит один символ - «/»,
  2. Фрагмент URI вложенного объекта формируется из фрагмента контейнера и сегмента, однозначно идентифицирующего вложенный объект в контейнере, разделенных символом «/». Если контейнер является объектом верхнего уровня, то разделитель не добавляется.
  3. Сегмент фрагмента вложенного объекта содержит:
    • имя feature контейнера, в которой содержится данный объект, если feature неколлекционная,
    • имя feature и идентификатор объекта внутри коллекции, разделенные двоеточием, в случае, если feature коллекционная,
  4. Идентификатором объекта внутри коллекции является строковое представление значения идентифицирующей feature объекта,
  5. Поиск идентифицирующей feature объекта производится по следующему алгоритму:
    • Проверяется, помечена ли коллекция аннотацией http://www.1c.ru/v8/bm/CollectionItemId. Если да, то идентифицирующей feature является feature объекта, имя которой указано в аннотации в атрибуте feature,
    • Если нет, то проверяется, являются ли элементы коллекции экземплярами класса java.util.Map.Entry. Если да, то идентифицирующей feature является feature с именем key,
    • Если нет, то проверяется, содержит ли объект feature с именем name. Если да, то она считается идентифицирующей,
    • Если нет, то считается, что у объекта нет идентифицирующей feature и URI для этого объекта построена быть не может.

В отличие от EMF, URI вложенных объектов BM содержат дополнительный контекст — строку, расположенную в query-части URI. Контекст представляет собой набор строковых атрибутов разделенных точкой с запятой. Первый атрибут присутствует всегда и содержит имя EMF-класса целевого объекта. Необходимость включения дополнительных атрибутов и их содержимое определяется расширениями механизма генерации URI, поставляемыми прикладными решениями (см. интерфейс com._1c.g5.v8.bm.core.IBmUriBuildContributor). Забегая вперед скажем, что при резолвинге URI контекст полностью игнорируется. Данная информация используется только для получения информации о целевом объекте, в случае, если сам объект недоступен. В частности это используется при сериализации ссылок на неподдерживаемые объекты во время экспорта в XML.

В случае возникновения необходимости построения URI объекта BM в прикладном коде (например, для создания прокси-объекта) крайне нежелательно производить построение URI вручную. Для этих целей стоит пользоваться методами, предоставляемыми утилитарным классом com._1c.g5.v8.bm.core.BmUriUtil.

Ниже представлены примеры URI объектов BM:

bm://v8_demo_conf/AccumulationRegister.Взаиморасчеты#/
bm://v8_demo_conf/Catalog.Банки#/
bm://v8_demo_conf/Catalog.Контрагенты.Form.ФормаВыбора.Form#/
bm://v8_demo_conf/Catalog.ВходящиеПисьма?CatalogAttribute#/attributes:Отправитель
bm://v8_demo_conf/Catalog.ВходящиеПисьма?Type#/producedTypes/objectType/type
bm://v8_demo_conf/Configuration?Language#/languages:Русский

Отслеживание обратных ссылок #

BM предоставляет возможность получения обратных ссылок, то есть всех не-containment-ссылок, указывающих на заданный объект. Для получения обратных ссылок в API BM имеется два метода:

  • IBmObject.bmGetReferences(),
  • IBmEngine.getBackReferences(IBmObject object).

Эти методы не эквивалентны. Первый метод возвращает ссылки из объектов, принадлежащих тому же «движку», что и объект, на котором производится вызов метода. Второй же возвращает ссылки из объектов, принадлежащих «движку», на котором производится вызов. При этом объект, передаваемый в качестве параметра, не обязательно должен принадлежать этому «движку». Эта функциональность была реализована для обеспечения кросс-проектного рефакторинга. Например, когда при переименовании объекта конфигурации должны быть обновлены ссылки на этот объект не только внутри этой конфигурации, но также и во внешних обработках и отчетах.

Также стоит отметить, что не все ссылки отслеживаются. Во-первых, не отслеживаются «прямые» ссылки, то есть ссылки, закодированные не посредством URI целевого объекта, а посредством числового идентификатора. Во-вторых, в BM есть возможность фильтрации ссылок. В случае, если пользователю BM заранее известно, что для определенного класса объектов нет необходимости в отслеживании обратных ссылок, пользователь может реализовать интерфейс com._1c.g5.v8.bm.core.IBmCrossReferenceFilter и передать его экземпляр в метод IBmEngineFactory.create, что позволит снизить нагрузку на механизм отслеживания обратных ссылок.

Резолвинг внешних объектов #

В BM существует возможность установки объектам ссылок на EMF-объекты, не хранящиеся в BM, или же на объекты, хранящиеся в других BM, а также возможность прозрачного резолвинга этих ссылок. Наличие этой возможности обеспечивает практически бесшовное взаимодействие с платформенными объектами, BSL-модулями и тому подобными сущностями.

Основные моменты, которые необходимо понимать при работе со ссылками на внешние объекты, заключаются в следующем:

  • Когда устанавливается ссылка на внешний объект, в хранилище BM она кодируется с помощью URI целевого объекта. URI целевого объекта возвращается методом EcoreUtil.getURI(EObject). Чтобы этот метод отработал корректно, целевой объект должен быть привязан к ресурсу и должна быть обеспечена возможность построения фрагмента для этого объекта в ресурсе,
  • При резолвинге ссылки на внешний объект сначала производится получение URI целевого объекта. Затем по URI выбирается подходящий экземпляр com._1c.g5.v8.bm.core.IBmExternalUriResolver (см. метод IBmExternalUriResolver.support(URI)). После этого резолвинг целевого объекта делегируется методу IBmExternalUriResolver.getObject(URI). Поэтому для обеспечения возможности резолвинга внешних объектов должен быть реализован интерфейс IBmExternalUriResolver и его экземпляр должен быть передан в метод IBmEngineFactory.create при создании «движка».

Поддержка EMF API #

BM не является полноценной реализацией EMF API. BM — это высокопроизводительное транзакционное объектное хранилище. На определенном уровне оно поддерживает совместимость с EMF с целью обеспечения более или менее безболезненной миграции на BM решений, использующих EMF. Поэтому EMF API поддерживается лишь на уровне, минимально необходимом для функционирования предыдущих версий 1C:EDT.

Далее перечислены основные аспекты, затрагивающие вопрос совместимости BM с EMF.

Ресурсы #

BM не оперирует понятиями ресурсов, а оперирует только объектами. При этом, если у объекта, подключенного к BM, вызвать метод eResource, то будет возвращена некоторый синтетический экземпляр com._1c.g5.v8.bm.core.internal.BmResource, у которого в коллекции contents размещен лишь только соответствующий объект верхнего уровня. В первую очередь метод eResource был реализован для обеспечения корректной работы утилитарного метода EcoreUtil.getURI, который внутри производит конкатенацию URI ресурса с фрагментом.

Основные особенности реализации BmResource:

  • getContents возвращает немодифицируемый список, содержащий только один объект верхнего уровня,
  • getURI возвращает URI со схемой bm и FQN соответствующего объекта верхнего уровня в иерархической части,
  • getResourceSet возвращает синтетический экземпляр com._1c.g5.v8.bm.internal.resources.BmResourceSet,
  • getURIFragment реализован без каких-либо особенностей,
  • getEObject реализован без каких-либо особенностей,
  • Все остальные методы не поддерживаются и выбрасывают исключение, либо, если было замечено, что они вызываются в коде 1C:EDT или Xtext, то не делают ничего и производят возврат значений null, false, emptyList и т. п. в зависимости от ситуации.

Особенности реализации BmResourceSet.

  • BmResourceSet создается на каждую транзакцию, и также есть один глобальный экземпляр, в котором размещаются ресурсы объектов, не привязанные к транзакции,
  • Ресурс объекта BM не может быть перепривязан к другому ресурс-сету путем добавления в коллекцию resources,
  • Коллекция resources не содержит ресурсы BM, а содержит только те ресурсы, которые были туда добавлены вами. Это было реализовано для поддержки предыдущих решений по внутреннему экспорту для обеспечения возможности резолвинга ссылок на объекты BM из объектов, не хранящихся в BM.
  • Остальные методы не реализованы, либо наследуют дефолтные реализации ResourceSetImpl, либо оставлены пустыми.

В заключение следует сказать, что при разработке механизмов на базе BM не следует пользоваться ресурсами и ресурс-сетами, так как, во-первых, как уже говорилось, данная функциональность была реализована на минимальном уровне лишь для обеспечения работоспособности существующих legacy-механизмов, соответственно, ее развитие не планируется/ Во-вторых, работа через ресурсы и ресурс-сеты существенно менее эффективна с точки зрения производительности, чем работа с API BM.

Транзиентные features #

BM игнорирует модификатор transient, таким образом features, помеченные данным модификатором, все равно записываются в хранилище.