LDAP-234: Rollback intermittently causes data loss

Moved stuff related to LDAP-176 from sandbox to core.
Minor formatting tweaks.
This commit is contained in:
Mattias Hellborg Arthursson
2013-07-30 12:46:26 +02:00
parent 895af4a44f
commit 79a922c778
11 changed files with 486 additions and 483 deletions

View File

@@ -0,0 +1,24 @@
/*
* 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);
}

View File

@@ -0,0 +1,135 @@
/*
* 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()};
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.regex.Pattern;
import java.util.regex.Matcher;
/**
* Attribute name Range Option used for <em>Incremental Retrieval of
* Multi-valued Properties</em>.
*
* @author Marius Scurtescu
*
* @see IncrementalAttributeMapper
* @since 1.3.2
*/
public class RangeOption implements Comparable {
public static final int TERMINAL_END_OF_RANGE = -1;
public static final int TERMINAL_MISSING = -2;
private int initial = 0;
private int terminal = TERMINAL_END_OF_RANGE;
private static Pattern RANGE_PATTERN = Pattern.compile("^Range=([0-9]+)(-([0-9]+|\\*))?$", Pattern.CASE_INSENSITIVE);
public RangeOption(int initial) {
this(initial, TERMINAL_END_OF_RANGE);
}
public RangeOption(int initial, int terminal) {
if (terminal < 0 && (terminal != TERMINAL_END_OF_RANGE && terminal != TERMINAL_MISSING)) {
throw new IllegalArgumentException("Illegal range-terminal: " + terminal);
}
if (initial < 0) {
throw new IllegalArgumentException("Illegal range-initial: " + initial);
}
if (terminal >= 0 && terminal < initial) {
throw new IllegalArgumentException("range-terminal cannot be smaller than range-initial: " + initial + "-" + terminal);
}
this.initial = initial;
this.terminal = terminal;
}
public boolean isTerminalEndOfRange() {
return terminal == TERMINAL_END_OF_RANGE;
}
public boolean isTerminalMissing() {
return terminal == TERMINAL_MISSING;
}
public int getInitial() {
return initial;
}
public int getTerminal() {
return terminal;
}
public boolean isFullRange() {
return getInitial() == 0 && getTerminal() == TERMINAL_END_OF_RANGE;
}
public String toString() {
StringBuilder rangeBuilder = new StringBuilder();
toString(rangeBuilder);
return rangeBuilder.toString();
}
public void toString(StringBuilder rangeBuilder) {
rangeBuilder.append("Range=").append(initial);
if (!isTerminalMissing()) {
rangeBuilder.append('-');
if (isTerminalEndOfRange())
rangeBuilder.append('*');
else
rangeBuilder.append(terminal);
}
}
public static RangeOption parse(String option) {
Matcher rangeMatcher = RANGE_PATTERN.matcher(option);
rangeMatcher.find();
if (!rangeMatcher.matches()) {
return null;
}
String initialStr = rangeMatcher.group(1);
int initial = Integer.parseInt(initialStr);
int terminal = TERMINAL_MISSING;
if (rangeMatcher.group(2) != null) {
String terminalStr = rangeMatcher.group(3);
if ("*".equals(terminalStr))
terminal = TERMINAL_END_OF_RANGE;
else
terminal = Integer.parseInt(terminalStr);
}
return new RangeOption(initial, terminal);
}
public int compareTo(Object o) {
RangeOption that;
if (o instanceof RangeOption) {
that = (RangeOption) o;
} else {
throw new IllegalArgumentException("A RangeOption instance cannot be compared to " + o.getClass());
}
if (this.getInitial() != that.getInitial())
throw new IllegalStateException("Ranges cannot be compared, range-initial not the same: " + this.toString() + " vs " + that.toString());
if (this.getTerminal() == that.getTerminal()) {
return 0;
}
if (that.getTerminal() == TERMINAL_MISSING) {
throw new IllegalStateException("Don't know how to deal with missing range-terminal: " + that.toString());
}
if (this.getTerminal() == TERMINAL_MISSING) {
throw new IllegalStateException("Don't know how to deal with missing range-terminal: " + this.toString());
}
if (this.getTerminal() == TERMINAL_END_OF_RANGE) {
return 1;
}
if (that.getTerminal() == TERMINAL_END_OF_RANGE) {
return -1;
}
return this.getTerminal() > that.getTerminal() ? 1 : -1;
}
public RangeOption nextRange(int pageSize) {
if (getTerminal() < 0) {
throw new IllegalStateException("Cannot generate next range, range-terminal: " + getTerminal());
}
if (pageSize < 0 && pageSize != TERMINAL_END_OF_RANGE) {
throw new IllegalArgumentException("Invalid page size: " + pageSize);
}
int initial = getTerminal() + 1;
int terminal = pageSize == TERMINAL_END_OF_RANGE ? TERMINAL_END_OF_RANGE : getTerminal() + pageSize;
return new RangeOption(initial, terminal);
}
}

View File

@@ -0,0 +1,7 @@
<html>
<body>
Support classes for Active Directory-specific functionality.
</body>
</html>

View File

@@ -0,0 +1,156 @@
/*
* 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;
}
}

View File

@@ -0,0 +1,184 @@
/*
* 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;
/**
* IncrementalAttributeMapper Tester.
*
* @author Marius Scurtescu
*/
public class RangeOptionTest extends TestCase {
public RangeOptionTest(String name) {
super(name);
}
public void testConstructorInvalid() {
try {
new RangeOption(101, 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RangeOption(-1, 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RangeOption(-10, 100);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RangeOption(0, -3);
fail("IllegalArgumentException expected");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testToString() throws Exception {
RangeOption range = new RangeOption(0, 100);
assertEquals("Range=0-100", range.toString());
range = new RangeOption(0, RangeOption.TERMINAL_END_OF_RANGE);
assertEquals("Range=0-*", range.toString());
range = new RangeOption(0, RangeOption.TERMINAL_MISSING);
assertEquals("Range=0", range.toString());
}
public void testParse() throws Exception {
RangeOption range = RangeOption.parse("Range=0-100");
assertEquals(0, range.getInitial());
assertEquals(100, range.getTerminal());
range = RangeOption.parse("range=0-100");
assertEquals(0, range.getInitial());
assertEquals(100, range.getTerminal());
range = RangeOption.parse("RANGE=0-100");
assertEquals(0, range.getInitial());
assertEquals(100, range.getTerminal());
range = RangeOption.parse("Range=0-*");
assertEquals(0, range.getInitial());
assertEquals(RangeOption.TERMINAL_END_OF_RANGE, range.getTerminal());
range = RangeOption.parse("Range=10");
assertEquals(10, range.getInitial());
assertEquals(RangeOption.TERMINAL_MISSING, range.getTerminal());
}
public void testParseInvalid() {
assertNull(RangeOption.parse("Range=10-"));
assertNull(RangeOption.parse("Range=10-a"));
assertNull(RangeOption.parse("lang-en"));
assertNull(RangeOption.parse("member;Range=10-100"));
assertNull(RangeOption.parse(";Range=10-100"));
assertNull(RangeOption.parse("Range=10-100;"));
assertNull(RangeOption.parse("Range=10-100;lang-de"));
}
public void testCompare() {
RangeOption range1 = RangeOption.parse("Range=10-500");
RangeOption range2 = RangeOption.parse("Range=10-500");
assertTrue(range1.compareTo(range2) == 0);
assertTrue(range2.compareTo(range1) == 0);
range1 = RangeOption.parse("Range=0-*");
range2 = RangeOption.parse("Range=0-*");
assertTrue(range1.compareTo(range2) == 0);
assertTrue(range2.compareTo(range1) == 0);
range1 = RangeOption.parse("Range=0");
range2 = RangeOption.parse("Range=0");
assertTrue(range1.compareTo(range2) == 0);
assertTrue(range2.compareTo(range1) == 0);
range1 = RangeOption.parse("Range=0-101");
range2 = RangeOption.parse("Range=0-100");
assertTrue(range1.compareTo(range2) > 0);
assertTrue(range2.compareTo(range1) < 0);
range1 = RangeOption.parse("Range=0-*");
range2 = RangeOption.parse("Range=0-100");
assertTrue(range1.compareTo(range2) > 0);
assertTrue(range2.compareTo(range1) < 0);
}
public void testCompareInvalid() {
RangeOption range1 = RangeOption.parse("Range=10-500");
RangeOption range2 = RangeOption.parse("Range=11-500");
try {
assertTrue(range1.compareTo(range2) == 0);
fail("IllegalStateException expected");
} catch (IllegalStateException expected) {
assertTrue(true);
}
range1 = RangeOption.parse("Range=10");
range2 = RangeOption.parse("Range=10-500");
try {
assertTrue(range1.compareTo(range2) == 0);
fail("IllegalStateException expected");
} catch (IllegalStateException expected) {
assertTrue(true);
}
range1 = RangeOption.parse("Range=10-500");
range2 = RangeOption.parse("Range=10");
try {
assertTrue(range1.compareTo(range2) == 0);
fail("IllegalStateException expected");
} catch (IllegalStateException expected) {
assertTrue(true);
}
}
public void testNext() {
RangeOption range = RangeOption.parse("Range=0-100");
range = range.nextRange(100);
assertEquals(101, range.getInitial());
assertEquals(200, range.getTerminal());
range = range.nextRange(10);
assertEquals(201, range.getInitial());
assertEquals(210, range.getTerminal());
range = range.nextRange(RangeOption.TERMINAL_END_OF_RANGE);
assertEquals(211, range.getInitial());
assertEquals(RangeOption.TERMINAL_END_OF_RANGE, range.getTerminal());
}
}