如何在 LotusScript 中处理组

Comments

是 Domino Directory 中的一种 Notes 文档类型。可以使用 NotesDocument 类对 Group 文档进行操作。问题是要知道字段中的值表示什么,并知道如何处理包含其他组的组(嵌套组),或者如何处理需要超出成员列表大小限制的组。

在本文中,我们将讨论两种处理组的方法。首先,看一下内置 LotusScript 类 NotesAdministrationProcess,该类可以在 Lotus Notes/Domino 6.0 及更高版本中使用。这个类可以向组中添加成员、重新命名组和删除组(以及与组不相关的其他许多函数)。还没有能够从组中删除用户的函数;但是,如果使用管理过程(AdminP)彻底删除用户的话,那么这些用户将从组中被删除。有关 NotesAdministrationProcess 类的详细信息,请参阅“LotusScript: The NotesAdministrationProcess Class in Notes/Domino 6”一文。

我们还提供了两个定制的 LotusScript 类,它们支持更大范围的组操作。该代码是为 Lotus Notes/Domino 6.0 及更高版本编写的;在版本 5 中使用它时,需要稍作改动。本文包含几个代理示例,它们将显示对于不同的任务应该如何使用这些类。不能在 COM 中使用这些类,因为它们在内部使用 List 数据类型,而 Visual Basic 不支持这种数据类型。

本文中所列的类的实际代码、所有代理示例、所有属性和方法的完整文档都包含在数据库示例中,可以从 Sandbox 下载这个数据库示例。(在数据库示例中,使用 GMan 帮助操作或从任何视图按 F1 就可以查看定制类的全部文档。)定制类名为 NotesGroupManager 和 NotesGroup。要使用它们,则需要将 LotusScript GroupManager 脚本库从数据库示例中复制到自己的应用程序中,并通过 Use 语句将其放入您的脚本中。这个类包含执行下列操作的函数:

  • 查找所有地址薄。
  • 搜索所有地址簿或指定子集中的 Group 文档。
  • 创建和删除组。
  • 添加和删除成员。
  • 对成员列表进行排序。
  • 处理包含其他组的组。
  • 确定某人是否是组中的成员,或者获取组中所有成员的列表,其中包括嵌套组的成员。
  • 通过将组分割成多个文档,管理拥有的成员比一个 Group 文档所能容纳的成员多的组。

数据库示例包含一些从公用地址簿模板(pubnames.ntf)中复制过来的设计元素,以便对数据库示例中的数据使用该工具,从而避免更改 Domino Directory 中的组。可以在任何应用程序中使用该代码。

本文假设您是经验丰富的 LotusScript 开发人员。

NotesAdministrationProcess 类的组方法

正如简介中所提到的,NotesAdministrationProcess 类允许执行一些简单的组操作(以及与组不相关的其他许多函数)。下面部分列出了相关方法。

每个方法都通过在 Notes 管理请求数据库(admin4.nsf)中创建请求文档来工作。在服务器上运行 AdminP 时,它会注意到这些请求,然后执行请求的所有操作。某些请求可能需要其他人来批准,而另外一些请求可能会被传递到其他 Notes 域,在那里进行处理。

使用该类的好处是:

  • 它允许发出请求,甚至在不能直接访问执行请求的服务器时也是如此。
  • 如果用户等待脚本完成,该类允许您更快地完成,因为实际工作是由服务器完成的。
  • AdminP 可以更彻底地删除和重新命名组。它不仅删除或更改组记录,还跟踪对原组名称的引用并更新这些引用。

缺点是:

  • 使用这种方法,仅能进行很少的操作。
  • 请求不一定立即执行。
  • 如果组中包含太多成员而不能将这些成员放入一个 Group 文档中,那么这种方法无法处理将用户添加到该组中的操作。

该类的大多数方法都可以返回管理请求数据库中新的请求文档的 note ID。如果不能创建请求(例如,因为当前用户没有授权),则会根据失败原因,返回空字符串("")或产生一个错误条件。

返回 note ID 的概念是:如果愿意,可以用监视文档来查看何时完成该请求。AdminP 创建响应文档来显示任务何时完成。根据服务器设置,AdminP 可能在与发出请求的服务器不同的服务器上运行,从而产生很长的延迟,同时要等待执行向管理服务器复制或从该服务器复制。您无法使用 AdminP 做任何事,无法手动进行访问。这项操作是由 AdminP 自动完成的。

在这里,我们将不再讨论这些方法的完整语法,但是建议您参阅 Domino Designer help。在那里,我们提供了这些语法的概括以及本中没有的一些有用的细节信息。

AddGroupMembers 方法

给定组名称(字符串)和用户名数组后,该方法会将这些名称添加到组中。这可能涉及到要搜索多个目录来查找组。如果没有这个给定名称的组,就需要创建该组并为它提供通用类型,这意味着它可以用于电子邮件或访问控制。组中已有的名称不必添加。

注意:Domino Designer 文档说明成员参数可以是包含单个用户名的字符串,但是好像一直到 Lotus Notes/Domino 6.5.4,这种方法才有用。可以使用一个元素的数组来添加单个用户名。

例 1:自动订阅邮寄列表

将下列代理(AdminP\Process Subscription Requests)被设置为在 mail-in 数据库中的新 Memo 文档和已更改 Memo 文档上运行。它将检查主题字段,以查看主题中是否包含单词“subscribe”,如果包含这个单词,则将用户添加到组中:

Option PublicOption Declare ' always use Option Declare
Sub Initialize
	Dim session As New NotesSession
	Dim adminp As NotesAdministrationProcess
	Dim db As NotesDatabase
	Dim coll As NotesDocumentCollection
	Dim docRequest As NotesDocument
	Dim strID As String
	Dim strNewMembersArray( ) As String
	Dim intNewMemberCount As Integer
	
	Set db = session.CurrentDatabase
	Set coll = db.UnprocessedDocuments
	Set docRequest = coll.GetFirstDocument( )
	Do Until docRequest Is Nothing
		If docRequest.Subject(0) = "subscribe" Then
			Redim Preserve strNewMembersArray(0 To intNewMemberCount) 
			  strNewMembersArray(intNewMemberCount) = 
			  docRequest.GetItemValue("From")(0)
			intNewMemberCount = intNewMemberCount + 1
		End If
		session.UpdateProcessedDoc docRequest
		Set docRequest = coll.GetNextDocument(docRequest)
	Loop
	If intNewMemberCount  > 0 Then ' There are some members to add
		Set adminp = session.CreateAdministrationProcess("bobbity")
		strID = adminp.AddGroupMembers("Hazardous Joke 
		  Mailing List", strNewMembersArray)
		If strID = "" Then ' Probable insufficient access
			Msgbox {Unable to create adminp request "Add or Modify 
			  Group"}, 0, {adminp failure}
		End If
	End If
End Sub

DeleteGroup

给定组名称后,该方法就会将这个组从 Domino Directory 中删除。此外,还将从其他所有组的 Member 列表中、从数据库的 ACL 中以及从所有数据库的文档 Reader 和 Author 姓名字段中删除这个组名称。假设服务器使用 Windows 操作系统,那么该方法还可以删除相同名称的 Windows 组。

RenameGroup

RenameGroup 方法使用两个字符串参数:组的原有名称和新名称。除了更改实际组记录中的名称,这个方法还可以更新数据库 ACL 列表、其他组的成员列表以及其他可能使用该组的位置。

GroupManager 脚本库

GroupManager 脚本库包含两个类和多个常量,可以将这些常量用作方法参数和属性值,它们在 On Error 语句中非常有用。其中的两个类是:

  • NotesGroupManager 跟踪 Domino Directory 并管理组信息的内存中的缓存。这使您可以有效地对多个组执行多个操作,而无需频繁将组重新加载到内存中。保存更改可能会被延迟到所有处理完成之后。NotesGroupManager 可以在递归或非递归模式下使用。在递归模式下,它将查看哪些组包含其他组并允许执行递归操作,比如从当前组及其所有子组中删除用户。
  • NotesGroup 包含单个组的信息。

通过提供组名称作为参数,大多数任务可以直接通过 NotesGroupManager 方法完成。如果需要使用 NotesGroup 类,那么可以使用 GetGroup 或 CreateGroup 方法从 NotesGroupManager 请求 NotesGroup 对象。这有助于执行一些高级操作(例如更改组的所有权),以及对同一组进行多个操作时使用更简单的代码,从而获得更高的效率。

子组、断接子组和递归模式

GroupManager 脚本库包含处理组(包含其他组作为成员的组)的层次结构,向下可以包含 6 层嵌套。在本文中,我们将另一个组的成员组称为子组

创建 NotesGroupManager 对象时,可以选择禁用递归功能并将组作为成员的“平面(flat)”集合来处理,以便获得更好的性能。这并不意味着子组的成员是主组的成员。只是表明您不能说明它们是成员,除非它们是主组的成员。

有一种特殊类型的子组,称为断接子组(breakout subgroup)。如果给定的总字段(在此例中,该字段包含成员的列表)的大小限制为 32 KB,那么当过多成员要放入主组时,就需要使用断接子组。组管理器自动创建足够的其他组来包含成员的完整列表并将这些组作为成员添加到主组中。

断接子组的名称是主组的名称后加上空格和数字。这是我们的代码的惯例,不是 Lotus Domino 的惯例。Lotus Domino 不会根据名称来对组进行不同处理,但是组管理器却会这样做。

注意:断接子组仅在递归模式下发挥其功能。

作为普通子组与断接子组的例子,可以考虑两个组层次结构,如图 1 中所示。

图 1. 普通子组与断接子组
普通子组与断接子组
普通子组与断接子组

Sailors 组包含一些海员的姓名,还包含其他两个组;每个 Pirate 是一个海员,每个 Naval Officer 也是一个海员。我们将它们放在单独的组中,这样会对我们有帮助。例如,Naval Officers 组允许访问非军官不能使用的全体人员绩效评估数据库,Pirates 组则可以用作 Pillaging Newsletter 的邮寄列表,这与一般海员无关。

与 Sailors 组相比,组 Hispaniola Crew 包含断接子组。它的子组不在其他任何地方使用,它们的存在不是为了方便和可以组织,而是因为 Notes 字段有大小限制。以后我们将听到更多关于这些组的讨论。

例 2:更多邮寄列表维护

与例 1 一样,代理 GMan Process Subscription Requests 根据用户的 mail-in 订阅请求,控制只使用邮件的组的成员资格。为了向代理提供一些处理的数据,数据库示例包含有一些 mail-in 订阅和不订阅请求的 Memos 视图。当然,通常 Domino Directory 绝对不会是 mail-in 数据库;将组记录放在同一数据库中仅是为了进行说明。

代理 GMan Process Subscription Requests 设置为在新邮件到达后运行。它处理 UnprocessedDocuments 集合中的每个备忘录。该代理的代码如下所示。所以可以尝试该代理,而不用创建 mail-in 数据库。有一个类似的代理“GMan Samples\1. Process subscription requests”,您可以在 Memos 视图中所选的文档上运行它。

Option Public
Option Declare	' Always use Option Declare
Use "GroupManager"
Sub Initialize
	Dim session As New NotesSession
	Dim db As NotesDatabase
	Dim coll As NotesDocumentCollection
	Dim doc As NotesDocument
	Dim strSubject As String, strVerb As String, strGroupName  
	  As String, strFrom As String
	Dim result As Boolean
	Dim gman As New NotesGroupManager(True)
	Call gman.LoadPublicAddressBooks
	gman.DefaultType = GROUP_TYPE_MAIL ' If we create 
	' a group, make it a mail-only group.
	
	Set db = session.CurrentDatabase
	Set coll = db.UnprocessedDocuments
	Set doc = coll.GetFirstDocument( )
	Do Until doc Is Nothing
		strSubject = Fulltrim(doc.GetItemValue("Subject")(0))
		' We expect subject = "subscribe" or "unsubscribe"  
		' followed by group name.
		strVerb = Lcase(Strleft(strSubject, " "))
		strGroupName = Strright(strSubject, " ")
		strFrom = doc.GetItemValue("From")(0)
		' Make sure group name specified is one we let people
		'subscribe to, e.g., don't allow changes to LocalDomainAdmins 
		'membership.
		If ValidateGroup(strGroupName) Then
			If strVerb = "unsubscribe" Then
				' Remove sender's email from group. Also from 
				' subgroups, in case list is so large that there are 
				' 'breakout' subgroups.
				Call gman.RemoveFromGroup(strGroupName, 
				  strFrom, GROUP_RECURSE)
				SendMessage db, strFrom, "Your unsubscribe request  
				  was processed", {You have been removed from group "} 
				  & strGroupName & {". So long!}
			Elseif strVerb = "subscribe" Then
				' Add member to the group with these options:
				'  -- Create the group if it doesn't exist.
				'  -- Don't add name if it's already in a subgroup.
				'  -- If name is already there but not an exact match, 
				' update it.
				result = gman.AddToGroup(strGroupName, strFrom, 0,  
				  GROUP_CREATE + GROUP_RECURSE + GROUP_UPDATE)
				If result Then
					' Not already in the group (or name changed) -- 
					' welcome them.
					SendMessage db, strFrom, "Your subscription to 
					  "& strGroupName & " was processed.", 
					  {Welcome to the "} & strGroupName & 
					  {" mailing list! Please review our policy 
					  at http://www.iupp.org before posting to 
					  this list.}
				End If
			End If ' Subscribe
		End If ' Group name valid
		Call session.UpdateProcessedDoc(doc)
		Set doc = coll.GetNextDocument(doc)
	Loop
	gman.SaveAll ' Or none of your group changes will be saved
End Sub

这里没有显示子例程 ValidateGroup 和 SendMessage,但是数据库示例中包含它们。ValidateGroup 的实现会因为允许这个代理管理的组的不同而有所变化,SendMessage 不在本文讨论范围之内。与例 1 不同,这个代理可以处理订阅和不订阅请求。大多数代码都可获取关于需要进行哪些操作的信息。该代码实际上是在下列代码行中执行的:

  • Use "GroupManager" 加载 GroupManager 库。
  • Dim gman As New NotesGroupManager(True) 创建 NotesGroupManager 对象。
  • Call gman.LoadPublicAddressBooks 告诉组管理器使用所有 Domino Directory/公用地址簿。
  • gman.DefaultType = GROUP_TYPE_MAIL 指定是否自动创建组(从而我们可以将某人添加到该组中),它将是邮寄列表组。
  • Call gman.RemoveFromGroup(strGroupName, strFrom, GROUP_RECURSE) 将某员工从组及所有子组中删除。默认情况下,仅从指定的组中删除这名员工。当处理很大以致需要断接子组的组时,这一点很重要。除非将员工从所有子组中删除,否则他仍是主组的成员。
  • result = gman.AddToGroup(strGroupName, strFrom, 0, GROUP_CREATE + GROUP_RECURSE + GROUP_UPDATE) 将某员工添加到组中。与这行代码有关的一些要点是:

    第三个参数(0)是目录的基于零的索引,如果某个组不存在,则会在目录中创建这个组。这个索引是以加载的 Domino Directory 的列表为基础。因为前面调用了 LoadPublicAddressBooks,所以 0 是主要 Domino Directory —— names.nsf 的索引。

    GROUP_RECURSE 选项指定如果某人已经是子组的成员,则不能将这个人添加到主组中。当处理的组很大以致要分出断接子组的时候,应该为所有添加项指定这个选项,以避免重复。

    GROUP_UPDATE 控制以下情形:如果组中一个成员的电子邮件地址与要添加的成员的地址相同,但这两个成员不完全相同(例如,如果“Hester Prynne <hester@bigredletter.org>”是组中的成员,而我们又请求添加“Hester A. Prynne <hester@bigredletter.org>”)。默认情况下,不会更新组;如果指定 GROUP_UPDATE,则需要更新组,使之完全与新名称匹配。
  • gman.SaveAll 保存通过组管理器进行的所有更改。

例 3:删除和创建组

代理“GMan Samples\2. Delete and Create Groups”显示如何删除和创建组,以及如何处理组的简单层次结构。该代理首先删除图 1 中显示的所有组。对于 Sailors、Naval Officers 和 Pirates 组,这个操作比较简单,因为我们知道这些组的名称,所以可以使用 gman.RemoveGroup。可是,对于组 Hispaniola Crew,情形则比较复杂一些,因为这些组中可能有未知数量的断接子组。删除主组不会自动删除任何子组,所以必须手工编码来删除子组。下面是用来删除所有这些组的代码:

	Dim gman As New NotesGroupManager(True)
	Dim group As NotesGroup
. . .
	Call gman.RemoveGroup("Pirates")
	Call gman.RemoveGroup("Sailors")
	Call gman.RemoveGroup("Naval Officers")
	
	' Because we'll be doing several operations on one group, 
	' get a NotesGroup object to make things more efficient.
	Set group = gman.GetGroup("Hispaniola Crew")
	If Not (group Is Nothing) Then
		Dim subgroups
		' Are there subgroups to hold overflow (e.g., "Hispaniola Crew 2")?
		subgroups = group.BreakoutSubgroups
		Forall subgroup In subgroups
			Dim strTemp
			strTemp = subgroup.Name
			Call group.RemoveMembers(strTemp, 0) 'Remove subgroup from 
			  main group.
			Call gman.RemoveGroup(strTemp) 'Delete the group altogether.
		End Forall
		gman.RemoveGroup("Hispaniola Crew")
	End If

BreakoutSubgroups 属性返回子组的名称,这些子组的名称符合代码命名这些名称的模式(主组名称后加上空格和数字)。在该例中,NotesGroupManager 类没有访问与组相关的断接子组的列表所需的属性,所以必须使用 NotesGroup 对象来获取特定组的详细信息。

另一种删除组及其所有断接子组的方法是确定它们的名称并通过该名称像下面这样删除任何组:

Sub DeleteBreakoutGroup(gman As NotesGroupManager, strName As String)
	Dim intInd As Integer
	If gman.RemoveGroup(strName) Then
		intInd = 2
		While gman.RemoveGroup(strName & " " & intInd)
			intInd = intInd + 1
		Wend
	End If
End Sub

如果实际存在要删除的组,RemoveGroup 方法会返回 True 的 Boolean 值,从而使您可以测试返回值,以确定是否需要查找更多具有相似名称的组。这是比较简单的编码,但是安全性比较差,因为不能绝对保证用户没有手工删除过一个断接子组,所以会漏删编号较大的组。

然后,我们想重新创建刚刚删除的组。有两种创建组的方法:像例 2 中那样使用 GROUP_CREATE 选项添加成员,或按如下所示使用 gman.CreateGroup:

	Redim values(0 To 3) As String
	Set group = gman.CreateGroup("Naval Officers", GROUP_TYPE_MULTI, 0)
	group.Description = "Current serving officers only."
	values(0) = "Horatio Hornblower/Hotspur/HRMN"
	values(1) = "Jack Aubrey/Surprise/HRMN"
	values(2) = "Stephen Maturin/Surprise/HRMN"
	values(3) = "James Hook/BadGuys/Neverland"
	Call group.AddMembers(values, 0) ' 2nd argument is options

注意,我们使用 NotesGroup 的 Description 属性来对列表描述字段进行赋值。只有在包含 NotesGroup 对象时才能进行这项操作,如果使用 gman.AddToGroup 创建组,那么就不会包含该对象。当然,通常可以在这之后使用 GetGroup 来获取 NotesGroup 对象,例如:

gman.GetGroup("Pirates").Description = "Scourges of the Sea"

到目前为止,我们已经讨论了两种向组添加成员的方法,gman.AddToGroup 和 group.AddMembers。用来添加和删除成员的所有方法都接受单个字符串或字符串数组,因此您可以同时处理多个成员。在这个示例中,我们没有对数组的元素进行赋值,并随后将数组传递到 AddMembers,而是编写了下列代码:

	Call group.AddMembers("Horatio Hornblower/Hotspur/HRMN", 0)
	Call group.AddMembers("Jack Aubrey/Surprise/HRMN", 0)
	Call group.AddMembers("Stephen Maturin/Surprise/HRMN", 0)
	Call group.AddMembers("James Hook/BadGuys/Neverland", 0)

注意我们使用缩写格式的 Notes ID。Group 文档中的用户名必须是规范格式(“CN=...”),但是组管理器代码接受这两种格式中的任何一种,然后自动进行格式转换。

第三种向组添加成员的方法是使用 NotesGroup 的 Members 属性,以便能够使用数组值对其赋值,同时更改整个组成员。下面是代理示例中的一些代码:

	Set group = gman.CreateGroup("Sailors", GROUP_TYPE_MULTI, 0)
	group.Description = "All navvies"
	values(0) = "Queequeg/Pequod/Whalers"
	values(1) = "Jack Dawson/Titanic/White Star"
	values(2) = "Pirates" ' the group
	values(3) = "Naval Officers" ' the group
	group.Members = values

这种指定组成员的方法通常是一种“平面”添加。成员列表完全是您所指定的项,即使子组中有一些重复名称也是如此。

例 4:创建断接子组

如前面例中所示,创建断接子组与创建普通子组没有任何不同。所有必须要做的就是确保在递归模式(换句话说,也就是 New 方法的参数是 True)下创建 NotesGroupManager 对象,如果组过大,将会自动进行这样的处理。

代理“GMan Samples\3. Add many members”显示了这种情况。要创建需要断接子组的很大的组,它包含 pirate 名称生成器,该生成器在形容词、名字和姓氏的列表中随机选择来组成姓名,如 Long John Silver 或 Whistling Ned Doggett。随机姓名是按如下所示方法添加到 Hispaniola Crew 组中的:

		If group.AddMembers(strCrewName, GROUP_RECURSE) Then
			intCount = intCount + 1
		End If

当处理可能包含断接子组的很大的组时,使用 GROUP_RECURSE 选项很重要。否则,可能会将断接子组中已经存在的某人添加到主组中。当删除成员时也要使用这个选项。

使用 AddMembers 的返回值确定姓名是否确实添加了。如果这名人员还不是成员,那么该方法将返回 True。在该例中,我们将执行这项操作来跟踪已经添加了多少个惟一姓名,从而可以知道何时停止添加。

如果想知道可以将多少成员放入一个组中,可以多次运行这个代理。大约有 16,000 种形容词、名字和姓氏的不同组合;您可以编辑该代理并添加列表,以增加组合的数量。

例 5:搜索组

NotesGroupManager 包含属性 CachedGroups,它是 NotesGroupManager 已经加载到其缓存中的所有组的列表。通常,当使用名称请求组时会加载组;在递归模式下,所请求组的所有子组也会加载。

可以使用 group.FTSearch 搜索与全文查询相匹配的组。group.FTSearch 使用查询字符串作为参数,在组管理器所知道的每个 Domino Directory 中,该字符串被用作 FTSearch 方法的参数。与搜索相匹配的 Group 文档将存储在缓存中。

如果 Domino Directory 是全文索引的,那么通过搜索 Members 字段中的常用名或电子邮件地址,您可以使用这个方法快速查找包含特定人员作为成员的所有组。然后可以使用 group.IsMember 属性清除错误匹配。下列代码来自代理“GMan Samples\4. Full Text Search”:

	strSearch = Trim(Inputbox( "Search string:"))
. . .
	lngCount = gman.FTSearch(strSearch)
	Dim groupList As Variant
	Dim strGroupDesc As String
	
	If lngCount = 0 Then
		Msgbox "No groups matched your query."
	Else
		groupList = gman.CachedGroups
		Forall group In groupList
			strGroupDesc =  strGroupDesc & ", " & group.Name
		End Forall
		Msgbox "The following " & lngCount & " groups matched 
		  your query: " & Mid$(strGroupDesc, 3)
	End If

如果只想要与搜索结果相匹配的组,而不想搜索它们的子组,那么必须在非递归模式下使用组管理器。

还有其他几种对缓存进行操作的方法:LoadAllGroups、LoadGroup、Uncache 和 ClearCache。LoadAllGroups 获取所有目录中关于所有组的信息,这样您可以使用循环(比如前面描述的循环)在这些目录中进行迭代。

结束语

在本文中提供这些示例是为了让您开始使用这个定制代码。还有一些我们没有提到的其他方法和属性,它们使您能够控制组所有权、对成员列表进行排序、直接访问 Group 文档来更改没有相应属性的任何字段,等等。所有这些都在 sample database 中进行了详细说明。

一旦有了源代码,就可以根据需要修改或扩展类。如果编写版本 5 或 COM 版本,建议您将其提交到 Sandbox,以便其他人也可以使用。如果遇到数据库示例文档或任何代码的问题,请给作者发邮件。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Lotus
ArticleID=58327
ArticleTitle=如何在 LotusScript 中处理组
publish-date=01042005