Сегодня детально расскажу про сердце JMatrixPlatform - статусно-ролевой доступ к данным. Это основа платформы, доступная сразу "из коробки", которая реализует продвинутый RBAC с привязкой прав к статусам объектов. Вы не найдёте в общем доступе внятного и современного описания такой методологии, тем более с примерами реализации "из коробки", а это означает, что сегодня очередной эпизод погружения в Области тьмы ИТ, куда не заглядывают модные фреймворки.

Тип, жизненные циклы и статусы

Любой объект в JMatrixPlatform имеет два обязательных свойства: тип (например, «Документ») и жизненный цикл - предопределённая последовательность статусов, отражающих текущее состояние объекта. Жизненный цикл, как и тип, указывается при создании объекта и может быть заменён позднее. У одного типа может быть несколько различных жизненных циклов, подстроенных под конкретный бизнес-процесс. При создании объекта, статус выставляется автоматически - берётся первым из указанного жизненного цикла, если это не переопределено в @Override методе createAction.

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

| Объект_Версия | Тип       | Жизненный цикл | Текущий статус | Возможные статусы |
|---------------|-----------|----------------|----------------|-------------------|
| Объект_1      | Тип1      | ЖЦ1            | Статус1        | Статус1*          |
|               |           |                |                | Статус2           |
|---------------|-----------|----------------|----------------|-------------------|
| Объект_2      | Тип1      | ЖЦ2            | Статус4        | Статус1           |
|               |           |                |                | Статус2           |
|               |           |                |                | Статус3           |
|               |           |                |                | Статус4*          |
|---------------|-----------|----------------|----------------|-------------------|
| Объект_3      | Тип1      | ЖЦ3            | Статус2        | Статус1           |
|               |           |                |                | Статус2*          |
|               |           |                |                | Статус3           |

Типичный жизненный цикл комплекта рабочей конструкторской документации:

Черновик -> Опубликован -> Согласование -> Архив -> Выдан в производство

Или справочного элемента:

Новый -> Активный -> Устаревший

Смена статуса объекта - это не просто замена текущего значения новым, а полноценный процесс "повышения" и "понижения", с фиксацией кто и когда менял статус объекта. Процесс повышения статуса называется promote, а понижения - demote (терминология заимствована из Enovia для преемственности) и выполняются соответствующими методами.

И самое главное - статусы используются для разграничения доступа к данным, например:

Роль/Статус        | Новый       | Активный                 | Устаревший
-------------------|-------------|--------------------------|---------------
ARLReferenceAdmin  | ALL         | READ, PROMOTE, DEMOTE    | READ, DEMOTE
JPublic            | #NOACCESS   | READ                     | READ
Скрытый текст

Сделали справочные элементы без статусов? Молодцы! А что делать, когда некоторые устареют, как их убрать из выбора в UI? Удалить? А что делать с историческими данными? Может дописать префикс "DELETED_" и затем в инструкции указать, что с таким префиксом нельзя использовать данные? Привет SAP!

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

Конфигурация платформы

Для начала определяемся с ролями. Наследуемся от JRole, и IDE сама попросит реализовать все обязательные @Override методы:

@JModelPart
public final class ARLReferenceAdmin extends JRole {
  public final static ARLReferenceAdmin ROLE = new ARLReferenceAdmin();

  //наследование ролей
  @Override
  public Set<JRole> getParents() {
    return Set.of(
      ARLRefDesigner.ROLE,
      ARLRefChangeManagementAdmin.ROLE,
      ARLRefApprover.ROLE);
  }

  @Override
  public String getDescription() {
    return "Администратор справочников";
  }
}

Для жизненного цикла, наследуемся от JPolicy:

@JModelPart
public final class ALCReference extends JPolicy {
  public final static ALCReference POLICY = new ALCReference();

  //указываем список доступных типов для этого жизненного цикла
  @Override
  public Set<JType> getTypes() {
    return Set.of(ATPReference.TYPE);
  }
  
  //указываем список статусов
  @Override
  public List<JState> getStates() {
    return List.of(
      New.STATE, 
      Active.STATE, 
      Inactive.STATE);
  }

  //статусы реализуются inner классами, так удобнее
  
  //ARLReferenceAdmin.ROLE - кастомная роль администратора справочника
  //JPublic.ROLE - системная публичная роль (доступ всем)
  //JOwner.ROLE - системная роль владельца объекта (т.е. тот кто создал объект)
  @JModelPart
  public static final class New extends JPolicy.JState {
    public static final New STATE = new New();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(ARLReferenceAdmin.ROLE, AccessType.ALL));
    }
  }

  @JModelPart
  public static final class Active extends JPolicy.JState {
    public static final Active STATE = new Active();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(JPublic.ROLE, AccessType.READ),
        new RoleAccess(ARLReferenceAdmin.ROLE, AccessType.PROMOTE, AccessType.DEMOTE));
    }
  }

  @JModelPart
  public static final class Inactive extends JPolicy.JState {
    public static final Inactive STATE = new Inactive();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(JPublic.ROLE, AccessType.READ),
        new RoleAccess(ARLReferenceAdmin.ROLE, AccessType.DEMOTE));
    }
  }
}

Расшифровка AccessType:

  public enum AccessType {
    ALL,//полный контроль
    READ,//чтение
    CREATE,//создание
    MODIFY,//модификация
    DELETE,//удаление
    RELEASE,//создание версии объекта
    PROMOTE,//"повысить" статус
    DEMOTE,//"понизить" статус
    CHECKIN,//загрузка файлов
    CHECKOUT//выгрузка файлов
  }

Ну и теперь при создании нового объекта, обязательно указывается тип и жизненный цикл нового объекта:

JDomainObject handbookElement = new JDomainObject();
handbookElement.create(ctx, ATPDocuments.TYPE, ALCDocument.POLICY);

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

Чтобы менять статус, доступны два метода:

JDomainObject object = new JDomainObject(oid);
//двинет статус "вправо" на один шаг
object.promote(ctx);
//двинет статус "влево" на один шаг
//и сбросит state_start и state_end текущего статуса
//как-будто ничего не было
object.demote(ctx);

//demote - это в основном административная опция,
//чтобы "откатить" назад, по заявке
//остальные пользователи должны двигать только вперёд
Скрытый текст

Естественно вся информация о движении статусов сохраняется в соответствующей отдельной таблице:

create table object_states (
    object_id uuid not null,
    policy_state varchar(100) not null,--имя статуса
    user_id uuid not null,--пользователь сменивший статус
    state_start timestamp not null,--дата установки статуса
    state_end timestamp--дата выхода из текущего статуса (promote)
);

Это даёт возможность проводить аудит изменений статусов и вычислять, например, слишком долгое согласование документа у конкретного ФИО, например:

SELECT user_id FROM object_states
WHERE policy_state = ? AND (state_end - state_start) >= ?

А затем передавать список специально обученным людям, курирующим процессы выпуска документации, которые проведут корректирующую беседу с "провинившимися" пользователями.

Аутентификация, авторизация, доступы

Keycloak - стандарт, скучно, но надёжно. Все роли, которые вы объявили в JMatrixPlatform, автоматически «улетают» в Keycloak и становятся доступными для назначения. Руками или через AD - не важно. Интересное дальше.

Роли пользователя пакуются в JWT, платформа вытаскивает их из каждого запроса и - прямо на уровне построения SQL добавляет условия фильтрации данных, на основе обработки статусно-ролевых настроек доступа.

Данные не покидают базу, если пользователь не имеет прав. Это не прослойка в коде или в контроллере. Это фильтрация на самом низком уровне. Т.е. все "лишние" данные отсекаются ещё до того, как покинут БД! Это ключевой механизм "безопасности" JMatrixPlatform. И не важно, есть ли у пользователя доступ к UI или контроллеру. При отсутствии нужных ролей, пользователь всё ровно ничего не увидит, не сможет прочитать или изменить.

Максимальный уровень доступа

Это самый быстрый и эффективный доступ к данным. Потому что ничего не проверяется. Вообще ничего. Если пользователю назначена роль JSystemAdmin, то всё что есть в БД отдаётся пользователю без сложный "утомительных" проверок.

PS Думаю тут понятно, что во всех остальных случаях, для обычных пользователей, проверка доступа в любом случае будет значительно медленнее, чем для системного администратора. И сравнивать скорость открытия таблицы на 10тыс. документов под JSystemAdmin и под обычным пользователем - некорректно, для администратора данные извлекутся гораздо быстрее.

Простой статусный доступ

Это классическая схема проверки доступа в PLM, который используется чаще всего. Здесь просто проверяется наличие доступа к конкретным статусам. И это самый эффективный и быстрый способ организации разграничения доступа.

Продублирую табличку, для наглядности:

Role/State          | New        | Active                   | Inactive
--------------------|------------|--------------------------|-------------------
ARLReferenceAdmin   | ALL        | READ, PROMOTE, DEMOTE    | READ, DEMOTE
JPublic             | #NOACCESS  | READ                     | READ

В этом случае, JMatrixPlatform автоматически ограничивает доступ пользователя с ролью JPublic.ROLE следующим способом:

WHERE ... AND policy_state IN (:state1, :state2)

-- где :state1 = 'ALCReference.Active'
-- где :state2 = 'ALCReference.Inactive'

А при наличии ARLReferenceAdmin.ROLE:

WHERE ... AND policy_state IN (:state1, :state2, :state3)

-- где :state1 = 'ALCReference.New'
-- где :state2 = 'ALCReference.Active'
-- где :state3 = 'ALCReference.Inactive'

PS Да, теоретически может возникнуть ситуация, что количество статусов в IN достигнет условно 1тыс.штук. Это актуально для fullTextSearch поиска, когда заранее неизвестен искомый тип и тогда нужно перебрать абсолютно все статусы. Но редко бывает, что у пользователя есть абсолютно все роли, поэтому в общем случае, количество статусов не превысит 100шт. В будущем планируется предусмотреть возможность использования временной таблицы статусов для фильтрации. Но пока в этом нет необходимости.

Доступ с фильтрацией

#Находится в стадии разработки

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

Функционал хэштегов уже встроен "в коробку" JMatrixPlatform и как раз призван обеспечить максимально быструю фильтрацию данных, с одинаковой сложностью, особенно в случае различных свойств связанных объектов. Хэштеги назначаются любому объекту или связи, вручную или автоматически, по @Override событиям:

JDomainObject object = new JDomainObject(oid);
object.getTags(ctx);
object.setTags(ctx, JDomainTags.of("tag1", "tag2"));
object.addTags(ctx, JDomainTags.of("tag3", "tag4"));
object.removeTags(ctx, JDomainTags.of("tag3", "tag4"));
object.clearTags(ctx);

Пример настройки статуса с проверкой тегов:

  @JModelPart
  public static final class Published extends JPolicy.JState {
    public static final Published STATE = new Published();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(Designer.ROLE, JDomainTags.of("immediate_check"), AccessType.READ));
    }
  }

JMatrixPlatform автоматически фильтрует данные следующим способом:

WHERE ... AND
  (
    policy_state IN (:state1, :state2)
    AND id IN (
      SELECT data_id FROM tags WHERE tag IN (:tag1)
    )
  )

-- где :state1 = 'ALCReference.Active'
-- где :state2 = 'ALCReference.Inactive'
-- где :tag1 = 'immediate_check'

-- ну или EXISTS вместо IN

Доступ с проверкой владельца

Применяется для ограниченного доступа к объектам, которые должны быть видны только владельцу (автору) и больше никому, пока объект не будет "опубликован для всех":

Role/State          | New         | Active                | Inactive
--------------------|-------------|-----------------------|-------------------
JOwner              | ALL         | READ                  | READ

JMatrixPlatform автоматически фильтрует данные следующим способом:

WHERE ... AND
  (
    policy_state IN (:state1, :state2)
       AND owner_id=:owner_id
  )

-- где :state1 = 'ALCReference.Active'
-- где :state2 = 'ALCReference.Inactive'
-- где :owner_id = ctx.getId()

Доступ с проверкой организации/проекта

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

Например, есть несколько проектов, внутри которых глубокая структура вложенности. Если пользователь добавлен в проект, то ему автоматически открывается доступ ко всем вложенным папкам и документам.

structure                | project_id 
-------------------------|------------
Проект1                  | prj_oid1   
├── Папка1               | prj_oid1   
│   ├── Папка2           | prj_oid1   
│   │   ├── Документ1    | prj_oid1   
│   │   └── Документ2    | prj_oid1   
...
Проект2                  | prj_oid2   
├── Папка3               | prj_oid2   
│   ├── Папка4           | prj_oid2   
│   │   ├── Документ3    | prj_oid2   
│   │   └── Документ4    | prj_oid2  
...

Важное условие - у всех вложенных объектов должен быть проставлен соответствующий oid верхнеуровнего проекта (это реализуется автоматически @Override событием createAction при создании связей между объектами общей структуры):

JDomainObject object = new JDomainObject(oid);
object.setProject(ctx, project_uuid);
object.setOrganization(ctx, org_uuid);

В конфигурации JState это выглядит так:

  @JModelPart
  public static final class Published extends JPolicy.JState {
    public static final Published STATE = new Published();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(JPublic.ROLE, true :projectCheck, false :orgCheck, AccessType.READ));
    }
  }

JMatrixPlatform автоматически фильтрует данные следующим способом:

WHERE ... AND
  (
    policy_state IN (:state1)
      AND project_id IN (:ctx_project_list)
  )

-- где :state1 = 'ALCReference.Active'
-- где :ctx_project_list = ctx.getProjects()

Тоже самое и в случае проверки организаций, к которым относится пользователь:

WHERE ... AND
  (
    policy_state IN (:state1)
      AND organization_id IN (:ctx_organization_list)
  )

-- где :state1 = 'ALCReference.Active'
-- где :ctx_organization_list = ctx.getOrganizations()

Ну и если нужна проверка проекта и организации одновременно:

WHERE ... AND
  (
    policy_state IN (:state1)
      AND project_id IN (:ctx_project_list)
      AND organization_id IN (:ctx_organization_list)
  )

-- где :state1 = 'ALCReference.Active'
-- где :ctx_project_list = ctx.getProjects()
-- где :ctx_organization_list = ctx.getOrganizations()

Доступ по ACL

#Находится в стадии разработки

Это уже тяжёлая артиллерия. Самый сложный и неэффективный механизм проверки доступа. Используется для особо "упоротых" требований, чтобы пользователь видел только внутреннюю структуру, причём в зависимости от роли, которая выдана на конкретном уровне, например:

structure                                    | пользователь | роль
---------------------------------------------|--------------|----------------
Проект1                                      |              |
├── Папка1                                   | User1        | ProjectReader
│   ├── Папка2                               |              |      
│   │   ├── Документ1                        |              |      
│   │   └── Документ2                        |              |      
│   └── Папка3                               |              |      
│       ├── Документ3                        |              |      
│       └── Документ4                        |              |      
├── Папка4                                   | User1        | ProjectDesigner
│   └── Документ5                            |              |      

Я лично не очень одобряю этот вариант разделения доступов и всегда пытаюсь отговорить заказчиков от его реализации, потому что он требует дополнительную структуру хранения и серьёзных вычислительных ресурсов. Предупреждать заказчика о последствиях для производительности всей системы - занятие бесполезное, требования "хочу и всё тут" никуда не денутся. А вот времена, когда можно было просто накинуть на сервер ресурсов и забыть о проблеме, - уже давно прошли. Стоимость ресурсов выросла значительно.

Чтобы ACL хоть как-то мог обеспечить приемлемый отклик, требуется серьёзная денормализация данных, с последующим поддержанием их консистентности с root записью. Иначе можно вообще "похоронить" сервер, если каждый раз запускать рекурсию, для определения верхнеуровневых прав. При особо "агрессивном" использовании этого подхода, ACL может легко распухнуть на несколько миллиардов записей, при общем скромном объёме основного контента в 50млн. объектов. А это означает, что потребуется постоянный мониторинг БД в части кэширования и партиционирования ACL-таблицы, которая может легко превысить доступную оперативную память, и БД начнёт активно использовать дисковые операции. А это гарантированное падение производительности всей системы.

Примерно так выглядит структура хранения ACL:

create table access_vectors (
    object_id uuid not null,
    owner_id uuid not null,
    user_id uuid not null,
    role varchar(100) not null,
    root boolean not null,--признак корневой записи для последующей денормализации
    originated timestamp--по этой дате фильтруются root записи для денормализации
);

create table access_vector_parents (
    object_id uuid not null,
    parent_object_id uuid not null,
    owner_id uuid not null,
    originated timestamp
);

Так выглядит конфигурация статуса с проверкой ACL:

  @JModelPart
  public static final class Published extends JPolicy.JState {
    public static final Published STATE = new Published();

    @Override
    public Set<RoleAccess> getAccess() {
      return Set.of(
        new RoleAccess(ProjectReader.ROLE, true :vectorCheck, AccessType.READ),
        new RoleAccess(ProjectDesigner.ROLE, true :vectorCheck, AccessType.ALL));
    }
  }

JMatrixPlatform автоматически фильтрует данные следующим способом:

WHERE ... AND
  (
    policy_state IN (:state1)
      AND id IN (
        SELECT object_id
        FROM access_vectors
        WHERE user_id=:ctx AND role IN (:role1, :role2)
      )
  )

-- где :state1 = 'ALCReference.Active'
-- где :ctx = ctx.getId()
-- где :role1 = ProjectReader
-- где :role2 = ProjectDesigner

-- ну или EXISTS вместо IN

Комбинация доступов

А что если в статусе будут перечислены взаимоисключающие настройки ролей? Что ж, ситуация весьма не редкая, когда в процессе кастомизации в статус бесконечно накидывают новые настройки, а старые не вычищают, типичный пример:

   |role |state |access|byOwner|byProject|byOrganization|byVector|TOTAL|
   |-----+------+------+-------+---------+--------------+--------+-----|
   |role1|state1|READ  |1      |0        |0             |0       |1    |
   |role2|state1|READ  |0      |1        |1             |0       |2    |
-->|role3|state1|READ  |0      |0        |0             |0       |0    |<--
   |role4|state1|READ  |0      |0        |0             |4       |4    |
   |role5|state1|READ  |0      |3        |0             |0       |3    |
   |role6|state1|READ  |0      |0        |3             |0       |3    |
   |-----+------+------+-------+---------+--------------+--------+-----|
-->|role6|state2|READ  |0      |1        |1             |0       |2    |<--
   |role7|state2|READ  |0      |0        |0             |4       |4    |
   |role8|state2|READ  |0      |3        |0             |0       |3    |
   |role9|state2|READ  |0      |0        |3             |0       |3    |

При условии, что у пользователя назначены все 9 ролей, JMatrixPlatform перед тем как сформировать итоговый фильтр для sql, рассчитает "сложность" для каждой роли и сформирует фильтр для самой "минимальной" сложности. Т.е. нет смысла добавлять в фильтр условия для owner, project, organization, если есть роль, которая даёт READ-доступ без ограничений.

  public abstract static class JState extends JModel implements IJTriggerState {
    public abstract Set<RoleAccess> getAccess();

    //утилита для расчёта минимальной сложности доступа
    List<DifficultyAccess> getMinDifficultyAccess(JContext ctx, AccessType accessType) {
      List<DifficultyAccess> result = new ArrayList<>();

      List<DifficultyAccess> difficultyAccessesList = getAccess()
          .stream()
          .filter(access -> access.hasAccess(ctx, accessType))
          .map(access -> new DifficultyAccess(this, access))
          .toList();

      if (difficultyAccessesList.isEmpty())
        return result;

      int mins = difficultyAccessesList
          .stream()
          .min(Comparator.comparing(DifficultyAccess::getDifficulty))
          .map(DifficultyAccess::getDifficulty)
          .orElseThrow();

      return difficultyAccessesList.stream()
          .filter(da -> da.getDifficulty() == mins)
          .collect(Collectors.toList());
    }

    @Getter
    static final class DifficultyAccess {
      private final JState state;
      private final RoleAccess roleAccess;
      //фильтруется только статус
      private int difficulty = 0;
  
      private DifficultyAccess(JState state, RoleAccess roleAccess) {
        this.state = state;
        this.roleAccess = roleAccess;
  
        if (roleAccess.isByOwner()) {
          //фильтруется только ctx.getId()
          difficulty = difficulty + 1;
        }
        if (roleAccess.isByProject() && roleAccess.isByOrganization()) {
          //фильтруется ctx.getProjects() и ctx.getOrganizations() одновременно
          difficulty = difficulty + 2;
        } else {
          if (roleAccess.isByProject() || roleAccess.isByOrganization()) {
            //фильтруется ctx.getProjects() ИЛИ ctx.getOrganizations(),
            difficulty = difficulty + 3;
          }
        }
        if (roleAccess.isByVector()) {
          //максимальная сложность с сопоставлениме ACL таблицы
          //содержащей "миллиард" записей
          difficulty = difficulty + 4;
        }
      }
  
      @Override
      public int hashCode() {
        return Objects.hash(
            state.getName(),
            roleAccess,
            difficulty);
      }
  
      @Override
      public boolean equals(Object o) {
        if (this == o)
          return true;
        if (o == null || getClass() != o.getClass())
          return false;
        DifficultyAccess that = (DifficultyAccess) o;
        return difficulty == that.difficulty &&
            Objects.equals(state, that.state) &&
            Objects.equals(roleAccess, that.roleAccess);
      }
    }
  }

Нужно больше статусов

Отличительная черта JMatrixPlatform - у каждого объекта помимо текущего статуса основного жизненного цикла предусмотрен "внешний" статус, который расширяет информацию о состоянии текущего объекта.

Например, в классическом PLM очень часто используется похожий жизненный цикл:

Черновик -> ... -> Согласование -> ... -> Архив

В статусе Согласование обычно запускается внешний процесс согласования, например через дополнительный модуль или вообще внешний сервис BPMN (Camunda), где могут легко отклонить документ или попросить какие-либо пояснения. Конечно же в этом случае, пользователю придёт соответствующее уведомление. Но чтобы показать пользователю результат "внешнего" согласования приходится обращаться за информацией к этим внешним сервисам, и такой запрос может быть весьма неэффективным и медленным, особенно для таблицы из тысячи документов:

Документ        | Статус            | Внешний статус
----------------|-------------------|--------------------
Документ1       | На согласовании   | Отклонён
Документ2       | На согласовании   | На рассмотрении
Документ3       | На согласовании   | Утверждение ГИП
Документ4       | На согласовании   | Согласован
Документ5       | На согласовании   | Возврат на доработку
Документ6       | На согласовании   | Утверждение экспертизы

Теперь любой внутренний модуль может легко транслировать свой статус рассмотрения непосредственно в объект:

JDomainObject object = new JDomainObject(oid);
object.setExternalState(ctx, externalState);

А внешняя система может сделать это через соответствующий POST запрос:

/jm-api/objects/${OID}/setExternalState/${EXTERNAL_STATE_NAME}

Это даёт возможность сразу отображать внешний статус объекта в UI, а также при необходимости легко фильтровать по нему.

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

Программные проверки доступа

Всё перечисленное в основном касается чтения данных, чтобы эффективно и быстро извлекать их из БД. Но что делать, если нужно проверять доступ, в зависимости от времени суток или от фазы луны? Касаемо чтения - пока такой возможности не предусмотрено. Для целей инжиниринга или промышленного сектора такие требования не особо актуальны. Во всяком случае, я таких не встречал.

Но вот в случае внесения изменений в данные, у JType предусмотрены соответствующие @Override методы (modifyCheck, deleteCheck и т.д.), в которых можно программно реализовать любую проверку:

@JModelPart
public final class ATPDocument extends JType {
  public final static ATPDocument TYPE = new ATPDocument();

  @Override
  public void modifyCheck(JContext ctx, UUID oid, JTriggerChanges changes) {
    JDomainObject object = new JDomainObject(oid);

    if (...) throw new JMatrixException("")
  }
}

Доступы к UI компонентам

Это очередная отличительная черта JMatrixPlatform - как я уже говорил, в прошлых статьях, кастомизация платформы заставляет раскладывать весь процесс на маленькие детали, вплоть до "кнопочек", соответственно внутри любого такого компонента можно реализовывать необходимые дополнительные проверки.

Например так выглядит кнопка редактирования формы. Она отображается только если у пользователя есть доступ к MODIFY текущего объекта:

@JModelPart
public final class JCDObjectFormEnableEdit extends JCommand {
  public static JCDObjectFormEnableEdit COMMAND = new JCDObjectFormEnableEdit();

  @Override
  public JSCall getHref(JContext ctx, JRequestParams requestParams) {
    return new JSCall("useForm", "jmxActivateEditMode", JSettings.empty());
  }

  //выполняется перед тем как отдать кнопку в UI
  @Override
  public boolean requiredShow(JContext ctx, JRequestParams requestParams) {
    JDomainObject object = new JDomainObject(requestParams.getRequiredOID());
    return object.checkAccess(ctx, AccessType.MODIFY);
  }

  @Override
  public String getLabel(JContext ctx, JRequestParams requestParams) {
    return ctx.getMessage("mx.Common.Label.EnableEdit");
  }

  @Override
  public String getIcon(JContext ctx, JRequestParams requestParams) {
    return "../images/jmxFormEdit.svg";
  }
}

Ну и естественно можно проверять наличие ролей:

  @Override
  public boolean requiredShow(JContext ctx, JRequestParams requestParams) {
    return ctx.isSystemAdmin() || ctx.containsRole(ARLReferenceAdmin.ROLE);
  }

Заключение

Как видите JMatrixPlatform использует "продвинутую" статусно-ролевую модель, которая даёт возможность достаточно просто и эффективно управлять доступом. Данные фильтруются ещё до того как покинут БД, с вполне понятной и предсказуемой производительностью. Без сюрпризов. А если всё-таки нужна проверка по фазе луны - @Override check* методы к вашим услугам. Хоть погоду за окном проверяйте. Я не осуждаю. Но и не приветствую :)

PS

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

Недавно вышло интересное мнение Тест для «сеньора»: в каком типе данных хранить номер паспорта?, где автор на собеседованиях задавал такой вопрос и по ответам разделял "инженеров и «операторов фреймворков»". И я полностью с ним согласен и ещё бы докинул сверху вопросы про наличие статусов у справочных элементов и их вариантов использования. Также бы спросил почему не предусмотрены такие колонки, как author_id, created, modified, которые, по моему мнению, должны быть почти в каждой таблице. Хорошо, что у меня нет "опции" проведения собеседований, а то никто бы наверно не получил плюсик :)