Support read-only attributes.

Also always explicitly request only the necessary attributes (makes
operational attributes work).

This closes #347
This commit is contained in:
Konrad Windszus
2016-10-13 11:08:47 +02:00
committed by Rob Winch
parent 4c4a94247a
commit 5cecd0b2f7
5 changed files with 54 additions and 13 deletions

View File

@@ -1690,9 +1690,9 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
}
// Make sure the class is OK before doing the lookup
odm.manageClass(clazz);
String[] attributes = odm.manageClass(clazz);
T result = lookup(dn, new ContextMapper<T>() {
T result = lookup(dn, attributes, new ContextMapper<T>() {
@Override
public T mapFromContext(Object ctx) throws javax.naming.NamingException {
return odm.mapFromLdapDataEntry((DirContextOperations) ctx, clazz);
@@ -1821,7 +1821,7 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
*/
@Override
public <T> List<T> find(Name base, Filter filter, SearchControls searchControls, final Class<T> clazz) {
Filter finalFilter = odm.filterFor(clazz, filter);
Filter finalFilter = odm.filterFor(clazz, filter);
// Search from the root if we are not told where to search from
Name localBase = base;
@@ -1829,6 +1829,10 @@ public class LdapTemplate implements LdapOperations, InitializingBean {
localBase = LdapUtils.emptyLdapName();
}
// extend search controls with the attributes to return
String[] attributes = odm.manageClass(clazz);
searchControls.setReturningAttributes(attributes);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("Searching - base=%1$s, finalFilter=%2$s, scope=%3$s", base, finalFilter, searchControls));
}

View File

@@ -62,4 +62,17 @@ public @interface Attribute {
* @return The LDAP syntax of this attribute.
*/
String syntax() default "";
/**
* A boolean parameter to indicate if the attribute should be read only.
* <p>
* This value allows attributes to be read on read, but not persisted, there
* are many operational and read-only ldap attributes which will throw errors
* if they are persisted back to ldap.
* see {@link org.springframework.ldap.odm.core.impl.AttributeMetaData}
*
* @return true is the attribute should not be written to ldap.
*/
boolean readonly() default false;
}

View File

@@ -19,6 +19,8 @@ package org.springframework.ldap.odm.core;
import org.springframework.LdapDataEntry;
import org.springframework.ldap.filter.Filter;
import java.util.Set;
import javax.naming.Name;
/**
@@ -93,7 +95,8 @@ public interface ObjectDirectoryMapper {
* managed classes.
*
* @param clazz the class to manage.
* @return all relevant attribute names used in the given class.
* @throws org.springframework.ldap.NamingException on error.
*/
void manageClass(Class<?> clazz);
String[] manageClass(Class<?> clazz);
}

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ldap.odm.core.impl;
import org.springframework.ldap.UncategorizedLdapException;
@@ -43,6 +42,7 @@ import java.util.TreeSet;
* @author Paul Harvey &lt;paul.at.pauls-place.me.uk>
*/
/* package */ final class AttributeMetaData {
private static final CaseIgnoreString OBJECT_CLASS_ATTRIBUTE_CI=new CaseIgnoreString("objectclass");
// Name of the LDAP attribute from the @Attribute annotation
@@ -75,6 +75,10 @@ import java.util.TreeSet;
private boolean isTransient = false;
private boolean isReadOnly = false;
private String[] attributes;
private DnAttribute dnAttribute;
// Extract information from the @Attribute annotation:
@@ -95,6 +99,7 @@ import java.util.TreeSet;
// Grab the @Attribute annotation
Attribute attribute = field.getAnnotation(Attribute.class);
List<String> attrList = new ArrayList<String>();
// Did we find the annotation?
if (attribute != null) {
// Pull attribute name, syntax and whether attribute is binary
@@ -104,10 +109,13 @@ import java.util.TreeSet;
// Would be more efficient to use !isEmpty - but that then makes us Java 6 dependent
if (localAttributeName != null && localAttributeName.length()>0) {
name = new CaseIgnoreString(localAttributeName);
attrList.add(localAttributeName);
}
syntax = attribute.syntax();
isBinary = attribute.type() == Attribute.Type.BINARY;
isReadOnly = attribute.readonly();
}
attributes = attrList.toArray(new String[attrList.size()]);
isObjectClass=name.equals(OBJECT_CLASS_ATTRIBUTE_CI);
@@ -225,7 +233,6 @@ import java.util.TreeSet;
// Reflection data
determineFieldType(field);
// Data from the @Attribute annotation
boolean foundAttributeAnnotation=processAttributeAnnotation(field);
@@ -246,7 +253,6 @@ import java.util.TreeSet;
}
}
public String getSyntax() {
return syntax;
}
@@ -271,6 +277,10 @@ import java.util.TreeSet;
return isId;
}
public boolean isReadOnly() {
return isReadOnly;
}
public boolean isTransient() {
return isTransient;
}
@@ -291,6 +301,10 @@ import java.util.TreeSet;
return valueClass;
}
public String[] getAttributes() {
return attributes;
}
public Class<?> getJndiClass() {
if(isBinary()) {
return byte[].class;
@@ -308,7 +322,7 @@ import java.util.TreeSet;
*/
@Override
public String toString() {
return String.format("name=%1$s | field=%2$s | valueClass=%3$s | syntax=%4$s| isBinary=%5$s | isId=%6$s | isList=%7$s | isObjectClass=%8$s",
getName(), getField(), getValueClass(), getSyntax(), isBinary(), isId(), isCollection(), isObjectClass());
return String.format("name=%1$s | field=%2$s | valueClass=%3$s | syntax=%4$s| isBinary=%5$s | isId=%6$s | isReadOnly=%7$s | isList=%8$s | isObjectClass=%9$s",
getName(), getField(), getValueClass(), getSyntax(), isBinary(), isId(), isReadOnly(), isCollection(), isObjectClass());
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.ldap.odm.core.impl;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -110,9 +111,15 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
}
@Override
public void manageClass(Class<?> clazz) {
public String[] manageClass(Class<?> clazz) {
// This throws exception if data is invalid
getEntityData(clazz);
EntityData entityData = getEntityData(clazz);
Set<String> managedAttributeNames = new HashSet<String>();
// extract all relevant attributes
for (Field field : entityData.metaData) {
managedAttributeNames.addAll(Arrays.asList(entityData.metaData.getAttribute(field).getAttributes()));
}
return managedAttributeNames.toArray(new String[managedAttributeNames.size()]);
}
/**
@@ -169,7 +176,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
jndiClass, javaClass, field.getName(), managedClass));
}
if (!converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) {
if (!attributeInfo.isReadOnly() && !converterManager.canConvert(javaClass, attributeInfo.getSyntax(), jndiClass)) {
throw new InvalidEntryException(String.format(
"Missing converter from %1$s to %2$s, this is needed for field %3$s on Entry %4$s",
javaClass, jndiClass, field.getName(), managedClass));
@@ -200,7 +207,7 @@ public class DefaultObjectDirectoryMapper implements ObjectDirectoryMapper {
// Grab the meta data for the current field
AttributeMetaData attributeInfo = metaData.getAttribute(field);
// We dealt with the object class field about, and the DN is set by the call to write the object to LDAP
if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass())) {
if (!attributeInfo.isTransient() && !attributeInfo.isId() && !(attributeInfo.isObjectClass()) && !(attributeInfo.isReadOnly())) {
try {
// If this is a "binary" object the JNDI expects a byte[] otherwise a String
Class<?> targetClass = attributeInfo.getJndiClass();