Support read-only attributes.
Also always explicitly request only the necessary attributes (makes operational attributes work). This closes #347
This commit is contained in:
committed by
Rob Winch
parent
4c4a94247a
commit
5cecd0b2f7
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user