ITPub博客

首页 > Linux操作系统 > Linux操作系统 > [转载]在 Apache 目录服务器中存储 Java 对象,第 2 部分

[转载]在 Apache 目录服务器中存储 Java 对象,第 2 部分

原创 Linux操作系统 作者:dinner1007 时间:2019-06-25 12:51:03 0 删除 编辑

在 Apache 目录服务器中存储 Java 对象,第 2 部分


在第 2 部分中将介绍如何在 Apache 目录服务器 (ApacheDS) 中存储 Java™ 对象,Bilal Siddiqui 将提供 9 个示例应用程序,演示在 第 1 部分 中学习的概念。除了介绍使用 ApacheDS 存储、搜索、检索和修改 Java 对象的所有步骤之外,Bilal 还将在总结全文时提供一个可重用的 Java 类,该类可以使用 LDAP 模式组件在 ApacheDS 中将这些功能组合在一起。

第 1 部分 中,我介绍了在 ApacheDS 中存储 Java 对象的概念方面的基础,解释了 ApacheDS 的核心架构,并讨论了它实现目录服务和可插入协议支持的方式。我还介绍了一些 LDAP 概念和术语,解释了 ApacheDS 如何实现 LDAP 协议,并介绍了用来在 ApacheDS 中存储和操纵对象的各种组件。最后,讨论了 Java 对象和 RMI 的基础,要想在 ApacheDS 中存储和检索 Java 对象,就必须理解它们。我还引入了一个示例应用程序 —— 一个面向制造企业的数据管理系统,并用它演示文中讨论的一些概念。

在本系列的第 2 部分,我几乎完全依靠示例(总共有 9 个示例)。这些示例基于第 1 部分介绍的数据管理系统,它们的作用是让您了解如何在 ApacheDS 中存储、搜索、检查和更新 Java 对象。

如果还没有下载和安装 ApacheDS,那么一定要在开始之前 下载和安装 ApacheDSJXplorer。可以下载 文章的完整源代码

注意!

请注意,要跟上本文中的示例,必须理解基本的 LDAP 术语和概念,例如专有名称 (DN)、相对专有名称 (RDN)、命名上下文、对象类和属性类型。如果还不熟悉这些术语,请在继续之前阅读 第 1 部分

应用程序 1. 存储 Java 对象

我先从几个应用程序开始,演示如何在 ApacheDS 中存储 Java 对象。出于这个目的,需要使用 Java 命名和目录接口 (JNDI),它提供了操作目录中的对象和属性的接口和方法。请参阅 在 Apache 目录服务器中存储 Java 对象,第 1 部分,获得 ApacheDS 如何使用 JNDI 接口公开目录服务的讨论。

JNDI 不是特定于 LDAP 的接口,因此可以拥有针对任何目录服务类型的 JNDI 实现。如果想实现自己的目录服务并用 JNDI 公开它的功能,则需要为目录服务实现 JNDI 接口。注意,Java 2 标准版 (J2SE) 提供了 LDAP 的客户端 JNDI 实现,可以用它与 ApacheDS 对话。在我的讨论中,我将使用这个客户端实现。

清单 1 是一个名为 StoreAlicePreferences 的简单应用程序。我将用这个应用程序介绍如何将用户 Alice 的选项作为 Java 对象存储到 ApacheDS 中。


清单 1. StoreAlicePreferences
public class StoreAlicePreferences {

public StoreAlicePreferences ()
{
try {
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//------------------------------------------
//Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//------------------------------------------
//Step3: Instantiate a Java object
//------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();

//------------------------------------------
//Step4: Store the Java object in ApacheDS
//------------------------------------------
String bindContext = "cn=preferences,uid=alice,ou=users";
ctx.bind( bindContext, preferences);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
StoreAlicePreferences storeAlicePref = new StoreAlicePreferences();
}
}

从清单 1 中的注释中可以看出,将 Java 对象(即 Alice 的选项)存储到 ApacheDS 中包括 4 个步骤。后面几节将详细讨论每个步骤。




回页首


步骤 1. 设置 ApacheDS 的 JNDI 属性

清单 1 中的第一步是将 ApacheDS 的属性文件读入 Properties 对象。这意味着首先必须将 JNDI 属性写入单独的属性文件,如清单 2 所示:


清单 2. ApacheDS.properties 文件
java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url=ldap://localhost:389/ou=system
java.naming.security.authentication=simple
java.naming.security.principal=uid=admin,ou=system

之所以需要单独的属性文件,是因为使用客户端 JNDI 实现的应用程序可能独立于特定的 JNDI 实现而工作。指定的 Java 应用程序(例如 StoreAlicePreferences)应当能够操作 ApacheDS 或其他任何目录服务。目录服务甚至不需要使用 LDAP。

考虑一个简单的场景,属性文件的价值就会变得很清楚。假设您已经开发了一个 Java 应用程序,该应用程序使用 LDAP 的客户端 JNDI 实现与 ApacheDS 进行对话。后来,您又购买了另一个不使用 LDAP 协议而使用其他协议的目录服务器。

在这种情况下,需要一个能够操作新目录服务器的新的客户端 JNDI 实现。Java 程序程序将在不需要任何重新编码的情况下继续工作,只需要更新属性文件来反映新目录服务器的属性即可。

虽然在应用程序中对属性进行硬编码不会有什么编程问题,但是这么做会让应用程序依赖那些您已对其进行了硬编码的特定实现。这有点违背使用 JNDI 的目的,它的目的是保持独立于特定的实现。

属性文件的细节

现在查看 清单 2 中所示的 ApacheDS.properties 文件。属性文件由许多名称-值对组成,每个名称代表一个属性。

在实例化公开名为 Context 的 JNDI 接口的对象时,会使用这些属性。Context 实际上是 JNDI 中最重要的接口。这个接口定义了操作命名上下文的方法。(在 第 1 部分 中的示例应用程序中,我介绍了命名上下文的概念。)

例如,Context 接口中定义的一个重要方法是 bind(),它将 Java 对象绑定到命名上下文。将对象绑定到命名上下文意味着要用特定名称将对象存储在目录的特定上下文中。稍后我将演示如何使用 bind() 方法。首先,我们来查看属性文件中的名称-值对。

名称-值对

清单 2 中的第一个名称-值对是 java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory,它设置了 JNDI 属性 java.naming.factory.initial。java.naming.factory.initial 属性指定了用作 JNDI 客户端实现的一部分的对象工厂的名称。这个对象工厂创建了 Context 对象,可以将这个对象与 ApacheDS 中的命名上下文一起使用。

因为使用基于 LDAP 的 JNDI 实现,所以可以指定 com.sun.jndi.ldap.LdapCtxFactory 作为这个属性的值。正如您可以猜到的,com.sun.jndi.ldap.LdapCtxFactory 类构建的 Context 对象能够根据 LDAP 协议与 ApacheDS 进行通信。

清单 2 中的第二个名称-值对是 java.naming.provider.url=ldap://localhost:389/ou=system。java.naming.provider.url 属性指定了要处理的完整目录上下文的 URL。

完整目录上下文包含两个组件:一个组件是 URL,ApacheDS 正在其指示的位置上进行侦听,另一个组件是命名上下文,ApacheDS 在该上下文中工作。字符串 ldap://localhost:389/ 指定 ApacheDS 在其指示的位置上进行侦听的 URL。字符串的其余部分(ou=system)指定要处理的命名上下文。

第三个名称-值对是 java.naming.security.authentication=simple。这个属性指定 ApacheDS 进行用户身份验证时使用的安全强度。这个属性可以是以下三个值之一:nonesimplestrong

  • 如果选择 “none”,则 ApacheDS 不使用身份验证,任何人都可以不指定口令就进行登录。
  • 如果选择 “simple”,则 ApacheDS 采用基于口令的简单身份验证,这意味着口令以明文方式在网络上传递。
  • 如果选择 “strong”,那么用户口令以散列值形式(而不是明文形式的实际口令)传递给 ApacheDS 进行身份验证。

ApacheDS 的当前版本不支持 “strong” 级的身份验证。

清单 2 中的第四个名称-值对是 java.naming.security.principal=uid=admin,ou=system。这个属性指定了要登录到 ApacheDS 的用户的 DN。(我使用 ApacheDS 管理员的 DN(uid=admin,ou=system)作为这个属性的值。)

我们已经在 清单 2 中查看了 ApacheDS.properties 文件中的 4 个名称-值对。现在再来看一下 清单 1 中的步骤 1 ,在这一步骤中,将把属性文件读入 Properties 对象中。不久,在处理 JNDI 时将会使用这个 Properties 对象。

设置用户口令

还需要在 Properties 对象中包含用户的口令。但是真正的应用程序通常不会在配置文件中存储用户口令;它会通过 GUI 接收用户的口令。在 清单 1 中,我将口令设为 java.naming.security.credentials 属性的值。这个属性实际上接收一个证明用户身份的凭证。可能有多种凭证(例如,口令或 Kerberos 票据);对于本文,我使用基于口令的身份验证。

所有属性均已设置,可以使用 Properties 对象了。




回页首


步骤 2. 获得 DirContext 对象

接下来,实例化叫做 InitialDirContext 的类。这个类是 JNDI 的一部分,该类用于公开叫做 DirContext 的接口。InitialDirContext 的构造函数接受上面讨论的 Properties 对象。

InitialDirContext 对象能够执行您想在 ApacheDS 上执行的所有目录操作,包括存储新对象、搜索已经存储的对象、向现有对象添加属性,等等。

DirContext 接口

DirContext 接口扩展了 Context 接口。Context 接口表示命名上下文,DirContext 接口则提供与添加、删除和管理命名上下文有关的属性的功能。

简言之,Context 接口提供命名功能,DirContext 接口扩展命名功能,添加对属性的支持。命名和属性功能共同构成了目录服务

您可能会说 InitialDirContext 对象是由工厂对象实例化的 DirContext 对象的包装器。在这个示例中,InitialDirContext 的构造函数使用了 清单 2 的第一个属性指定的上下文工厂对象(即 com.sun.jndi.ldap.LdapCtxFactory)。工厂对象实例化了一个公开 DirContext 对象的对象,而 InitialDirContext 对象则使用这个 DirContext 对象执行客户机应用程序要求的目录操作。

使用 ApacheDS 的优势

ApacheDS 目录服务的主要优势在于:让客户机应用程序独立于任何特定实现。客户机应用程序在配置文件中指定工厂方法,InitialDirContext 对象用工厂方法实例化了 DirContext 对象,该对象包含处理远程目录服务通信所需的所有逻辑。

例如,清单 1 使用了来自 Sun Microsystem 的 com.sun.jndi.ldap.LdapCtxFactory 工厂对象。这个工厂对象创建的 DirContext 对象能够制定 ApacheDS 可以理解的 LDAP 请求。

如果以后想使用一些非 LDAP 服务运行 StoreAlicePreferences 应用程序(来自 清单 1),那么只需根据非 LDAP 服务的业务逻辑,用新的工厂对象交换 清单 2 中的工厂对象的名称即可。然后 StoreAlicePreferences 就可以开始使用非 LDAP 服务了。




回页首


步骤 3. 实例化 Java 对象

接下来将实例化叫做 MessagingPreferences 的类,如清单 3 所示,这个类代表 Alice 的消息传递选项。(请回忆一下第 1 部分中对 消息传递选项 的讨论。)


清单 3. MessagingPreferences 类
public class MessagingPreferences extends 
Preferences implements java.io.Serializable {
static final long serialVersionUID = -1240113639782150930L;

//Methods of the MessagingPreferences class
}

现在您还可以调用 MessagingPreferences 类的方法来设置 Alice 的选项。

在清单 3 中,MessagingPreferences 类实现了 Serializable 接口(第 1 部分的 “序列化 Java 对象” 一节中介绍过),在继续进行后面的内容之前,我将简要讨论一下叫做 serialVersionUID 的内容。

获得 serialVersionUID

建议让所有可序列化的类都包含类型为 long、名为 serialVersionUID 的私有静态数据成员。不需要在可序列化类中到处使用这个数据成员。Java 运行库在序列化和反序列化期间使用这个数据成员。

Java 对象序列化规范(请参阅 参考资料)指定了一个复杂的算法来计算 serialVersionUID 的值。该算法使用了可序列化的名称、类实现的所有接口的名称、可序列化类的所有数据成员,等等。不需要考虑这个复杂算法的细节;Java 平台提供了叫做 serialver 的工具,该工具也可以计算这个值。

要为 MessagingPreferences 对象建立 serialVersionUID,可以从命令行按如下所示方式使用 serialver 工具:

X:jdk1.5inserialver MessagingPreferences

正如您可以看到的,我在 清单 3 中已经为 MessagingPreferences 类实现了同样的操作。




回页首


步骤 4. 在 ApacheDS 中存储 Java 对象

现在已经设置了 DirContext 对象和 MessagingPreferences 对象,可以继续后面的步骤了。剩下的是用 DirContext 对象在 ApacheDS 中存储 MessagingPreferences。

在 LDAP 服务器中存储数据条目叫做绑定 操作。Context 接口有一个方法叫做 bind(),可以用它在 ApacheDS 中存储 Java 对象。在 清单 1 的步骤 4 中可以看到 bind() 的用法。

设置 Context.bind() 的参数

Context.bind() 方法采用了两个参数。第一个参数(cn=preferences,uid=alice,ou=users,ou=system)指定存储 Java 对象的命名上下文。这个命名上下文可以分成两部分:cn=preferences 和 uid=alice,ou=users,ou=system,中间用逗号分隔。

因为新条目表示 Alice 的视图选项,所以可以用 cn=preferences 作为它的 RDN。注意,字符串 uid=alice,ou=users,ou=system 与 Alice 的数据条目的 DN 相同,第一次看到它是在第 1 部分的 “创建 RDN” 一节中。

基于这些内容,新条目的 DN 是 cn=preferences,uid=alice,ou=users,ou=system,它是传递给 bind() 方法的第一个参数的值。

Context.bind() 方法调用的第二个参数是步骤 3 中的 MessagingPreferences 对象。bind() 方法调用不返回任何结果。




回页首


运行第一个应用程序!

我把前面介绍的四个步骤组合在 清单 1 所示的一个应用程序 StoreAlicePreferences 中。在本文的 源代码 中也可以找到一些示例应用程序。

在运行 StoreAlicePreferences 应用程序之前,必须在 ApacheDS 中存储一个 DN 等于 uid=alice,ou=users,ou=system 的条目。在第 1 部分的 “创建 RDN” 小节中,我们已经创建了名为 Alice 的用户。

在运行 StoreAlicePreferences 应用程序之后,可以通过在 LDAP 浏览器中(在这个示例中是 JXplorer)展开 Alice 条目,确定已经将 Alice 的消息传递选项存储为 Java 对象。您应当看到 Alice 展开的视图,如图 1 所示:


图 1. Alice 的消息传递选项已经存储!

应用程序说明

图 1 所示的 MessagingPreferences 对象包含三个属性。这三个属性是 javaClassName、javaClassNames 和 javaSerializedData,在第 1 部分的 “在 ApacheDS 中存储 Java 对象” 小节中已经讨论过它们。

在 StoreAlicePreferences 应用程序中(在步骤 4)的 bind() 方法调用上,我没有包含这些属性,所以您可能想知道它们是怎么到达 ApacheDS 的。答案是:bind() 方法自己编写了这些属性!带两个参数的 Context.bind() 方法不接受任何属性。但是,正如第1 部分的 “在 ApacheDS 中存储 Java 对象” 一节中解释的那样,LDAP 需要 javaClassName、javaClassNames 和 javaSerializedData 属性。所以 Context.bind() 方法自己编写了这些属性。

下一节将介绍带有三个参数的 bind() 方法,该方法采用了一组参数,并将它们与 Java 对象存储在一起。

图 1 所示的 MessagingPreferences 对象使用了 javaContainer 对象类。第 1 部分的 “在 ApacheDS 中存储 Java 对象” 一节中已讨论了这个类。如果您愿意,不使用 javaContainer 类也能把 Java 对象写入 ApacheDS,就像下面的示例应用程序演示的那样。




回页首


应用程序 2. 存储带有属性的 Java 对象

在这个示例中,将学习如何用前面提到过的带三个参数的 bind() 方法将属性添加到 Java 对象中。参见清单 4 中叫做 StoreBobPreferences 的示例应用程序。这个应用程序为名为 Bob 的用户创建了一个条目,并且还在该条目中存储 Bob 的选项(即属性),它用一个操作就完成了这两个步骤。


清单 4. StoreBobPreferences
public class StoreBobPreferences{

public StoreBobPreferences ()
{
try {

//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "apacheds.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//----------------------------------------------
//Step2: Fetching a DirContext object
//----------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//----------------------------------------------
//Step3A: Instantiate a Java Object
//----------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();

//----------------------------------------------
//Step3B: Instantiate BasicAttribute object
//----------------------------------------------
Attribute objclass = new BasicAttribute("objectClass");

//----------------------------------------------
//Step3C: Supply value of attribute
//----------------------------------------------
objclass.add("person");

//----------------------------------------------
//Step3D: Put the attribute in attribute collection
//----------------------------------------------
Attributes attrs = new BasicAttributes(true);
attrs.put(objclass);

//----------------------------------------------
//Step4: Store the Java object in ApacheDS
//----------------------------------------------
String bindContext = "uid=Bob,ou=users";
ctx.bind( bindContext, preferences, attrs);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
StoreBobPreferences storeBobPref = new StoreBobPreferences();
}
}

清单 4 中的大部分内容包含的步骤与 清单 1 相同,区别在于步骤 3 中有一些额外的代码,我将在下面对此进行解释。(注意,清单 4 中的步骤 3A 与 清单 1 中的步骤 3 相同,所以我将从步骤 3B 开始。)

步骤 3B. 实例化 BasicAttribute

在 StoreBobPreferences 应用程序与 StoreAlicePreferences 有所不同的第一个步骤中,实例化一个名为 BasicAttribute 的 JNDI 类,该类公开了一个叫做 Attribute 的 JNDI 接口。Attribute 接口可以表示 LDAP 数据条目的单一属性。BasicAttribute 类提供了 Attribute 接口的基本实现(功能有限)。鼓励大家让自己的应用程序和实现拥有自己的 Attribute 接口实现(通过扩展 BasicAttribute 类);但在这里,BasicAttribute 类提供了足够本文演示目的的功能。

BasicAttribute 构造函数采用属性名称作为参数。请注意 清单 4 中的步骤 3B,我构建的第一个 BasicAttribute 对象使用 objectClass 作为参数。这意味着 BasicAttribute 对象表示名为 objectClass 的属性。

对于想添加到 Bob 的数据条目中的每个属性,都要实例化一个 BasicAttribute 类。

步骤 3C. 为每个属性提供值

当您为想要包含在 Bob 数据条目中的每个属性提供一个 BasicAttribute 对象时,还要为每个属性提供值。要提供值,则需要调用 Attribute 接口的 add() 方法。add() 方法只采用了一个参数,该参数是想要提供的值的字符串形式。

add() 方法接受的实际是 Java Object 类的实例。因为所有 Java 对象都扩展自 Object 类,所以可以将字符串值传递给 add() 方法。如果某些属性是多值的,那么可以多次调用属性的 add() 方法来提供必要的值。

步骤 3D. 创建属性集合

您已经有了所有属性,现在需要将它们放入 Attribute 对象集合中。JNDI 提供了一个叫做 Attributes 的接口,并在叫做 BasicAttributes 的类中提供了该接口的基本实现。您可以实例化一个 BasicAttributes 对象,并多次调用对象的 put() 方法,将所有 Attribute 对象(一次一个地)放入集合中。

清单 4 的步骤 4 所示,接下来将调用带有三个参数的 bind() 方法。带有三个参数的 bind() 方法与 清单 1 中使用的带有两个参数的该方法类似,第三个参数是刚刚创建的属性集合。




回页首


应用程序 3. 存储编组的 Java 对象

在存储 Java 对象的最后这个练习中,我将介绍如何存储编组的 Java 对象,第 1 部分的 “表示编组 Java 对象” 小节中已经简要讨论过它。

您打算以编组形式存储的 Java 对象应该是可序列化的(就像在 清单 1 的步骤 3 中创建的可序列化的 MessagingPreferences 对象)。为了进行演示,我采用了同一个 MessagingPreferences 对象来演示如何编组对象并将它存储在 ApacheDS 中。

首先请参见清单 5 中的 StoreAlicePreferencesInMarshalledForm 应用程序,它演示了在 ApacheDS 中存储已编组 Java 对象的所有步骤:


清单 5. StoreAlicePreferencesInMarshalledForm
public class StoreAlicePreferencesInMarshalledForm {
public StoreAlicePreferencesInMarshalledForm ()
{
try {
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//------------------------------------------
//Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//---------------------------------------------
//Step3: Instantiate a Java Object
//---------------------------------------------
MessagingPreferences preferences = new MessagingPreferences();
MarshalledObject mObj= new MarshalledObject(preferences );

//--------------------------------------------
//Step4: Storing Java object in ApacheDS
//--------------------------------------------
String bindContext = "cn=marshalledPreferences,uid=alice,ou=users";
ctx.bind( bindContext, mObj);
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
StoreAlicePreferencesInMarshalledForm storeAlicePref =
new StoreAlicePreferencesInMarshalledForm();
}
}

J2SE 将内部处理编组过程,所以清单 5 中的应用程序非常类似 清单 1 所示的 StoreAlicePreferences 应用程序。如果比较两个应用程序,就会看到 清单 5 的步骤 3 中 只有一行额外代码。在实例化 MessagingPreferences 对象后,还要实例化一个 java.rmi.MarshalledObject,将 preferences 对象传递给 java.rmi.MarshalledObject 构造函数。然后 java.rmi.MarshalledObject 类将处理编组过程,并拥有 MessagingPreferences 对象的编组版本。

在步骤 4 中,只存储(或绑定)了编组对象而不是原来的 MessagingPreferences 对象。

这一节的内容结束了在 ApacheDS 中存储 Java 对象的讨论。现在来看两个示例,它们将演示在 ApacheDS 中进行的数据搜索。




回页首


应用程序 4. 搜索存储的数据

先从 ApacheDS 上的一个简单搜索开始。假设在 ApacheDS 中有许多用户。而您想要找到用户 Alice 的所有详细信息。下面列出了关于 Alice 的所有已知事项:

  • Alice 是一名用户,所以在用户的数据组织单元中应当可以查找她的数据条目。(在第 1 部分已经介绍了组织单元或 “ou” 的概念。)
  • Alice 的用户名是 “alice”(不区分大小写)。
  • Alice 是一个人,所以她的数据条目使用的对象类必须直接或间接地扩展 person 对象类。

现在请参见清单 6,它显示了名为 SearchForAlice 的应用程序。SearchForAlice 演示了一个非常简单的搜索场景;后面我将扩展这个应用程序,使其适合更高级的搜索场景。


清单 6. SearchForAlice
public class SearchForAlice {
public SearchForAlice() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";

//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute uid = new BasicAttribute("uid");
Attribute objclass = new BasicAttribute("objectClass");

//adding attribute values
uid.add("Alice");
objclass.add("person");

//Instantiate Attributes object and put search attributes in it.
Attributes attrs = new BasicAttributes(true);
attrs.put(uid);
attrs.put(objclass);

//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);

if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();

//Step 8:
String entryRDN = sr.getName();
System.out.println("RDN of the Searched entry: "+entryRDN);

//Step 9:
Attributes srAttrs = sr.getAttributes();

if (srAttrs != null) {
//Step 10:
for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;)
{
Attribute attr = (Attribute) e.nextElement();

//Step 11:
String attrID = attr.getID();
System.out.println("Attribute Name: "+attrID);
System.out.println("Attribute Value(s):");

NamingEnumeration e1 = attr.getAll();
while (e1.hasMore())
System.out.println(" "+e1.nextElement());
}//for()
}//if (srAttrs)
}
}//if (ne != null)

} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
SearchForAlice searchAlice = new SearchForAlice();
}
}

清单 6 中的搜索应用程序包含 11 个步骤。前两个步骤您应该可以从 清单 1 中回想起来,即装入 JNDI 属性并实例化一个 DirContext 对象。

可以回想一下,在讨论 清单 1 的步骤 1 中名为 java.naming.provider.url 的 JNDI 属性时,我提到过供应者的 URL 包含两个组件,其中一个是要操作的目录上下文。您可能已经注意到 清单 4 中 java.naming.provider.url 的值是 ou=system。ou=system 字符串可在其中进行工作的目录上下文。所以,所有搜索操作实际上都是在这个目录上下文中执行的。

因为要在这个示例中执行搜索操作,所以可以把 ou=system 目录上下文叫做搜索上下文。现在来看一下搜索应用程序的剩余步骤:

  • 步骤 3. 缩小搜索上下文:已知 Alice 是一个用户,所以不必在整个 ou=system 搜索上下文中搜索 Alice,只需在用户的组织单元(即 ou=users)中搜索即可。
  • 步骤 4. 创建搜索属性:将关于 Alice 的已知信息作为搜索属性。因为知道 Alice 的 uid 和对象类,所以可以创建一个只有两个属性的集合:uid 和 objectClass。可以在 清单 6 的步骤 4 看到这些。(从 第 1 部分 的讨论您应该可以想起 uid 是 RDN 的组件,而不是属性。但在指定搜索参数时,JNDI 要求指定 uid 值,就像它是属性一样。)
  • 步骤 5. 执行搜索:在这里调用 清单 6 的步骤 2 得到的 DirContext 对象的 search() 方法。search() 方法采用了两个参数:第一个参数是在练习的第 3 步创建的搜索上下文,第二个参数是第 4 步的两个属性的集合。search() 方法返回 NamingEnumeration 对象,其中包含搜索结果。

步骤 1 到 5 设置搜索操作。其余的步骤处理 NamingEnumeration 对象并提取搜索结果。

  • 步骤 6. 提取搜索结果清单 6 的步骤 6 中的 NamingEnumeration 对象包含搜索结果集合。集合中的每个搜索结果都由一个 SearchResult 对象表示。要提取单个的搜索结果,只需在 NamingEnumeration 对象上进行迭代即可。
  • 步骤 7. 处理单个搜索结果:注意,每个搜索结果都包含关于单一数据条目的信息。可以从 SearchResult 对象得到数据条目的两部分信息(即 RDN 和它的所有属性)。
  • 步骤 8. 调用 getName():SearchResult 对象的 getName() 方法返回所搜索条目的 RDN。Alice 的 RDN 是 uid=alice。
  • 步骤 9. 调用 getAttributes():SearchResult 对象的 getAttributes() 方法返回 Attributes 对象,它包含与所搜索条目相关的所有属性值。Attributes 对象表示的属性集合与 清单 6 的步骤 4 中创建的属性集合类似。
  • 步骤 10. 调用 getAll():Attributes 对象的 getAll() 方法返回一个枚举,其中包含集合中的所有属性。
  • 步骤 11. 处理属性:最后,从集合中取出一个属性,并调用其 getID() 和 getAll() 方法。getID() 方法以字符串形式返回属性的名称。getAll() 方法以枚举的形式返回所有属性值。



回页首


应用程序 5. 按名称搜索

在前面的搜索示例中,我们已查看了如何在知道用户 uid 时搜索用户。在这个示例中,将学习如何修改应用程序,用 Alice 的用户名而不是 uid 来搜索她。

回忆一下 第 1 部分的图 18,在该图中,用户名存储为用户数据条目的 cn 属性的值。所以,在这个应用程序中,您将搜索 cn 属性,如清单 7 所示:


清单 7. SearchForAliceByCN
public class SearchForAliceByCN {
public SearchForAliceByCN() {
try
{
//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";

//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");

//putting attribute values
cn.add("Alice");
objclass.add("person");

//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);

//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);

if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();

//Step 8:
String entryRDN = sr.getName();

//Step 9:
Attributes srAttrs = sr.getAttributes();

if (srAttrs != null) {
//Step 10:
for (Enumeration e = attrs.getAll() ; e.hasMoreElements() ;)
{
Attribute attr = (Attribute) e.nextElement();

//Step 11:
String attrID = attr.getID();
System.out.println("Attribute Name: "+attrID);
System.out.println("Attribute Value(s):");

NamingEnumeration e1 = attr.getAll();
while (e1.hasMore())
System.out.println(" "+e1.nextElement());
}//for()
}//if (srAttrs)
}
}//if (ne != null)

} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
SearchForAliceByCN searchAlice = new SearchForAliceByCN();
}
}

SearchForAliceByCN 应用程序显示了用 Alice 的用户名搜索她的步骤。应用程序与前面的 SearchForAlice 应用程序非常相似,只有一个区别。在 清单 6 的步骤 4 中,我们为便于搜索创建了一个由 uid 和 objectClass 属性组成的集合。而在这个 应用程序的步骤 4 中,创建的是 cn 和 objectClass 属性的集合。

关于匹配规则

搜索 cn 有一个要点需要注意。在 第 1 部分的图 13 可以看到,cn 属性类型有一个叫做 SUBSTR 的字段,它定义了子字符串匹配的匹配规则。

在 cn 属性的示例中,SUBSTR 字段的值是 caseIgnoreMatch,所以在搜索 cn 属性的特定值时,即使搜索的名称只与 cn 属性值的子字符串匹配,也认为匹配成功。而且,子字符串匹配是不区分大小写的。

所以,如果搜索 “alice”,那么所有名字、中间名或姓为 “Alice” 的用户都会包含在搜索结果中。




回页首


应用程序 6. 反序列化 Java 对象

前面已经看到了如何在 ApacheDS 中存储 Java 对象并搜索与存储的对象有关的属性。现在将学习如何搜索和反序列化 Java 对象。反序列化是序列化的反向操作,在这里将根据 Java 对象的序列化形式创建一个 Java 对象。

清单 8 所示的应用程序搜索并反序列化 Alice 的 MessagingPreferences 对象。请回想一下 清单 1 中存储在 ApacheDS 中的 MessagingPreferences 对象。

FetchAliceMessagingPreferences 应用程序是 清单 7 中看到的 SearchForAliceByCN 应用程序的增强版。实际上,清单 8 与 清单 7 在步骤 8 之前都相同,在步骤 8 中,将提取 Alice 数据条目的 RDN。从清单 8 中的步骤 8 中,将开始查找 Alice 的 Preferences 对象:


清单 8. FetchAliceMessagingPreferences
public class FetchAliceMessagingPreferences {
public FetchAliceMessagingPreferences() {
try
{

//------------------------------------------
//Step1: Setting up JNDI properties for ApacheDS
//------------------------------------------
InputStream inputStream = new FileInputStream( "ApacheDS.properties");
Properties properties = new Properties();
properties.load(inputStream);
properties.setProperty("java.naming.security.credentials", "secret");

//------------------------------------------
// Step2: Fetching a DirContext object
//------------------------------------------
DirContext ctx = new InitialDirContext(properties);

//---------------------------------------------
//Step3: Setting search context
//---------------------------------------------
String searchContext = "ou=users";

//--------------------------------------------
//Step4: Creating search attributes for Alice
//--------------------------------------------
Attribute cn = new BasicAttribute("cn");
Attribute objclass = new BasicAttribute("objectClass");

//putting attribute values
cn.add("Alice");
objclass.add("person");

//Instantiate an Attributes object and put search attributes in it
Attributes attrs = new BasicAttributes(true);
attrs.put(cn);
attrs.put(objclass);

//------------------------------------------
//Step5: Executing search
//------------------------------------------
NamingEnumeration ne = ctx.search(searchContext, attrs);

if (ne != null)
{
//Step 6: Iterating through SearchResults
while (ne.hasMore()) {
//Step 7: Getting individual SearchResult object
SearchResult sr = (SearchResult) ne.next();

//Step 8:
String entryRDN = sr.getName();

//---------------------------------------------
//Step9: Setting a new search context
//---------------------------------------------
searchContext = entryRDN + "," + searchContext;

//---------------------------------------------
//Step10: Creating search controls
//---------------------------------------------
SearchControls ctls = new SearchControls();
ctls.setReturningObjFlag(true);
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

//---------------------------------------------
//Step11: Creating filter
//---------------------------------------------
String filter = "(|(javaClassName=MessagingPreferences)
(javaClassName=ShippingPreferences))";

//------------------------------------------
//Step12: Executing search
//------------------------------------------
NamingEnumeration ne1 = ctx.search(searchContext, filter, ctls);

if (ne != null)
{
//Step13: Iterating through SearchResults
while (ne1.hasMore()) {
//Step14: Getting individual SearchResult object
SearchResult sr1 = (SearchResult) ne1.next();

//Step15: Getting preferences object
Object obj = sr1.getObject();

if (obj != null) {
if (obj instanceof MessagingPreferences){
MessagingPreferences pref =
(MessagingPreferences) obj;
}
else if (obj instanceof ShippingPreferences) {
ShippingPreferences pref = (ShippingPreferences) obj;
}
}
}//while(
}//if
}//while
}//if (ne != null)
} catch (Exception e) {
System.out.println("Operation failed: " + e);
}
}

public static void main(String[] args) {
FetchAliceMessagingPreferences searchAlicePreferences =
new FetchAliceMessagingPreferences();
}
}

搜索已访问过的上下文

在进入搜索和反序列化 Alice 的 MessagingPreferences 对象的步骤之前,您可能想回顾一下 图 1,该图显示了位于 Alice 数据条目中的 Alice 的 MessagingPreferences 对象。所以,需要在 Alice 的数据条目中查找 MessagingPreferences 对象。

如何在数据条目 “内部进行查找” 呢?针对这一目的,我们将明确使用搜索上下文 的概念。在这个示例中,需要缩小在第一个应用程序(清单 6 所示的 SearchForAlice)中介绍的搜索上下文。

这项工作在 清单 8 的步骤 9 进行,在这一步骤中将把 Alice 的 RDN(uid=alice)与原来用于搜索 Alice 数据条目的上下文(ou=users,ou=system)合并在一起。生成的上下文(uid=alice,ou=users,ou=system)就可以用来查看 Alice 数据条目内部。

现在来看一下 FetchAliceMessagingPreferences 应用程序剩下的步骤。

构建和使用搜索控件

清单 8 的步骤 10 中,实例化了一个 SearchControls 对象,可以用它构建一些搜索控件。搜索控件有两个主要用途:

  • 指定搜索结果中包含的数据类型。在这个示例中,因为您想要检索 Java 对象,所以将调用 SearchControls 对象的 setReturningObjFlag() 方法。这个方法在搜索控件中设置标志,以指定将要执行的获得某一对象的搜索操作。
  • 指定搜索范围。“搜索范围” 意味着您想在特定数据条目中搜索,还是还想在比该条目更低的级别上进行搜索。通过调用 SearchControls 对象的 setSearchScope() 方法,可以设置搜索范围。

过滤搜索结果

清单 8 的步骤 11 中,制作了名为 “filter” 的字符串。可以看到 filter 字符串的值是 (|(javaClassName=MessagingPreferences) (javaClassName=ShippingPreferences))。括号中的这两个属性-值对为一个名为 javaClassName 的属性指定了不同的值。还请注意这两个属性-值对之前的 “OR”。这意味着要查询的是 MessagingPreferences ShippingPreferences 对象。

这个 filter 字符串充当搜索结果的过滤器。这意味着搜索操作之后返回的搜索结果将只包含符合搜索过滤器指定标准的结果。

在寻找大量属性值时要使用这类搜索过滤器。关于搜索过滤器的更多细节,请参阅 参考资料 部分,其中包含介绍 LDAP 应用程序中搜索过滤器概念的文章的链接。

提取和处理搜索结果

清单 8 的步骤 12 中,您可以调用 search() 方法,并随该方法调用一起传递搜索上下文、搜索过滤器和搜索控件。

请注意 清单 6 的步骤 5 清单 8 中使用的搜索方法调用之间的区别:在清单 6 中,使用的是带两个参数的 search() 方法,而在 清单 8 中,使用的是该方法的带三个参数的形式。

清单 8 中的步骤 13 和 14 分别与 清单 7 中的步骤 6 和 7 相同。在这些步骤中,处理 NamingEnumeration 对象,并以 SearchResult 对象的形式提取单个的搜索结果。

最后,在步骤 15,调用 SearchResult 对象的 getObject() 方法。getObject() 方法返回一个 Java 对象。

对于 getObject() 方法返回的实例属于哪个类,没法确定,因为在搜索请求中指定了两个类(MessagingPreferences 或 ShippingPreferences)。返回的结果可能是其中一个类的实例。所以,首先必须检查对象属于哪个实例,并进行相应的类型转换。检查并转换之后,Java 对象就处于控制之下,然后就可以调用它的方法了。




回页首


应用程序 7. 将 Java 对象解编

在前面的应用程序中,学习了如何反序列化已经序列化的 Java 对象。接下来,将通过应用程序 FetchAliceMarshalledPreferences 学习如何解编一个已编组的 Java 对象。


清单 9. FetchAliceMarshalledPreferences
public class FetchAliceMarshalledPreferences {
public FetchAliceMarshalledPreferences() {
try
{

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130274/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论