Simplifying Attribute Access and Manipulation with DirContextAdapter
Java LDAP API 的一个鲜为人知(可能被低估)的功能是能够注册一个 @"34" 以从找到的 LDAP 条目中自动创建对象。Spring LDAP 利用此功能在某些搜索和查找操作中返回 @"36" 实例。
A little-known — and probably underestimated — feature of the Java LDAP API is the ability to register a DirObjectFactory
to automatically create objects from found LDAP entries.
Spring LDAP makes use of this feature to return DirContextAdapter
instances in certain search and lookup operations.
DirContextAdapter
是用于处理 LDAP 属性的有用工具,尤其是在添加或修改数据时。
DirContextAdapter
is a useful tool for working with LDAP attributes, particularly when adding or modifying data.
Search and Lookup Using ContextMapper
每当在 LDAP 树中找到一个条目时,Spring LDAP 都会使用它的属性和 Distinguished Name (DN) 来构造一个 @"37"。这使我们能够使用 @"40" 代替 @"39" 来转换找到的值,如下所示:
Whenever an entry is found in the LDAP tree, its attributes and Distinguished Name (DN) are used by Spring LDAP to construct a DirContextAdapter
.
This lets us use a ContextMapper
instead of an AttributesMapper
to transform found values, as follows:
public class PersonRepoImpl implements PersonRepo {
...
*private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
return p;
}
}*
public Person findByPrimaryKey(
String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapClient.search().name(dn).toObject(*new PersonContextMapper()*);
}
}
如前一个示例中所示,我们无需通过 Attributes
和 Attribute
类便可根据名称直接获取属性值。在处理多值属性时,这点尤其有用。从多值属性中提取值通常需要通过 Attributes
实现在 NamingEnumeration
中返回的属性值的循环。DirContextAdapter
在 getStringAttributes()
或 getObjectAttributes()
方法中执行此操作。以下示例使用 getStringAttributes
方法:
As shown in the preceding example, we can retrieve the attribute values directly by name without having to go through the Attributes
and Attribute
classes.
This is particularly useful when working with multi-value attributes.
Extracting values from multi-value attributes normally requires looping through a NamingEnumeration
of attribute values returned from the Attributes
implementation.
DirContextAdapter
does this for you
in the getStringAttributes()
or getObjectAttributes()
methods.
The following example uses the getStringAttributes
method:
getStringAttributes()
private static class PersonContextMapper implements ContextMapper {
public Object mapFromContext(Object ctx) {
DirContextAdapter context = (DirContextAdapter)ctx;
Person p = new Person();
p.setFullName(context.getStringAttribute("cn"));
p.setLastName(context.getStringAttribute("sn"));
p.setDescription(context.getStringAttribute("description"));
// The roleNames property of Person is an String array
*p.setRoleNames(context.getStringAttributes("roleNames"));*
return p;
}
}
Using AbstractContextMapper
Spring LDAP 提供名为 AbstractContextMapper
的 ContextMapper
抽象基本实现。此实现会自动负责将提供的 Object
参数强制转换为 DirContexOperations
。使用 AbstractContextMapper
,前面的 PersonContextMapper
可重写如下:
Spring LDAP provides an abstract base implementation of ContextMapper
, called AbstractContextMapper
.
This implementation automatically takes care of the casting of the supplied Object
parameter to DirContexOperations
.
Using AbstractContextMapper
, the PersonContextMapper
shown earlier can thus be re-written as follows:
AbstractContextMapper
private static class PersonContextMapper *extends AbstractContextMapper* {
public Object *doMapFromContext*(DirContextOperations ctx) {
Person p = new Person();
p.setFullName(ctx.getStringAttribute("cn"));
p.setLastName(ctx.getStringAttribute("sn"));
p.setDescription(ctx.getStringAttribute("description"));
return p;
}
}
Adding and Updating Data by Using DirContextAdapter
虽然在提取属性值时有用,但 `DirContextAdapter
在管理添加和更新数据所涉及的详细信息方面功能更加强大。
`
While useful when extracting attribute values, DirContextAdapter
is even more powerful for managing the details
involved in adding and updating data.
Adding Data by Using DirContextAdapter
以下示例使用 DirContextAdapter
来实现 Adding Data 中展示的 create
存储库方法的改进实现:
The following example uses DirContextAdapter
to implement an improved implementation of the create
repository method presented in Adding Data:
DirContextAdapter
public class PersonRepoImpl implements PersonRepo {
...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
*context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription());*
ldapClient.bind(dn).object(context).execute();
}
}
请注意,我们使用 DirContextAdapter
实例作为 bind 的第二个参数,该参数应为 Context
。第三个参数为 null
,因为我们没有显式指定属性。
Note that we use the DirContextAdapter
instance as the second parameter to bind, which should be a Context
.
The third parameter is null
, since we do not specify the attributes explicitly.
还要注意在设置 objectclass
属性值时使用 setAttributeValues()
方法。objectclass
属性是多值的。类似于提取多值属性数据的麻烦,构建多值属性是一项繁琐而冗长的工作。通过使用 setAttributeValues()
方法,你可以让 DirContextAdapter
为你处理这项工作。
Also note the use of the setAttributeValues()
method when setting the objectclass
attribute values.
The objectclass
attribute is multi-value. Similar to the troubles of extracting muti-value attribute data,
building multi-value attributes is tedious and verbose work. By using the setAttributeValues()
method, you can have DirContextAdapter
handle that work for you.
Updating Data by Using DirContextAdapter
我们之前看到,使用 modifyAttributes
进行更新是推荐的方法,但这样做要求我们执行计算属性修改和相应构建 ModificationItem
数组的任务。DirContextAdapter
可以为我们完成所有这些操作,如下所示:
We previously saw that updating by using modifyAttributes
is the recommended approach, but that doing so requires us to perform
the task of calculating attribute modifications and constructing ModificationItem
arrays accordingly.
DirContextAdapter
can do all of this for us, as follows:
DirContextAdapter
public class PersonRepoImpl implements PersonRepo {
...
public void update(Person p) {
Name dn = buildDn(p);
*DirContextOperations context = ldapClient.search().name(dn).toEntry();*
context.setAttributeValue("cn", p.getFullname());
context.setAttributeValue("sn", p.getLastname());
context.setAttributeValue("description", p.getDescription());
*ldapClient.modify(dn).attributes(context.getModificationItems()).execute();*
}
}
在调用 SearchSpec#toEntry
时,结果默认是 DirContextAdapter
实例。虽然 lookup
方法返回一个 Object
,但 toEntry
自动将返回值强制转换为 DirContextOperations
(DirContextAdapter
实现的接口)。
When calling SearchSpec#toEntry
, the result is a DirContextAdapter
instance by default.
While the lookup
method returns an Object
, toEntry
automatically casts the return value to a DirContextOperations
(the interface that DirContextAdapter
implements).
需要注意的是,LdapTemplate#create
和 LdapTemplate#update
方法中有重复的代码。此代码将域对象映射到上下文。可以将其提取到独立的方法中,如下所示:
Notice that we have duplicate code in the LdapTemplate#create
and LdapTemplate#update
methods. This code maps from a domain object to a context. It can be extracted to a separate method, as follows:
public class PersonRepoImpl implements PersonRepo {
private LdapClient ldapClient;
...
public void create(Person p) {
Name dn = buildDn(p);
DirContextAdapter context = new DirContextAdapter(dn);
context.setAttributeValues("objectclass", new String[] {"top", "person"});
mapToContext(p, context);
ldapClient.bind(dn).object(context).execute();
}
public void update(Person p) {
Name dn = buildDn(p);
DirContextOperations context = ldapClient.search().name(dn).toEntry();
mapToContext(person, context);
ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
}
protected void mapToContext (Person p, DirContextOperations context) {
context.setAttributeValue("cn", p.getFullName());
context.setAttributeValue("sn", p.getLastName());
context.setAttributeValue("description", p.getDescription());
}
}
DirContextAdapter
and Distinguished Names as Attribute Values
在 LDAP 中管理安全组时,通常具有表示已分辨名称的属性值。由于已分辨名称相等性与字符串相等性不同(例如,已分辨名称相等性忽略空格和大小写差异),因此使用字符串相等性计算属性修改无效。
When managing security groups in LDAP, it is common to have attribute values that represent distinguished names. Since distinguished name equality differs from String equality (for example, whitespace and case differences are ignored in distinguished name equality), calculating attribute modifications using string equality does not work as expected.
例如,如果 member
属性的值是 cn=John Doe,ou=People
,并且我们调用 ctx.addAttributeValue("member", "CN=John Doe, OU=People")
,则现在此属性被认为有两个值,即使这些字符串实际上表示相同的已分辨名称。
For instance, if a member
attribute has a value of cn=John Doe,ou=People
and we call ctx.addAttributeValue("member", "CN=John Doe, OU=People")
,
the attribute is now considered to have two values, even though the strings actually represent the same
distinguished name.
自 Spring LDAP 2.0 起,向属性修改方法提供 javax.naming.Name
实例可使 DirContextAdapter
在计算属性修改时使用已分辨名称相等性。如果我们修改前面的示例为 ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))
,则该示例不会呈现修改,如下例所示:
As of Spring LDAP 2.0, supplying javax.naming.Name
instances to the attribute modification methods makes DirContextAdapter
use distinguished name equality when calculating attribute modifications. If we modify the earlier example to be
ctx.addAttributeValue("member", LdapUtils.newLdapName("CN=John Doe, OU=People"))
, it does not render a modification, as the following example shows:
public class GroupRepo implements BaseLdapNameAware {
private LdapClient ldapClient;
private LdapName baseLdapPath;
public void setLdapClient(LdapClient ldapClient) {
this.ldapClient = ldapClient;
}
public void setBaseLdapPath(LdapName baseLdapPath) {
this.setBaseLdapPath(baseLdapPath);
}
public void addMemberToGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(groupName);
Name userDn = buildPersonDn(
person.getFullname(),
person.getCompany(),
person.getCountry());
DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
ctx.addAttributeValue("member", userDn);
ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
}
public void removeMemberFromGroup(String groupName, Person p) {
Name groupDn = buildGroupDn(String groupName);
Name userDn = buildPersonDn(
person.getFullname(),
person.getCompany(),
person.getCountry());
DirContextOperation ctx = ldapClient.search().name(groupDn).toEntry();
ctx.removeAttributeValue("member", userDn);
ldapClient.modify(groupDn).attributes(ctx.getModificationItems()).execute();
}
private Name buildGroupDn(String groupName) {
return LdapNameBuilder.newInstance("ou=Groups")
.add("cn", groupName).build();
}
private Name buildPersonDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance(baseLdapPath)
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
}
}
在前面的示例中,我们实现了 BaseLdapNameAware
来获取 Obtaining a Reference to the Base LDAP Path 中描述的 LDAP 基础路径。这很重要,因为作为成员属性值的特有名称必须始终是从目录根开始的绝对名称。
In the preceding example, we implement BaseLdapNameAware
to get the base LDAP path as described in Obtaining a Reference to the Base LDAP Path.
This is necessary because distinguished names as member attribute values must always be absolute from the directory root.
A Complete PersonRepository
Class
为了说明 Spring LDAP 和 DirContextAdapter
的有用性,以下示例显示了 LDAP 的完整 Person
存储库实现:
To illustrate the usefulness of Spring LDAP and DirContextAdapter
, the following example shows a complete Person
Repository implementation for LDAP:
import java.util.List;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.ldap.LdapName;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.ContextMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.WhitespaceWildcardsFilter;
import static org.springframework.ldap.query.LdapQueryBuilder.query;
public class PersonRepoImpl implements PersonRepo {
private LdapClient ldapClient;
public void setLdapClient(LdapClient ldapClient) {
this.ldapClient = ldapClient;
}
public void create(Person person) {
DirContextAdapter context = new DirContextAdapter(buildDn(person));
mapToContext(person, context);
ldapClient.bind(context.getDn()).object(context).execute();
}
public void update(Person person) {
Name dn = buildDn(person);
DirContextOperations context = ldapClient.lookupContext(dn);
mapToContext(person, context);
ldapClient.modify(dn).attributes(context.getModificationItems()).execute();
}
public void delete(Person person) {
ldapClient.unbind(buildDn(person)).execute();
}
public Person findByPrimaryKey(String name, String company, String country) {
Name dn = buildDn(name, company, country);
return ldapClient.search().name(dn).toObject(getContextMapper());
}
public List<Person> findByName(String name) {
LdapQuery query = query()
.where("objectclass").is("person")
.and("cn").whitespaceWildcardsLike("name");
return ldapClient.search().query(query).toList(getContextMapper());
}
public List<Person> findAll() {
EqualsFilter filter = new EqualsFilter("objectclass", "person");
return ldapClient.search().query((query) -> query.filter(filter)).toList(getContextMapper());
}
protected ContextMapper getContextMapper() {
return new PersonContextMapper();
}
protected Name buildDn(Person person) {
return buildDn(person.getFullname(), person.getCompany(), person.getCountry());
}
protected Name buildDn(String fullname, String company, String country) {
return LdapNameBuilder.newInstance()
.add("c", country)
.add("ou", company)
.add("cn", fullname)
.build();
}
protected void mapToContext(Person person, DirContextOperations context) {
context.setAttributeValues("objectclass", new String[] {"top", "person"});
context.setAttributeValue("cn", person.getFullName());
context.setAttributeValue("sn", person.getLastName());
context.setAttributeValue("description", person.getDescription());
}
private static class PersonContextMapper extends AbstractContextMapper<Person> {
public Person doMapFromContext(DirContextOperations context) {
Person person = new Person();
person.setFullName(context.getStringAttribute("cn"));
person.setLastName(context.getStringAttribute("sn"));
person.setDescription(context.getStringAttribute("description"));
return person;
}
}
}
在某些情况下,对象的可分辨名称 (DN) 是使用对象的属性构造的。在前面的示例中, |
In several cases, the Distinguished Name (DN) of an object is constructed by using properties of the object.
In the preceding example, the country, company and full name of the |