UML软件工程组织

使用XDE模式进行模型驱动开发:案例研究
Jerry Zhou 软件工程师, IBM
本文通过IBM VisualAge Pacbase连接IBM Rational Rose XDE,展示了一个定制模式的范例,并且深入详细地解释了其目的,是如何构建的,以及如何解决产生的问题。

介绍
这篇技术文章介绍了一个定制模式的范例,并且深入详细地解释了其目的,是如何构建的,以及如何解决产生的问题。
此模式使用VAP(VisualAge Pacbase)链接IBM Rational Rose XDE。我们将主要介绍VAP和与此工具相关的特定业务环境、概念和术语表,以确保可以充分地理解此模式范例。

此文章主要考虑三类不同的读者:

  • VisualAge Pacbase用户将会为展示的XDE模式阅读此文章,提取VAP信息,并使用UML类图预先填充他们的VAP数据存储库。他们可以集中在第1,2部分。这篇文章介绍了几个XDE的高级概念:日志,模型转换原理和性能优化。
  • XDE高级用户和模式开发者可以容易地重用此文章的这些模式,来创建和增强他们自己的定制模式。他们将会集中在第3,4部分。基础的模式原理(参数,代码模板,呼出,扩充和绑定...)已经在其它文章中广泛地涵盖到。因此,如果你还没有创建一个模式的经验,我们建议阅读一些基础的读物作为一个起点(参见下面的参考书目)。
  • 传统的J2EE建模人员将要阅读这篇文章,以将其作为拉动现有Cobol资产,并将他们集成到一个J2EE结构中的一个MDD实现的现实例子。它也会吸引涉及这些主题的技术方面读者的兴趣。

 

目标应用程序开发环境
 

IBM Rational Rose XDE
XDE产品充分继承了成功的Rational Rose产品,并且以此为基础,使用世界上最高级的软件建模能力扩展了你的IDE。它主要定位在J2EE体系结构上,但是我们也将阐明它如何使你创建一个复合的面向服务的集成Cobol程序的应用程序。

一些XDE的关键特性

  • 模型驱动开发
  • 双向工程,包括立即相应的模型代码同步
  • 数据库设计
  • Web发布和报告
  • 包括可视化执行跟踪的运行时分析
  • 用户可定义的模式和代码模板。

XDE集成到WebSphere Studio Application Developer,以产生一个唯一的设计到编码的J2EE的综合实践。
 

IBM VisualAge Pacbase
VisualAge Pacbase(VAP)是一个模型驱动的,基于数据存储库的应用开发(AD)工具,是为企业范围的可量测性、可靠性和性能而设计的。VAP提供的生命周期覆盖了从分析和设计到生产和维护。VAP以不同的平台和构架为目标:大型机,Unix 服务器,Windows,程序组和事物性环境。

它也包括一个电子商务模型,允许你产生Java代理,并将一个J2EE Web 应用程序连接到传统的Cobol应用程序。这个模型介绍了一些特定的概念,它将会在本文章的其余部分广泛地涉及到。让我们只是简要地定义他们。

图1:VAP分层组织文件夹

 

  • 文件夹(Folder):此VAP实体在关联数据间操作一个导航树。它自动处理CRUD动作。一个文件夹是实现一个特定用例所必需的一组数据,但是它应当被设计为可在多个环境下重用。让我们考虑一个典型的例子:一个顾客档案包括一些清单,并且每个清单都包含若干行。
    所有这些概念都属于相同的文件夹,并可以被Customer ManagementCustomer Litigation用例所使用。
  • 根节点(Root Node):一个文件夹定义了一个导航的开始点:其根节点。回到我们的例子中,Customer将在根节点。
  • 子节点(Child Node):每个节点可以有若干依赖的节点连接到它。它们是其子节点。Bill是Customer的子节点。Line是Bill的子节点。节点可以被看作为UML类。
  • 数据元素(Data Element):每个节点包含若干数据元素。它们定义了节点的状态。每个Customer有一个名字,年龄和地址。数据元素可以看作为UML属性。
  • 键(Key):当一个数据元素被用来确定每个特殊实例时,它就是一个键。不同的工具和技术,包括VAP,都使用这个概念。

注意:所有这些实体都保存在VAP数据存储库中,并且可以通过几个项目被复用。比如:一个Zip代码实体和相关的格式规则,一旦在资料库中被定义,无论何时需要都可以复用。这种方法使得你可以创建企业级的可维护的应用软件。

工具集成
第一次看上去,两边看上去都是独立的。但是现代Web应用程序需要你连接它们,并且构建一个端对端的全面的IT体系架构。使用VAP,组织可以充分地利用公司资产。它们可以集成Cobol和Java方式和技术,以最有效地使用他们的信息技术系统。

图2:一个混合J2EE构件和现有Cobol后端 资产的web应用程序的全面运行时体系架构

XDE和VAP目前使用一个VAP电子商务模型所搭载的连接器来进行协作。此连接器解析UML类图,以提取VAP实体:文件夹,节点和数据元素。其使用现有的VAP实体使它们一致,并将他们集成到VAP数据存储库。

在这里展示的例子包括VAP,但是它可以很容易地适合任何使用如下过程的现有传统应用程序:

  1. 确定现有服务。这可能需要一些代码重构。
  2. 创建这些服务的一个UML概要远景
  3. 使用XDE模式引擎驱动,以从这些概要描述中产生一个PSM和整个J2EE业务层。

案例研究
VAP使用XDE在其数据存储库中预定义程序基本结构。此协作过程利用了UML建模,并使VAP现有资产最大化。但是它需要更新UML分析类图,并使他们适应一些VAP建模规则:命名约定,依赖关系和特定构造型。这些规则是插入的和错误倾向,并且他们影响源类和关联关系。

我们需要一个解决此问题的完整方法。MDA和MDD提供了一个有效的解决方案,以连接这两边,并构建一个端到端的应用程序。让我们首先定义这些正在形成标准的一些基本状况。

模型驱动体系结构和开发
在最近几个月,许多组织已经开始将精力集中在模型驱动构架(MDA)上,MDA被看作是一个应用程序设计和实现的方法。由于几个原因,这是一个非常实际的开发。MDA鼓励在软件开发过程中有效地使用系统模型,并且当创建类似系统时,它支持最佳实践的复用。由于被对象管理组(OMG)所定义,MDA是一个组织和管理企业构架的方法,该构架由自动化工具和服务所支撑,既可以定义模型,也可以促进不同模型类型间的转换。

建模的基本原理
模型提供一个物理系统的抽象,当关注于相关的对象时,使工程师可以通过忽略无关细节来思考此系统。所有的工程形式依靠模型来理解复杂的,真实世界的系统。模型可以按许多方式使用:预测系统质量,当系统状况发生变更时考虑特定属性,并且与不同利益相关者沟通关键系统特性。模型可能被作为实现物理系统而开发的一个先驱,或者他们可能作为一种理解系统行为的帮助,派生于一个现有系统或开发中的系统。

视图和模型转换
由于你可能对系统的许多方面感兴趣,你可以使用不同的建模概念和符号去突出一个或多个系统特定的透视图或视图,这取决于不同点上所相关的内容。此外,在一些实例中,你可以使用提示或者规则扩大模型,这有助于将他们从一个表示法转换到另一个表示法。在一个相同级别的摘要,经常有必要去转换到系统的不同视图中(例如,从一个结构视图到一个行为视图),并且模型转换将有助于此。在其他的情况下,一个转换提供一个特定的透视图,将模型从一个摘要级别转换到另一个,通常是通过增加转换规则所提供的更多的详细说明,从一个更多摘要的视图到更少摘要的视图。

-- --摘自白皮书:An introduction to Model Driven Architecture - MDA and today's systems by Alan Brown - IBM - 12 January 2004

MDA和MDD间的定位

MDD是一个更整体的法,它认为在一个软件开发项目中产生的主要资产是模型,而不是程序。MDD方法的目标是:

  • 改进软件质量
  • 提高抽象级别
  • 增强沟通
  • 减少开发和维护周期
  • 使用一个可视化建模的体系架构
  • 通过自动产生资产的:需求、代码、部署工件、测试用例,消除错误产生的风险

 

图3:应用程序开发周期

在IBM所发展的MDD方法中,模型是主要的工作产品。他们不仅用于描述应用程序,而且也用于业务,企业架构,测试和部署模型。Rational Rose XDE 产品家族支持MDD。它使MDA最佳实践自动化,并支持相关的标准(UML,PIM,PSM,交换模型,模型转换)。

需求
 

  • VAP是一个数据导向的工具。但是UML分析者应当能够产生类图,并且不使用特殊建模规则将它们导入到VAP中。分析者应当不必因为使用VAP技术约束而烦扰。实际上,他们甚至不知道它们。
  • 连接必须是非强制的,也就是,保持分析模型。命名和输入约定不同于Java和Cobol。因此连接应当实施一个缺省的转换行为,但也应当容易地使一个客户去建立客户规则。例如,布尔类型在Cobol中不存在,可以有几个替代类型来模拟它。
  • 分析模型可以发展,连接应当考虑此发展,以保持同步
  • 最重要的需求是非常直截的:此过程必须保持是 简单和可适应的。

 

提议的解决方案
本节讨论的主要目标是VAP用户。XDE模式设计者将要在后续的部分中找到许多实施细节。

为了改进现有的连接,我们决定使用一个MDD的方法并依赖XDE模式引擎。实际上,分析模型是一个一般的平台无关模型(PIM)。
作为将此模型适配到VAP需求的替代,我们可以使用XDE模式引擎来提取VAP相关的信息,并将它保存在一个新的模型中。此平台特定模型(PSM)包括所有的VAP特定语义。

图4:XDE和VAP集成 -- 模型转换

PSM被用来填充VAP数据存储库,但是也可以使用其他XDE模式产生,例如产生一个J2EE模型层。大多数需要的Java代码就可以自动的产生。此MDD方法基于XDE模式引擎和自动模型转换。它可以适用于任何J2EE项目,以加速开发。

如何安装此模式
XDE提供了一个标准的导入导出格式,大家都知道的可复用资源规格(RAS)。我们的模式就是打包成一个RAS资源。你可以下载它,并使用Import > RAS File特性将它迁移到你的XDE环境中。

一个新的VAP连接的项目将会在你导入一个RAS文件后,被创建到你的工作区中。

注意:Java呼出依赖于几个XDE库。你必须在你的环境中定义一个RATIONAL类路径变量,并且定位这个Rational 产品的安装目录中,以编译这些呼出类( 参见下面的屏幕快照。)。

图5:定义Rational类路径变量

 

解决方案分析
 

例子
我们使用一个简单的例子开始这个分析。让我们考虑下面的源模型:

  • Customer是VAP Folder Root
  • Bill 和 Address 是 Child Nodes

关系可能(或者还没有)在分析模型中的这些类之间定义。

图6:源类和一个类图

VAP XDE 连接利用了如下所示的几个规则:

  • 特定的构造型
  • 连接到依赖类的聚合
  • 命名约定

 

图7:产生类及其类图

我们的模式样例应当会自动处理所有这些约束。基本上,它是将图6中的类图转换到图7的类图。

参数
 

图8:模式参数

此模式定义了5个参数:

参数 类型 来源 注释
RootNode 用户 用户指定一个单一的根节点

 
ChildNode 用户 用户可以指定任意数量的子节点。

 
AttributesRootNode 属性 计算的 此参数来自于根节点

 
AttributesChildNode 属性 计算的 此参数来自于子节点

 
Key 属性 用户 User may tag some attributes as keys 用户可以将一些属性标记为键(Key)。
Key是可选的,因为它们也可以在VAP工作台上指定。

 

 

扩展Root Context
你可以探究此模式的Root Context来学习更多的关于它的扩展构架。模式的Root Context定义了元素,这将会在模式展开时产生。为了符合PSM命名约定,名字是按照脚本来实施需求的。在可选元素中,呼出是被用于过滤已产生的元素的。例如,只有关键属性需要被固化。

图9:XDE Pattern Explorer -- root context

此模式将会创建:

  • 一个包
  • 若干类
  • 这些类之间的几个关联关系
  • 从输入的值直接产生的属性
  • 每个键的一个特定构造型

属性(Attributes)参数被用于在目标类中产生属性。键(Keys)参数只用于确定键(keys)。Root Context(除了对System Class)下定义的UML实体被动态地计算。这将最终产生在一个全面的复制:类和相关联的属性被复制到一个新的包里(并且适合于VAP需求)。

Java 呼出
每个XDE模式都可以包括呼出。它们给了你一个机会来控制扩展。我们的模式应用3个呼出(也就是3 个 Java 类)。他们已经被使用了6次。我们同样已经创建了一个抽象呼出:LoggableCallout增加了日志记录支持我们的呼出。

XDE Callout Java Class 用法
OnScriptlet NamingConventions 执行命名约定。
这些约定被集成在模型转换过程中。
参见"4.2 Naming Conventions" 部分,可获得更多详细内容

 
OnPostMapping OptimizeTuples 分析合并(akamappings或者tuples)并且只保持需要的实例。
15对300最初的合并是一个非常普通的比例,也就是不到20倍!
参见"4.3 Performance Optimization"部分,以获得更多详细内容。

 
Optional Element
x2
AttributesRootNode 每个目标类包括2个metaAttributes
首先包括一个特定的构造型:"VAPID" 并且表示键
其次说明类的简单属性



AttributesRootNodeCallout 过滤了参数,并确保简单属性和键没有匹配错误。

 
Optional Element
x2
AtributesChildNode 在此模式中的Optional Element实施是任意的:2个呼出同时有2个输入参数(aka用户数据)。
你可以执行一个单一的呼出(使用4个不同的用户数据)或者4个不同的呼出缺少任何用户数据。我们中间的例子证明这两个解决方案。你应该混合使用这些策略来尝试并保持你的模式尽可能地简单。

 

 

文档
我们已经为此模式使用XDE标准机制定义了一些文档。

  • 当应用时消息展示给用户
  • 定制向导图像
  • 在XDE内,HTML文档通过XML机制装载(参见RASDefaultProfile.xsd XML schema)。

这份文件改进了用户的经验,并保证你的模式在用户中接受。

绑定
平台特定的层明显地依赖于平台的独立层。分析将会在PIM里开始变化。然后他们可以动态地应用到PSM,利用XDE绑定机制。为了能够自动地再运用它,一个XDE模式绑定可以保存模式应用的值。这个绑定的位置是这个包,这个包接受模式的绑定协作结构。

现有绑定被显示在模式的浏览器里(Pattern Applications部分)。使用Find Applied Patterns按钮来增加此部分。绑定已经保存的模型必须打开。

图10: Pattern Explorer -- 任何定位现有的绑定

不幸地是,你不能同时重复运用几个绑定。你不得不交替地选择他们并重复运用这个模式:向导频道被预先加载好。然后你可以保持这些值或更新他们。实际上,这些同步是半自动的:当模型更新超出这个XDE模式引擎限制,你需要手动更新这些模式绑定。

进一步--一个可复用的工具箱
在我们工作中,我们确定了几个技术问题。他们符合非常普遍的需要,这些需要不直接连接到我们的业务环境。那就是为什么我们决定分布呈现他们,并且允许你在你自己的模式里复用他们的原因。

跟踪和日志记录
不幸地是,定制模式通常在第一次尝试时不能正确地工作。你会跟踪执行特殊需求的呼出。除此之外,你可能希望在执行时对用户显示消息。我想你正不赞成并对此感到疑惑:"怎样使用刚才这个漂亮的在Java中实施的System.out机制?" 此办法不可行;你的消息消失了进入到XDE引擎深处....

实际上,XDE提供了一个定制的API去记录消息。我们已经在我们的例子中执行了一个方便的Util类,以记录消息。 LoggableCallout抽象类执行XDEExec( )标准方法。它处理记录日志服务,并执行到internalExec( )方法。

图11:LoggableCallout结构

为重用LoggableCallout,继承它,并在你现有的呼出中将Exec( )重新命名为internalExec() 。记录日志服务现在是可用的,无偿使用。参见本文的结尾处的注释的源代码,可以学习关于此类的更多内容。

命名约定
MDD意味着不同的环境,他们中的每一个定义了不同的惯例。这些惯例将不会干扰"外部的涉众." 通过扩展名,类型定义(比如,十进制对浮点或者双精度型)可以在我们的环境中也被认为是一个命名惯例。

我们已经执行了一个特定的callout,当模型转换时给了你机会指定被应用的特定规则。callout依赖XDE scriptlets(参见关于scriptlets的参考)。你可以轻松地插入你的代码到这个Java类并增加你的命名规则。

如果它已经通过模式开发者被指定,此模式引擎委派解释scriptlets到OnScriptletcallout的任务。

应用下面的过程:

  1. 在你的模式中声明OnScriptletcallout 类:NamingConventions
图12:NamingConventions callout 规格说明

 

  1. 无论何时你想要控制扩展,在你的模式设计阶段指定scriptlets

在这个callout里包括一个前缀来区别每个scriptlet。比如,我们使用 "CLASS"前缀来定义类名字。

<%="CLASS"+RootNode.getName()%>
  1. 在运行时间,此XDE引擎扩展了scriptlet。然而,它为每个scriptlet调用了NamingConventionscallout。这给了你机会来转换最后得到的串和设置你的命名约定。不要忘记删除这个前缀。

使用这个直截的方法,你可以在你的产生类里控制几乎每个字符串。

< ... >

/**
*
This method must implement the Callout Business Logic
*/
protected void internalExec(IRXEPatCalloutData arg0) throws IOException {

IRXEPatternsScriptletCalloutData data = (IRXEPatternsScriptletCalloutData) arg0;

try {

//
Scriptlet Formula
// Ex: "<%=inputClass%>"
String scriptlet = data.getScriptlet();

//
Expanded Scriptlet evaluated by XDE engine (prior to callout invocation)
// Ex: "ClassName"
String expandedScriptlet = data.getExpandedScriptlet();

//
Just keep XDE engine's result by default
String result = expandedScriptlet;

// CLASS NAMES
//*********************************
if (expandedScriptlet.indexOf("CLASS")== 0) {
// Remove "CLASS" prefix and apply Renaming rules
result = namingConventionsClassName(expandedScriptlet.substring(5));
}

<... continued ... >
<...
Control your own prefix and scriptlets here ...>

// Expanded value is reaffected to store the result
data.setExpandedScriptlet(result);

} catch (Exception e) {
error("NamingConventions Callout: " + e.getMessage());
}
}


/**
*
Implements your Naming Conventions for *** CLASS NAMES ***
*You can override the default behavior (i.e. uppercase the Class Names)
*/
private String namingConventionsClassName(String value) {
return value.toUpperCase();
}


 
清单1:NamingConventions Callout: 代码摘录

 

性能优化
模板参数的多样性可以强于一个。XDE模式引擎首先计算争论值的Cartesian结果,然后在每个mapping上扩展模式(akatuple)。一个相称的链接存在于这些组合和模式扩展过成中。当我们开始增加多重属性到我们的模式中时,我们面临着执行的问题。在我们的用例里,由于我们得到了许多复制,不是所有的mapping都是合适的。参见以下)。那就是为什么我们决定创建一个PostMapping callout 来去除这些不必要的映射的原因。

此概念最好的解释要用具体的例子来说明。

我们的模式定义了5个参数:

  • RootNode
  • RootAttributes
  • ChildNode
  • ChildAttributes
  • Key

考虑下面(更简单的)的例子

图13:带有3个类的图例

下面的表格为你显示了模式的应用:

参数名称 类型 基数 注释 扩展
系数(*)
RootNode Class 1 Customer 必须指定一个唯一的Class。

 
NA
RootAttributes Attribute special lastName
firstName
这些参数是一个模型元素集。 它自动地通过XDE引擎(从RootNode过滤)计算。 .因此它的基数依赖所选择的RootNode。

 
2
ChildNode Class 0..n Address
Bill
在此例子中,我们指定2个子类。 但是这些参数是直接链接到ChildAttribute,因此它不能参与到模式的扩展系数。

 
NA
ChildAttributes Attribute special number
street
zipcode
city


id
amount
taxes
 
这些参数是模型元素的收集。它自动地通过XDE引擎(从ChildNode过滤)计算。

但是XDE保持在一个指定类里对属性包含的跟踪:
-和地址关联的数字,街道,邮政编码以及城市
-和账单有关的序列号,数量以及利率。

你将不会有地址和数量的许多结合。
那就是为什么ChildNode's 扩展系数是1(否则,它就会是2)

 
(4+3=)
7
Key Attribute 0..n number
id
我们的例子指定2个keys。但是XDE引擎不能预知属于Bill类的id,因为这个参数是由用户指定的,不是计算出来的。

你将会得到Address和id的结合。
因此,Key扩展系数是2。

注意:一个街道号通常不是一个有效的函数的key(除了在一条相同的街道 ;-)

 
2

本例的(*),你可以得到(2 x 7 x 2 =) 28个组合。

Mapping RootNode RootAttribute ChildNode ChildAttributes Key
1 Customer lastName Address number number
2 Customer lastName Address number id
3 Customer lastName Address street number
4 Customer lastName Address street id
5 Customer lastName Address zipcode number
6 Customer lastName Address zipcode id
7 Customer lastName Address city number
8 Customer lastName Address city id
9 Customer lastName Bill id number
10 Customer lastName Bill id id
11 Customer lastName Bill amount number
12 Customer lastName Bill amount id
13 Customer lastName Bill taxes number
14 Customer lastName Bill taxes id
15 Customer firstName Address number number
16 Customer firstName Address number id
17 Customer firstName Address street number
18 Customer firstName Address street id
19 Customer firstName Address zipcode number
20 Customer firstName Address zipcode id
21 Customer firstName Address city number
22 Customer firstName Address city id
23 Customer firstName Bill id number
24 Customer firstName Bill id id
25 Customer firstName Bill amount number
26 Customer firstName Bill amount id
27 Customer firstName Bill taxes number
28 Customer firstName Bill taxes id

让我们把它们称为mappings。下面的图表显示了每个映射的组合。你可以激活跟踪,在LoggableCallout/////LoggableCallout在这个输出视图里来示每个映射。你能够看到每个参数被多次求值。基本上,一个好的优化参数的办法是除去没有用的映射,使用一个PostMapping/////PostMapping呼出,如OptimizeTuples

此呼出:

  • 提取每个映射
  • 执行一个最优算法,
  • 将唯一需要的映射返回到XDE引擎。

 

图14: PostMapping callout specification

在我们的例子里,一个理想的最优化会导致7个映射(我将会让你找到正确的答案:-)OptimizeTuples/////OptimizeTuples执行3个简单的运算法则分成3个阶段:

  1. 为每个Key选择一个mapping(akatuple)(如在前面的表所示)。
  2. 为每个Child Attribute选择一个mapping(显示为蓝色)
    数字和序列号是Keys。我们不需要选择他们做为属性。
  3. 为每个Root Attribute 选择一个mapping(显示为桔红色)

每个阶段被重复地求值:我们寻找keys,然后子属性和最终的根属性获得了较好的结果。最终,我们获得8个mapping(也就是3.5倍优化=少于扩展持续时间3.5倍)。当你指定更多的参数(10倍,20倍)时,这个比率可以非常快地达到高的值。

注意:参见在本文后部分的注释源代码可以学到关于这个类的更多内容。

当你的模式扩展时,如果你在扩展你的模式时正面临参数的问题,你可以直接查看PostMapping呼出。

透视图
如先前所提到的,VAP电子商务模型Module产生Java 代理,这将会在运行时间上与现有的后端程序相合。这些代理可以被反工程到XDE,并且集成到一个J2EE框架使用.....你猜它...XDE模式。你可以容易地构建一个模式,这将使这个代理做为一个单一的参数并且产生的大多数代码需要封装到这技术的类,并集成它到你的想要的框架里。你也可以扩展这个机制到你的MVC框架的每一层,并使用XDE代码产生能力来产生其它的J2EE组件,提高你的开发人员的效率和最终代码的标准化。

在这篇文章里接近表达给你一个真实生命的基于XDE模式机制之上的例子。这个引擎远远高于在XDE之内的标准的模式(GoF或者J2EE模式)。它可以非常快速地变成你的秘密武器来产生UML和Java代码并提高你项目的生产力。使用一个J2EE框架讲稿大大帮助你使你的方法标准化,并且最优化这些产生的过程。不用担心写每个程序的细节,以及产生它的构建障碍!

最终,XDE模式运行你生产出标准,可维护的并且适用的Java代码。

特别感谢Serge Bonnaud (IBM SWG -- France) 和 John Hsia (IBM SWG -- US) ,没有他们这项工作不会成为可能。

参考
VAP

IBM 官方网站

XDE

DeveloperWorks Resources Pattern Articles (RAS, scriptlets, documentation...)

MDA

An introduction to Model Driven Architecture
Part I: MDA and today's systems

MDA: An architecture for the e-business era

 

附录--已注释源代码的Java呼出
LoggableCallout.java

此抽象呼出不是在一个模式里直接指定的。

package com.ibm.parislab.xdebridge.callout;

import java.io.IOException;
import com.rational.rxe.IRXEUtil;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCalloutdata;

/**
* @author Jerry Zhou
* @author Thierry Matusiak
*
* Inheriting classes must implement a single method:<BR>
* <I>internalExec( )</I>, which corresponds to the standard XDE Exec( ) method.
*/
public abstract class LoggableCallout {

/**
* This method must implement the Callout Business Logic
*/
protected abstract void internalExec(IRXEPatCalloutdata arg0) throws IOException;


/**
* Toggle this value to trace and debug execution
* This Class manages errors, messages and traces (during debug phase)
*traces can be deactivated
*/
private static final boolean trACE = false;

/**
* Specific Output tab used to display messages
* This Tab will appear in XDE Output View (in the Modeling Perspective)
*/
private static final String TAB = "VAPConnect";

private IRXEUtil util = null;

/**
* Clears the output
*/
public void clearOutput() {
try {
util.clearGuiOutputWindow(TAB);
} catch (IOException e) {
// nothing to do
}
}

/**
* Logs the specified message during execution
*/
public void log(String message) {
try {
util.writeToGuiOutputWindow(message + "\n", TAB);
} catch (IOException e) {
// nothing to do
}
}

/**
* Logs the specified message if trace is enabled<BR>
* It allows to display Debugging messages during pattern development<BR>
* Do not forget to toggle trACE constant value before delivery ;-)
*/
public void trace(String message) {
if (trACE) {
log(message);
}
}

/**
* Logs the errors during execution
*/
public void error(String message) {
log("An error has occurred during VAPConnect Pattern evaluation - " + message);
}

/**
* XDE pattern engine invokes this standard method
*This method inserts in XDE standard runtime behavior
*It initializes the logging mechanism and forwards invocation to the subClass.
*/
public final void Exec(IRXEPatCalloutdata arg0) throws IOException {

// Log initialization
util = arg0.getrXE().getUtil();
util.createGuiOutputWindowTab(TAB);

// Execution is delegated to the subclass
internalExec(arg0);
}

}

 

NamingConventions.java

图15: NamingConventions callout 规格说明

 


 
package com.ibm.parislab.xdebridge.callout;

import java.io.IOException;

import com.rational.rxe.IRXEClass;
import com.rational.rxe.IRXEClassProxy;
import com.rational.rxe.IRXEElement;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCTArgumentValue;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCalloutdata;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsCallout;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsScriptletCalloutdata;

/**
* @author Jerry Zhou
* @author Thierry Matusiak
*
* You can easily define Scriptlets in your pattern: <%=myScriptlet%> <BR>
* This Callout gives you the opportunity to control every scriptlet during pattern expansion.<BR>
*
*/
public class NamingConventions extends LoggableCallout implements IRXEPatternsCallout {

/**
* This method must implement the Callout Business Logic
*/
protected void internalExec(IRXEPatCalloutdata arg0) throws IOException {

IRXEPatternsScriptletCalloutdata data = (IRXEPatternsScriptletCalloutdata) arg0;

try {

// Scriptlet Formula
// Ex: "<%=inputClass%>"
String scriptlet = data.getScriptlet();

// Expanded Scriptlet evaluated by XDE engine (prior to callout invocation
// Ex: "ClassName"
String expandedScriptlet = data.getExpandedScriptlet();

// Just keep XDE engine's result by default
String result = expandedScriptlet;

// CLASS NAMES
//*********************************
if (expandedScriptlet.indexOf("CLASS")== 0) {
// Remove "CLASS" prefix and apply Renaming rules
result = namingConventionsClassName(expandedScriptlet.substring(5));
}

// ATtrIBUTE NAMES
//*********************************
if (expandedScriptlet.indexOf("ATtr") == 0) {
// Remove "ATtr" prefix and apply Renaming rules
result = namingConventionsAttributeName(expandedScriptlet.substring(4));
}

// KEY NAMES
//*********************************
if (expandedScriptlet.indexOf("KEY") == 0) {
// Remove "KEY" prefix and apply Renaming rules
result = namingConventionsKeyName(expandedScriptlet.substring(3));
}

// ATtrIBUTE TYPES
//*********************************
if (expandedScriptlet.indexOf("TYPE") == 0) {
// Remove "TYPE" prefix and apply Renaming rules
result = namingConventionsAttributeType(expandedScriptlet.substring(4));
}

// Add your own scriptlets and prefix here

// Expanded value is reaffected to store the result
data.setExpandedScriptlet(result);

} catch (Exception e) {
error("NamingConventions Callout: " + e.getMessage());
}
}


/**
* Implements your Naming Conventions for *** CLASS NAMES ***
* Override default behaviors to adapt them to your needs
*/
private String namingConventionsClassName(String value) {
return value.toUpperCase();
}

/**
* Implements your Naming Conventions for *** ATtrIBUTE NAMES ***
*/
private String namingConventionsAttributeName(String value) {
StringBuffer temp = new StringBuffer();
String tempString;
boolean upper = false;
int len=value.length();
for (int i = 0; i < len; i++) {
// Get rid of blanks
tempString = value.substring(i, i + 1);
if (!(tempString.equals(" "))) {
if (upper) {
// Capitalize every words' first letter
tempString = tempString.toUpperCase();
upper = false;
}
temp.append(tempString);
} else {
upper = true;
}
}
return temp.toString();
}

/**
* Implement your Naming Conventions for *** ATtrIBUTE TYPES ***
*/
private String namingConventionsAttributeType(String value) {
String temp = value;
if (value.equals("boolean")) {
temp = "Integer";
}
return temp;
}

/**
* Implement your Naming Conventions for *** KEY NAMES ***
*/
private String namingConventionsKeyName(String value) {
// refers to Attributes' rules
return namingConventionsAttributeName(value);
}

}
 

 

OptimizeTuples.java

图16: OptimizeTuples callout 规格说明

 


 
package com.ibm.parislab.xdebridge.callout;

import java.io.IOException;
import java.util.ArrayList;

import com.rational.rxe.IRXEAttribute;
import com.rational.rxe.IRXEAttributeProxy;
import com.rational.rxe.IRXEClass;
import com.rational.rxe.IRXEClassProxy;
import com.rational.rxe.IRXEElement;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatApplicationMap;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCTArgumentValue;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCalloutdata;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatExpansionMap;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsCallout;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsPostMappingCalloutdata;

/**
* @author Jerry Zhou
* @author Thierry Matusiak
*
* XDE Pattern expansion:<BR>
* 1. Tuples (aka mappings)are defined by the engine<BR>
* 2. This callout is invoked. It gives you the opportunity to delete some tuples<BR>
* 3. Every remaining tuple will then be evaluated<BR>
*
*/
public class OptimizeTuples extends LoggableCallout implements IRXEPatternsCallout {

/**
* These transient variables store the current tuple
*/
private String rootClazz;
private String rootAttribute;
private String childClazz;
private String childAttribute;
private String keyClass;
private String key;

/**
* Combinations are stored in this table
* [0]=rootClass
* [1]=rootAttribute
* [2]=childClass
* [3]=childAttribute
* [4]=keyClass
* [5]=key
*
* The third dimension stores Unique ID and Explicit Name
*/
private String[][][] params;

/**
* How many tuples have been created by the XDE engine ?
*/
private int count;

/**
* Every tuple can be rejected or accepted
*/
private boolean[] rejectTab;

/**
* Populates the current tuple (uses Unique IDs)
*/
private void evalTuple(int i) {
rootClazz = params[i][0][0];
rootAttribute = params[i][1][0];
childClazz = params[i][2][0];
childAttribute = params[i][3][0];
keyClass = params[i][4][0];
key = params[i][5][0];
}

/**
* Populates the current tuple to display (uses Explicit Names)
*/
private void evalTupleTotrace(int i) {
rootClazz = params[i][0][1];
rootAttribute = params[i][1][1];
childClazz = params[i][2][1];
childAttribute = params[i][3][1];
keyClass = params[i][4][1];
key = params[i][5][1];
}

/**
* Combination extraction
* We need to extract every tuple before starting to optimize.
*/
private void extractTuples(IRXEPatApplicationMap appMap) throws IOException {
for (int i = 0; i < count; i++) {
IRXEPatExpansionMap expMap = appMap.getExpansionMap(i);

IRXEAttribute key;
IRXEElement element;
IRXEPatCTArgumentValue argVal;

// Root Class
argVal = expMap.getArgumentValueByName("RootNode");
element = argVal.getLanguageElement();
IRXEClass rootClazz = new IRXEClassProxy(element);

// Root Attributes
argVal = expMap.getArgumentValueByName("AttributesRootNode");
element = argVal.getLanguageElement();
IRXEAttribute rootAttribute = new IRXEAttributeProxy(element);

// Child Class
argVal = expMap.getArgumentValueByName("ChildNode");
element = argVal.getLanguageElement();
IRXEClass childClazz = new IRXEClassProxy(element);

// Child Attributes
argVal = expMap.getArgumentValueByName("AttributesChildNode");
element = argVal.getLanguageElement();
IRXEAttribute childAttribute = new IRXEAttributeProxy(element);

// Use IDs rather than Names to ensure unicity (names can poduce duplicates)
// We store names for tracing purpose only
params[i][0][0] = rootClazz.getID();
params[i][1][0] = rootAttribute.getID();
params[i][2][0] = childClazz.getID();
params[i][3][0] = childAttribute.getID();

params[i][0][1] = rootClazz.getName();
params[i][1][1] = rootAttribute.getName();
params[i][2][1] = childClazz.getName();
params[i][3][1] = childAttribute.getName();

// Key
try {
argVal = expMap.getArgumentValueByName("Keys");
element = argVal.getLanguageElement();
if (element != null) {
key = new IRXEAttributeProxy(element);
params[i][4][0] = key.getContainer().getID();
params[i][5][0] = key.getID();
params[i][4][1] = key.getContainer().getName();
params[i][5][1] = key.getName();
}
} catch (Exception e) {
params[i][4][0] = "null";
params[i][5][0] = "null";
params[i][4][1] = "null";
params[i][5][1] = "null";
}
}
}

/**
* Combination optimization:
* Step 1 - Select a tuple for every Key
* Step 2 - Select a tuple for every Child Attribute
* Step 3 - Select a tuple for every Root Attribute
*
* Note: Root and Child Classes will necessarily be present
* We do not need to select them explicitly
*
*/
private void optimizeTuples() {
ArrayList rootAttributes = new ArrayList();
ArrayList childAttributes = new ArrayList();
ArrayList keys = new ArrayList();

// Reject everything by default
// You can build a more efficient algorithm using this approach
// It is a better solution than accepting everything by default and trying to eliminate some tuples
for (int i = 0; i < count; i++) {
rejectTab[i] = true;
}

// Step 1 - Select a tuple for every Key
for (int i = 0; i < count; i++) {
evalTuple(i);
if (!(keys.contains(key))) {
if (rootAttribute.equals(key) || childAttribute.equals(key)) {
keys.add(key);
// When we select a key, we also select the other parameters belonging to the same tuple.
// We can update the associated variables to take this into account.
// This approach linked to the separation into 3 independent phases optimizes the algorithm.
rootAttributes.add(rootAttribute);
childAttributes.add(childAttribute);
rejectTab[i] = false;
}
}
}

// Step 2 - Select a tuple for every Child Attribute
for (int i = 0; i < count; i++) {
evalTuple(i);
if (!(childAttributes.contains(childAttribute))) {
if (!(keys.contains(childAttribute))) {
keys.add(key);
rootAttributes.add(rootAttribute);
childAttributes.add(childAttribute);
rejectTab[i] = false;
}
}
}

// Step 3 - Select a tuple for every Root Attribute
for (int i = 0; i < count; i++) {
evalTuple(i);
if (!(rootAttributes.contains(rootAttribute))) {
if (!(keys.contains(rootAttribute))) {
keys.add(key);
rootAttributes.add(rootAttribute);
childAttributes.add(childAttribute);
rejectTab[i] = false;
}
}
}
}

/**
* Delete unnecessary tuples
*/
private void deleteTuples(IRXEPatApplicationMap appMap) throws IOException {
//We use a separate index because the appMap is rehashed when an element is removed
// Example
// Initial state: # 1 # 2 # 3 # 4 # 5 #
// If you remove 2, you get :
// # 1 # 3 # 4 # 5 # #
// And not:
// # 1 # # 3 # 4 # 5 # Otherwise, using the i variable would have been ok
int index = 0;
for (int i = 0; i < count; i++) {
evalTupleTotrace(i);
String accept = "# Accepted - ";
if (rejectTab[i])
accept = "Rejected - ";

trace(accept + " Root: " + rootClazz + " - Child: " + childClazz
+ " - AttrRoot: " + rootAttribute + " - Key: " + key
+ " - AttrChild: " + childAttribute + " - KeyClass: " + keyClass);

// Remove unnecessary combinations
if (rejectTab[i]) {
appMap.removeExpansionMap(index);
} else {
index++;
}
}
}

/**
* This method must implement the Callout Business Logic
*/
protected void internalExec(IRXEPatCalloutdata arg0) throws IOException {
try {
IRXEPatternsPostMappingCalloutdata data = (IRXEPatternsPostMappingCalloutdata) arg0;
IRXEPatApplicationMap appMap = data.getApplicationMap();
count = appMap.getExpansionCount();
params = new String[count][6][2];
rejectTab = new boolean[count];

// Main algorithm
// 1. Tuples extraction
// 2. Optimization process
// 3. Tuples deletion

log("number of tuples BEFORE optimization = " + count);

extractTuples(appMap);

optimizeTuples();

deleteTuples(appMap);

count = appMap.getExpansionCount();
log("number of tuples AFTER optimization = " + count + "\n");

} catch (Exception e) {
error("PostMapping Callout: " + e.getMessage());
}
}

}

 

AttributesRootNode.java

图17: AttributesRootNode callout 规格说明

 

package com.ibm.parislab.xdebridge.callout;

import java.io.IOException;

import com.rational.rxe.IRXEAttribute;
import com.rational.rxe.IRXEAttributeProxy;
import com.rational.rxe.IRXEClassProxy;
import com.rational.rxe.IRXEElement;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCTArgumentValue;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatCalloutdata;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsCallout;
import com.rational.xde.pcc.rxe.interfaces.IRXEPatternsOptionalElementCalloutdata;

/**
* @author Jerry Zhou
* @author Thierry Matusiak
*
* This Callout filters attributes and keys
* - attributes must be generated once
* - keys must be generated once with a "VAPID" stereotype
*
*/
public class AttributesRootNode extends LoggableCallout implements IRXEPatternsCallout {


/**
* This method must implement the Callout Business Logic
*/
protected void internalExec(IRXEPatCalloutdata arg0) throws IOException {

try {

IRXEPatternsOptionalElementCalloutdata data = (IRXEPatternsOptionalElementCalloutdata) arg0;

// This static value is specified during pattern creation:
// "key" for keys and "simpleAttribute" for attributes


String userData = data.getUserData();

/*
* 1. Extract "Key" in the current tuple
*/
Object[] argVals = (Object[]) data.lookupParameterValues("Keys");

// If no key has been specified in the wizard, you can reject every "key" callout
if ((argVals == null) || (argVals.length == 0)) {
if ("key".equals(userData)) {
data.reject("");
}
return;
}

IRXEPatCTArgumentValue argVal = (IRXEPatCTArgumentValue) argVals[0];
IRXEElement element = argVal.getLanguageElement();
IRXEAttribute key = new IRXEAttributeProxy(element);


/*
* 2. Extract "AttributeRootNode" attribute in the current tuple
*/
argVals = (Object[]) data.lookupParameterValues("AttributesRootNode");
argVal = (IRXEPatCTArgumentValue) argVals[0];
element = argVal.getLanguageElement();
IRXEAttribute attribute = new IRXEAttributeProxy(element);

/*
* 3. Extract "RootNode" class in the current tuple
*/
argVals = (Object[]) data.lookupParameterValues("RootNode");
argVal = (IRXEPatCTArgumentValue) argVals[0];
element = argVal.getLanguageElement();
IRXEClassProxy rootNode = new IRXEClassProxy(element);


trace("RootNode Callout - User Data: "+userData);
trace("Attribute: "+attribute.getName()+ " - Class: "+attribute.getContainer().getName());
trace("Key: "+key.getName()+ " - Class: "+key.getContainer().getName());

/*
* 4. Accept or reject the attribute
*
* Is it a key ?
* Yes: Accept it as a key but not as a simpleAttribute
* No: Accept it as a simpleAttribute but not as a key
* A specific stereotype is added for keys
*/
boolean isKeyAttribute = attribute.getID().equals(key.getID());

if (("key".equals(userData) && (!isKeyAttribute)) || (!"key".equals(userData)) && isKeyAttribute) {
data.reject("");
trace("reject\n");
}
else {
trace("accept\n");
}

} catch (Exception e) {
error("Root Node Optional Callout: "+e.getMessage());
}
}

}
 

注意:ttributesChildNode.java 正确地遵循了相同的原理。它计算ChildNode 和 ChildNodeAttributes,而不是 RootNode.

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的英文原文。
     
  • 请参加Rational 中国论坛关于本文的讨论。
     
 
 

版权所有:UML软件工程组织