From 5cecd0b2f746e9a218941ef1ef0d68ea5d657156 Mon Sep 17 00:00:00 2001 From: Konrad Windszus Date: Thu, 13 Oct 2016 11:08:47 +0200 Subject: [PATCH] Support read-only attributes. Also always explicitly request only the necessary attributes (makes operational attributes work). This closes #347 --- .../ldap/core/LdapTemplate.java | 10 +++++--- .../ldap/odm/annotations/Attribute.java | 13 ++++++++++ .../ldap/odm/core/ObjectDirectoryMapper.java | 5 +++- .../ldap/odm/core/impl/AttributeMetaData.java | 24 +++++++++++++++---- .../impl/DefaultObjectDirectoryMapper.java | 15 ++++++++---- 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java index f149165d..2cf3ead1 100644 --- a/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java +++ b/core/src/main/java/org/springframework/ldap/core/LdapTemplate.java @@ -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 result = lookup(dn, attributes, new ContextMapper() { @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 List find(Name base, Filter filter, SearchControls searchControls, final Class 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)); } diff --git a/core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java b/core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java index a82b18cb..9c1e1e64 100755 --- a/core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java +++ b/core/src/main/java/org/springframework/ldap/odm/annotations/Attribute.java @@ -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. + *

+ * 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; + } diff --git a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java index eee53bee..dac3c7b9 100644 --- a/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/ObjectDirectoryMapper.java @@ -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); } diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java index 6e615680..ac82cca7 100755 --- a/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/AttributeMetaData.java @@ -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 attrList = new ArrayList(); // 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()); } } diff --git a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java index d72c564b..05fcd7de 100644 --- a/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java +++ b/core/src/main/java/org/springframework/ldap/odm/core/impl/DefaultObjectDirectoryMapper.java @@ -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 managedAttributeNames = new HashSet(); + // 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();