Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1172461
  • 博文数量: 312
  • 博客积分: 12522
  • 博客等级: 上将
  • 技术积分: 3376
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-27 18:35
文章分类

全部博文(312)

文章存档

2016年(3)

2015年(1)

2013年(1)

2012年(28)

2011年(101)

2010年(72)

2009年(13)

2008年(93)

分类: 项目管理

2008-04-14 17:31:47

ClearQuest的Hook机制

级别: 初级

嘉 吴, IBM中国软件开发实验室工程师

2005 年 11 月 01 日

本文全面介绍了ClearQuest的Hook机制,并且通过制订一个具体的变更管理模板,为其定义了多种不同类型的、完成不同功能的Hooks,以便于大家在实际操作中用较短的时间了解hooks,并掌握基本的定义Hooks的方法。

ClearQuest是业界领先的变更管理工具。变更可以是新的需求、软件缺陷、各种工单等。 ClearQuest更能让客户根据自己的具体需求,灵活的设计变更管理流程。客户可以定义变更的字段和值的范围、变更的各种状态、引起状态转换的操作,和变更之间的关系等等。除此以外,ClearQuest还提供了Hook机制,让变更管理系统的定义更加灵活、功能更加强大。

本文全面介绍了ClearQuest的Hook机制,并且通过制订一个具体的变更管理模板,为其定义了多种不同类型的、完成不同功能的Hooks,以便于大家在实际操作中用较短的时间了解hooks,并掌握基本的定义Hooks的方法。





回页首


在ClearQuest里,Hook就是用户用某种语言编写的一小段代码,会在操作某个记录(Record)的特定时候触发执行。它可以检查某个记录的字段是否满足复杂的约束条件,可以限制特定的当前用户才有权限操作,可以限制上传文件的大小,可以在对特定的事件记录日志,可以动态灵活的生成各种选择列表,甚至可以更灵活的和其他Rational工具,如ClearCase、Requirement工具等集成。





回页首


在ClearQuest中定义了四种Hook:

1. 字段(Field )Hook

2. 操作(Action )Hook

3. 记录类型(Entity)Hook

4. 全局(Global) Hook





回页首


字段 Hook可以在运行时检查字段的值约束条件,赋予它缺省值,或者根据当前情况调整其他字段等。

根据触发时机,字段Hook又可以分为以下几类:

1. 缺省值 (Default Value)Hook

缺省值Hook设置字段的缺省值。在ClearQuest Designer中,可以直接为记录字段设置常量的缺省值。如果缺省值要求是动态生成的,还可以编写字段的缺省值Hook脚本来实现。缺省值Hook在开始SUBMIT类型操作时触发调用。

2. 选择列表(Chioce List)Hook

选择列表Hook生成并返回字段可选择的有效值列表。选择列表可以用下拉列表框控件展示。在ClearQuest Designer中,可以直接为记录字段设置简单的可选择的有效值列表。如果可选择的有效值生成规则复杂,则需借助编写选择列表Hook脚本来动态生成。选择列表Hook紧接着Default Value Hook后触发调用。

3. 权限检查(Permission)Hook

记录的字段是否必须设定由其在各个状态下的Behavior属性指定。其可为只读的、强制设定的、可选的和使用Hook四种类型。如果在某一状态下,属性Behavior设定为使用Hook,则会调用这个字段的权限检查(Permission)Hook来限制满足条件的用户才能存取本字段。其可用于设定复杂的安全检查机制。权限检查Hook在最初存取字段时触发调用。

4. 值改变(Value Changed)Hook

当字段的值发生变化时触发值改变(Value Changed)Hook。值改变Hook可用于设定和本字段的值有联动关系的其他字段值。

5. 有效验证(Validation)Hook

当字段的值发生变化时,在运行完值改变Hook后还会进一步触发有效验证Hook,可用于检查当前记录的字段是否有效、是否满足设定的规则。





回页首


操作 Hook可以检查执行操作的权限,验证整个记录的有效性,或当操作完成后发送通知。根据触发时机,操作Hook又可以分为以下几类:

1. 权限控制(Access Control )Hook
权限控制Hook用于检查当前用户是否有权限执行本操作。

2. 初始化(Initialization)Hook
初始化Hook用于在执行本操作前初始化该记录,比如设定字段值等。它在显示记录表单前触发调用。

3. 有效验证(Validation)Hook
在提交记录的变更前(也就是点击Apply按钮提交变更到数据库去)会触发有效验证Hook,用于验证整个记录复杂的规则。

4. 提交(Commit)Hook
在提交记录的变更到数据库前最后触发运行的是提交Hook。

5. 通知(Notification)Hook
在提交记录的变更到数据库后触发运行。





回页首


记录类型hook是针对某一记录类型定义的hook,在Record Scripts中编写。它的使用方法有三种:

1) 在本记录类型的其他Hook中调用,使用entity类的FireNamedHook方法直接调用。

2) 将其和某个Form上的某个控件的某个事件关联起来,当发生此事件时触发hook。具体操作是双击要关联的控件,在控件的Extended tab中,为此控件的具体事件选择想要触发的记录类型Hook。

例如,下图中是在对defect form上的Apply按钮的Click事件设置相应的记录类型hook。



3) 创建类型为"RECORD_SCRIPT_ALIAS"的操作,将其和记录类型hook关联起来。 "RECORD_SCRIPT_ALIAS"类型其实提供了一种自定义操作的方式。其具体的操作内容由和其相关的记录类型hook来决定。





回页首


全局Hook实际上就是全局函数,可以在任何hooks中调用。调用方式就像在程序里调用一个函数一样。以Perl脚本为例:


$result = OneofMyGlobalHooks($session, $entity);

除了Global Hook, 其他三种Hook都是和某一个记录类型相关的,均在对某一特定纪录对象的操作中触发执行。Global Hook是不会被直接触发执行的。





回页首


ClearQuest中能定义这么多种类的Hook,它们的调用顺序又是怎样的呢?我们从四个阶段来说明:

开始执行一个操作时,顺序调用:

1) 操作 Access Control Hook

2) 字段Permission Hook

3) 操作 Initialization Hook

4) 字段Default Value Hook

5) 字段Choice List Hook


 

设置字段值时,顺序调用:

6) 字段Value Changed Hook

7) 字段Validation Hook

8) 如果需要重新计算字段的可选择有效值列表,字段Choice List Hook


 

验证记录有效性时,顺序调用:

9) 字段Validation Hook

10)操作 Validation Hook


 

提交记录(到数据库)时,顺序调用:

11)操作 Commit Hook

12)操作 Notification Hook





回页首


在ClearQuest Designer中,可以定义一种特殊的操作:BASE类型的操作。和其他类型的操作一样,BASE类型的操作也可以定义上边的五种操作 Hook。Base 类型的操作的各个Hook会在所有其它操作的相同类型的Hook触发前调用。

可以定义多个Base 类型的操作和它们的各种Hooks。而Base操作的同一种类型的Hook的调用顺序按照其在ClearQuest Designer定义的顺序来执行。

例如,在某个模板中为某记录类型顺序定义操作 myBaseAction1和myBaseAction2,均为BASE类型。两者均定义了Initialization Hook。则在这个模板所对应的用户数据库中,在针对这种类型的记录的所有操作的初始化阶段,都会先调用myBaseAction1的Initialization Hook脚本,再调用myBaseAction2的Initialization Hook的脚本,最后调用本操作自身的Initialization Hook的脚本(如果定义的话)。





回页首


Hook根据定义所用的语言不同,还可以分为以下几类:

1. VB脚本

2. Perl脚本

3. 常量

4. 常量列表

所有的Hook类型,不管是字段还是操作的,都支持VB脚本和Perl脚本。一个Hook可以同时定义这两种脚本,其中Windows平台上缺省使用VB脚本,而Unix和Linux平台上只能使用Perl脚本。如果想改变Windows平台上的缺省脚本语言,可以在模板的属性里面设置。如下图所示:



常量就是定义其为一个常量值;常量列表就是定义字段选择列表。只有字段Default Value Hook和字段Choice List Hook支持常量和常量列表。

ClearQuest以前还提供用SQL语言编写Hook的功能,现在已经取消。但是ClearQuest系统仍能兼容运行以前定义的SQL Hook。

Hook类型 和 Hook语言对应表:







回页首


一个Hook脚本其实就是一个方法,可以用VB脚本语言或者Perl脚本语言编写。ClearQuest提供了这两种脚本语言的API。VB脚本的API是用一个COM库来实现,Perl脚本的则是用一个名为CQPerlExt的perl package实现。他们都提供了一些ClearQuest重要的类和方法。

下图是此API提供的ClearQuest类和它们之间的关系:



由于Hook都是在对一个记录的操作中触发执行的,ClearQuest会传给被触发的Hook当前记录对象(entity)。在VB脚本中它是缺省操作对象;在Perl脚本中它是$entity。从这个记录对象出发,可以得到象session对象,、记录的各个fieldinfo对象、当前的database对象,等等。

以Perl脚本为例,下面这段代码取出id为MyDB00000001的defect记录,改变了它的description字段值,并提交改变,写到数据库中:


$session = $entity->GetSession();
$anotherEntity = $session->LoadEntity("defect", "MyDB00000001");
$anotherEntity->EditEntity("modify");
$anotherEntity->SetFieldValue("description", "updated description");
$anotherEntity->Validate();
$anotherEntity->Commit();

关于具体的类及其方法的使用描述,请参见ClearQuest随身附带的文档"cq_api.pdf"。





回页首


ClearQuest自带了一个名为dbwin32.exe的工具,在ClearQuest安装目录下。其可用于显示调用Session类的OutputDebugString()方法输出的文本,以及其他一些ClearQuest的一些输出信息。如果想跟踪hook的执行情况,则启动这个工具即可收集这些输出信息。

如果想让dbwin32.exe输出较详细的信息,需要在Windows注册表以下位置添加一个类型为REG_DWORD的键HookDebug: My Computer \HKEY_CURRENT_USER \Software \Rational Software \ClearQuest \ \Common \ 设置这个键的值为1。

假设我们在某一个hook中定义了如下代码:


				$session->OutputDebugString("\nOpenning exported.defect...");
		$DefectId = 4;
		$session->OutputDebugString("\nExported DefectId is ".$DefectId);
		

则在调用这个hook时,如果已打开dbwin32,则在其窗口会显示:







回页首


在这个例子中,我们创建一个简单的缺陷跟踪处理模板,定义了包括字段缺省Hook,字段值改变Hook,操作权限控制Hook,操作提交Hook,记录类型hook,全局Hook,以及BASE类型操作的通知Hook。这个缺陷跟踪处理系统其实并不完整,但我们的目的就是让大家尽快的对不同类型的Hook及其使用和编写有一个全面、基本的认识。在本例中,我们均使用Perl来编写hooks。

我们在ClearQuest Designer中创建一个基于Blank的模板,创建"defect"记录类型,并为其定义如下的字段:

1) DefectId:整型

2) Description:短字符串

3) OsType:短字符串

4) OsVersion:短字符串

5) StepToRecreate:多行字符串。

"defect"记录类型的字段定义窗口如下图所示:



其中,DefectId和OsVersion均是只读字段。整型DefectId由系统自动累加生成。OsType的值是固定的几个常量之一;OsVersion值则由OsType设定的值来确定。

然后,为其定义如下的状态转换图:



在ClearQuest Designer中,状态转换定义如下图所示:



其中,Dup是DUPLICATE类型,Open是SUBMIT类型。另外还定义Delete类型的"Delete" 操作。只有用户组defectcoordinators的用户才有Delete一个defect的权限。

当验证通过(即Accept)一个fixed defect时,此defect的所有duplicate的defect,包括它的duplicate的duplicate等等,也同时验证通过。同时,在本系统中禁止直接验证某一个duplicate defect。

每当一个操作发生并提交,系统都会在自己的一个log文件中记录相关信息。所以我们还定义了一个Base类型的操作"BaseLog"。

在ClearQuest Designer中,操作定义如下图所示:



为了实现上述功能,我们定义了如下的几个Hook:

1) 字段DefectId的Default Value Hook:

在ClearQuest Designer中打开模板,选择Record Types ->defect ->Fields,双击Fields。点击DefectId字段的Default Value hook,选择SCRIPTS ->Perl:



现在可以开始编写DefectId字段的Default Value hook了:



编写如下的脚本:


sub defectid_DefaultValue{
	my($entityDef, $session, $querydef, $queryfields, $queryfield, $resultset, 
	$fetchStatus, $DefectId);
	# 创建一个用于查询Defect记录的查询对象,直接定义其使用的SQL语句:
	$entitydef = $entity->GetEntityDefName();
	$session = $entity->GetSession();
	$querydef = $session->BuildQuery($entitydef);
	$querydef->SetSQL("select max(defectid) from defect");
	# 运行这个查询,得到当前数据库中DefectId的最大值:
	$resultset = $session->BuildResultSet($querydef);
	$resultset->Execute();
	$fetchStatus = $resultset->MoveNext();
	if ($fetchStatus eq $CQPerlExt::CQ_SUCCESS) {
		$DefectId = $resultset->GetColumnValue(1);
		if ($DefectId > 0){
			$DefectId++;
		}else{
$DefectId = 1;
		}
		$entity->SetFieldValue($fieldname,"$DefectId");
	}else {
		$session->OutputDebugString("\nThis should not happen. fetchStatus is ".$fetchStatus);
	}
}

2) OsType的Choice List Hook:

点击DefectId字段的Choice List hook,选择CONSTANT_LIST,并输入"windows","unix","linux"选项,如图所示:



3) OsType的值改变Hook:

同样的,点击DefectId字段的Value Changed hook,选择SCRIPTS ->Perl。编写如下的脚本:


sub ostype_ValueChanged{
	my($OsType, $session);
	$OsType = $entity->GetFieldValue($fieldname)->GetValue();
	$session = $entity->GetSession();
	if ($OsType eq "windows"){
		$entity->SetFieldValue("OsVersion","Win2k Sp4");
	}elsif ($OsType eq "linux"){
		$entity->SetFieldValue("OsVersion", "Redhat AS4");
	}elseif ($OsType eq "unix"){
		$entity->SetFieldValue("OsVersion", "AIX 5L");
	}else{
		$entity->SetFieldValue("OsVersion", "unknown");
	}
}

4) Accept操作的AccessControl Hook:

在本系统中,对于Duplicate的defects不能直接操作。所以编写AccessControl Hook,判断当前的状态是否为Duplicate,如果是,则拒绝执行。

在ClearQuest Designer中打开模板,选择Record Types ->defect ->States and Actions ->Actions,双击Actions。点击Accept操作的Access Control hook,选择SCRIPTS ->Perl:



现在可以开始编写Accept操作的Access Control hook了:



编写如下的脚本:


sub defect_AccessControl {
    # Set $result to 1 if the user has permission to perform
    # this action, otherwise set it to 0.
	my($session, $state);
	$session = $entity->GetSession();
	$state = $entity->GetFieldValue("State")->GetValue();
	if($state eq "duplicate"){
		$result = 0;
	}else{
		$result = 1;
	}
return $result;
}

5) Accept操作的Commit Hook:

同样的,点击Accept操作的Commit hook,选择SCRIPTS ->Perl,编写如下的脚本:


sub defect_Commit {
    # This hook is fired during the "commit" step of an entity update, so
    # it is the appropriate place to put activity which should be bundled
    # into the same transaction as the commit, such as subactions or an
    # update of external data storage.
		my($duplicates, $count, $session, $link, $dup, $validresult);
		$session = $entity->GetSession();
	$duplicates = $entity->GetDuplicates();
	if(!$duplicates){
		die;
	}
	$count = $duplicates->Count();
	for($i=0; $i<$count; $i++){
		$link = $duplicates->Item($i);
		$dup = $link->GetChildEntity();
		$dup->EditEntity("Accept");
		$validresult = $dup->Validate();
		$session->OutputDebugString("\nAccept Validation Error: ".$validresult);
		# 调用其duplicate defect的Commit方法,又会触发调用其duplicate defect的Commit Hook,从而递归验证关闭所有相关联的defects。
		$dup->Commit();
	}
}

6) 全局 Hook

我们编写一个全局Hook,定义一个简单的方法"BuildLogEntry"来组装一个字串:

在模板中,右击Global Scripts ->Perl,选择Add添加global script,并编写如下脚本:




sub BuildLogEntry {
   my($action, $login) = @_;
   return "\n*** Action committed ***"."\nAction Taken: ".$action."\nUser: ".$login;
}

7) "LogBase"的Notification Hook:

为实现在日志文件中记录所有提交执行的操作的功能,我们定义了BASE类型操作 "LogBase",并为其定义了Notification Hook。这个Hook调用了Global Hook"BuildLogEntry"。

点击LogBase 操作的Commit hook,选择SCRIPTS ->Perl,编写如下的脚本:


sub defect_Notification {
    # Post-commit notifications about actions may be handled here.
	my($session, $logFile, $login, $logEntry);
	$session = $entity->GetSession();
	# 操作执行的信息记录在f:\\tmp\\actions.log中
	open($logFile, "+>>f:\\tmp\\actions.log") || $session->OutputDebugString("open actions.log failed");
	$login = $session->GetUserLoginName();
	$logEntry = BuildLogEntry($actionname, $login);
	print $logFile $logEntry;
	close($logFile);
	}

8) Delete权限控制Hook:
限制只有特定组"defectcoordinators"的用户才能删除defects。

这个功能也可以对Delete操作的权限控制直接定义用户组来实现。这里是为了演示Hook的功能。使用Hook可以实现更复杂的逻辑。

点击Delete操作的Access Control hook,选择SCRIPTS ->Perl,编写如下的脚本:


sub defect_AccessControl {
	my $session = $entity->GetSession();
	my @groups;
	$groups = $session->GetUserGroups();
	$session->OutputDebugString("\nThe first groups is ".@$groups[0]);
	$result = 0;
	my $group;
	for $group (@$groups){
		if ($group eq "defectcoordinators"){
			$result = 1;
			break;
		}
	}
}

9) 记录类型hook

最后,为了演示记录类型hook的使用方法,我们再定义一个类型为RECORD_SCRIPT_ALIAS的操作"ExportDefect"。它所关联的record scripts是我们定义的记录类型hook "defect_ExportDefectToTxt"。defect_ExportDefectToTxt将当前defect基本信息写到一个文本文件中:

首先先定义一个记录类型hook"defect_ExportDefectToTxt"。在模板中,右击Record Types ->defect ->Record Scripts ->Perl,选择Add添加record script,并编写如下脚本:




sub defect_ ExportDefectToTxt {
		my($result);
    if (ref ($param) eq "CQEventObject") {
    # add your CQEventObject parameter handling code here
		my($MYFILE, $session, $DefectId, $description);
		$session = $entity->GetSession();
		$DefectId = $entity->GetFieldValue("defectid")->GetValue();
		open($MYFILE, "+>f:\\tmp\\Exported_".$DefectId.".defect") || 
		$session->OutputDebugString("\nExport File Open failed!");
		$DefectId = $entity->GetFieldValue("DefectId")->GetValue();
		$description = $entity->GetFieldValue("Description")->GetValue();
		print $MYFILE ("\nDefectID: " . $DefectId . "\nDefectDescription: " . $description);
		close($MYFILE);
    } elsif (ref (\$param) eq "SCALAR") {
        # add your scalar parameter handling code here
    } else {
        # add your handling code for other type parameters here, for example:
        # die("Unknown parameter type");
}
	}

然后,回到actions定义窗口,点击操作类型为RECORD_SCRIPT_ALIAS的 "ExportDefect"的Record Scripts,选择刚才定义的record script。这样,在ClearQuest客户端选择操作"ExportDefect"就会触发defect_ ExportDefectToTxt Hook脚本。



最后,为defect记录类型定义一个表单。如下图所示:







回页首


现在,我们来实际演示一下在实际操作中这些Hooks是如何发生作用的。

首先创建用户组"defectcoordinators"和用户"Mary""Rose",其中"Mary"是"defectcoordinators"的组员。根据刚才创建的模板生成一个用户数据库,将用户组"defectcoordinators"和用户"Rose"加入此数据库。然后打开ClearQuest客户端,以用户Mary的身份连接到刚才创建的用户数据库,做如下操作:

1)创建一个defect纪录。DefectId值是系统调用Defect的Default Value Hook自动生成的。如下图所示:



2)从下拉列表中选择OsType的值,由于触发了OsType的Value Changed Hook,OsVersion的值会自动设上:



3)我们创建4个defects用于测试系统duplicate的功能。通过Duplicate操作将defect2、defect4设为defect1的duplicate,defect4又设为defect2的duplicate。



4)试图将defect2设为验证通过,打开defect2,点击其Action列表中的Accept。由于Accept的Access Control Hook不允许针对duplicate状态下的defect操作,所以得到错误信息,操作不能执行:



6) 对defect1进行Fix和Accept操作,当Apply Accept操作(也就是提交Accept操作)时,defect1的Accept 操作的Commit hook被调用,所有直接和间接的duplicate defects都同时被验证通过:(需要刷新才能看到所有defect的最新状态)



7)察看系统的actions.log日志文件记录了所有提交执行的操作。这是目前actions.log的最后部分:


*** Action committed ***
Action Taken: Dup
User: Mary
*** Action committed ***
Action Taken: Dup
User: Mary
*** Action committed ***
Action Taken: Dup
User: Mary
*** Action committed ***
Action Taken: Fix
User: Mary
*** Action committed ***
Action Taken: Accept
User: Mary

8) 在defect 记录的表单中,有我们自定义的操作Exportdefect选项:



对defect4点击Exportdefect进行操作,触发调用了我们定义的记录类型hook "defect_ExportToTxt",当前的defect信息会写入对应的文件Exported_4.defect中,其内容为:


DefectID: 4
DefectDescription: duplicate to 1

9) 以另外一个用户Rose的身份登陆,尝试删除某个defect。Delete的Access Control Hook检测到Rose不是defectcoordinators组的成员,操作不能执行:



在刚才的一系列操作中,我们触发调用了本模板定义的所有Hooks,均能正常工作。





回页首


Hooks是ClearQuest提供的一个很重要、很有用的功能。利用它就可以非常灵活的定义变更管理模板,扩展ClearQuest的功能。同时,使用编写Hooks也是非常的方便。



 

吴嘉,现为IBM中国软件开发实验室工程师,参与过多个开发、测试、技术支持项目。今年加入Rational Engineering部门,参与ClearQuest产品的开发工作。

阅读(1231) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~