对HelloDocument
说声招呼
本文为您提供了一个完整的,独立的IBM FileNet P8应用程序的概述。 P8是用于企业内容管理(ECM)的IBM平台。 尽管大多数实际的P8程序都是J2EE或.Net之类的较大框架的一部分,但您作为开发人员的起点可能是独立程序。 通过使用独立程序,您可以专注于P8细节,而不必关注大型框架的复杂性。
该示例HelloDocument
应用程序执行许多任务,其中一些您可能不希望知道。 但是,您可能需要在应用程序中做一些单独的事情。 除此之外,您还可以使用此代码中说明的技术并将其扩展为更适合您的用例的事情。 同样重要的是,一旦运行HelloDocument
,您就可以确信已正确配置了环境。 这样一来,您便可以专注于自己的应用程序详细信息,而无需对环境产生持续的疑问。
要理解本文,您应该对Java™有一定的了解,并且能够跟随Java源代码的描述。 本文没有介绍HelloDocument.java的每一行,而是介绍了说明CE API要点的重点。 该代码受到了严重的注释,如果您喜欢冒险,可以跳过本文,直接转到源代码本身(请参阅可下载的资源 )。
HelloDocument
的结构为一个400-500行的Java源文件。 即使允许源代码中包含大量注释,也只是为了打个招呼而已。 HelloDocument.java中有些内容很可能会转移到单独的类或源文件中。 它们全部显示在一个地方,因此您可以确保拥有所有东西。 例如,HelloDocument.java源文件中包含完整的Java身份验证和授权服务(JAAS)登录序列,包括处理回调的内部类。 对于真正的应用程序而言,那肯定是不寻常的选择。 HelloDocument
类实现PrivilegedAction
接口并将其大部分业务逻辑封装在run方法内,只是为了轻松说明显式的JAAS登录模型。 事实证明, HelloDocument
的业务逻辑不超过50-100行。
本文是在该领域的当前版本为P8 4.0.1,而P8 4.5.0正在缩短其开发周期时撰写的。 该代码可在任一版本中运行。 (本文和该系列文章中的其他人并没有花太多时间在P8 3.x上的API上。在某些情况下,这些API根本不同。)由于所用的API类和方法是该API的核心,因此非常稳定。 HelloDocument
很可能在未来几个P8发布周期中都不会进行任何修改地运行。 HelloDocument
用Java编写。 由于P8 Content Java API与P8 Content .Net API的主要区别在于命名约定和其他次要考虑因素,因此以HelloDocument
等.Net语言重写HelloDocument
将是一项简单的音译工作。 (这两个API相似之处的一个例外是身份验证。尽管本文讨论了HelloDocument
Java身份验证,但对身份验证的一般讨论不在本文讨论范围之内。)
那么, HelloDocument
实际做什么? 它创建一个文档,从文件中上传内容,将其检入并将其归档在文件夹中。 然后,它回读并将下载的内容与原始文件内容进行比较。 (可选)您可以配置应用程序以在下载和比较期间跳过文件的一部分。 尽管文档的创建和归档在许多用例中都是常见的,但比较步骤却并非如此。 实际上,您永远不必验证上传的内容。 下载期间的这些额外片段仅用于说明API编码方面的内容。 HelloDocument
的设计使您在运行CE之前不必在CE中进行任何特殊设置(除了确保您具有创建新文件夹和文档的访问权限外)。 在环境中运行后,您可以重复运行它而不会出错。
组态
嵌入式配置
HelloDocument
由几个配置项驱动。 其中一些功能,例如内容引擎(CE)连接统一资源标识符(URI),对于大多数P8应用程序来说都是通用的。 其他特定于HelloDocument
示例。 在实际的应用程序中,几乎可以肯定不会对这些配置项进行硬编码。 您将使用命令行参数,配置文件或其他某种机制将配置与应用程序代码本身分离。 为方便起见, HelloDocument
在名为ConfigInfo
的静态内部类中将所有这些配置项都定义为常量,如清单1所示。在代码的其他地方,您看到对ConfigInfo.SOME_VALUE的引用时,它们都是在引用这些常量。
清单1.静态内部类ConfigInfo
private static final class ConfigInfo
{
// . . .
/**
* This URI tells us how to find the CE and what protocol to use.
*/
static String CE_URI = "http://myCEserver:7001/wsi/FNCEWS40DIME";
/**
* This ObjectStore must already exist.
*/
static String OBJECT_STORE_NAME = "MyObjectStore";
// . . .
}
ConfigInfo
每个项目都有注释,说明其用法。 您应该查看每个项目并将其设置为适合您的环境的值。
ConfigInfo
其他配置项目
因为这是一篇有关编写代码的文章,所以它不会详细介绍如何设置Java类路径和其他外部配置项。 P8平台文档介绍了如何设置胖客户端环境。 每个品牌的J2EE应用服务器都不同。 文档的同一部分还描述了如何配置JAAS设置。 本文其余部分中的示例和说明都依赖于正确配置的内容。
获取或获取?
API中的Java对象与CE存储库中的对象在字面上并不相同。 它仅表示对该CE对象的引用,通过它您可以检查属性值,通过遵循对象值属性(OVP)进行导航以及进行各种更新。 当API往返服务器时,会发生API对象与CE对象之间的实际匹配。 服务器往返的次数通常是应用程序性能的主要因素,因此API可以非常精确地控制往返真正的时间以及每次往返的数据。 HelloDocument
在最小化往返方面非常谨慎,但是在最小化请求或响应中涉及的数据量方面并不是特别谨慎。 您可以通过属性过滤器和其他机制来调整这些有效负载大小。 这些属性过滤器将在本系列的后续文章中进行更详细的讨论。 现在,您真正需要知道的是,如果您不使用属性过滤器,则该API可以正常工作。 对API有了基本了解之后,您肯定会希望使用属性过滤器,因为它们可以通过减小有效负载大小和组合某些往返来显着提高性能。
实例化Java对象时,API具有有用的约定。 工厂方法(以及一些其他类型的方法)的名称使用前缀get指示本地操作,使用前缀fetch指示将进行到CE服务器的往返。 (还有第三个动词:当您的意图不仅是要实例化Java对象,还要实例化存储库或其他地方的新CE对象时,请使用create 。)在API行话中,无需往返即可实例化Java对象。到服务器的过程称为无获取实例化 。 因此,例如, conn = Factory.Connection.getConnection(ConfigInfo.CE_URI)
是本地的。 在HelloDocument
源代码的许多地方, no R/T
的注释用于强调无获取实例化或其他人可能天真的认为需要往返的操作。 请注意,尽可能使用get方法。
当您使用无访存实例化时,可能会发生奇怪的事情。 API认为您引用的CE对象确实存在。 如果您愿意的话,可以告诉API一个完整的谎言,尽管这通常效率不高。 当涉及该对象的往返发生时,便会进行计算。 到那时,CE服务器有条不紊地将您的对象引用与实际的CE对象进行协调。 当然,创建CE对象是按照您期望的方式处理的特殊情况。 在其他情况下(超出本文的范围),引用不存在的CE对象可能会很有用。 它们仅在CE听到它们时就必须存在。
最重要的是,通过使用无访存实例化可以提高性能,但是相应的代价是,应用程序的错误处理可能必须在以后的阶段处理丢失的对象。 实际上,该错误处理成本不是太高。
获取到ObjectStore
HelloDocument.run
方法是发生自上而下的主要业务逻辑的地方。 该方法的开头以及Connection
的实例变量说明了一种非常常见的编码模式(请参见清单2)。 毫无疑问,大多数内容应用程序仅处理CE存储库中的对象,而大多数内容处理程序仅处理单个存储库。
清单2. HelloDocument.run
和朋友
/**
* All interaction with the server will make use of this Connection object.
* Connections are actually stateless, so you don't have to worry about
* holding open some CE resource.
*
* no R/T
*/
private Connection conn = Factory.Connection.getConnection(ConfigInfo.CE_URI);
// ...
/**
* This method contains the actual business logic. Authentication has
* already happened by the time we get here.
*/
public Object run()
{
// Standard Connection -> Domain -> ObjectStore
//no R/T
Domain dom = Factory.Domain.getInstance(conn, null);
//no R/T
ObjectStore os = Factory.ObjectStore.getInstance(dom,
ConfigInfo.OBJECT_STORE_NAME);
String containmentName = createAndFileDocument(dom, os);
File f = new File(ConfigInfo.LOCAL_FILE_NAME);
long fileSize = f.length();
System.out.println("Local content size is " + fileSize + " for file "
+ ConfigInfo.LOCAL_FILE_NAME);
long skipPoint = 0L;
if (ConfigInfo.USE_SKIP)
{
long midPoint = fileSize / 2;
// pick a random point in the second half of the content
skipPoint = midPoint + (long)Math.floor((Math.random() * midPoint));
}
System.out.println("Will skip to " + skipPoint + " of " + fileSize);
readAndCompareContent(os, containmentName, skipPoint);
return null;
}
到达ObjectStore
的编码模式如下:
ObjectStore
- 获取一个
Connection
对象。 - 获取一个
Domain
对象。 - 获取一个
ObjectStore
对象。
Connection
是一个相当轻量级的类。 它告诉API如何连接到CE服务器。 因为从CE服务器的角度来看,API与CE服务器之间的交互是无状态的,所以Connection
对象不会占用任何昂贵的服务器端资源。 Connection
保存的主要信息项是要使用的URI。 API从URI推导连接方法和CE的位置。 您应该特别注意Connection
对象不保存用户信息。 可以为Connection
设置其他配置参数,但是本文不介绍该机制。
Domain
是在ObjectStore
级别或更高级别持有P8资源的对象。 要实例化Java Domain
对象,您需要一个Connection
和一个域名。 如今,P8安装只有一个域,因此API允许您将域名的null传递给factory方法。 域的名称是在P8安装时建立的,如果您对此感到好奇,可以查看Domain
的Name属性值。
一个ObjectStore
对象代表一个CE存储库。 请注意,用于创建ObjectStore
对象的工厂方法未采用Connection
对象。 相反,它需要一个Domain
对象。 (应该说一个ObjectStore
的作用域是Domain
,这是正确的,但本文没有对作用域做更多的介绍。)在内部,API使用相同的Connection
对象作为Domain
和从该对象实例化的对象。 后来,当ObjectStore
本身用于实例化对象时, Connection
会自动在API中传递给它们。
这部分代码也是通常注意使用工厂方法的好地方。 CE API具有大量的工厂类:每个CE类大约一个。 为了方便起见,将它们组织到com.filenet.api.core.Factory
类中的嵌套类中。 要查找任何特定CE类的工厂类,请在Factory
内查找名为Document
或Folder
的内部类或所需的任何CE类。 在该内部类中,您只会找到一些适合于为该CE类实例化Java对象的方法。 很容易弄清楚这些工厂方法的参数是什么。 工厂方法在返回特定类型的意义上是类型安全的。 例如, Factory.Folder
方法每个都返回Folder
类型的对象。 通常,工厂方法和类型安全性背后的想法是减少编程错误的机会。 由于您不必使用强制转换,因此编译器很可能会遇到任何问题。 这比运行时类型检查更可取。
还有另一类类型安全的方法(在HelloDocument
中用得很少)。 用API行话来说,这些是商品方法 。 一个示例是ObjectStore.getObject
,它实际上可以返回任何独立的CE类的对象。 商品方法的目的是用于某些应用程序编码模式,这些模式以通用方式处理多种类型。 当您处理事先已知的特定类型时,通常没有太多使用商品方法的意义。
创建一个新Document
方法HelloDocument.createAndFileDocument
内的第一行代码为创建具有内容的新文档奠定了基础(请参见清单3)。 在这些代码行的末尾,已准备好您要执行的大多数操作的Java Document
实例,但是尚未创建CE Document
实例(因为尚未进行到服务器的必需往返操作) )。
清单3.内部HelloDocument.createAndFileDocument
,第1部分
//no R/T
ContentTransfer ct = Factory.ContentTransfer.createInstance();
ct.setCaptureSource(fis);
// optional
ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME);
// optional
ct.set_ContentType("application/octet-stream");
ContentElementList cel = Factory.ContentElement.createList();
cel.add(ct);
//no R/T
Document doc = Factory.Document.createInstance(os, null);
//not required
doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE);
doc.set_ContentElements(cel);
//no R/T
doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION);
清单3中的操作顺序似乎有些混乱,直到您了解CE对象模型,尤其是Document
及其内容之间的关系为止。 单个内容(例如,电子表格或文本文档)位于称为content元素的对象中。 更具体地说,由于内容与Document
一起存储在存储库中,因此您将使用一个称为ContentTransfer
的内容元素子类。 (当实际位存储在其他位置时,将使用另一个子类ContentReference
。) Document
可以具有任意数量的内容元素,并且由于它们是CE行话中的从属对象 (它们不能独立存在;它们不能独立存在;它们不能独立存在)。他们需要一个包含Document
), Document
属性ContentElements的类型为ContentElementList
。
现在,希望情况会更加清楚。 您可以使用工厂方法创建ContentTransfer
对象, ContentElementList
对象和Document
对象。 您将ContentTransfer
对象添加到ContentElementList
对象,然后从ContentElementList
设置Document
的ContentElements属性值。 这些事情的顺序具有一定的灵活性,因此,如果您觉得更有意义,则可以在自己的应用程序中重新排列它们。
此代码很好地说明了类型安全的属性访问器方法的使用。 例如,当设置内容元素的检索名称(一个可选属性,用作以后下载内容的应用程序的文件名提示)时,您调用ct.set_RetrievalName(ConfigInfo.LOCAL_FILE_NAME)
。 该方法仅接受字符串参数,因此如果您尝试使用整数值,则将是编译时错误。 相应的getter方法ContentTransfer.get_RetrievalName
也是类型安全的,并返回字符串值。 set_和get_的Java API命名约定(带有下划线)充当一个信号,使您知道您正在处理CE存储库意义上的属性,而不仅仅是一个典型的Java对象字段。 每个CE类上的每个系统定义的属性都有类型安全的访问器。 对于本质上为只读的属性,没有类型安全的setter方法。
DocumentTitle属性的设置看起来与其他setter方法调用有很大不同: doc.getProperties().putValue("DocumentTitle", ConfigInfo.DOCUMENT_TITLE)
。 那里发生了什么事? 许多人没有意识到DocumentTitle不是系统定义的属性(从CE服务器的角度来看,系统定义的属性是CE服务器为其创建值或影响CE服务器行为的属性,或两者兼而有之)。 相反,它是在ObjectStore
创建时通过AddOn
定义的,并且您很少看到没有DocumentTitle属性的ObjectStore
。 但是,由于它不是系统定义的属性,因此API中没有针对它的类型安全的访问器方法。 相反,您必须通过商品方法设置其值。
方法Document.getProperties
返回Properties
对象,表示为本地缓存属性值Document
和Properties
类包含了获取和设置属性值的商品的方法。 在任何实际的应用程序中,您肯定会处理自定义属性,因此,应确保您了解商品属性值访问的模型。
如前所述,即使在checkin
方法调用之后,仍未在CE存储库中创建Document
。 相反,据说它具有一个或多个挂起的动作。 暂挂操作是对象的内部标记,其中包含需要在服务器上执行的操作。 (API中提供了一些工具来检查挂起的动作,但是您可能永远不需要这样做。)当您调用factory方法创建Java Document
对象时,它会自动使用“ Create
挂起”动作对其进行标记。 当您调用checkin方法时,它添加了Checkin
未决操作。 人们有时会惊讶地发现,对于整个API中的所有类,只有18种类型的待处理操作。 该数量如此之低,因为CE中的许多操作实际上包括对对象和属性的常规CRUD(创建,检索,更新,删除)操作。 无论涉及哪个属性或类,更新属性值始终是通过“ Update
挂起操作或作为某些其他挂起操作类型的辅助来完成的。 您可以将未决操作视为对CE服务器的指示; 它们告诉CE服务器,当对象到达服务器端时该如何处理。
现在再仔细检查一下checkin
方法调用: doc.checkin(AutoClassify.DO_NOT_AUTO_CLASSIFY, CheckinType.MAJOR_VERSION)
。 它采用在单独的常量类中定义的两个参数。 这些用于参数值的常量类使用类型安全的枚举模式进行编码。 由于该API最初是为Java编写的,并且仍然可以在Java 1.4环境中运行,因此Java语言enum
不可用。 类型安全的枚举通过强制您使用适当的选项列表中的常量来提供其类型安全。 例如,Java编译器不允许您意外地将两个参数的顺序颠倒到checkin
方法。 这将导致编译时错误而不是运行时错误。 在没有这种类型的安全性的情况下,您可能会得到错误的行为,没有任何错误的迹象。
归档Document
稍后,本文将返回Folder
的实例化,但现在让我们看一下Document
到该Folder
中的归档。 API中有一些称为Folder.file
和Folder.unfile
。 许多人感到惊讶的是,这些方法实际上是辅助方法,而不是API的基础。 当然,在文件夹中进行归档的概念是一项基本的CE功能,但是归档的行为实际上是创建将Folder
和受管者链接在一起的关系对象之一。 无论出于何种原因,这种想法都使许多初学者有一个暂停的理由,因此清单4显示了如何在不调用Folder.file
方法的情况下归档Document
。
清单4.内部HelloDocument.createAndFileDocument
,第2部分
Folder folder = instantiateFolder(os);
//no R/T
DynamicReferentialContainmentRelationship rcr =
Factory.DynamicReferentialContainmentRelationship.createInstance(os, null,
AutoUniqueName.AUTO_UNIQUE,
DefineSecurityParentage.DO_NOT_DEFINE_SECURITY_PARENTAGE);
rcr.set_Tail(folder);
rcr.set_Head(doc);
rcr.set_ContainmentName(ConfigInfo.CONTAINMENT_NAME);
类DynamicReferentialContainmentRelationship
(DRCR)的名称很长,但是创建名称很容易。 它将文件夹链接到包含的文档。 如果将这种关系的图形视为从Folder
到Document
的箭头,则可以看到为什么将关系的OVP链接到Folder
称为Tail并将关系的OVP链接到Document
称为Head的原因 。
DRCR是动态的,因为它的Head属性是在目标Document
版本化时自动更新的。 要实例化DRCR,请调用适用的工厂方法,该方法使用“ Create
暂挂”操作创建一个Java实例,然后设置一些系统属性。 在这种情况下,使用AutoUniqueName.AUTO_UNIQUE指示CE服务器在发生冲突的情况下修改包含名称(包含名称在特定Folder
必须是唯一的)。 另一种选择是使操作失败并出现异常。
DRCR是可子类化的,因此有朝一日您可能会发现创建子类并添加自己的一些元数据属性很有用。 请记住,尚未在CE信息库中创建DRCR(实际上,也不是目标Document
)。
保存到存储库
现在,终于到了将内容实际写入CE存储库的时候了。 在Document
和DRCR上调用save方法很简单,但是为了保持最小化往返的主题,请通过UpdatingBatch
保存它们(参见清单5)。
清单5.内部HelloDocument.createAndFileDocument
,第3部分
UpdatingBatch ub = UpdatingBatch.createUpdatingBatchInstance(dom,
RefreshMode.REFRESH);
ub.add(doc, null);
ub.add(rcr, null);
System.out.println("Doing updates via UpdatingBatch");
ub.updateBatch();
如果将UpdatingBatch
视为将事物运送到CE服务器的存储桶, UpdatingBatch
概念上讲,它很简单。 您创建UpdatingBatch
实例,向其中添加对象,然后调用updateBatch
方法将所有内容发送到服务器。 当然,仅将具有更改的对象添加到CE存储库中才有意义。 除了最大程度地减少往返次数外, UpdatingBatch
还具有另一个重要特征。 UpdatingBatch
所有内容都作为服务器上的原子事务发生。 要么全部成功,要么全部失败。 这对于HelloDocument
无关紧要,但是找到需要这种事务行为的实际用例并不罕见。 UpdatingBatch
是一种以合理的性能成本获得它的好方法。
当创建UpdatingBatch
,我们提供了一个参数来告诉API将经过刷新的对象实例从往返返回到服务器。 通常,您不需要刷新对象(或者您将使用属性过滤器来限制刷新的大小),但是在这种情况下,我们希望使用包含名称来说明HelloDocument
其他内容。 由于我们指示服务器自动选择一个唯一的包含名称,因此它可能不同于我们与DRCR一起使用的名称。 刷新的属性值告诉我们CE服务器实际使用了什么。
实例化Folder
我们跳过了Folder
对象的实例化,因此让我们现在返回到该实例。 清单6显示了HelloDocument.instatiateFolder。
清单6. HelloDocument.instantiateFolder
private Folder instantiateFolder(ObjectStore os)
{
Folder folder = null;
try
{
//no R/T
folder = Factory.Folder.createInstance(os, null);
//no R/T
Folder rootFolder = Factory.Folder.getInstance(os, null, "/");
folder.set_Parent(rootFolder);
folder.set_FolderName(ConfigInfo.FOLDER_NAME);
//R/T
folder.save(RefreshMode.NO_REFRESH);
}
catch (EngineRuntimeException ere)
{
// Create failed. See if it's because the folder exists.
ExceptionCode code = ere.getExceptionCode();
if (code != ExceptionCode.E_NOT_UNIQUE)
{
throw ere;
}
System.out.println("Folder already exists: /" + ConfigInfo.FOLDER_NAME);
//no R/T
folder = Factory.Folder.getInstance(os, null, "/" + ConfigInfo.FOLDER_NAME);
}
return folder;
}
instantiateFolder
方法的instantiateFolder
有点人为,因为我们希望让它无需多次设置即可轻松运行HelloDocument
多次。 我们所做的选择是尝试创建Folder
,如果失败,则退回到无提取实例。 您可能不会在实际的应用程序中这样做,因为您几乎总是在以低效的方式进行操作。 作为替代方案,您可以对Folder
进行真正的提取,如果不存在,则退回到创建Folder
。 这仍然需要往返来检查Folder
是否存在(您的应用程序可能大多数时候都知道存在)。 因此,就性能而言,最好的解决方案可能是假设该Folder
存在于存储库中,以无提取方式实例化Folder
。 这确实意味着您需要在以后的代码中调整错误处理,以解决实际上不存在的可能性。 (我们不想在错误处理代码中使这个例子变得复杂。)
阅读内容
本文并没有花太多时间描述从存储库中读取内容的过程,因为大多数逻辑只是普通的Java流处理。 取而代之的是,我们将从清单7开始重点说明几点。
清单7.内部HelloDocument.readAndCompareContent
String fullPath = "/" + ConfigInfo.FOLDER_NAME + "/" + containmentName;
System.out.println("Document: " + fullPath);
//no R/T
Document doc = Factory.Document.getInstance(os, null, fullPath);
//R/T
doc.refresh(new String[] {PropertyNames.CONTENT_ELEMENTS});
InputStream str = doc.accessContentStream(0);
只是为了证明它是可以做到的,从配置值和从CE服务器返回的实际包含名称构建了到Document
的完整路径。 您可以轻松地从刷新中获取已创建Document
的ID值,并使用该ID实例化Document
。 (由于CE服务器必须将路径转换为ID,因此使用ID比使用路径稍微更有效。)
通过工厂方法无休止地实例化Document
,然后立即调用refresh方法以获得ContentElements属性的值。 在这种情况下,主要是为了显示刷新调用。 通过工厂的fetchInstance
实例化Document
会产生相同的性能效率,尤其是如果使用适当的属性过滤器来限制获取的数据时,尤其如此。 Document.accessContentStream
方法是用于在适用的依赖于ContentTransfer
对象上调用accessContentStream
的便捷方法。
认证方式
本节返回被掩盖的HelloDocument
的较早部分。 尽管本文没有详细介绍身份验证,但确实描述了HelloDocument
的身份验证代码在做什么。
在CE模型中,身份验证完全委托给JAAS。 这意味着您永远不会真正登录到CE API或CE。 相反,CE API希望您在执行任何CE调用之前已执行JAAS登录序列。 这种方法的一个优点是,CE API不需要将可能用于身份验证的各种方法编码到其中。 借助JAAS的可插入体系结构,您可以使用传统的用户ID /密码方案,指纹读取器,手持式身份验证器小工具以及许多其他技术。 无论您如何进行身份验证,您的CE API应用程序代码都保持不变。
使用CE API帮助器方法进行身份验证
查看清单8,并注意HelloDocument.main
与身份验证相关的顶级代码。 本文返回到loginAndRun方法的主题,但现在来看一下else子句。
CE API UserContext类具有一些与身份验证相关的便捷方法。 严格提供这些信息是为了使它在限于传统用户ID /密码方案的遗留系统中易于进行身份验证。 使用UserContext.createSubject
方法创建JAAS Subject
的实例。 UserContext
对象还可以维护JAAS Subject
的堆栈,其中最高的Subject
实际上用于CE往返。 UserContext.pushSubject
和popSubject
方法实现了标准的堆栈范例。 堆栈本身存储在线程本地存储中,这意味着它与特定的执行线程相关联。 请注意, popSubject
调用位于finally
块内,以确保无论发生什么其他事件都将其调用。 那很重要。 尽管对于独立的HelloDocument
应用程序无关紧要,但是在J2EE应用程序中HelloDocument
重要,在J2EE应用程序中,线程池通常用于服务请求。 您永远都不知道最终会在哪里重用代码,因此您应该养成以正确方式进行编码的习惯。 完成CE调用后,您想清除线程的身份验证上下文。
清单8. HelloDocument
身份验证方法
public static void main(String[] args) throws LoginException
{
System.out.println("CE is at " + ConfigInfo.CE_URI);
System.out.println("ObjectStore is " + ConfigInfo.OBJECT_STORE_NAME);
HelloDocument fd = new HelloDocument();
if (ConfigInfo.USE_EXPLICIT_JAAS_LOGIN)
{
loginAndRun(fd, ConfigInfo.USERID, ConfigInfo.PASSWORD);
}
else
{
// This is the standard Subject push/pop model for the helper methods.
Subject subject = UserContext.createSubject(fd.conn, ConfigInfo.USERID,
ConfigInfo.PASSWORD, ConfigInfo.JAAS_STANZA_NAME);
UserContext.get().pushSubject(subject);
try
{
fd.run();
}
finally
{
UserContext.get().popSubject();
}
}
}
标准JAAS认证
如果不使用UserContext.pushSubject
激活用于CE往返的Subject
,会发生什么? 在这种情况下,CE API内部人员会寻找环境 JAAS Subject
。 这里所说的环境是指一个Subject
已经与线程通过标准JAAS机制有关。 例如,在Web应用程序中,您可以登录到J2EE Web容器。 在HelloDocument
,登录是作为loginAndRun
方法的一部分显式完成的。
清单9. loginAndRun
private static final void loginAndRun(HelloDocument fd, String userid,
String password) throws LoginException
{
LoginContext lc = new LoginContext(ConfigInfo.JAAS_STANZA_NAME,
new HelloDocument.CallbackHandler(userid, password));
lc.login();
Subject subject = lc.getSubject();
Subject.doAs(subject, fd);
}
如清单9所示, loginAndRun
方法非常简单。 它实现了标准的JAAS登录序列,尽管此清单未显示内部类HelloDocument.CallbackHandler
的实现,因为它也是标准的JAAS票价。 不幸的是,在这种简单性上有一个狡猾的欺骗。 清单9的最后一行中的Subject.doAs
方法调用是特定于每个J2EE应用程序服务器的。 这个细节在J2EE规范中没有完全解决,因此每个供应商都必须按照自己的意愿实施它。 J2EE规范可能最终将解决此问题,因此您将不必总是在登录调用中具有特定于供应商的逻辑。 当然,如果使用真正的J2EE客户端而不是胖客户端,则很有可能使用J2EE容器进行身份验证并让该容器处理细节。
结论
本文向您介绍了完整的HelloDocument
应用程序。 您了解了如何连接到ObjectStore
,如何创建Folder
, Document
和其他类型的对象,如何读取和设置属性值以及如何上载和下载内容。 尽管这些东西仍然构成相当简单的应用程序,但是它们引入了可用于很多东西的编码模式。
如果您刚开始编写P8代码,则可以使用HelloDocument
作为起点。 可通过本文随附的链接下载完整的源文件。 您可以通过将其更改为尝试各种方法来进行试验。 实际上,如果您想快速生成一个小型应用程序,那么您将不是第一个在修改后的HelloDocument.java副本之上构建它的人。 去吧!
祝您玩得开心,请继续关注本系列的更多文章!
翻译自: https://www.ibm.com/developerworks/data/library/techarticle/dm-0810carpenter/index.html