* Made sure that DistinguishedName#immutableDistinguishedName returns truly immutable instances (i.e. the Rdns and Components will also be immutable)

* BaseLdapPathBeanPostProcessor now always provides immutable DistinguishedName instances.
* DirContextAdapter#getDn now returns a copy of the internally kept DistinguishedName instance.
* AbstractContextSource#getBase now returns a copy of the internally kept DistinguishedName instance.
This commit is contained in:
Mattias Arthursson
2008-11-17 14:06:44 +00:00
parent bea272e7b4
commit 8abe6d59e5
7 changed files with 348 additions and 236 deletions

View File

@@ -1204,7 +1204,7 @@ public class DirContextAdapter implements DirContextOperations {
* @see org.springframework.ldap.support.DirContextOperations#getDn()
*/
public Name getDn() {
return dn;
return new DistinguishedName(dn);
}
/*

View File

@@ -16,6 +16,7 @@
package org.springframework.ldap.core;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
@@ -57,8 +58,11 @@ import org.springframework.ldap.support.ListComparator;
* <dt>Name[2]</dt>
* <dd>uid=adam.skogman</dd>
* </dl>
*
* Example:
* </p>
* <p>
* <code>Name</code> instances, and consequently <code>DistinguishedName</code>
* instances are naturally mutable, which is useful when constructing
* DistinguishedNames. Example:
*
* <pre>
* DistinguishedName path = new DistinguishedName(&quot;dc=jayway,dc=se&quot;);
@@ -68,7 +72,16 @@ import org.springframework.ldap.support.ListComparator;
* </pre>
*
* will render <code>uid=adam.skogman,ou=People,dc=jayway,dc=se</code>.
*
* </p>
* <p>
* <b>NOTE:</b> The fact that DistinguishedName instances are mutable needs to
* be taken into careful account, as this means that they may be modified
* involuntarily. This means that whenever a <code>DistinguishedName</code>
* instance is kept for reference (e.g. for identification of a domain entry) or
* as a constant, you should consider getting an immutable copy of the instance
* using {@link #immutableDistinguishedName()} or
* {@link #immutableDistinguishedName(String)}.
* </p>
* <p>
* <b>NB:</b>As of version 1.3 the default toString representation of
* DistinguishedName now defaults to a compact one, without spaces between the
@@ -761,13 +774,32 @@ public class DistinguishedName implements Name {
}
/**
* Return an unmodifialbe copy of this instance. Note that the individual
* Rdns will still be possible to modify.
* Return an immutable copy of this instance. It will not be possible to add
* or remove any Rdns to or from the returned instance, and the respective
* Rdns will also be immutable in turn.
*
* @return a copy of this instance backed by an immutable list.
* @since 1.2
*/
public DistinguishedName immutableDistinguishedName() {
return new DistinguishedName(Collections.unmodifiableList(getNames()));
List listWithImmutableRdns = new ArrayList(names.size());
for (Iterator iterator = names.iterator(); iterator.hasNext();) {
LdapRdn rdn = (LdapRdn) iterator.next();
listWithImmutableRdns.add(rdn.immutableLdapRdn());
}
return new DistinguishedName(Collections.unmodifiableList(listWithImmutableRdns));
}
/**
* Create an immutable DistinguishedName instance, suitable as a constant.
*
* @param dnString the DN string to parse.
* @return an immutable DistinguishedName corresponding to the supplied DN
* string.
* @since 1.3
*/
public static final DistinguishedName immutableDistinguishedName(String dnString) {
return new DistinguishedName(dnString).immutableDistinguishedName();
}
}

View File

@@ -17,6 +17,8 @@
package org.springframework.ldap.core;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
@@ -35,224 +37,232 @@ import org.springframework.ldap.support.ListComparator;
* @author Mattias Hellborg Arthursson
*/
public class LdapRdn implements Serializable, Comparable {
private static final long serialVersionUID = 5681397547245228750L;
private static final long serialVersionUID = 5681397547245228750L;
private List components = new LinkedList();
private List components = new LinkedList();
/**
* Default constructor. Create an empty, uninitialized LdapRdn.
*/
public LdapRdn() {
}
/**
* Default constructor. Create an empty, uninitialized LdapRdn.
*/
public LdapRdn() {
}
/**
* Parse the supplied string and construct this instance accordingly.
*
* @param string
* the string to parse.
*/
public LdapRdn(String string) {
DnParser parser = DefaultDnParserFactory.createDnParser(string);
LdapRdn rdn;
try {
rdn = parser.rdn();
} catch (ParseException e) {
throw new BadLdapGrammarException("Failed to parse Rdn", e);
} catch (TokenMgrError e) {
throw new BadLdapGrammarException("Failed to parse Rdn", e);
}
this.components = rdn.components;
}
/**
* Parse the supplied string and construct this instance accordingly.
*
* @param string the string to parse.
*/
public LdapRdn(String string) {
DnParser parser = DefaultDnParserFactory.createDnParser(string);
LdapRdn rdn;
try {
rdn = parser.rdn();
}
catch (ParseException e) {
throw new BadLdapGrammarException("Failed to parse Rdn", e);
}
catch (TokenMgrError e) {
throw new BadLdapGrammarException("Failed to parse Rdn", e);
}
this.components = rdn.components;
}
/**
* Construct an LdapRdn using the supplied key and value.
*
* @param key
* the attribute name.
* @param value
* the attribute value.
*/
public LdapRdn(String key, String value) {
components.add(new LdapRdnComponent(key, value));
}
/**
* Construct an LdapRdn using the supplied key and value.
*
* @param key the attribute name.
* @param value the attribute value.
*/
public LdapRdn(String key, String value) {
components.add(new LdapRdnComponent(key, value));
}
/**
* Add an LdapRdnComponent to this LdapRdn.
*
* @param rdnComponent
* the LdapRdnComponent to add.s
*/
public void addComponent(LdapRdnComponent rdnComponent) {
components.add(rdnComponent);
}
/**
* Add an LdapRdnComponent to this LdapRdn.
*
* @param rdnComponent the LdapRdnComponent to add.s
*/
public void addComponent(LdapRdnComponent rdnComponent) {
components.add(rdnComponent);
}
/**
* Gets all components in this LdapRdn.
*
* @return the List of all LdapRdnComponents composing this LdapRdn.
*/
public List getComponents() {
return components;
}
/**
* Gets all components in this LdapRdn.
*
* @return the List of all LdapRdnComponents composing this LdapRdn.
*/
public List getComponents() {
return components;
}
/**
* Gets the first LdapRdnComponent of this LdapRdn.
*
* @return The first LdapRdnComponent of this LdapRdn.
* @throws IndexOutOfBoundsException
* if there are no components in this Rdn.
*/
public LdapRdnComponent getComponent() {
return (LdapRdnComponent) components.get(0);
}
/**
* Gets the first LdapRdnComponent of this LdapRdn.
*
* @return The first LdapRdnComponent of this LdapRdn.
* @throws IndexOutOfBoundsException if there are no components in this Rdn.
*/
public LdapRdnComponent getComponent() {
return (LdapRdnComponent) components.get(0);
}
/**
* Get the LdapRdnComponent at index <code>idx</code>.
*
* @param idx
* the 0-based index of the component to get.
* @return the LdapRdnComponent at index <code>idx</code>.
* @throws IndexOutOfBoundsException
* if there are no components in this Rdn.
*/
public LdapRdnComponent getComponent(int idx) {
return (LdapRdnComponent) components.get(idx);
}
/**
* Get the LdapRdnComponent at index <code>idx</code>.
*
* @param idx the 0-based index of the component to get.
* @return the LdapRdnComponent at index <code>idx</code>.
* @throws IndexOutOfBoundsException if there are no components in this Rdn.
*/
public LdapRdnComponent getComponent(int idx) {
return (LdapRdnComponent) components.get(idx);
}
/**
* Get a properly rfc2253-encoded String representation of this LdapRdn.
*
* @return an escaped String corresponding to this LdapRdn.
* @throws IndexOutOfBoundsException
* if there are no components in this Rdn.
*/
public String getLdapEncoded() {
if (components.size() == 0) {
throw new IndexOutOfBoundsException("No components in Rdn.");
}
StringBuffer sb = new StringBuffer(100);
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
sb.append(component.encodeLdap());
if (iter.hasNext()) {
sb.append("+");
}
}
/**
* Get a properly rfc2253-encoded String representation of this LdapRdn.
*
* @return an escaped String corresponding to this LdapRdn.
* @throws IndexOutOfBoundsException if there are no components in this Rdn.
*/
public String getLdapEncoded() {
if (components.size() == 0) {
throw new IndexOutOfBoundsException("No components in Rdn.");
}
StringBuffer sb = new StringBuffer(100);
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
sb.append(component.encodeLdap());
if (iter.hasNext()) {
sb.append("+");
}
}
return sb.toString();
}
return sb.toString();
}
/**
* Get a String representation of this LdapRdn for use in urls.
*
* @return a String representation of this LdapRdn for use in urls.
*/
public String encodeUrl() {
StringBuffer sb = new StringBuffer(100);
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
sb.append(component.encodeUrl());
if (iter.hasNext()) {
sb.append("+");
}
}
/**
* Get a String representation of this LdapRdn for use in urls.
*
* @return a String representation of this LdapRdn for use in urls.
*/
public String encodeUrl() {
StringBuffer sb = new StringBuffer(100);
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
sb.append(component.encodeUrl());
if (iter.hasNext()) {
sb.append("+");
}
}
return sb.toString();
}
return sb.toString();
}
/**
* Compare this LdapRdn to another object.
*
* @param obj
* the object to compare to.
* @throws ClassCastException
* if the supplied object is not an LdapRdn instance.
*/
public int compareTo(Object obj) {
LdapRdn that = (LdapRdn) obj;
Comparator comparator = new ListComparator();
return comparator.compare(this.components, that.components);
}
/**
* Compare this LdapRdn to another object.
*
* @param obj the object to compare to.
* @throws ClassCastException if the supplied object is not an LdapRdn
* instance.
*/
public int compareTo(Object obj) {
LdapRdn that = (LdapRdn) obj;
Comparator comparator = new ListComparator();
return comparator.compare(this.components, that.components);
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
LdapRdn that = (LdapRdn) obj;
return this.getComponents().equals(that.getComponents());
}
LdapRdn that = (LdapRdn) obj;
return this.getComponents().equals(that.getComponents());
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return this.getClass().hashCode() ^ getComponents().hashCode();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return this.getClass().hashCode() ^ getComponents().hashCode();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return getLdapEncoded();
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
public String toString() {
return getLdapEncoded();
}
/**
* Get the value of this LdapRdn. Note that if this Rdn is multi-value the
* first value will be returned. E.g. for the Rdn
* <code>cn=john doe+sn=doe</code>, the return value would be
* <code>john doe</code>.
*
* @return the (first) value of this LdapRdn.
* @throws IndexOutOfBoundsException
* if there are no components in this Rdn.
*/
public String getValue() {
return getComponent().getValue();
}
/**
* Get the value of this LdapRdn. Note that if this Rdn is multi-value the
* first value will be returned. E.g. for the Rdn
* <code>cn=john doe+sn=doe</code>, the return value would be
* <code>john doe</code>.
*
* @return the (first) value of this LdapRdn.
* @throws IndexOutOfBoundsException if there are no components in this Rdn.
*/
public String getValue() {
return getComponent().getValue();
}
/**
* Get the key of this LdapRdn. Note that if this Rdn is multi-value the
* first key will be returned. E.g. for the Rdn
* <code>cn=john doe+sn=doe</code>, the return value would be
* <code>cn</code>.
*
* @return the (first) key of this LdapRdn.
* @throws IndexOutOfBoundsException
* if there are no components in this Rdn.
*/
public String getKey() {
return getComponent().getKey();
}
/**
* Get the key of this LdapRdn. Note that if this Rdn is multi-value the
* first key will be returned. E.g. for the Rdn
* <code>cn=john doe+sn=doe</code>, the return value would be
* <code>cn</code>.
*
* @return the (first) key of this LdapRdn.
* @throws IndexOutOfBoundsException if there are no components in this Rdn.
*/
public String getKey() {
return getComponent().getKey();
}
/**
* Get the value of the LdapComponent with the specified key (Attribute
* name).
*
* @param key
* the key
* @return the value.
* @throws IllegalArgumentException
* if there is no component with the specified key.
*/
public String getValue(String key) {
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
if (StringUtils.equals(component.getKey(), key)) {
return component.getValue();
}
}
/**
* Get the value of the LdapComponent with the specified key (Attribute
* name).
*
* @param key the key
* @return the value.
* @throws IllegalArgumentException if there is no component with the
* specified key.
*/
public String getValue(String key) {
for (Iterator iter = components.iterator(); iter.hasNext();) {
LdapRdnComponent component = (LdapRdnComponent) iter.next();
if (StringUtils.equals(component.getKey(), key)) {
return component.getValue();
}
}
throw new IllegalArgumentException("No RdnComponent with the key "
+ key);
}
throw new IllegalArgumentException("No RdnComponent with the key " + key);
}
/**
* Create an immutable copy of this instance. It will not be possible to add
* or remove components or modify the keys and values of these components.
*
* @return an immutable copy of this instance.
* @since 1.3
*/
public LdapRdn immutableLdapRdn() {
List listWithImmutableRdns = new ArrayList(components.size());
for (Iterator iterator = components.iterator(); iterator.hasNext();) {
LdapRdnComponent rdnComponent = (LdapRdnComponent) iterator.next();
listWithImmutableRdns.add(rdnComponent.immutableLdapRdnComponent());
}
List unmodifiableListOfImmutableRdns = Collections.unmodifiableList(listWithImmutableRdns);
LdapRdn immutableRdn = new LdapRdn();
immutableRdn.components = unmodifiableListOfImmutableRdns;
return immutableRdn;
}
}

View File

@@ -170,7 +170,9 @@ public class LdapRdnComponent implements Comparable, Serializable {
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == LdapRdnComponent.class) {
// Slightly more lenient equals comparison here to enable immutable
// instances to equal mutable ones.
if (obj != null && obj instanceof LdapRdnComponent) {
LdapRdnComponent that = (LdapRdnComponent) obj;
return StringUtils.equalsIgnoreCase(this.key, that.key)
&& StringUtils.equalsIgnoreCase(this.value, that.value);
@@ -192,4 +194,31 @@ public class LdapRdnComponent implements Comparable, Serializable {
LdapRdnComponent that = (LdapRdnComponent) obj;
return this.toString().compareTo(that.toString());
}
/**
* Create an immutable copy of this instance. It will not be possible to
* modify the key or the value of the returned instance.
*
* @return an immutable copy of this instance.
* @since 1.3
*/
public LdapRdnComponent immutableLdapRdnComponent() {
return new ImmutableLdapRdnComponent(key, value);
}
private static class ImmutableLdapRdnComponent extends LdapRdnComponent {
private static final long serialVersionUID = -7099970046426346567L;
public ImmutableLdapRdnComponent(String key, String value) {
super(key, value);
}
public void setKey(String key) {
throw new UnsupportedOperationException("SetValue not supported for this immutable LdapRdnComponent");
}
public void setValue(String value) {
throw new UnsupportedOperationException("SetKey not supported for this immutable LdapRdnComponent");
}
}
}

View File

@@ -215,7 +215,7 @@ public abstract class AbstractContextSource implements BaseLdapPathContextSource
* @return the base suffix
*/
protected DistinguishedName getBase() {
return base;
return new DistinguishedName(base);
}
/*

View File

@@ -31,18 +31,17 @@ import org.springframework.util.StringUtils;
* processed bean.
* <p>
* If the <code>baseLdapPath</code> property of this
* <code>BeanPostProcessor</code> is set, that value will be used. Otherwise,
* in order to determine which base LDAP path to supply to the instance the
* <code>BeanPostProcessor</code> is set, that value will be used. Otherwise, in
* order to determine which base LDAP path to supply to the instance the
* <code>ApplicationContext</code> is searched for any beans that are
* implementations of {@link BaseLdapPathSource}. If one single occurrence
* is found, that instance is queried for its base path, and that is what will
* be injected. If more than one {@link BaseLdapPathSource} instance is
* configured in the <code>ApplicationContext</code>, the name of the one to
* use will need to be specified to the <code>baseLdapPathSourceName</code>
* property; otherwise the post processing will fail. If no
* {@link BaseLdapPathSource} implementing bean is found in the context and
* the <code>basePath</code> property is not set, post processing will also
* fail.
* implementations of {@link BaseLdapPathSource}. If one single occurrence is
* found, that instance is queried for its base path, and that is what will be
* injected. If more than one {@link BaseLdapPathSource} instance is configured
* in the <code>ApplicationContext</code>, the name of the one to use will need
* to be specified to the <code>baseLdapPathSourceName</code> property;
* otherwise the post processing will fail. If no {@link BaseLdapPathSource}
* implementing bean is found in the context and the <code>basePath</code>
* property is not set, post processing will also fail.
*
* @author Mattias Hellborg Arthursson
* @since 1.2
@@ -64,7 +63,7 @@ public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, Applica
}
else {
BaseLdapPathSource ldapPathSource = getBaseLdapPathSourceFromApplicationContext();
baseLdapPathAware.setBaseLdapPath(ldapPathSource.getBaseLdapPath());
baseLdapPathAware.setBaseLdapPath(ldapPathSource.getBaseLdapPath().immutableDistinguishedName());
}
}
return bean;
@@ -87,8 +86,9 @@ public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, Applica
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object,
* java.lang.String)
*
* @seeorg.springframework.beans.factory.config.BeanPostProcessor#
* postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// Do nothing for this implementation
@@ -108,7 +108,7 @@ public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, Applica
* @param basePath the base path.
*/
public void setBasePath(DistinguishedName basePath) {
this.basePath = basePath;
this.basePath = basePath.immutableDistinguishedName();
}
/**
@@ -116,8 +116,8 @@ public class BaseLdapPathBeanPostProcessor implements BeanPostProcessor, Applica
* the base path. This method is typically useful if several ContextSource
* instances have been configured.
*
* @param contextSourceName the name of the <code>ContextSource</code>
* bean to use for determining the base path.
* @param contextSourceName the name of the <code>ContextSource</code> bean
* to use for determining the base path.
*/
public void setBaseLdapPathSourceName(String contextSourceName) {
this.baseLdapPathSourceName = contextSourceName;

View File

@@ -17,17 +17,15 @@
package org.springframework.ldap.core;
import java.util.Enumeration;
import java.util.List;
import javax.naming.CompositeName;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import org.springframework.ldap.BadLdapGrammarException;
import org.springframework.ldap.core.DistinguishedName;
import junit.framework.TestCase;
import org.springframework.ldap.BadLdapGrammarException;
import com.gargoylesoftware.base.testing.EqualsTester;
/**
@@ -140,7 +138,8 @@ public class DistinguishedNameTest extends TestCase {
// original object
final Object originalObject = new DistinguishedName("cn=john.doe, OU=Users,OU=Some company,C=SE");
// another object that has the same values as the original (case is ignored)
// another object that has the same values as the original (case is
// ignored)
final Object identicalObject = new DistinguishedName("cn=john.doe, OU=Users,OU=SOME COMPANY,C=SE");
// another object with different values
@@ -485,13 +484,10 @@ public class DistinguishedNameTest extends TestCase {
assertEquals("cn=john doe,ou=company1,dc=mycompany,dc=com", tested.toString());
}
public void testUnmodifiableDistinguishedName() throws Exception {
DistinguishedName name = new DistinguishedName("cn=john doe");
DistinguishedName result = name.immutableDistinguishedName();
List names = result.getNames();
public void testUnmodifiableDistinguishedNameFailsToAddRdn() throws Exception {
DistinguishedName result = DistinguishedName.immutableDistinguishedName("cn=john doe");
try {
names.add(new LdapRdnComponent("somekey", "somevalue"));
result.add(new LdapRdn("somekey", "somevalue"));
fail("UnsupportedOperationException expected");
}
catch (UnsupportedOperationException expected) {
@@ -499,6 +495,51 @@ public class DistinguishedNameTest extends TestCase {
}
}
public void testUnmodifiableDistinguishedNameFailsToModifyRdn() throws Exception {
DistinguishedName result = DistinguishedName.immutableDistinguishedName("cn=john doe");
LdapRdn ldapRdn = result.getLdapRdn(0);
try {
ldapRdn.addComponent(new LdapRdnComponent("somekey", "somevalue"));
fail("UnsupportedOperationException expected");
}
catch (UnsupportedOperationException expected) {
assertTrue(true);
}
}
public void testUnmodifiableDistinguishedNameFailsToModifyRdnComponentKey() throws Exception {
DistinguishedName result = DistinguishedName.immutableDistinguishedName("cn=john doe");
LdapRdnComponent component = result.getLdapRdn(0).getComponent();
try {
component.setKey("somekey");
fail("UnsupportedOperationException expected");
}
catch (UnsupportedOperationException expected) {
assertTrue(true);
}
}
public void testUnmodifiableDistinguishedNameFailsToModifyRdnComponentValue() throws Exception {
DistinguishedName result = DistinguishedName.immutableDistinguishedName("cn=john doe");
LdapRdnComponent component = result.getLdapRdn(0).getComponent();
try {
component.setValue("somevalue");
fail("UnsupportedOperationException expected");
}
catch (UnsupportedOperationException expected) {
assertTrue(true);
}
}
public void testUnmodifiableDistinguishedNameEqualsIdenticalMutableOne() throws Exception {
DistinguishedName immutable = DistinguishedName.immutableDistinguishedName("cn=john doe");
DistinguishedName mutable = new DistinguishedName("cn=john doe");
assertTrue(immutable.equals(mutable));
}
/**
* Test for LDAP-97.
*/