围绕EMF探索(1)之存储
EMF(Eclipse Modeling Framework)的应用会越来越多,而围绕EMF的query,
validate, transaction, persistency等等讲逐渐被大家使用,以及EMF eCore也有非常大的参考价值。今天先讲讲EMF的存储与查询方面的内容。
首先讲讲EMF对象的存储
这两天抽空玩了玩EMF的对象存储,采用了Eclipse Teneo这个组件。这是一种桥梁式的组件,为EMF对象提供了一个持久化的解决方案。目前支持Hibernate和JDO的实现方式。Eclipse
Teneo的前身是elver persistency组件,后并入Eclipse EMF Tools项目工程下。
Teneo让EMF对象的存储变得非常简单。这样我们只需要关注EMF对象的关系,而不用过多的考虑每一个eObject该如何与数据库表对应。
在使用Hibernate的情况下,Teneo会根据ePackage所描述的Model中的eObject对象之间的关系,自动的产生hiernate.hbm.xml映射文件。当然我们可以手工指定mapping文件来强制描述eObject与数据库表结构的关系。详细可以参看:
http://www.elver.org/hibernate/hibernate_details.html#orgeneration
简单的采用Teneo操作eObjects是非常容易的事情,在elver上也提供了那个经典的“Library
Model”实例的实现例子,可参考:
http://www.elver.org/hibernate/tutorialone/tutorial1_intro.html
final HbDataStore dataStore = HbHelper.INSTANCE.createRegisterDataStore(dataStoreName);
dataStore.setEPackages(new EPackage[] { LibraryPackage.eINSTANCE });
final SessionFactory sessionFactory = dataStore.getSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
LibraryAdapterFactory adapterFactory = new LibraryAdapterFactory();
Library library = LibraryFactory.eINSTANCE.createLibrary();
library.eAdapters().add( adapterFactory.createAdapter(library) );
library.setName("My Library");
session.save(library);
Writer writer = LibraryFactory.eINSTANCE.createWriter();
writer.setName("Writer One");
Book book = LibraryFactory.eINSTANCE.createBook();
book.setPages(305);
book.setTitle("The Hobbit");
book.setCategory(BookCategory.SCIENCE_FICTION_LITERAL);
book.getWriter().add(writer);
library.getWriters().add(writer);
library.getBooks().add(book);
session.getTransaction().commit();
session.close();
再说说EMF的查询吧。
Teneo本身提供了一对查询的支持,比如针对Hibernate的实现,支持对HQL的检索。
Query query = session.createQuery("SELECT book FROM Book book, Writer writ WHERE "
+ " book.title='1984' AND book.author=writ AND writ.name='G. Orwell'");
books = query.list();
但是,Teneo仅仅支持Hibernate的HQL,或JDO的JDOQL,略显得不够完善,或者说,让对象检索变得有些“污染”。
我比较喜Eclipse EMF项目工程下有EMF
Query组件,这个组件为“内存中的eObjects”的检索提供一套“查询条件”,但目前EMF Query仅支持内存中的对象关系,这就局限来其现实中的应用价值。
当然,EMF Query和Teneo还是在某些不要求效率的情况下能够配合起来的。
public Collection queryLargeBook(EObject root) ...{
SELECT select = new SELECT(new FROM(root), new WHERE(
new EObjectAttributeValueCondition(LibraryPackage.eINSTANCE
.getBook_Pages(), new NumberCondition.IntegerValue(
new Integer(10), new Integer(Integer.MAX_VALUE)))));
return select.execute();
}
Query query = session.createQuery("FROM Library");
List libraries = query.list();
Library library = (Library) libraries.get(0);
Collection books = queryLargeBook(library);
在内部实现上,EMF
Query会把所有的源对象都装载进内存中,在内存中遍历,并依次判断是否符合所设定的查询条件。下表中代码截取自emf query的Select对象,演示了遍历寻找符合条件的对象的算法。
protected void doResume() ...{
EObject eObject = null;
setResultSet(new QueryResultSet());
boolean canPrune = getFromClause().canBePruned();
WHERE whereClause = getWhereClause();
while (it.hasNext() && (isCancelled() == false)) ...{
eObject = (EObject) it.next();
if (whereClause.matches(eObject)) ...{
addEObject(eObject);
if (getResultSet().size() == maximumResultSize) ...{
break;
}
}
}
}
EMF
Query构造了条件(Condition)对象,这个Condition有点“(函数式编程)Function Programming”的影子了,确切的说,应该是“一元谓词(UnaryPredicate)”。有关java的FP,可参考我的另一篇blog:http://blog.csdn.net/james999/archive/2005/01/09/246404.aspx
不过,EMF Query的Condition并没有基于任何开源FP的基础,既没有基于apache的Common
Functor,也没有基于JGA(http://jga.sourceforge.net/)。
EMF Query内部结构和实现,我目前还没有看完,姑且先不细说,留待后面补充。
围绕EMF探索(2)之再探查询组件
本篇专门讲讲EMF Query这个组件的设计原理、结构和算法。
在上一篇中,已经简单介绍了EMF
Query,其是为“内存中的eObjects”的检索提供一套“查询条件”,可以依据eObject的结构关系进行查询,也可以依据eObject的对象进行查询。
EMQ Query的主结构关系如下图所示:
其中咖啡色表示最基本的设计思想的基类,把握住了这几个类(接口),则就把握了整个EMF
Query的设计原理。
其中蓝色的图形表示主要所需要关注的实现类和构架。
从大的方面说,整个EMF Query是由围绕查询语句的构造和执行来展开的。
(1)查询语句的结构
查询语句的结构一般如下所示:
SELECT
FROM [source]
WHERE [condition] |
整个查询语句(SELECT)是由两个查询子句(Query
Clause)组成,分别是FROM和WHERE。其中FROM表示了所需要查询的对象源(EObject Source),WHERE则表示了所需要匹配的条件。
对于EObject Source,其表示的就是一组eObject对象的集合。
(2)查询语句的执行
整个查询语句的执行规则就是,将所需要检索的对象源(EObject
Source)转换成一棵对象树,通过遍历这棵对象树,依次将树中的对象与所需要检索的条件进行匹配,选择匹配成功的。
EObject eObject = null;
boolean canPrune = getFromClause().canBePruned();
WHERE whereClause = getWhereClause();
while (it.hasNext() && (isCancelled()
== false)) {
eObject = (EObject) it.next();
if (whereClause.matches(eObject)) {
addEObject(eObject);
if (getResultSet().size() == maximumResultSize) {
break;
}
}
if (canPrune && whereClause.shouldPrune(eObject))
{
it.prune();
}
} |
有关上面那张类图的结构就不再多叙述,相信一看就明白。
接下来讲讲两个比较值得关注的地方,一个是看得见的条件(Condition),还有一个是看不见的Tree
Prune。
条件(Condition)
与条件相关的类几乎占据了整个Query组件的一半,但是这些Condition是非常简单的。如果想真正理解这个条件的构造机制和原理,那么建议大家花点时间了解一下Java
Funtion Programming(Java的函数式编程)。整个Condition就是围绕一个“一元谓词”而构造的,而每个Condition实际上就代表一个算子(arithmetic
operator)。
其实上一篇已经提到了我曾经写过一篇有关Java函数式编程的blog,参见http://blog.csdn.net/james999/archive/2005/01/09/246404.aspx
。
大家在使用或阅读Query源码的时候,可能会涉及到有的Condition会存在Adapter的。其实这就是利用了一个简单的Adapter模式代码,将需要匹配的EObject转换成当前Condition可以识别的值,用于计算。
对象树的裁减
前面讲过在执行检索过程中,要检索的对象源(EObject
Source)转换成一棵对象树。其实是获取一个新的EMF所提供的TreeIterator对象,这是一个允许被裁减(Prune)的针对树的遍历指针。
TreeIterator的默认遍历方式是采用“树的深度遍历算法”,而裁减(Prune)则表示:当遍历到某个节点时,如果执行裁减行为,则此节点后的所有子节点将不再遍历——就像一棵树,被削了一个枝杈似的。
因为EMF的对象是支持包含关系的,而且很容易通过getContents()接口获取相关的内容对象。虽然如果不进行适当的裁减操作,完整的遍历一棵树,是比较缓慢的。
EMF Query在构造Condition的时候,提供了PruneHandler接口,用于判断当前的eObject对象是否需要进行裁减遍历行为。
围绕EMF探索(3)之初探OCL
围绕EMF模型,有个很重要的基础性组件叫EMF
OCL,这是一个实现了OCL2.0标准的组件,提供了对OCL Expression的解析、校验。OCL全称是Object
Constraint Language。
对OCL之前接触不是很多。两年前,在MDAChina还存在的时候,见到一些朋友谈论它。但对当时的技术和应用来说,OCL还是过于“理想化”。事实上,直到今天,OCL在国内的应用也是很稀少的。
如果不是因为探索EMF OCL这个组件的原因,我想我也很难去主动钻研OCL的那有些晦涩的Expression。虽然在UML2中,其已经成为一个用于准确描述模型的关键特性。
其实,使用EMF OCL非常简单,使用QueryFactory构造一个Query对象,设置表达式(Express),以及表达式所依赖的Context(在EMF
OCL中,一般是Classifier)。然后对给定eObject对象进行检查和计算。——就这么简单。
Query query = QueryFactory.eINSTANCE.createQuery(
"Book.allInstances()->select(b
: Book | b <> self and b.title = self.title)",
LibraryPackage.eINSTANCE.getBook());
query.setExtentMap(extents);
Collection result = query.evaluate(myBook); |
当然,想很熟练的应用EMF
OCL,那么就必须会构造正确的Expression。
所以,不得不花费点时间学习OCL。
OCL的概念和作用就不用在这里啰嗦了,以前的MDAChina那帮爱好者们写了很多有关这方面的文章(可惜mdachina已经不存在),比较典型的就是“MDA之路”那个兄弟写的几篇blog
文章,
http://www.blogjava.net/wxb_nudt/archive/2006/11/29/2188.html
和http://www.blogjava.net/wxb_nudt/archive/2005/04/04/2858.html 。
对于更精细的了解OCL,可以读读OCL之父Addison
Wesley的著作《Object Constraint Language, The: Getting Your Models Ready
for MDA》第二版,可以从下面的地址获取电子书:
http://www.netyi.net/training/4d9e427c-3d58-4f74-b540-ad642c9afccc
另外,建议大家再看看《UML2.0参考手册》附录B部分的,这部分内容也是讲OCL的,而且讲了OCL的Basic
Type和Syntax。
粗略的把这两本书中一些相关的章节翻了翻,还是很有收获的。当然限于时间问题,只是翻翻而已,留个大概映像,以后慢慢再研究查阅了。
针对OCL,这里简单提几个重点性内容:
OCL是一种语言,而且是一个Query-Only语言,利用OCL所塑造的Expression为模型中的类与类、类方法、类属性做了一层约束。既然是一种语言,那么就具有最基本的两个特性:类型和语法。
OCL具有最基本的built-in类型,同时,会将Meta-Model中的所有自定义classifier都认为是类型。针对built-in类型,会有相应的很多逻辑操作相关联,比如“and,
or, xor,
not”或者“*,
+, -,
/”等等。这方面可以参考,里面有专门的一章节介绍OCL的类型和逻辑操作。
针对Classifier之类的类型,则存在三个纬度:作用于Classifier的约束;作用于操作(方法)的约束;作用于属性的约束。这三个层面的约束,有不同的语法表达。
Contraints on Classifiers |
context Student
inv: self.GPA > 2.0 |
Contraints on Operators |
context Course::registerStudent(s : Student) : boolean
pre: s.tuitionPaid
= true |
Contraints on Attributes |
context School::tuition : float
init: 2500.00 |
另外,提醒一下。从Eclipse
CVS中抓下的ocl plugin project,需要依赖eclipse orbit包中的lpgjavaruntime组件,可以去http://download.eclipse.org/tools/orbit/downloads/下载,其实就是net.sourceforge.lpg.lpgjavaruntime
包。
先讲讲OCL的小知识吧,今天只顾钻研OCL的基础知识和语法了,还没有来得及研究EMF
OCL的源码和结构。简单的扫了几眼,着实比EMF Query复杂很多,看来要花费更多的时间在上面了。
围绕EMF探索(4)之Validation组件图
EMF Validation Framework提供了对EMF eObjects的校验框架。这种校验框架支持两个层面的:第一个层面可以对一个集合内的eObjects进行校验,第二个层面可以时时地对对象内的属性(值)变更进行校验。
这几天利用有限的空余时间在钻研EMF Validation Framework。原以为Validation会比较简单(使用Validation倒是比较简单),其实却不然。为了弄清楚Validation内部实现类及构架机制,着实耗费了不少时间。
EMF的Validation
Framework使用是很比较简单的,最简单的方式可以通过扩展org.eclipse.emf.validation.constraintProviders扩展点,通过构造特定的AbstractModelConstraint实现类来对eObjects进行校验。但EMF
Validation Framework的结构是非常复杂的,这也是由于Validation试图提供更加复杂全面的检验框架:
(1)
提供对Batch Validator和Live Validator的支持
(2)
提供对多种声明Constraint的方式支持,目前支持Java类的方式,就是前面提到的通过实现用户自己的AbstractModelConstraint类;支持EMF
Constraint和灵活的OCL Constraint方式。
(3)
提供了较为简单的Validation Adaptor方式进行校验
(4)
试图提供一个Validation Client Context来规避不必要的Constraint
(5)
提供了便于管理和维护的Validation Service类
在eclipse所提供的帮助站点上,提供了对Validation
Framework的简要介绍,以及简单的Examples。
http://help.eclipse.org/help32/topic/org.eclipse.emf.validation.doc/tutorials/validationTutorial.html
但是这个tutorial并没有对EMF
Validation进行太深入地介绍,仅仅只是介绍了比较浅显的“How to use it”,而没有深入的介绍Validation的构架、对象逻辑等等。
为了便于理解EMF Validation,将主要的Component
Diagram绘制如下。这个组件图要抽象了很多,其实里面的组件有些是相当的复杂的。比如Constraint Parser和Model
Constraint。
时间不早了,有关组件的详细解释先不写了,留着下篇吧。不过这个组件图应该可以帮助大家在阅读Validation代码的时候起到一定辅助作用。
围绕EMF探索(5)之深入Validation框架
在EMF的eCore框架中,本身提供了对Validation
Framework的支持,而EMFT的Validation组件则是在这个基础上又扩展的大量的功能。如果大家采用Validator
Adaptor方式,可能会更加体会到对Evalidator的应用。
但是由于EValidator是在注册过程中,是依据EPackage来匹配的,针对一个ePackage一般只能注册一个Evalidator对象。这就限制的应用的扩展性。
EValidator.Registry.INSTANCE.put(
LibraryPackage.eINSTANCE, new LibraryValidator()); |
而EMFT的Validation
Framework则在这个基础之上进行了扩展,但是Validation Framework没有在EObjectValidator的基础上进行扩展,而是另辟蹊径,构造了自己的一套实现构架,甚至完全抛弃了EMF
eCore所提供的DiagnosticChain机制,而是采用eclipse runtime IStatus对象来记录校验的结果。
为了便于理解,绘制了一张EMFT
Validation Framework的主要构思图:
整个EMFT Validation
Framework的核心就是两个概念:IValidator和Constraint。其中IValidator是有别于EMF eCore的EValidator。IValidator是一个验证执行器,为了支持对Batch和Live两种模式的支持,所以有不同的接口和实现类。Batch模式就是可以对批量对象进行验证,而Live模式则可以在对象值变更的时候相应验证。
IValidator执行器会从Validation
Service模块中获取所匹配的Constraint进行验证,当然,为了优化和便于管理,Validation Framework还提供了对Context、Binding、ProviderOperation等方面的支持。不论如何,最终的解决目的就是为了找出合适的Constraint进行验证。
有关Constraint的代码,几乎占据了Validation
Framework代码量的大部分,其实解决的目的就是为了可以方面的支持多种Constraint Model,目前支持三种方式:Java
Code,EMF Model,以及OCL。
在Validation Framework构架中,真正用于constraint
validate是ImodelConstraint接口,不同的Constraint Model类型下会有不同的实现。
因为Validation Framework这套构架依赖于在plugin.xml中公国描述和申明来注册相应的constrain实现,所以需要不同的Parser负责解析和管理。看看下面的类图,应该就比较清晰了。
当然,在我们使用Validation
Framework这套框架的过程中,是不会接触到 这些parser的,甚至根本不知道IModelConstraint的存在。
比如,针对java模式,一般我们会继承一个AbstractModelConstraint类来实现。如下图所示:
事实上,这是一个很简单的Adapter模式的应用,具体就没有必要细说了,类图已经很清晰的反映了一切。
|