LDAP-234: Rollback intermittently causes data loss
Finalized incremental attributes retrieval support. Integrated incremental attributes retrieval support in compansating transaction classes. Integration tests for Active Directory (not enabled on CI machine).
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.core;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attributes;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Utility that helps with reading all attribute values from Active Directory using <em>Incremental Retrieval of
|
||||
* Multi-valued Properties</em>.
|
||||
*
|
||||
* @author Mattias Hellborg Arthursson
|
||||
* @since 1.3.2
|
||||
* @see <a href="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</a>
|
||||
* @see {@link org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper}
|
||||
*/
|
||||
public interface IncrementalAttributesMapper extends AttributesMapper {
|
||||
/**
|
||||
* Get all of the collected values for the specified attribute.
|
||||
*
|
||||
* @param attributeName the attribute to get values for.
|
||||
* @return the collected values for the specified attribute. Will be <code>null</code>
|
||||
* if the requested attribute has not been returned by the server (attribute did not exist).
|
||||
*/
|
||||
List getValues(String attributeName);
|
||||
|
||||
/**
|
||||
* Get all collected values for all managed attributes as an Attributes instance.
|
||||
*
|
||||
* @return an Attributes instance populated with all collected values.
|
||||
*/
|
||||
Attributes getCollectedAttributes();
|
||||
|
||||
/**
|
||||
* Check whether another query iteration is required to get all values for all attributes.
|
||||
*
|
||||
* @return <code>true</code> if there are more values for at least one of the managed attributes,
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
boolean hasMore();
|
||||
|
||||
/**
|
||||
* Get properly formatted attributes for use in the next query. The attribute names included will
|
||||
* include Range specifiers as needed and only the attributes that have not been retrieved in full
|
||||
* will be included.
|
||||
*
|
||||
* @return an array of Strings to be used as input to e.g.
|
||||
* {@link org.springframework.ldap.core.LdapTemplate#lookup(javax.naming.Name, String[], org.springframework.ldap.core.AttributesMapper)}
|
||||
* in the next iteration.
|
||||
*/
|
||||
String[] getAttributesForLookup();
|
||||
|
||||
/**
|
||||
* Goes through all of the attributes to record their values and figure out whether a new query iteration
|
||||
* is needed to get more values.
|
||||
*
|
||||
* @param attributes attributes from a SearchResult.
|
||||
* @return this instance.
|
||||
* @throws javax.naming.NamingException
|
||||
*/
|
||||
Object mapFromAttributes(Attributes attributes) throws NamingException;
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.core.support;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.IncrementalAttributesMapper;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NamingEnumeration;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility class that helps with reading all attribute values from Active Directory using <em>Incremental Retrieval of
|
||||
* Multi-valued Properties</em>.
|
||||
* <p/>
|
||||
* <p>Example usage of this attribute mapper:
|
||||
* <pre>
|
||||
* List values = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, "oneAttribute");
|
||||
* Attributes attrs = DefaultIncrementalAttributeMapper.lookupAttributeValues(ldapTemplate, theDn, new Object[]{"oneAttribute", "anotherAttribute"});
|
||||
* </pre>
|
||||
* For greater control, e.g. explicitly specifying the requested page size, create and use an instance yourself:
|
||||
* <pre>
|
||||
*
|
||||
* IncrementalAttributesMapper incrementalAttributeMapper = new DefaultIncrementalAttributeMapper(10, "someAttribute");
|
||||
* while (incrementalAttributeMapper.hasMore()) {
|
||||
* ldap.lookup(entrDn, incrementalAttributeMapper.getAttributesForLookup(), incrementalAttributeMapper);
|
||||
* }
|
||||
*
|
||||
* List values = incrementalAttributeMapper.getValues("someAttribute");
|
||||
* </pre>
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>NOTE:</b> Instances of this class are highly stateful and must not be reused or shared between threads in any way.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>NOTE:</b> Instances of this class can only be used with <b>lookups</b>. No support is given for searches.
|
||||
* </p>
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
* @author Mattias Hellborg Arthursson
|
||||
* @see <a href="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</a>
|
||||
* @see #lookupAttributes(org.springframework.ldap.core.LdapOperations, javax.naming.Name, String[])
|
||||
* @see #lookupAttributeValues(org.springframework.ldap.core.LdapOperations, javax.naming.Name, String)
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public class DefaultIncrementalAttributesMapper implements AttributesMapper, IncrementalAttributesMapper {
|
||||
private final static Log log = LogFactory.getLog(DefaultIncrementalAttributesMapper.class);
|
||||
|
||||
private Map stateMap = new LinkedHashMap();
|
||||
private Set rangedAttributesInNextIteration = new LinkedHashSet();
|
||||
|
||||
/**
|
||||
* This guy will be used when an unmapped attribute is encountered. This really should never happen,
|
||||
* but this saves us a number of null checks.
|
||||
*/
|
||||
private final static IncrementalAttributeState NOT_FOUND_ATTRIBUTE_STATE = new IncrementalAttributeState() {
|
||||
public String getRequestedAttributeName() {
|
||||
throw new UnsupportedOperationException("This method should never be called");
|
||||
}
|
||||
|
||||
public boolean hasMore() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void calculateNextRange(RangeOption responseRange) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
public String getAttributeNameForQuery() {
|
||||
throw new UnsupportedOperationException("This method should never be called");
|
||||
}
|
||||
|
||||
public void processValues(Attributes attributes, String attributeName) throws NamingException {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
public List getValues() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an instance for the requested attribute.
|
||||
*
|
||||
* @param attributeName the name of the attribute that this instance handles.
|
||||
* This is the attribute name that will be requested, and whose
|
||||
* values are managed.
|
||||
*/
|
||||
public DefaultIncrementalAttributesMapper(String attributeName) {
|
||||
this(RangeOption.TERMINAL_END_OF_RANGE, attributeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the requested attributes.
|
||||
*
|
||||
* @param attributeNames the name of the attributes that this instance handles.
|
||||
* These are the attribute names that will be requested, and whose
|
||||
* values are managed.
|
||||
*/
|
||||
public DefaultIncrementalAttributesMapper(String[] attributeNames) {
|
||||
this(RangeOption.TERMINAL_END_OF_RANGE, attributeNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the requested attribute with a specific page size.
|
||||
*
|
||||
* @param pageSize the requested page size that will be included in range query attribute names.
|
||||
* @param attributeName the name of the attribute that this instance handles.
|
||||
* This is the attribute name that will be requested, and whose
|
||||
* values are managed.
|
||||
*/
|
||||
public DefaultIncrementalAttributesMapper(int pageSize, String attributeName) {
|
||||
this(pageSize, new String[]{attributeName});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance for the requested attributes with a specific page size.
|
||||
*
|
||||
* @param pageSize the requested page size that will be included in range query attribute names.
|
||||
* @param attributeNames the name of the attributes that this instance handles.
|
||||
* These are the attribute names that will be requested, and whose
|
||||
* values are managed.
|
||||
*/
|
||||
public DefaultIncrementalAttributesMapper(int pageSize, String[] attributeNames) {
|
||||
for (int i = 0; i < attributeNames.length; i++) {
|
||||
String attributeName = attributeNames[i];
|
||||
this.stateMap.put(attributeName, new DefaultIncrementalAttributeState(attributeName, pageSize));
|
||||
this.rangedAttributesInNextIteration.add(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
public final Object mapFromAttributes(Attributes attributes) throws NamingException {
|
||||
if (!hasMore()) {
|
||||
throw new IllegalStateException("No more attributes!");
|
||||
}
|
||||
|
||||
// Reset the affected attributes.
|
||||
rangedAttributesInNextIteration = new HashSet();
|
||||
|
||||
NamingEnumeration attributeNameEnum = attributes.getIDs();
|
||||
while (attributeNameEnum.hasMore()) {
|
||||
String attributeName = (String) attributeNameEnum.next();
|
||||
|
||||
String[] attributeNameSplit = attributeName.split(";");
|
||||
IncrementalAttributeState state = getState(attributeNameSplit[0]);
|
||||
if (attributeNameSplit.length == 1) {
|
||||
// No range specification for this attribute
|
||||
state.processValues(attributes, attributeName);
|
||||
} else {
|
||||
for (int i = 0; i < attributeNameSplit.length; i++) {
|
||||
String option = attributeNameSplit[i];
|
||||
|
||||
RangeOption responseRange = RangeOption.parse(option);
|
||||
|
||||
if (responseRange != null) {
|
||||
state.processValues(attributes, attributeName);
|
||||
state.calculateNextRange(responseRange);
|
||||
if (state.hasMore()) {
|
||||
rangedAttributesInNextIteration.add(state.getRequestedAttributeName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private IncrementalAttributeState getState(String attributeName) {
|
||||
Object mappedState = stateMap.get(attributeName);
|
||||
if (mappedState == null) {
|
||||
log.warn("Attribute '" + attributeName + "' is not handled by this instance");
|
||||
mappedState = NOT_FOUND_ATTRIBUTE_STATE;
|
||||
}
|
||||
|
||||
return (IncrementalAttributeState) mappedState;
|
||||
}
|
||||
|
||||
public final List getValues(String attributeName) {
|
||||
return getState(attributeName).getValues();
|
||||
}
|
||||
|
||||
public Attributes getCollectedAttributes() {
|
||||
BasicAttributes attributes = new BasicAttributes();
|
||||
|
||||
Set attributeNames = stateMap.keySet();
|
||||
for (Iterator iterator = attributeNames.iterator(); iterator.hasNext(); ) {
|
||||
String attributeName = (String) iterator.next();
|
||||
|
||||
BasicAttribute oneAttribute = new BasicAttribute(attributeName);
|
||||
List values = getValues(attributeName);
|
||||
if (values != null) {
|
||||
for (Iterator valueIterator = values.iterator(); valueIterator.hasNext(); ) {
|
||||
Object oneValue = valueIterator.next();
|
||||
oneAttribute.add(oneValue);
|
||||
}
|
||||
}
|
||||
|
||||
attributes.put(oneAttribute);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
public final boolean hasMore() {
|
||||
return rangedAttributesInNextIteration.size() > 0;
|
||||
}
|
||||
|
||||
public final String[] getAttributesForLookup() {
|
||||
String[] result = new String[rangedAttributesInNextIteration.size()];
|
||||
int index = 0;
|
||||
for (Iterator iterator = rangedAttributesInNextIteration.iterator(); iterator.hasNext(); ) {
|
||||
String next = (String) iterator.next();
|
||||
IncrementalAttributeState state = (IncrementalAttributeState) stateMap.get(next);
|
||||
result[index++] = state.getAttributeNameForQuery();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attribute, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attribute name of the attribute to request.
|
||||
* @return an Attributes instance, populated with all found values for the requested attribute.
|
||||
* Never <code>null</code>, though the actual attribute may not be set if it was not
|
||||
* set on the requested object.
|
||||
*/
|
||||
public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String attribute) {
|
||||
return lookupAttributes(ldapOperations, new DistinguishedName(dn), attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attributes, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attributes names of the attributes to request.
|
||||
* @return an Attributes instance, populated with all found values for the requested attributes.
|
||||
* Never <code>null</code>, though the actual attributes may not be set if they was not
|
||||
* set on the requested object.
|
||||
*/
|
||||
public static Attributes lookupAttributes(LdapOperations ldapOperations, String dn, String[] attributes) {
|
||||
return lookupAttributes(ldapOperations, new DistinguishedName(dn), attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attribute, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attribute name of the attribute to request.
|
||||
* @return an Attributes instance, populated with all found values for the requested attribute.
|
||||
* Never <code>null</code>, though the actual attribute may not be set if it was not
|
||||
* set on the requested object.
|
||||
*/
|
||||
public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String attribute) {
|
||||
return lookupAttributes(ldapOperations, dn, new String[]{attribute});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attributes, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attributes names of the attributes to request.
|
||||
* @return an Attributes instance, populated with all found values for the requested attributes.
|
||||
* Never <code>null</code>, though the actual attributes may not be set if they was not
|
||||
* set on the requested object.
|
||||
*/
|
||||
public static Attributes lookupAttributes(LdapOperations ldapOperations, Name dn, String[] attributes) {
|
||||
return loopForAllAttributeValues(ldapOperations, dn, attributes).getCollectedAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attribute, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attribute name of the attribute to request.
|
||||
* @return a list with all attribute values found for the requested attribute.
|
||||
* Never <code>null</code>, an empty list indicates that the attribute was not set or empty.
|
||||
*/
|
||||
public static List lookupAttributeValues(LdapOperations ldapOperations, String dn, String attribute) {
|
||||
return lookupAttributeValues(ldapOperations, new DistinguishedName(dn), attribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup all values for the specified attribute, looping through the results incrementally if necessary.
|
||||
*
|
||||
* @param ldapOperations The instance to use for performing the actual lookup.
|
||||
* @param dn The distinguished name of the object to find.
|
||||
* @param attribute name of the attribute to request.
|
||||
* @return a list with all attribute values found for the requested attribute.
|
||||
* Never <code>null</code>, an empty list indicates that the attribute was not set or empty.
|
||||
*/
|
||||
public static List lookupAttributeValues(LdapOperations ldapOperations, Name dn, String attribute) {
|
||||
List values = loopForAllAttributeValues(ldapOperations, dn, new String[]{attribute}).getValues(attribute);
|
||||
if(values == null) {
|
||||
values = Collections.emptyList();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
private static DefaultIncrementalAttributesMapper loopForAllAttributeValues(LdapOperations ldapOperations, Name dn, String[] attributes) {
|
||||
DefaultIncrementalAttributesMapper mapper = new DefaultIncrementalAttributesMapper(attributes);
|
||||
while (mapper.hasMore()) {
|
||||
ldapOperations.lookup(dn, mapper.getAttributesForLookup(), mapper);
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class keeps track of the state of an individual attribute in the process of collecting
|
||||
* multi-value attributes using ranges. Holds the values collected thus far, the next applicable range,
|
||||
* and the actual (requested) attribute name.
|
||||
*/
|
||||
private final static class DefaultIncrementalAttributeState implements IncrementalAttributeState {
|
||||
private final String actualAttributeName;
|
||||
private List values = null;
|
||||
private final int pageSize;
|
||||
boolean more = true;
|
||||
|
||||
private RangeOption requestRange;
|
||||
|
||||
private DefaultIncrementalAttributeState(String actualAttributeName, int pageSize) {
|
||||
this.actualAttributeName = actualAttributeName;
|
||||
this.pageSize = pageSize;
|
||||
this.requestRange = new RangeOption(0, pageSize);
|
||||
}
|
||||
|
||||
public boolean hasMore() {
|
||||
return more;
|
||||
}
|
||||
|
||||
public String getRequestedAttributeName() {
|
||||
return actualAttributeName;
|
||||
}
|
||||
|
||||
public void calculateNextRange(RangeOption responseRange) {
|
||||
more = requestRange.compareTo(responseRange) > 0;
|
||||
|
||||
if (more) {
|
||||
requestRange = responseRange.nextRange(pageSize);
|
||||
}
|
||||
}
|
||||
|
||||
public String getAttributeNameForQuery() {
|
||||
StringBuilder attributeBuilder = new StringBuilder(actualAttributeName);
|
||||
|
||||
if (!(requestRange.isFullRange())) {
|
||||
attributeBuilder.append(';');
|
||||
requestRange.appendTo(attributeBuilder);
|
||||
}
|
||||
|
||||
return attributeBuilder.toString();
|
||||
}
|
||||
|
||||
public void processValues(Attributes attributes, String attributeName) throws NamingException {
|
||||
Attribute attribute = attributes.get(attributeName);
|
||||
NamingEnumeration valueEnum = attribute.getAll();
|
||||
|
||||
initValuesIfApplicable();
|
||||
while (valueEnum.hasMore()) {
|
||||
values.add(valueEnum.next());
|
||||
}
|
||||
}
|
||||
|
||||
private void initValuesIfApplicable() {
|
||||
if (values == null) {
|
||||
values = new LinkedList();
|
||||
}
|
||||
}
|
||||
|
||||
public List getValues() {
|
||||
if (values != null) {
|
||||
return new ArrayList(values);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Mattias Hellborg Arthursson
|
||||
*/
|
||||
private static interface IncrementalAttributeState {
|
||||
boolean hasMore();
|
||||
|
||||
void calculateNextRange(RangeOption responseRange);
|
||||
|
||||
String getAttributeNameForQuery();
|
||||
|
||||
String getRequestedAttributeName();
|
||||
|
||||
void processValues(Attributes attributes, String attributeName) throws NamingException;
|
||||
|
||||
List getValues();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
package org.springframework.ldap.core.support;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
@@ -24,10 +24,10 @@ import java.util.regex.Matcher;
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
*
|
||||
* @see IncrementalAttributeMapper
|
||||
* @see DefaultIncrementalAttributesMapper
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public class RangeOption implements Comparable {
|
||||
class RangeOption implements Comparable {
|
||||
public static final int TERMINAL_END_OF_RANGE = -1;
|
||||
public static final int TERMINAL_MISSING = -2;
|
||||
|
||||
@@ -79,13 +79,12 @@ public class RangeOption implements Comparable {
|
||||
|
||||
public String toString() {
|
||||
StringBuilder rangeBuilder = new StringBuilder();
|
||||
|
||||
toString(rangeBuilder);
|
||||
appendTo(rangeBuilder);
|
||||
|
||||
return rangeBuilder.toString();
|
||||
}
|
||||
|
||||
public void toString(StringBuilder rangeBuilder) {
|
||||
public void appendTo(StringBuilder rangeBuilder) {
|
||||
rangeBuilder.append("Range=").append(initial);
|
||||
|
||||
if (!isTerminalMissing()) {
|
||||
@@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<body>
|
||||
|
||||
Support classes the core Spring LDAP package.
|
||||
Support classes for the core Spring LDAP package.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
|
||||
/**
|
||||
* @since 1.3.2
|
||||
* @author Marius Scurtescu
|
||||
*/
|
||||
public interface AttributeValueProcessor {
|
||||
public void process(Object value);
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.NamingEnumeration;
|
||||
|
||||
/**
|
||||
* Utility class that helps with reading all attribute values from Active Directory using <em>Incremental Retrieval of
|
||||
* Multi-valued Properties</em>.
|
||||
* <p/>
|
||||
* <p>Example usage of this attribute mapper:
|
||||
* <pre>
|
||||
* public void retrieveAttributeIncrementally(LdapTemplate ldap, LdapName entrDn, String attributeName, AttributeValueProcessor valueProcessor)
|
||||
* {
|
||||
* IncrementalAttributeMapper incrementalAttributeMapper = new IncrementalAttributeMapper(attributeName, valueProcessor);
|
||||
*
|
||||
* while (incrementalAttributeMapper.hasMore())
|
||||
* {
|
||||
* ldap.lookup(entrDn, incrementalAttributeMapper.getAttributesArray(), incrementalAttributeMapper);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
* @see <a href="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</a>
|
||||
* @since 1.3.2
|
||||
*/
|
||||
public class IncrementalAttributeMapper implements AttributesMapper {
|
||||
private final String attributeName;
|
||||
private final AttributeValueProcessor valueProcessor;
|
||||
|
||||
private boolean more = true;
|
||||
private int pageSize;
|
||||
private RangeOption requestRange = new RangeOption(0, pageSize);
|
||||
private boolean omitFullRange = true;
|
||||
|
||||
public IncrementalAttributeMapper(String attributeName, AttributeValueProcessor valueProcessor) {
|
||||
this(attributeName, valueProcessor, RangeOption.TERMINAL_END_OF_RANGE);
|
||||
}
|
||||
|
||||
public IncrementalAttributeMapper(String attributeName, AttributeValueProcessor valueProcessor, int pageSize) {
|
||||
this.attributeName = attributeName;
|
||||
this.pageSize = pageSize;
|
||||
this.requestRange = new RangeOption(0, pageSize);
|
||||
this.valueProcessor = valueProcessor;
|
||||
}
|
||||
|
||||
public boolean isOmitFullRange() {
|
||||
return omitFullRange;
|
||||
}
|
||||
|
||||
public void setOmitFullRange(boolean omitFullRange) {
|
||||
this.omitFullRange = omitFullRange;
|
||||
}
|
||||
|
||||
public Object mapFromAttributes(Attributes attributes) throws NamingException {
|
||||
if (!more) {
|
||||
throw new IllegalStateException("No more attributes!");
|
||||
}
|
||||
|
||||
more = false;
|
||||
|
||||
NamingEnumeration attributeNameEnum = attributes.getIDs();
|
||||
|
||||
while (attributeNameEnum.hasMore()) {
|
||||
String attributeName = (String) attributeNameEnum.next();
|
||||
|
||||
if (attributeName.equals(this.attributeName)) {
|
||||
processValues(attributes, this.attributeName);
|
||||
} else if (attributeName.startsWith(this.attributeName + ";")) {
|
||||
String[] attributeNameSplit = attributeName.split(";");
|
||||
for (int i = 0; i < attributeNameSplit.length; i++) {
|
||||
String option = attributeNameSplit[i];
|
||||
|
||||
RangeOption responseRange = RangeOption.parse(option);
|
||||
|
||||
if (responseRange != null) {
|
||||
more = requestRange.compareTo(responseRange) > 0;
|
||||
|
||||
if (more) {
|
||||
requestRange = responseRange.nextRange(pageSize);
|
||||
}
|
||||
|
||||
processValues(attributes, attributeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void processValues(Attributes attributes, String attributeName) throws NamingException {
|
||||
Attribute attribute = attributes.get(attributeName);
|
||||
NamingEnumeration valueEnum = attribute.getAll();
|
||||
|
||||
while (valueEnum.hasMore()) {
|
||||
valueProcessor.process(valueEnum.next());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasMore() {
|
||||
return more;
|
||||
}
|
||||
|
||||
public String[] getAttributesArray() {
|
||||
StringBuilder attributeBuilder = new StringBuilder(attributeName);
|
||||
|
||||
if (!(omitFullRange && requestRange.isFullRange())) {
|
||||
attributeBuilder.append(';');
|
||||
|
||||
requestRange.toString(attributeBuilder);
|
||||
}
|
||||
|
||||
return new String[]{attributeBuilder.toString()};
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @since 1.3.2
|
||||
* @author Marius Scurtescu
|
||||
*/
|
||||
public class ListAttributeValueProcessor implements AttributeValueProcessor {
|
||||
private List values = new LinkedList();
|
||||
|
||||
public void process(Object value) {
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
public List getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
<html>
|
||||
<body>
|
||||
|
||||
Support classes for Active Directory-specific functionality.
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -15,22 +15,22 @@
|
||||
*/
|
||||
package org.springframework.ldap.transaction.compensating;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper;
|
||||
import org.springframework.ldap.core.IncrementalAttributesMapper;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.naming.Name;
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.ModificationItem;
|
||||
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationRecorder;
|
||||
import org.springframework.util.Assert;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A {@link CompensatingTransactionOperationRecorder} keeping track of
|
||||
@@ -71,8 +71,16 @@ public class ModifyAttributesOperationRecorder implements
|
||||
// Get the current values of all referred Attributes.
|
||||
String[] attributeNameArray = (String[]) set.toArray(new String[set
|
||||
.size()]);
|
||||
Attributes currentAttributes = (Attributes) ldapOperations.lookup(dn,
|
||||
attributeNameArray, getAttributesMapper());
|
||||
|
||||
// LDAP-234: We need to explicitly an IncrementalAttributesMapper in
|
||||
// case we're working against AD and there are too many attribute values to be returned
|
||||
// by one query.
|
||||
IncrementalAttributesMapper attributesMapper = getAttributesMapper(attributeNameArray);
|
||||
while (attributesMapper.hasMore()) {
|
||||
ldapOperations.lookup(dn, attributesMapper.getAttributesForLookup(), attributesMapper);
|
||||
}
|
||||
|
||||
Attributes currentAttributes = attributesMapper.getCollectedAttributes();
|
||||
|
||||
// Get a compensating ModificationItem for each of the incoming
|
||||
// modification.
|
||||
@@ -93,13 +101,8 @@ public class ModifyAttributesOperationRecorder implements
|
||||
* @return the {@link AttributesMapper} to use for getting the current
|
||||
* Attributes of the target DN.
|
||||
*/
|
||||
AttributesMapper getAttributesMapper() {
|
||||
return new AttributesMapper() {
|
||||
public Object mapFromAttributes(Attributes attributes)
|
||||
throws NamingException {
|
||||
return attributes;
|
||||
}
|
||||
};
|
||||
IncrementalAttributesMapper getAttributesMapper(String[] attributeNames) {
|
||||
return new DefaultIncrementalAttributesMapper(attributeNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.core.support;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper;
|
||||
import org.springframework.ldap.core.support.RangeOption;
|
||||
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
|
||||
/**
|
||||
* IncrementalAttributesMapper Tester.
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
* @author Mattias Hellborg Arthursson
|
||||
*/
|
||||
public class DefaultIncrementalAttributesMapperTest extends TestCase {
|
||||
private DefaultIncrementalAttributesMapper tested;
|
||||
|
||||
public DefaultIncrementalAttributesMapperTest(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception {
|
||||
tested = new DefaultIncrementalAttributesMapper("member");
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception {
|
||||
tested = null;
|
||||
}
|
||||
|
||||
public void testGetAttributesArray() throws Exception {
|
||||
String[] attributes = tested.getAttributesForLookup();
|
||||
|
||||
assertEquals(1, attributes.length);
|
||||
assertEquals("member", attributes[0]);
|
||||
|
||||
tested = new DefaultIncrementalAttributesMapper(10, "member");
|
||||
|
||||
attributes = tested.getAttributesForLookup();
|
||||
|
||||
assertEquals(1, attributes.length);
|
||||
assertEquals("member;Range=0-10", attributes[0]);
|
||||
}
|
||||
|
||||
public void testGetAttributesArrayWithTwoAttributes() {
|
||||
tested = new DefaultIncrementalAttributesMapper(20, new String[]{"member", "cn"});
|
||||
String[] attributes = tested.getAttributesForLookup();
|
||||
|
||||
assertEquals(2, attributes.length);
|
||||
|
||||
assertEquals("member;Range=0-20", attributes[0]);
|
||||
assertEquals("cn;Range=0-20", attributes[1]);
|
||||
}
|
||||
|
||||
public void testLoopEmpty() throws Exception {
|
||||
assertTrue(tested.hasMore());
|
||||
|
||||
Attributes attributes = new BasicAttributes();
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertNull(tested.getValues("member"));
|
||||
}
|
||||
|
||||
public void testLoop() throws Exception {
|
||||
Attributes attributes = createAttributes("member", new RangeOption(0, 10));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(tested.hasMore());
|
||||
assertEquals(11, tested.getValues("member").size());
|
||||
|
||||
attributes = createAttributes("member", new RangeOption(11), 5);
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertEquals(16, tested.getValues("member").size());
|
||||
}
|
||||
|
||||
public void test1LoopWithPageSizeExact() throws Exception {
|
||||
tested = new DefaultIncrementalAttributesMapper(10, "member");
|
||||
|
||||
Attributes attributes = createAttributes("member", new RangeOption(0, 10));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertEquals(11, tested.getValues("member").size());
|
||||
}
|
||||
|
||||
public void test2LoopsWithPageSizeExact() throws Exception {
|
||||
tested = new DefaultIncrementalAttributesMapper(20, "member");
|
||||
|
||||
Attributes attributes = createAttributes("member", new RangeOption(0, 10));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(tested.hasMore());
|
||||
assertEquals(11, tested.getValues("member").size());
|
||||
|
||||
attributes = createAttributes("member", new RangeOption(11, 30));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertEquals(31, tested.getValues("member").size());
|
||||
}
|
||||
|
||||
public void test2LoopsWithPageSize() throws Exception {
|
||||
tested = new DefaultIncrementalAttributesMapper(20, "member");
|
||||
|
||||
Attributes attributes = createAttributes("member", new RangeOption(0, 10));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(tested.hasMore());
|
||||
assertEquals(11, tested.getValues("member").size());
|
||||
|
||||
attributes = createAttributes("member", new RangeOption(11), 5);
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertEquals(16, tested.getValues("member").size());
|
||||
}
|
||||
|
||||
public void testLoopWithTwoRangedAttributesLoopOnOneAttribute() throws Exception {
|
||||
tested = new DefaultIncrementalAttributesMapper(10, new String[]{"member", "cn"});
|
||||
|
||||
Attributes attributes = createAttributes("member", new RangeOption(0, 5));
|
||||
attributes.put(createRangeAttribute("cn", new RangeOption(0, 10), 10));
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(tested.hasMore());
|
||||
assertEquals(6, tested.getValues("member").size());
|
||||
assertEquals(10, tested.getValues("cn").size());
|
||||
|
||||
assertEquals(1, tested.getAttributesForLookup().length);
|
||||
|
||||
attributes = createAttributes("member", new RangeOption(6), 5);
|
||||
|
||||
tested.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(tested.hasMore());
|
||||
assertEquals(11, tested.getValues("member").size());
|
||||
}
|
||||
private Attributes createAttributes(String attributeName, RangeOption range) {
|
||||
return createAttributes(attributeName, range, range.getTerminal() - range.getInitial() + 1);
|
||||
}
|
||||
|
||||
private Attributes createAttributes(String attributeName, RangeOption range, int valueCnt) {
|
||||
Attributes attributes = new BasicAttributes();
|
||||
|
||||
Attribute attribute = createRangeAttribute(attributeName, range, valueCnt);
|
||||
attributes.put(attribute);
|
||||
|
||||
return attributes;
|
||||
|
||||
}
|
||||
|
||||
private Attribute createRangeAttribute(String attributeName, RangeOption range, int valueCnt) {
|
||||
Attribute attribute = new BasicAttribute(attributeName + ";" + range.toString());
|
||||
for (int i = 0; i < valueCnt; i++) {
|
||||
attribute.add("value" + (range.getInitial() + i - 1));
|
||||
}
|
||||
return attribute;
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
package org.springframework.ldap.core.support;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.springframework.ldap.core.support.RangeOption;
|
||||
|
||||
/**
|
||||
* IncrementalAttributeMapper Tester.
|
||||
* IncrementalAttributesMapper Tester.
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
*/
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright 2005-2010 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.ldap.support.ad;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.BasicAttributes;
|
||||
|
||||
/**
|
||||
* IncrementalAttributeMapper Tester.
|
||||
*
|
||||
* @author Marius Scurtescu
|
||||
*/
|
||||
public class IncrementalAttributeMapperTest extends TestCase {
|
||||
private IncrementalAttributeMapper incrementalAttributeMapper;
|
||||
private ListAttributeValueProcessor valueProcessor;
|
||||
|
||||
public IncrementalAttributeMapperTest(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception {
|
||||
valueProcessor = new ListAttributeValueProcessor();
|
||||
incrementalAttributeMapper = new IncrementalAttributeMapper("member", valueProcessor);
|
||||
}
|
||||
|
||||
public void tearDown() throws Exception {
|
||||
incrementalAttributeMapper = null;
|
||||
valueProcessor = null;
|
||||
}
|
||||
|
||||
public void testGetAttributesArray() throws Exception {
|
||||
String[] attributes = incrementalAttributeMapper.getAttributesArray();
|
||||
|
||||
assertEquals(1, attributes.length);
|
||||
assertEquals("member", attributes[0]);
|
||||
|
||||
incrementalAttributeMapper = new IncrementalAttributeMapper("member", valueProcessor, 10);
|
||||
|
||||
attributes = incrementalAttributeMapper.getAttributesArray();
|
||||
|
||||
assertEquals(1, attributes.length);
|
||||
assertEquals("member;Range=0-10", attributes[0]);
|
||||
}
|
||||
|
||||
public void testLoopEmpty() throws Exception {
|
||||
assertTrue(incrementalAttributeMapper.hasMore());
|
||||
|
||||
Attributes attributes = new BasicAttributes();
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(0, valueProcessor.getValues().size());
|
||||
}
|
||||
|
||||
public void testLoop() throws Exception {
|
||||
Attributes attributes = createAttributes(new RangeOption(0, 10), true);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(11, valueProcessor.getValues().size());
|
||||
|
||||
attributes = createAttributes(new RangeOption(11), 5, false);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(16, valueProcessor.getValues().size());
|
||||
}
|
||||
|
||||
public void test1LoopWithPageSizeExact() throws Exception {
|
||||
incrementalAttributeMapper = new IncrementalAttributeMapper("member", valueProcessor, 10);
|
||||
|
||||
Attributes attributes = createAttributes(new RangeOption(0, 10), true);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(11, valueProcessor.getValues().size());
|
||||
}
|
||||
|
||||
public void test2LoopsWithPageSizeExact() throws Exception {
|
||||
incrementalAttributeMapper = new IncrementalAttributeMapper("member", valueProcessor, 20);
|
||||
|
||||
Attributes attributes = createAttributes(new RangeOption(0, 10), true);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(11, valueProcessor.getValues().size());
|
||||
|
||||
attributes = createAttributes(new RangeOption(11, 30), false);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(31, valueProcessor.getValues().size());
|
||||
}
|
||||
|
||||
public void test2LoopsWithPageSize() throws Exception {
|
||||
incrementalAttributeMapper = new IncrementalAttributeMapper("member", valueProcessor, 20);
|
||||
|
||||
Attributes attributes = createAttributes(new RangeOption(0, 10), true);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertTrue(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(11, valueProcessor.getValues().size());
|
||||
|
||||
attributes = createAttributes(new RangeOption(11), 5, false);
|
||||
|
||||
incrementalAttributeMapper.mapFromAttributes(attributes);
|
||||
|
||||
assertFalse(incrementalAttributeMapper.hasMore());
|
||||
assertEquals(16, valueProcessor.getValues().size());
|
||||
}
|
||||
|
||||
private Attributes createAttributes(RangeOption range, boolean emptyPlain) {
|
||||
return createAttributes(range, range.getTerminal() - range.getInitial() + 1, emptyPlain);
|
||||
}
|
||||
|
||||
private Attributes createAttributes(RangeOption range, int valueCnt, boolean emptyPlain) {
|
||||
Attributes attributes = new BasicAttributes();
|
||||
|
||||
if (emptyPlain) {
|
||||
attributes.put(new BasicAttribute("member"));
|
||||
}
|
||||
|
||||
Attribute attribute = new BasicAttribute("member;" + range.toString());
|
||||
for (int i = 0; i < valueCnt; i++) {
|
||||
attribute.add("value" + (range.getInitial() + i - 1));
|
||||
}
|
||||
attributes.put(attribute);
|
||||
|
||||
return attributes;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,13 @@
|
||||
*/
|
||||
package org.springframework.ldap.transaction.compensating;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import org.easymock.MockControl;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.core.IncrementalAttributesMapper;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor;
|
||||
|
||||
import javax.naming.NamingException;
|
||||
import javax.naming.directory.Attribute;
|
||||
import javax.naming.directory.Attributes;
|
||||
@@ -23,16 +30,6 @@ import javax.naming.directory.BasicAttributes;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.ModificationItem;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.easymock.MockControl;
|
||||
import org.springframework.ldap.core.AttributesMapper;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.LdapOperations;
|
||||
import org.springframework.ldap.transaction.compensating.ModifyAttributesOperationExecutor;
|
||||
import org.springframework.ldap.transaction.compensating.ModifyAttributesOperationRecorder;
|
||||
import org.springframework.transaction.compensating.CompensatingTransactionOperationExecutor;
|
||||
|
||||
public class ModifyAttributesOperationRecorderTest extends TestCase {
|
||||
private MockControl ldapOperationsControl;
|
||||
|
||||
@@ -40,7 +37,7 @@ public class ModifyAttributesOperationRecorderTest extends TestCase {
|
||||
|
||||
private MockControl attributesMapperControl;
|
||||
|
||||
private AttributesMapper attributesMapperMock;
|
||||
private IncrementalAttributesMapper attributesMapperMock;
|
||||
|
||||
private ModifyAttributesOperationRecorder tested;
|
||||
|
||||
@@ -49,8 +46,8 @@ public class ModifyAttributesOperationRecorderTest extends TestCase {
|
||||
ldapOperationsMock = (LdapOperations) ldapOperationsControl.getMock();
|
||||
|
||||
attributesMapperControl = MockControl
|
||||
.createControl(AttributesMapper.class);
|
||||
attributesMapperMock = (AttributesMapper) attributesMapperControl
|
||||
.createControl(IncrementalAttributesMapper.class);
|
||||
attributesMapperMock = (IncrementalAttributesMapper) attributesMapperControl
|
||||
.getMock();
|
||||
|
||||
tested = new ModifyAttributesOperationRecorder(ldapOperationsMock);
|
||||
@@ -79,14 +76,14 @@ public class ModifyAttributesOperationRecorderTest extends TestCase {
|
||||
public void testRecordOperation() {
|
||||
final ModificationItem incomingItem = new ModificationItem(
|
||||
DirContext.ADD_ATTRIBUTE, new BasicAttribute("attribute1"));
|
||||
ModificationItem[] incomingMods = new ModificationItem[] { incomingItem };
|
||||
ModificationItem[] incomingMods = new ModificationItem[]{incomingItem};
|
||||
final ModificationItem compensatingItem = new ModificationItem(
|
||||
DirContext.ADD_ATTRIBUTE, new BasicAttribute("attribute2"));
|
||||
|
||||
final Attributes expectedAttributes = new BasicAttributes();
|
||||
|
||||
tested = new ModifyAttributesOperationRecorder(ldapOperationsMock) {
|
||||
AttributesMapper getAttributesMapper() {
|
||||
IncrementalAttributesMapper getAttributesMapper(String[] attributeNames) {
|
||||
return attributesMapperMock;
|
||||
}
|
||||
|
||||
@@ -101,14 +98,24 @@ public class ModifyAttributesOperationRecorderTest extends TestCase {
|
||||
|
||||
DistinguishedName expectedName = new DistinguishedName("cn=john doe");
|
||||
ldapOperationsControl.setDefaultMatcher(MockControl.ARRAY_MATCHER);
|
||||
ldapOperationsControl.expectAndReturn(ldapOperationsMock.lookup(
|
||||
expectedName, new String[] { "attribute1" },
|
||||
attributesMapperMock), expectedAttributes);
|
||||
|
||||
attributesMapperControl.expectAndReturn(
|
||||
attributesMapperMock.hasMore(), true);
|
||||
attributesMapperControl.expectAndReturn(
|
||||
attributesMapperMock.getAttributesForLookup(),
|
||||
new String[]{"attribute1"});
|
||||
ldapOperationsControl.expectAndReturn(
|
||||
ldapOperationsMock.lookup(expectedName, new String[]{"attribute1"}, attributesMapperMock),
|
||||
expectedAttributes);
|
||||
attributesMapperControl.expectAndReturn(attributesMapperMock.hasMore(), false);
|
||||
attributesMapperControl.expectAndReturn(
|
||||
attributesMapperMock.getCollectedAttributes(),
|
||||
expectedAttributes);
|
||||
|
||||
replay();
|
||||
// Perform test
|
||||
CompensatingTransactionOperationExecutor operation = tested
|
||||
.recordOperation(new Object[] { expectedName, incomingMods });
|
||||
.recordOperation(new Object[]{expectedName, incomingMods});
|
||||
verify();
|
||||
|
||||
// Verify outcome
|
||||
|
||||
@@ -13,10 +13,11 @@ include 'samples/demos/demo'
|
||||
include 'samples/demos/demo-tiger'
|
||||
include 'samples/samples-utils'
|
||||
include 'samples/simple-odm'
|
||||
include 'test-support'
|
||||
include 'test/integration-tests'
|
||||
include 'test/integration-tests-openldap'
|
||||
include 'test/integration-tests-sunone'
|
||||
include 'test-support'
|
||||
include 'test/integration-tests-ad'
|
||||
|
||||
rootProject.children.each { p->
|
||||
def name = p.name
|
||||
|
||||
@@ -50,4 +50,5 @@
|
||||
<xi:include href="user-authentication.xml" />
|
||||
<xi:include href="ldif-parsing.xml" />
|
||||
<xi:include href="odm.xml" />
|
||||
<xi:include href="utilities.xml" />
|
||||
</book>
|
||||
|
||||
24
src/docbkx/utilities.xml
Normal file
24
src/docbkx/utilities.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<chapter id="utilities">
|
||||
<title>Utilities</title>
|
||||
<sect1 id="incremental-attributes">
|
||||
<title>Incremental Retrieval of Multi-Valued Attributes</title>
|
||||
|
||||
<para>When there are a very large number of attribute values (>1500) for a specific attribute,
|
||||
Active Directory will typically refuse to return all these values at once. Instead
|
||||
the attribute values will be returned according to the
|
||||
<ulink url="http://www.watersprings.org/pub/id/draft-kashi-incremental-00.txt">Incremental Retrieval of Multi-valued Properties</ulink>
|
||||
method. This requires the calling part to inspect the returned attribute for specific markers and,
|
||||
if necessary, make additional lookup requests until all values are found.
|
||||
</para>
|
||||
<para>
|
||||
Spring LDAP's <code>org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper</code>
|
||||
helps working with this kind of attributes, as follows:
|
||||
<programlisting>
|
||||
Attributes attrs = DefaultIncrementalAttributeMapper.lookupAttributes(ldapTemplate, theDn, new Object[]{"oneAttribute", "anotherAttribute"});
|
||||
</programlisting>
|
||||
This will parse any returned attribute range markers and make repeated requests as necessary until all values
|
||||
for all requested attributes have been retrieved.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
14
test/integration-tests-ad/build.gradle
Normal file
14
test/integration-tests-ad/build.gradle
Normal file
@@ -0,0 +1,14 @@
|
||||
apply from: JAVA_SCRIPT
|
||||
|
||||
dependencies {
|
||||
compile project(":spring-ldap-test"),
|
||||
project(":spring-ldap-core-tiger")
|
||||
|
||||
provided "org.springframework:spring-jdbc:$springVersion",
|
||||
"org.springframework:spring-orm:$springVersion"
|
||||
|
||||
testCompile "org.springframework:spring-test:$springVersion",
|
||||
"junit:junit:$junitVersion"
|
||||
}
|
||||
|
||||
test.enabled = false // TODO this should be enabled depending on build parameter
|
||||
@@ -0,0 +1,210 @@
|
||||
package org.springframework.ldap.itest.ad;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.ldap.NameNotFoundException;
|
||||
import org.springframework.ldap.SizeLimitExceededException;
|
||||
import org.springframework.ldap.core.DirContextAdapter;
|
||||
import org.springframework.ldap.core.DirContextOperations;
|
||||
import org.springframework.ldap.core.DistinguishedName;
|
||||
import org.springframework.ldap.core.LdapTemplate;
|
||||
import org.springframework.ldap.core.support.DefaultIncrementalAttributesMapper;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.naming.directory.BasicAttribute;
|
||||
import javax.naming.directory.DirContext;
|
||||
import javax.naming.directory.ModificationItem;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Mattias Hellborg Arthursson
|
||||
*/
|
||||
@ContextConfiguration("classpath:/incrementalAttributeMapperTest.xml")
|
||||
public class IncrementalAttributeMapperITest extends AbstractJUnit4SpringContextTests {
|
||||
|
||||
private static final DistinguishedName BASE_DN = new DistinguishedName("ou=dummy,dc=261consulting,dc=local");
|
||||
private static final DistinguishedName OU_DN = new DistinguishedName("ou=dummy");
|
||||
private static final DistinguishedName GROUP_DN = new DistinguishedName(OU_DN).append("cn", "testgroup");
|
||||
private static final String DEFAULT_PASSWORD = "ahcoophah5Oi4oh";
|
||||
|
||||
@Autowired
|
||||
private LdapTemplate ldapTemplate;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager transactionManager;
|
||||
|
||||
@Before
|
||||
public void prepareTestData() throws UnsupportedEncodingException {
|
||||
cleanup();
|
||||
|
||||
createBaseOu();
|
||||
for (int i = 0; i < 1502; i++) {
|
||||
createUser("test" + i);
|
||||
}
|
||||
|
||||
createGroup();
|
||||
}
|
||||
|
||||
private void createGroup() {
|
||||
DirContextAdapter ctx = new DirContextAdapter(GROUP_DN);
|
||||
|
||||
ctx.addAttributeValue("objectclass", "top");
|
||||
ctx.addAttributeValue("objectclass", "group");
|
||||
ctx.addAttributeValue("cn", "testgroup");
|
||||
ctx.addAttributeValue("sAMAccountName", "TESTGROUP");
|
||||
|
||||
for (int i = 0; i < 1501; i++) {
|
||||
ctx.addAttributeValue("member", buildUserRefDn("test" + i));
|
||||
}
|
||||
|
||||
ldapTemplate.bind(ctx);
|
||||
}
|
||||
|
||||
private String buildUserRefDn(String username) {
|
||||
return new DistinguishedName(BASE_DN).append("cn", username).toString();
|
||||
}
|
||||
|
||||
private void createBaseOu() {
|
||||
createOu();
|
||||
}
|
||||
|
||||
private void createUser(String username) throws UnsupportedEncodingException {
|
||||
DirContextAdapter ctx = new DirContextAdapter(new DistinguishedName(OU_DN).append("cn", username));
|
||||
|
||||
ctx.addAttributeValue("objectclass", "top");
|
||||
ctx.addAttributeValue("objectclass", "person");
|
||||
ctx.addAttributeValue("objectclass", "organizationalPerson");
|
||||
ctx.addAttributeValue("objectclass", "user");
|
||||
|
||||
ctx.setAttributeValue("givenName", username);
|
||||
ctx.setAttributeValue("userPrincipalName", username + "@example.com");
|
||||
ctx.setAttributeValue("cn", username);
|
||||
ctx.setAttributeValue("description", "Dummy user");
|
||||
ctx.setAttributeValue("sAMAccountName", username.toUpperCase() + "." + username.toUpperCase());
|
||||
ctx.setAttributeValue("userAccountControl", "512");
|
||||
|
||||
String newQuotedPassword = "\"" + DEFAULT_PASSWORD + "\"";
|
||||
ctx.setAttributeValue("unicodePwd", newQuotedPassword.getBytes("UTF-16LE"));
|
||||
|
||||
ldapTemplate.bind(ctx);
|
||||
}
|
||||
|
||||
private void createOu() {
|
||||
DirContextAdapter ctx = new DirContextAdapter(OU_DN);
|
||||
|
||||
ctx.addAttributeValue("objectClass", "top");
|
||||
ctx.addAttributeValue("objectClass", "organizationalUnit");
|
||||
|
||||
ctx.setAttributeValue("ou", "dummy");
|
||||
ctx.setAttributeValue("description", "dummy description");
|
||||
|
||||
ldapTemplate.bind(ctx);
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
try {
|
||||
ldapTemplate.lookup(OU_DN);
|
||||
} catch (NameNotFoundException e) {
|
||||
// Nothing to cleanup
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
ldapTemplate.unbind(OU_DN, true);
|
||||
// Everything is deleted
|
||||
return;
|
||||
} catch (SizeLimitExceededException e) {
|
||||
// There's more to delete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void verifyRetrievalOfLotsOfAttributeValues() {
|
||||
DistinguishedName testgroupDn = new DistinguishedName(OU_DN).append("cn", "testgroup");
|
||||
|
||||
// The 'member' attribute consists of > 1500 entries and will not be returned without range specifier.
|
||||
DirContextOperations ctx = ldapTemplate.lookupContext(testgroupDn);
|
||||
assertNull(ctx.getStringAttribute("member"));
|
||||
|
||||
DefaultIncrementalAttributesMapper attributeMapper = new DefaultIncrementalAttributesMapper(new String[]{"member", "cn"});
|
||||
assertTrue("There should be more results to get", attributeMapper.hasMore());
|
||||
|
||||
String[] attributesArray = attributeMapper.getAttributesForLookup();
|
||||
assertEquals(2, attributesArray.length);
|
||||
assertEquals("member", attributesArray[0]);
|
||||
assertEquals("cn", attributesArray[1]);
|
||||
|
||||
// First iteration - there should now be more members left, but all cn values should have been collected.
|
||||
ldapTemplate.lookup(testgroupDn, attributesArray, attributeMapper);
|
||||
|
||||
assertTrue("There should be more results to get", attributeMapper.hasMore());
|
||||
// Only member attribute should be requested in this query.
|
||||
attributesArray = attributeMapper.getAttributesForLookup();
|
||||
assertEquals(1, attributesArray.length);
|
||||
assertEquals("member;Range=1500-*", attributesArray[0]);
|
||||
|
||||
// Second iteration - all data should now have been collected.
|
||||
ldapTemplate.lookup(testgroupDn, attributeMapper.getAttributesForLookup(), attributeMapper);
|
||||
assertFalse("There should be no more results to get", attributeMapper.hasMore());
|
||||
|
||||
List memberValues = attributeMapper.getValues("member");
|
||||
assertNotNull(memberValues);
|
||||
assertEquals(1501, memberValues.size());
|
||||
|
||||
List cnValues = attributeMapper.getValues("cn");
|
||||
assertNotNull(cnValues);
|
||||
assertEquals(1, cnValues.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void JiraLdap234ITest() {
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
|
||||
try {
|
||||
transactionTemplate.execute(new TransactionCallback<Object>() {
|
||||
@Override
|
||||
public Object doInTransaction(TransactionStatus status) {
|
||||
ModificationItem modificationItem = new ModificationItem(
|
||||
DirContext.ADD_ATTRIBUTE,
|
||||
new BasicAttribute("member", buildUserRefDn("test" + 1501)));
|
||||
ldapTemplate.modifyAttributes(GROUP_DN, new ModificationItem[]{modificationItem});
|
||||
|
||||
// The below should cause a rollback
|
||||
throw new RuntimeException("Simulate some failure");
|
||||
}
|
||||
});
|
||||
|
||||
fail("RuntimeException expected");
|
||||
} catch (RuntimeException expected) {
|
||||
DefaultIncrementalAttributesMapper attributeMapper = new DefaultIncrementalAttributesMapper(new String[]{"member"});
|
||||
while (attributeMapper.hasMore()) {
|
||||
ldapTemplate.lookup(GROUP_DN, attributeMapper.getAttributesForLookup(), attributeMapper);
|
||||
}
|
||||
|
||||
// LDAP-234: After rollback the attribute values were cleared after rollback
|
||||
assertEquals(
|
||||
1501,
|
||||
DefaultIncrementalAttributesMapper.lookupAttributeValues(
|
||||
ldapTemplate, GROUP_DN, "member").size());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<bean id="contextSourceTarget" class="org.springframework.ldap.core.support.LdapContextSource">
|
||||
<property name="url" value="ldaps://127.0.0.1:13636" />
|
||||
<property name="userDn" value="CN=ldaptest,CN=Users,DC=261consulting,DC=local" />
|
||||
<property name="password" value="Buc8xe6AZiewoh7" />
|
||||
<property name="base" value="DC=261consulting,DC=local" />
|
||||
<property name="pooled" value="true" />
|
||||
</bean>
|
||||
|
||||
<bean id="contextSource" class="org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy">
|
||||
<constructor-arg ref="contextSourceTarget" />
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.ldap.transaction.compensating.manager.ContextSourceTransactionManager">
|
||||
<property name="contextSource" ref="contextSourceTarget" />
|
||||
</bean>
|
||||
|
||||
<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
|
||||
<property name="contextSource" ref="contextSource" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@@ -16,4 +16,4 @@ dependencies {
|
||||
"com.sun:ldapbp:1.0"
|
||||
}
|
||||
|
||||
test.enabled = false // FIXME this needs to be enabled (error due to missing host)
|
||||
test.enabled = false // TODO this should be enabled depending on build parameter
|
||||
@@ -16,4 +16,4 @@ dependencies {
|
||||
"gsbase:gsbase:$gsbaseVersion"
|
||||
}
|
||||
|
||||
test.enabled = false // FIXME this needs to be enabled (error due to missing host)
|
||||
test.enabled = false // TODO this should be enabled depending on build parameter
|
||||
Reference in New Issue
Block a user