使用EMFValidation框架来验证EMF模型

EMF(Eclipse Modeling Framework)建模框架能够帮助我们将模型 (UML, XSD 等 ) 转 换成为健壮且功能丰富的 Java 代码。使用 EMF 来搭建应用程序,不但能极大的提高开发效 率,而且还能利用 EMF 提供的很多特性来保证模型的健壮和完善,主要包括模型变化通知机 制,模型的持久化以及模型验证框架。本文将要介绍的 EMF Validation Framework,是 EMF 的一个重要部分,通过使用 EMF Validation Framework,我们能方便的对定义的 EMF 模型 添加验证约束,以保证模型数据遵从于用户自定义的约束。

EMF Validation Framework 提供了对 EMF eObjects 的校验框架,和 EMF EValidaTor API 相比

它能提供更复杂全面的验证并且易用易扩展。EMF Validation Framework 提供了灵活的 验证定义方式,支持两种验证触发机制:Batch 和 Live。它支持用 JAVA 和 OCL 语言来实 现约束,此外它还支持自定义验证时的模型扫描算法,并提供了一个 Validation Client Context 来规避不必要的验证。

EMF Validation Framework 的实现原理

在 EMF Validation Framework 框架中有几个重要的概念,这些概念构成了验证框架的基 本部分,我们在下面对其逐一介绍:

约束(constraints)

所有的约束必须实现 IModelConstraint 接口,定义了验证执行的逻辑(validate()), 并拥有一个约束描述符(一个实现 IConstraintDescripTor 接口的类),该描述符包含了这 个约束的源信息,例如,验证模式是 live 还是 batch,验证的目标对象等,图 1 可以给您 一个基本的概念。

图 1. 约束

约束的具体实现可以用 JAVA 或者 OCL 语言,用 JAVA 实现的您可以参考下面的图 2, 其约束都是 AbstractModelConstraint 的子类,它必须实现 validate() 方法。这个方法通 过输入的验证上下文(IValidationContext)获取目标对象、触发的事件类型等信息,实现 业务验证逻辑,并将验证结果通过状态信息(IStatus)报告给用户。

图 2. 约束抽象类和约束上下文等

验证上下文(Validation Context)

在上面的图 2 中,我们简单说明了约束和约束上下文之间的关系,约束上下文记录了当 前验证操作的有关信息,包括验证的目标对象,触发实时验证的事件类型,模型变化值等。除了记录这些上下文信息外,validation context 还提供了一些提高验证效率的方法。例如 方法 skipCurrentConstraintFor()可以用来指定一些的目标对象是“合格的”可以不执行 某些验证方法,方法 get/putCurrentConstraintData() 可以用来缓存验证对象。

验证模式(Validation Modes)

EMF Validation Framework 提供两种验证模式:批量验证模式(Batch) 和实时验证模 式(Live)。

批量验证模式可以对一个集合内的 EObjects 进行校验 , 通常由用户的动作触发,例如 用户点击验证菜单项对整个模型进行验证。批量验证模式时,输入的通常是一个模型元素集 合,输出的验证状态信息(IStatus)包括验证过程中所发现的所有问题 ,因此这个结果通 常是多状态的。

批量验证模式的示例代码如清单 1 所示:

清单 1. 批量验证模式的示例代码

List bjects = myResource.getContents();  IValidaTor validaTor = ModelValidationService.getInstance ().newValidaTor(EvaluationMode.BATCH);  IStatus results = validaTor.validate(objects);  if (!results.isOK()) {   ErrorDialog.openError(null, "Validation", "Validation Failed",  results);  }

实时验证模式则用来实时地对对象内的属性(值)变更进行校验。和批量验证模式不同的 是,它的输入是属性(值)变更的通知消息,清单 2 就是实时验证模式的代码示例。

清单 2. 实时验证模式的代码示例

List notifications = transaction.getChanges();  IValidaTor validaTor = ModelValidationService.getInstance().  newValidaTor(EvaluationMode.LIVE);  IStatus results = validaTor.validate(notifications);  if (!results.isOK()) {   ErrorDialog.openError(null, "Validation", "Validation Failed",  results);  }

约束绑定(Constraint Binding)

定义了这么多的约束,具体的应用又是如何选择约束来完成任务呢?约束绑定就是设计来 完成这个任务的 ,通过 org.eclipse.emf.validation.constraintBindings扩展点,应用可 以明确出它拥有的对象,绑定到它所需要的约束。这样验证操作就能够确保对象符合了相应 的约束了。在下面的章节将会详细介绍如何进行约束绑定。

使用 EMF Validation Framework 来“保护”library 模型

EMF Validation 是一个非常容易使用的框架,本节将通过一个简单的例子来说明如何使 用 EMF Validation Framework 来保护您的模型。简单起见,我们使用 Java 编程语言来定 义模型约束。

第一步 准备 Library 模型

为了容易理解,我们就使用 EMF 相关文章中最常见的 Library 样例作为被保护的模型, 如图 3 所示,Library 模型很简单,仅仅包含三个类:Library, Writer, Book,以及一个 BookCategory 枚举类型。

图 3. Library 模型

我们在 Eclipse 中创建一个 Java 项目,在”New Java Project”向导中,将工程的名称 设置为 test.emf.validation,并选择分离源代码目录和输出目录。在新建好的 test.emf.validation 项目中建立一个新的 model 目录,并将 library.ecore 文件保存到 这个目录中。

为了生成模型的 Java 实现,我们首先需要利用 EMF 提供的向导将 .ecore 模型转化为 .genmodel 模型。这可以通过如图 4 所示的”New EMF GeneraTor Model”向导来进行。

图 4. 使用新建向导生成 Library.genmodel 模型

我们将 Library.genmodel 生成到 model 目录下,并双击其进行编辑。如图 5 所示,在 .genmodel 的编辑器中,我们选择 Library 包,并修改其”Base Package”属性为 emf.model 。这个属性会影响生成的 Java 代码的包名称。

图 5. 修改 Library 模型的 Base Package 属性

接下来,我们就可以生成 Library 模型的 Java 实现了。如图 6 所示,在 Library 包 上单击右键,并在弹出菜单中选择 Generate Model Code 项,在 test.emf.validation 项 目中生成 Library 的 Java 实现。

图 6. 生成 Library 模型的 Java 实现

同样地,选择 Generate Edit Code 菜单项和 Generate EdiTor Code 菜单项可以生成 Library 模型的编辑器代码。在进行完迄今为止的这些步骤之后,我们可以开始编写代码来 利用 EMF Validation Framework“保护”Library 模型了。

第二步 定义 batch constraint

前面提到过,用 JAVA 实现的约束都需要扩展 AbstractModelConstrain,实现 validate() 方法。在清单 3 的代码中,我们定义了一个 constraint 用来验证图书馆,书 籍,作者三者的名字都不能为空,这个方法通过输入的验证上下文(IValidationContext) 获取目标对象,实现了基本的验证逻辑,并将验证结果通过状态信息(IStatus)报告给用户 ,使用起来非常直观。

清单 3. 代码示例

public class NonEmptyNamesConstraint extends AbstractModelConstraint  {  public IStatus validate(IValidationContext ctx) {   EObject eObj = ctx.getTarget();   EMFEventType eType = ctx.getEventType();   // batch validation 时调用   if (eType == EMFEventType.NULL) {   String name = null;   if (eObj instanceof Writer) {    name = ((Writer)eObj).getName();   } else if (eObj instanceof Library) {    name = ((Library)eObj).getName();   } else if (eObj instanceof Book) {    name = ((Book)eObj).getTitle();   }   if (name == null || name.length() == 0) {  return ctx.createFailureStatus(new Object[] {eObj.eClass().getName ()});   }   }   return ctx.createSuccessStatus();  }  }

我们通过 IValidationContext 的 getTarget() 方法来获取等待验证的目标模型元素, 并且只有在 eventType 的值为 EMFEventType.NULL 也就是批量验证模式下才进行名字不为 空的验证。如果验证失败,会返回一个 FailureStatus 对象,验证成功则返回 SuccessStatus 对象。

第三步 创建 constraint provider extension

定义约束后,我们必须让框架底层的验证服务知道它的存在,这是通过创建一个 constraintProviders 扩展点来实现。

如清单 4 所示,在定义一个 constraintProviders 扩展点时,我们首先要指定一个名字 空间,这个名字空间就是我们要验证的模型,然后我们要指定约束的实现方式,实现类,触 发模式以及一个唯一的 statusCode。接下来我们要定义在验证失败时用户看到的提示消息 message,message 可以包含任意数目的替代符 {},这些替代符的值可以在验证失败时通过 调用 ctx.createFailureStatus(new Object[] {…}) 来进行设置。最后我们需要定义的就 是验证的目标对象了,这里我们设定对 Library,Writer,Book 都进行验证。

清单 4. 定义一个 constraintProviders 扩展点

                                              All items in a library model should have some  unique identifier or name.                          The required feature ‘ name ’ of ‘ {} ’ must be  set.                                                       

第四步 将定义好的 Constraint 绑定到应用程序

到目前为止,我们已经定义了一个约束并且将它注册到验证服务。接下来我们需要把这个 约束绑定到应用程序,让应用程序知道它的存在。清单 5 介绍了如何通过定义一个 constraintBindings 的扩展点来实现。

清单 5. 定义一个 constraintBindings 的扩展点

                           context="emf.validation.example.libraryContext"       category="emf.validation.example.library"/>  

可以看到,在进行约束绑定时我们首先要定义一个客户端上下文(client context),这 通过定义一个对象选择器(IClientSelecTor)来限定上下文所包含的模型对象集合。接下来 再定义用来检查这些模型对象数据的约束,也就是我们在前面所定义的用来检查图书馆对象 名称是否为空的约束。

第五步 通过 validation service 来调用 batch constraint

完成上面这些步骤后,我们可以通过验证服务来调用约束对模型进行验证,见清单 6 的 代码。

清单 6. 调用约束对模型进行验证

IBatchValidaTor validaTor = (IBatchValidaTor) ModelValidationService.getInstance()  .newValidaTor(EvaluationMode.BATCH);  validaTor.setIncludeLiveConstraints(true);  IStatus status = validaTor.validate(selectedEObjects);

第六步 运行前面创建的 constraint

在 Eclipse 中点击 Run->Run.., 在弹出的窗口中创建并运行一个 Eclipse Application。在新的工作平台中新建一个项目,然后通过如图 7 所示的“New Library Model”向导创建一个图书馆模型。

图 7. 创建模型项目

为了验证之前定义的约束是否生效,创建如图 8 所示的图书馆模型,可以看到该模型只 定义了一个没有名称的图书馆对象:

图 8. 图书馆模型

如图 9 所示,选中 library 对象,点击右键,选择 validate 菜单项。

图 9. validate 操作

如图 10 所示,一个验证失败的警告框弹出,点击 Details,可以看到验证失败的原因是 图书馆的名字属性没有设置。

图 10. 验证结构提示

第七步 把 batch constraint 转变成 live constraint

我们可以把之前定义的 batch constraint 修改成 live constraint,这样当模型指定的 属性(值)发生变化时验证服务会收到相应的通知从而触发验证。通知消息包括了诸如发生 改变的属性及该属性变化前后的值等信息。在扩展点定义中去声明那些需要在取值发生变化 时进行实时验证的属性。

清单 7 是对之前定义的 bacth constraint 定义的修改,当事件类型不为空是,也就是 说验证服务收到了模型属性变更的通知事件,此时触发验证检查新的属性值是否为空。

清单 7. 之前定义的 bacth constraint 定义的修改

public class NonEmptyNamesConstraint extends AbstractModelConstraint  {  public IStatus validate(IValidationContext ctx) {   EObject eObj = ctx.getTarget();   EMFEventType eType = ctx.getEventType();   // bacth validation 时调用   if (eType == EMFEventType.NULL) {   String name = null;   if (eObj instanceof Writer) {    name = ((Writer)eObj).getName();   } else if (eObj instanceof Library) {    name = ((Library)eObj).getName();   } else if (eObj instanceof Book) {    name = ((Book)eObj).getTitle();   }   if (name == null || name.length() == 0) {  return ctx.createFailureStatus(new Object[] {eObj.eClass().getName ()});   }   // live validation 时调用   } else {   Object newValue = ctx.getFeatureNewValue();   if (newValue == null || ((String)newValue).length() == 0) {  return ctx.createFailureStatus(new Object[] {eObj.eClass().getName ()});   }   }   return ctx.createSuccessStatus();  }  }

下面清单 8 的代码是对 constraintProviders 扩展点的修改,可以看到首先验证模式由 batch 变成了 live,此外我们定义了感兴趣的验证目标 EClass, 目标属性 EStructuralFeature 和触发事件类型。

清单 8. constraintProviders 扩展点的修改

                                            All items in a library model should have some  unique identifier or name.                            A {0} has been found to have no unique identifier  (name or title).                                                                                                                                                                                                                                                                                 

第八步 调用 live constraint

live constraint 的调用方式和之前介绍的 batch constraint 的调用方式有所 不同,请参考清单 9 中的代码。我们知道 live validation 在收到注册目标对象属性 (值)变更的通知消息时触发,因此在调用 live constraint 之前需要先定义一个  EContentAdapter,然后这个 EContentAdapter 绑定到目标资源,这样目标资源发生属性 (值)变更时所发出的通知消息就能被我们的constraint 获知。

清单 9. live constraint 的调用方式

  Resource r = (Resource)i.next();  if (!resourceHasAdapter(r)) {  EContentAdapter liveValidationContentAdapter =  new LiveValidationContentAdapter();  r.eAdapters().add(liveValidationContentAdapter);  }

总结

通过一个简单的例子您已经基本了解了使用 EMF Validation Framework 进行模型验证的 基本过程,可以看到通过使用 EMF Validation Framework 我们能方便地对 EMF 模型添加验 证约束来保证模型数据遵从于用户自定义的规则。

天才是百分之一的灵感加上百分之九十九的努力

使用EMFValidation框架来验证EMF模型

相关文章:

你感兴趣的文章:

标签云: