/*
* ====================
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2008-2009 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License("CDDL") (the "License"). You may not use this file
* except in compliance with the License.
*
* You can obtain a copy of the License at
* http://IdentityConnectors.dev.java.net/legal/license.txt
* See the License for the specific language governing permissions and limitations
* under the License.
*
* When distributing the Covered Code, include this CDDL Header Notice in each file
* and include the License file at identityconnectors/legal/license.txt.
* If applicable, add the following below this CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
* ====================
*/
package org.identityconnectors.ldap;
import static java.util.Collections.unmodifiableSet;
import static org.identityconnectors.common.CollectionUtil.newCaseInsensitiveMap;
import static org.identityconnectors.common.CollectionUtil.newCaseInsensitiveSet;
import static org.identityconnectors.ldap.LdapUtil.addStringAttrValues;
import static org.identityconnectors.ldap.LdapUtil.attrNameEquals;
import static org.identityconnectors.ldap.LdapUtil.getStringAttrValue;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import javax.naming.NameClassPair;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import org.identityconnectors.framework.common.objects.AttributeInfo.Flags;
/**
* Implements {@link LdapNativeSchema} by reading it from the server.
*/
public class ServerNativeSchema implements LdapNativeSchema {
// The LDAP directory attributes to expose as framework attributes.
private static final Set<String> LDAP_DIRECTORY_ATTRS;
private final LdapConnection conn;
private final DirContext schemaCtx;
private final Set<String> structuralLdapClasses = newCaseInsensitiveSet();
private final Map<String, Set<String>> ldapClass2MustAttrs = newCaseInsensitiveMap();
private final Map<String, Set<String>> ldapClass2MayAttrs = newCaseInsensitiveMap();
private final Map<String, Set<String>> ldapClass2Sup = newCaseInsensitiveMap();
private final Map<String, LdapAttributeType> attrName2Type = newCaseInsensitiveMap();
static {
LDAP_DIRECTORY_ATTRS = newCaseInsensitiveSet();
LDAP_DIRECTORY_ATTRS.add("createTimestamp");
LDAP_DIRECTORY_ATTRS.add("modifyTimestamp");
LDAP_DIRECTORY_ATTRS.add("creatorsName");
LDAP_DIRECTORY_ATTRS.add("modifiersName");
LDAP_DIRECTORY_ATTRS.add("entryDN");
}
public ServerNativeSchema(LdapConnection conn) throws NamingException {
this.conn = conn;
schemaCtx = conn.getInitialContext().getSchema("");
try {
initObjectClasses();
initAttributeDescriptions();
} finally {
schemaCtx.close();
}
}
public Set<String> getStructuralObjectClasses() {
return unmodifiableSet(structuralLdapClasses);
}
public Set<String> getRequiredAttributes(String ldapClass) {
return getAttributes(ldapClass, true);
}
public Set<String> getOptionalAttributes(String ldapClass) {
return getAttributes(ldapClass, false);
}
private Set<String> getAttributes(String ldapClass, boolean required) {
Set<String> result = newCaseInsensitiveSet();
Queue<String> queue = new LinkedList<String>();
Set<String> visited = new HashSet<String>();
queue.add(ldapClass);
while (!queue.isEmpty()) {
String current = queue.remove();
if (!visited.contains(current)) {
visited.add(current);
Set<String> attrs = required ? ldapClass2MustAttrs.get(current) : ldapClass2MayAttrs.get(current);
if (attrs != null) {
result.addAll(attrs);
}
Set<String> supClasses = ldapClass2Sup.get(current);
if (supClasses != null) {
queue.addAll(supClasses);
}
}
}
return result;
}
public Set<String> getEffectiveObjectClasses(String ldapClass) {
Set<String> result = newCaseInsensitiveSet();
Queue<String> classQueue = new LinkedList<String>();
classQueue.add(ldapClass);
while (!classQueue.isEmpty()) {
String classToVisit = classQueue.remove();
if (!result.contains(classToVisit)) {
result.add(classToVisit);
Set<String> supClasses = ldapClass2Sup.get(classToVisit);
if (supClasses != null) {
classQueue.addAll(supClasses);
}
}
}
return result;
}
public LdapAttributeType getAttributeDescription(String ldapAttrName) {
return attrName2Type.get(ldapAttrName);
}
private void initObjectClasses() throws NamingException {
DirContext objClassCtx = (DirContext) schemaCtx.lookup("ClassDefinition");
NamingEnumeration<NameClassPair> objClassEnum = objClassCtx.list("");
while (objClassEnum.hasMore()) {
String objClassName = objClassEnum.next().getName();
Attributes attrs = objClassCtx.getAttributes(objClassName);
boolean abstractAttr = "true".equals(getStringAttrValue(attrs, "ABSTRACT"));
boolean structuralAttr = "true".equals(getStringAttrValue(attrs, "STRUCTURAL"));
boolean auxiliaryAttr = "true".equals(getStringAttrValue(attrs, "AUXILIARY"));
boolean structural = structuralAttr || !(abstractAttr || auxiliaryAttr);
Set<String> mustAttrs = newCaseInsensitiveSet();
addStringAttrValues(attrs, "MUST", mustAttrs);
Set<String> mayAttrs = newCaseInsensitiveSet();
addStringAttrValues(attrs, "MAY", mayAttrs);
// The objectClass attribute must not be required, since it is handled internally by the connector.
if (mustAttrs.remove("objectClass")) {
mayAttrs.add("objectClass");
}
Set<String> supClasses = newCaseInsensitiveSet();
addStringAttrValues(attrs, "SUP", supClasses);
if (structural && supClasses.isEmpty()) {
// Hack for OpenDS, whose "referral" object class does not specify SUP.
supClasses.add("top");
}
Set<String> names = newCaseInsensitiveSet();
addStringAttrValues(attrs, "NAME", names);
for (String name : names) {
if (structural) {
structuralLdapClasses.addAll(names);
}
ldapClass2MustAttrs.put(name, mustAttrs);
ldapClass2MayAttrs.put(name, mayAttrs);
ldapClass2Sup.put(name, supClasses);
}
}
}
private void initAttributeDescriptions() throws NamingException {
DirContext attrsCtx = (DirContext) schemaCtx.lookup("AttributeDefinition");
NamingEnumeration<NameClassPair> attrsEnum = attrsCtx.list("");
while (attrsEnum.hasMore()) {
String attrName = attrsEnum.next().getName();
Attributes attrs = attrsCtx.getAttributes(attrName);
boolean singleValue = "true".equals(getStringAttrValue(attrs, "SINGLE-VALUE"));
boolean noUserModification = "true".equals(getStringAttrValue(attrs, "NO-USER-MODIFICATION"));
String usage = getStringAttrValue(attrs, "USAGE");
boolean userApplications = "userApplications".equals(usage) || usage == null;
Set<String> names = newCaseInsensitiveSet();
addStringAttrValues(attrs, "NAME", names);
for (String name : names) {
// The objectClass attribute must not be writable, since it is handled internally by the connector.
boolean objectClass = attrNameEquals(name, "objectClass");
boolean binary = conn.isBinarySyntax(attrName);
Class<?> type;
if (binary) {
type = byte[].class;
} else {
type = String.class;
}
Set<Flags> flags = EnumSet.noneOf(Flags.class);
if (!singleValue) {
flags.add(Flags.MULTIVALUED);
}
if (noUserModification || objectClass) {
flags.add(Flags.NOT_CREATABLE);
flags.add(Flags.NOT_UPDATEABLE);
}
// XXX perhaps this should be true for binary attributes too.
if (!userApplications) {
flags.add(Flags.NOT_RETURNED_BY_DEFAULT);
}
attrName2Type.put(name, new LdapAttributeType(type, flags));
}
}
for (String dirAttrName : LDAP_DIRECTORY_ATTRS) {
attrName2Type.put(dirAttrName, new LdapAttributeType(String.class, EnumSet.of(Flags.NOT_CREATABLE, Flags.NOT_UPDATEABLE, Flags.NOT_RETURNED_BY_DEFAULT)));
}
}
}