6.2.5.1. 约束

Constraints - 约束 可以配置 行级访问控制,即能管理对于数据特定行的访问。与影响整个实体类别的权限许可不同,约束至影响特定的实体实例。约束可以设置增删查改(CRUD)操作,所以在加载或者禁用实体实例操作的时候,框架会过滤掉某些符合约束配置的实体。另外,也可以添加自定义约束,不限于 CRUD 操作。

从用户自身的访问组上的约束开始,直到其访问组层级上的所有访问组约束都会对该用户生效。所以,当用户所在访问组树形层级越低,给用户配置的约束会越多。

所有客户端层通过标准 DataManager 发起的操作都会触发约束检查。如果实体不满足约束,添加、更改或删除的时候会抛出 RowLevelSecurityException 异常。参阅 数据访问检查 章节了解框架中不同机制如何使用安全约束。

约束有两种类型:在数据库检查约束和内存中检查的约束。

  1. 对在数据库检查的约束,其条件通过 JPQL 子句设置。设置以后会被追加到查询语句之后,这样不满足条件的结果在数据库级别会被过滤掉。数据库检查的约束只能用到查询操作中,并且只影响加载的对象关系图中的根节点实体。

  2. 对在内存检查的约束,其条件通过 Java 代码(如果约束是设计时定义)或 Groovy 表达式(如果约束是运行时定义)设置。这类表达式执行在对象图中的每个实体上,当不满足条件时,数据会被从对象图中过滤掉。

设计时定义约束

约束可以在一个继承了 AnnotatedAccessGroupDefinition 的类中定义,那个类用于定义 访问组。继承类必须存于 core 模块。下面是一个访问组的示例,为 CustomerOrder 实体定义了几个约束:

@AccessGroup(name = "Sales", parent = RootGroup.class)
public class SalesGroup extends AnnotatedAccessGroupDefinition {

    @JpqlConstraint(target = Customer.class, where = "{E}.grade = 'B'") (1)
    @JpqlConstraint(target = Order.class, where = "{E}.customer.grade = 'B'") (2)
    @Override
    public ConstraintsContainer accessConstraints() {
        return super.accessConstraints();
    }

    @Constraint(operations = {EntityOp.CREATE, EntityOp.READ, EntityOp.UPDATE, EntityOp.DELETE}) (3)
    public boolean customerConstraints(Customer customer) {
        return Grade.BRONZE.equals(customer.getGrade());
    }

    @Constraint(operations = {EntityOp.CREATE, EntityOp.READ, EntityOp.UPDATE, EntityOp.DELETE}) (4)
    public boolean orderConstraints(Order order) {
        return order.getCustomer() != null && Grade.BRONZE.equals(order.getCustomer().getGrade());
    }

    @Constraint(operations = {EntityOp.UPDATE, EntityOp.DELETE}) (5)
    public boolean orderUpdateConstraints(Order order) {
        return order.getAmount().compareTo(new BigDecimal(100)) < 1;
    }
}
1 - 只加载 grade 属性是 B(对应于 Grade.BRONZE 枚举值) 的 customer。
2 - 只加载 grade 属性是 B 的用户的 orders。
3 - 内存约束,从加载的对象图中过滤掉 grade 属性不是 Grade.BRONZE 的 customer。
4 - 内存约束,允许使用 grade == Grade.BRONZE 的 customer 的 orders。
5 - 内存约束,允许修改或删除 amount < 100 的 orders。

编写 JPQL 约束是需要遵从以下规则:

  • {E} 需要作为被加载实体的别名,当执行查询语句时,它会被查询语句中真正使用的别名替代。

  • 以下预定义常量可以用作 JPQL 参数:

    • session$userLogin – 当前用户的 login 登录名,(如果是替代用户 – 则为被替代用户的 login)。

    • session$userId – 当前用户的 ID,(如果是替代用户 – 则为被替代用户的 ID)。

    • session$userGroupId – 当前用户的 group ID,(如果是替代用户 − 则为被替代用户的 group ID)。

    • session$XYZ – 当前用户会话的其它任意属性,将 XYZ 替换为属性名使用。

  • where 属性中的内容会被添加到 where 子句并用 and 连接。不需要显式添加 where 单词,系统会自动添加。

  • join 属性中的内容会被添加到 from 子句,该字段需要用逗号“,”、joinleft join 开头。

运行时定义约束

如需创建约束,打开 Access Groups - 访问组 界面,选择一个需要创建约束的组,然后切换到 Constraints - 约束 标签页。约束编辑界面带有 Constraint Wizard - 约束向导,能帮助用实体属性创建简单的 JPQL 和 Groovy 表达式。当选择 Custom - 自定义 作为操作类型时,需要填写 Code - 代码 字段,设置一个用于标记该约束的代码。

Join ClauseWhere Clause 字段内的 JPQL 编辑器支持实体名称和属性的自动完成功能。如需调用自动完成功能,按下 Ctrl+Space。如果是在输入“.”之后出现的智能提示,则会显示符合当前上下文的实体属性列表,否则显示所有数据模型的实体。

Groovy 内存约束中,使用 {E} 占位符变量表示被检查实体实例。另外,userSession 作为 UserSession 类型的变量也会传递给脚本。下面的例子展示的约束用来检查实体是由当前用户创建的:

{E}.createdBy == userSession.user.login

当违反约束时,会给用户展示一个通知消息。每个约束的通知消息标题和内容都可以做本地化:使用 Access Groups - 访问组 界面 Constraints - 约束 标签页的 Localization - 本地化 按钮。

Checking constraints in application code

开发者可以使用以下 Security 接口检查某一实体的约束条件:

  • isPermitted(Entity, ConstraintOperationType) - 根据操作类型检查是否约束。

  • isPermitted(Entity, String) - 根据输入字符串检查自定义约束。

也可以基于 ItemTrackingAction 连接 action 和特定约束。在 action 的 XML 节点中设置 constraintOperationType 属性或者使用 setConstraintOperationType() 方法设置。注意,约束的代码会在客户端层执行,所以不能使用中间层的类。

示例:

<table>
    ...
    <actions>
        <action id="create"/>
        <action id="edit" constraintOperationType="update"/>
        <action id="remove" constraintOperationType="delete"/>
    </actions>
</table>