|
对于最新稳定版本,请使用 Spring LDAP 4.0.0! |
简化属性访问和作DirContextAdapter
Java LDAP API 的一个鲜为人知且可能被低估的功能是能够注册DirObjectFactory自动从找到的 LDAP 条目创建对象。
Spring LDAP利用此功能返回DirContextAdapter某些搜索和查找作中的实例。
DirContextAdapter是处理LDAP属性的有用工具,尤其是在添加或修改数据时。
使用搜索和查找上下文地图
每当在 LDAP 树中发现条目时,Spring LDAP 都会利用其属性和区分名(DN)来构建DirContextAdapter.
这让我们可以使用上下文地图而不是属性映射器对求值进行变换,具体如下:
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());
}
}
如前例所示,我们可以直接通过名称检索属性值,而无需经过属性和属性类。
这在处理多值属性时尤其有用。
从多值属性中提取取值通常需要循环于命名枚举属性值从属性实现。DirContextAdapter帮你做这件事
在getStringAttributes()或getObjectAttributes()方法。
以下示例使用了getStringAttributes方法:
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;
}
}
用摘要上下文映射器
Spring LDAP 提供了一个抽象的基础实现上下文地图叫摘要上下文映射器.
该实现会自动处理所提供对象参数 到DirContex运营.
用摘要上下文映射器这PersonContextMapper因此,之前所示可以重新写成如下:
摘要上下文映射器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;
}
}
通过使用DirContextAdapter
`
虽然在提取属性值时很有用,DirContextAdapter更适合管理细节
参与数据的添加和更新。
通过使用DirContextAdapter
以下示例使用DirContextAdapter以实现改进的创造在添加数据中介绍的存储方法:
DirContextAdapterpublic 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实例作为第二个绑定参数,应为上下文.
第三个参数为零,因为我们没有明确指定属性。
还要注意setAttributeValues()在设置对象类属性值。
这对象类属性是多值的。类似于提取多值属性数据的困难,
构建多价值属性既繁琐又冗长。通过使用setAttributeValues()方法,你可以拥有DirContextAdapter这部分工作帮你处理。
通过使用DirContextAdapter
我们之前看到过通过使用modifyAttributes是推荐的方法,但这需要我们去执行
计算属性修改和构造的任务修改物品相应地进行阵列调整。DirContextAdapter可以帮我们完成所有这些,具体如下:
DirContextAdapterpublic 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默认实例。
虽然查找方法返回对象,收件人自动将返回值投射为DirContextOperations(接口DirContextAdapter工具)。
注意我们在Ldap模板#创建和Ldap模板#更新方法。这段代码将域对象映射到上下文。它可以提取到另一个方法,具体如下:
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以及作为属性价值的杰出名称
在管理 LDAP 中的安全组时,通常会使用属性值表示 杰出的名字。由于区分名称相等式与字符串相等式不同(例如空白和大小写差异) 在区分名称相等中被忽略),但使用字符串等式计算属性修改并不如预期。
例如,如果成员属性的值为cn=约翰·多,ou=人物我们称ctx.addAttributeValue(“成员”,“CN=约翰·多,OU=人民”),
该属性现在被视为有两个值,尽管字符串实际上表示的是相同的
杰出之名。
截至春季LDAP 2.0,供应javax.naming.Name属性的实例 修改方法所做的DirContextAdapter在计算属性修改时使用区分名称相同。如果我们将前例修改为ctx.addAttributeValue(“member”,LdapUtils.newLdapName(“CN=John Doe, OU=People”)),它不会渲染修改,如下示例所示:
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以获得基础LDAP路径,详见《获取对基础LDAP路径的引用》。
这是必要的,因为作为成员属性值的区分名称必须始终从目录根节点保持绝对。
A 完整PersonRepository类
为了说明 Spring LDAP 的实用性 和DirContextAdapter,以下示例显示完备人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)是通过利用对象的属性构造的。
在上述例子中,国家、公司及人在DN中使用,这意味着更新这些属性实际上需要通过使用更名()除了更新属性值。
由于这高度依赖于具体实现,你需要自己跟踪,要么不允许用户更改这些属性,要么执行更名()在你的作中更新()如果需要,也可以用方法。
请注意,通过使用对象-目录映射(ODM),如果你正确注释域类,库可以自动帮你处理这些问题。 |