org.springframework.ldap.core.DistinguishedName Maven / Gradle / Ivy
/*
* Copyright 2005-2013 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 org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.ldap.BadLdapGrammarException;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.ldap.support.ListComparator;
import org.springframework.util.Assert;
import javax.naming.CompositeName;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.ldap.Rdn;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
/**
* Default implementation of a {@link Name} corresponding to an LDAP path. A
* Distinguished Name manipulation implementation is included in JDK1.5
* (LdapName), but not in prior releases.
*
* A DistinguishedName
is particularly useful when building or
* modifying an LDAP path dynamically, as escaping will be taken care of.
*
* A path is split into several names. The {@link Name} interface specifies that
* the most significant part be in position 0.
*
* Example:
*
*
* - The path
* - uid=adam.skogman, ou=People, ou=EU
* - Name[0]
* - ou=EU
* - Name[1]
* - ou=People
* - Name[2]
* - uid=adam.skogman
*
*
*
* Name
instances, and consequently DistinguishedName
* instances are naturally mutable, which is useful when constructing
* DistinguishedNames. Example:
*
*
* DistinguishedName path = new DistinguishedName("dc=jayway,dc=se");
* path.add("ou", "People");
* path.add("uid", "adam.skogman");
* String dn = path.toString();
*
*
* will render uid=adam.skogman,ou=People,dc=jayway,dc=se
.
*
*
* NOTE: 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 DistinguishedName
* 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)}.
*
*
* NB:As of version 1.3 the default toString representation of
* DistinguishedName now defaults to a compact one, without spaces between the
* respective RDNs. For backward compatibility, set the
* {@link #SPACED_DN_FORMAT_PROPERTY} ({@value #SPACED_DN_FORMAT_PROPERTY}) to
* true
.
* @author Adam Skogman
* @author Mattias Hellborg Arthursson
*/
public class DistinguishedName implements Name {
/**
* System property that will be inspected to determine whether
* {@link #toString()} will format the DN with spaces after each comma or
* use a more compact representation, i.e.:
* uid=adam.skogman, ou=People, dc=jayway, dc=se
rather than
* uid=adam.skogman,ou=People,dc=jayway,dc=se
. A value other
* than null or blank will trigger the spaced format. Default is the compact
* representation.
*
* Valid values are:
*
* - blank or null (property not set)
* - any non-blank value
*
* @since 1.3
* @see #toCompactString()
*/
public static final String SPACED_DN_FORMAT_PROPERTY = "org.springframework.ldap.core.spacedDnFormat";
/**
* System property that will be inspected to determine whether creating a
* DistinguishedName will convert the keys to lowercase, convert
* the keys to uppercase, or leave the keys as they were in the
* original String, ie none. Default is to convert the keys to
* lowercase.
*
* Valid values are:
*
* - "lower" or blank or null (property not set)
* - "upper"
* - "none"
*
* @since 1.3.1
* @see #KEY_CASE_FOLD_LOWER
* @see #KEY_CASE_FOLD_UPPER
* @see #KEY_CASE_FOLD_NONE
*/
public static final String KEY_CASE_FOLD_PROPERTY = "org.springframework.ldap.core.keyCaseFold";
public static final String KEY_CASE_FOLD_LOWER = "lower";
public static final String KEY_CASE_FOLD_UPPER = "upper";
public static final String KEY_CASE_FOLD_NONE = "none";
private static final String[] MANGLED_DOUBLE_QUOTES = new String[]{"\\\\\""};
private static final String[] PROPER_DOUBLE_QUOTES = new String[]{"\\\""};
private static final Log log = LogFactory.getLog(DistinguishedName.class);
private static final boolean COMPACT = true;
private static final boolean NON_COMPACT = false;
private static final long serialVersionUID = 3514344371999042586L;
/**
* An empty, unmodifiable DistinguishedName.
*/
public static final DistinguishedName EMPTY_PATH = new DistinguishedName(Collections.EMPTY_LIST);
private List names;
/**
* Construct a new DistinguishedName with no components.
*/
public DistinguishedName() {
names = new LinkedList();
}
/**
* Construct a new DistinguishedName
from a String.
*
* @param path a String corresponding to a (syntactically) valid LDAP path.
*/
public DistinguishedName(String path) {
if (StringUtils.isBlank(path)) {
names = new LinkedList();
}
else {
parse(path);
}
}
/**
* Construct a new DistinguishedName
from the supplied
* List
of {@link LdapRdn} objects.
*
* @param list the components that this instance will consist of.
*/
public DistinguishedName(List list) {
this.names = list;
}
/**
* Construct a new DistinguishedName
from the supplied
* {@link Name}. The parts of the supplied {@link Name} must be
* syntactically correct {@link LdapRdn}s.
*
* @param name the {@link Name} to construct a new
* DistinguishedName
from.
*/
public DistinguishedName(Name name) {
Assert.notNull(name, "name cannot be null");
if (name instanceof CompositeName) {
parse(LdapUtils.convertCompositeNameToString((CompositeName) name));
return;
}
names = new LinkedList();
for (int i = 0; i < name.size(); i++) {
names.add(new LdapRdn(name.get(i)));
}
}
/**
* Parse the supplied String and make this instance represent the
* corresponding distinguished name.
*
* @param path the LDAP path to parse.
*/
protected void parse(String path) {
DnParser parser = DefaultDnParserFactory.createDnParser(unmangleCompositeName(path));
DistinguishedName dn;
try {
dn = parser.dn();
}
catch (ParseException e) {
throw new BadLdapGrammarException("Failed to parse DN", e);
}
catch (TokenMgrError e) {
throw new BadLdapGrammarException("Failed to parse DN", e);
}
this.names = dn.names;
}
/**
* If path is surrounded by quotes, strip them. JNDI considers forward slash
* ('/') special, but LDAP doesn't. {@link CompositeName#toString()} tends
* to mangle a {@link Name} with a slash by surrounding it with quotes
* ('"').
*
* @param path Path to check and possibly strip.
* @return A String with the possibly stripped path.
*/
private String unmangleCompositeName(String path) {
String tempPath;
// Check if CompositeName has mangled the name with quotes
if (path.startsWith("\"") && path.endsWith("\"")) {
tempPath = path.substring(1, path.length() - 1);
}
else {
tempPath = path;
}
tempPath = StringUtils.replaceEach(tempPath, MANGLED_DOUBLE_QUOTES, PROPER_DOUBLE_QUOTES);
return tempPath;
}
/**
* Get the {@link LdapRdn} at a specified position.
*
* @param index the {@link LdapRdn} to retrieve.
* @return the {@link LdapRdn} at the requested position.
*/
public LdapRdn getLdapRdn(int index) {
return (LdapRdn) names.get(index);
}
/**
* Get the {@link LdapRdn} with the specified key. If there are several
* {@link Rdn}s with the same key, the first one found (in order of
* significance) will be returned.
*
* @param key Attribute name of the {@link LdapRdn} to retrieve.
* @return the {@link LdapRdn} with the requested key.
* @throws IllegalArgumentException if no Rdn matches the given key.
*/
public LdapRdn getLdapRdn(String key) {
for (Iterator iter = names.iterator(); iter.hasNext();) {
LdapRdn rdn = (LdapRdn) iter.next();
if (StringUtils.equals(rdn.getKey(), key)) {
return rdn;
}
}
throw new IllegalArgumentException("No Rdn with the requested key: '" + key + "'");
}
/**
* Get the value of the {@link LdapRdnComponent} with the specified key
* (Attribute value). If there are several Rdns with the same key, the value
* of the first one found (in order of significance) will be returned.
*
* @param key Attribute name of the {@link LdapRdn} to retrieve.
* @return the value.
* @throws IllegalArgumentException if no Rdn matches the given key.
*/
public String getValue(String key) {
return getLdapRdn(key).getValue();
}
/**
* Get the name List
.
*
* @return the list of {@link LdapRdn}s that this
* DistinguishedName
consists of.
*/
public List getNames() {
return names;
}
/**
* Get the String representation of this DistinguishedName
.
* Depending on the setting of property
* org.springframework.ldap.core.spacedDnFormat
a space will be
* added after each comma, to make the result more readable. Default is
* compact representation, i.e. without any spaces.
*
* @return a syntactically correct, properly escaped String representation
* of the DistinguishedName
.
* @see #SPACED_DN_FORMAT_PROPERTY
*/
public String toString() {
String spacedFormatting = System.getProperty(SPACED_DN_FORMAT_PROPERTY);
if (StringUtils.isBlank(spacedFormatting)) {
return format(COMPACT);
}
else {
return format(NON_COMPACT);
}
}
/**
* Get the compact String representation of this
* DistinguishedName
. Add no space after each comma, to make it
* compact.
*
* @return a syntactically correct, properly escaped String representation
* of the DistinguishedName
.
*/
public String toCompactString() {
return format(COMPACT);
}
/**
* Builds a complete LDAP path, ldap encoded, useful as a DN.
*
* Always uses lowercase, always separates with ", " i.e. comma and a space.
*
* @return the LDAP path.
*/
public String encode() {
return format(NON_COMPACT);
}
private String format(boolean compact) {
// empty path
if (names.size() == 0)
return "";
StringBuffer buffer = new StringBuffer(256);
ListIterator i = names.listIterator(names.size());
while (i.hasPrevious()) {
LdapRdn rdn = (LdapRdn) i.previous();
buffer.append(rdn.getLdapEncoded());
// add comma, except in last iteration
if (i.hasPrevious()) {
if (compact) {
buffer.append(",");
}
else {
buffer.append(", ");
}
}
}
return buffer.toString();
}
/**
* Builds a complete LDAP path, ldap and url encoded. Separates only with
* ",".
*
* @return the LDAP path, for use in an url.
*/
public String toUrl() {
StringBuffer buffer = new StringBuffer(256);
for (int i = names.size() - 1; i >= 0; i--) {
LdapRdn n = (LdapRdn) names.get(i);
buffer.append(n.encodeUrl());
if (i > 0) {
buffer.append(",");
}
}
return buffer.toString();
}
/**
* Determines if this DistinguishedName
path contains another
* path.
*
* @param path the path to check.
* @return true
if the supplied path is conained in this
* instance, false
otherwise.
*/
public boolean contains(DistinguishedName path) {
List shortlist = path.getNames();
// this path must be at least as long
if (getNames().size() < shortlist.size())
return false;
// must have names
if (shortlist.size() == 0)
return false;
Iterator longiter = getNames().iterator();
Iterator shortiter = shortlist.iterator();
LdapRdn longname = (LdapRdn) longiter.next();
LdapRdn shortname = (LdapRdn) shortiter.next();
// find first match
while (!longname.equals(shortname) && longiter.hasNext()) {
longname = (LdapRdn) longiter.next();
}
// Done?
if (!shortiter.hasNext() && longname.equals(shortname))
return true;
if (!longiter.hasNext())
return false;
// compare
while (longname.equals(shortname) && longiter.hasNext() && shortiter.hasNext()) {
longname = (LdapRdn) longiter.next();
shortname = (LdapRdn) shortiter.next();
}
// Done
if (!shortiter.hasNext() && longname.equals(shortname))
return true;
else
return false;
}
/**
* Add an LDAP path last in this DistinguishedName. E.g.:
*
*
* DistinguishedName name1 = new DistinguishedName("c=SE, dc=jayway, dc=se");
* DistinguishedName name2 = new DistinguishedName("ou=people");
* name1.append(name2);
*
*
* will result in ou=people, c=SE, dc=jayway, dc=se
*
* @param path the path to append.
* @return this instance.
*/
public DistinguishedName append(DistinguishedName path) {
getNames().addAll(path.getNames());
return this;
}
/**
* Append a new {@link LdapRdn} using the supplied key and value.
*
* @param key the key of the {@link LdapRdn}.
* @param value the value of the {@link LdapRdn}.
* @return this instance.
*/
public DistinguishedName append(String key, String value) {
add(key, value);
return this;
}
/**
* Add an LDAP path first in this DistinguishedName. E.g.:
*
*
* DistinguishedName name1 = new DistinguishedName("ou=people");
* DistinguishedName name2 = new DistinguishedName("c=SE, dc=jayway, dc=se");
* name1.prepend(name2);
*
*
* will result in ou=people, c=SE, dc=jayway, dc=se
*
* @param path the path to prepend.
*/
public void prepend(DistinguishedName path) {
ListIterator i = path.getNames().listIterator(path.getNames().size());
while (i.hasPrevious()) {
names.add(0, i.previous());
}
}
/**
* Remove the first part of this DistinguishedName
.
*
* @return the removed entry.
*/
public LdapRdn removeFirst() {
return (LdapRdn) names.remove(0);
}
/**
* Remove the supplied path from the beginning of this
* DistinguishedName
if this instance starts with
* path
. Useful for stripping base path suffix from a
* DistinguishedName
.
*
* @param path the path to remove from the beginning of this instance.
*/
public void removeFirst(Name path) {
if (path != null && this.startsWith(path)) {
for (int i = 0; i < path.size(); i++)
this.removeFirst();
}
}
/**
* @see java.lang.Object#clone()
*/
public Object clone() {
try {
DistinguishedName result = (DistinguishedName) super.clone();
result.names = new LinkedList(names);
return result;
}
catch (CloneNotSupportedException e) {
log.fatal("CloneNotSupported thrown from superclass - this should not happen");
throw new RuntimeException("Fatal error in clone", e);
}
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object obj) {
// A subclass with identical values should NOT be considered equal.
// EqualsBuilder in commons-lang cannot handle subclasses correctly.
if (obj == null || obj.getClass() != this.getClass()) {
return false;
}
DistinguishedName name = (DistinguishedName) obj;
// compare the lists
return getNames().equals(name.getNames());
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return this.getClass().hashCode() ^ getNames().hashCode();
}
/**
* Compare this instance to another object. Note that the comparison is done
* in order of significance, so the most significant Rdn is compared first,
* then the second and so on.
*
* @see javax.naming.Name#compareTo(java.lang.Object)
*/
public int compareTo(Object obj) {
DistinguishedName that = (DistinguishedName) obj;
ListComparator comparator = new ListComparator();
return comparator.compare(this.names, that.names);
}
public int size() {
return names.size();
}
public boolean isEmpty() {
return names.size() == 0;
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#getAll()
*/
public Enumeration getAll() {
LinkedList strings = new LinkedList();
for (Iterator iter = names.iterator(); iter.hasNext();) {
LdapRdn rdn = (LdapRdn) iter.next();
strings.add(rdn.getLdapEncoded());
}
return Collections.enumeration(strings);
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#get(int)
*/
public String get(int index) {
LdapRdn rdn = (LdapRdn) names.get(index);
return rdn.getLdapEncoded();
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#getPrefix(int)
*/
public Name getPrefix(int index) {
LinkedList newNames = new LinkedList();
for (int i = 0; i < index; i++) {
newNames.add(names.get(i));
}
return new DistinguishedName(newNames);
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#getSuffix(int)
*/
public Name getSuffix(int index) {
if (index > names.size()) {
throw new ArrayIndexOutOfBoundsException();
}
LinkedList newNames = new LinkedList();
for (int i = index; i < names.size(); i++) {
newNames.add(names.get(i));
}
return new DistinguishedName(newNames);
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#startsWith(javax.naming.Name)
*/
public boolean startsWith(Name name) {
if (name.size() == 0) {
return false;
}
DistinguishedName start = null;
if (name instanceof DistinguishedName) {
start = (DistinguishedName) name;
}
else {
return false;
}
if (start.size() > this.size()) {
return false;
}
Iterator longiter = names.iterator();
Iterator shortiter = start.getNames().iterator();
while (shortiter.hasNext()) {
Object longname = longiter.next();
Object shortname = shortiter.next();
if (!longname.equals(shortname)) {
return false;
}
}
// All names in shortiter matched.
return true;
}
/**
* Determines if this DistinguishedName
ends with a certian
* path.
*
* If the argument path is empty (no names in path) this method will return
* false
.
*
* @param name The suffix to check for.
*
*/
public boolean endsWith(Name name) {
DistinguishedName path = null;
if (name instanceof DistinguishedName) {
path = (DistinguishedName) name;
}
else {
return false;
}
List shortlist = path.getNames();
// this path must be at least as long
if (getNames().size() < shortlist.size())
return false;
// must have names
if (shortlist.size() == 0)
return false;
ListIterator longiter = getNames().listIterator(getNames().size());
ListIterator shortiter = shortlist.listIterator(shortlist.size());
while (shortiter.hasPrevious()) {
LdapRdn longname = (LdapRdn) longiter.previous();
LdapRdn shortname = (LdapRdn) shortiter.previous();
if (!longname.equals(shortname))
return false;
}
// if short list ended, all were equal
return true;
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#addAll(javax.naming.Name)
*/
public Name addAll(Name name) throws InvalidNameException {
return addAll(names.size(), name);
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#addAll(int, javax.naming.Name)
*/
public Name addAll(int arg0, Name name) throws InvalidNameException {
DistinguishedName distinguishedName = null;
try {
distinguishedName = (DistinguishedName) name;
}
catch (ClassCastException e) {
throw new InvalidNameException("Invalid name type");
}
names.addAll(arg0, distinguishedName.getNames());
return this;
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#add(java.lang.String)
*/
public Name add(String string) throws InvalidNameException {
return add(names.size(), string);
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#add(int, java.lang.String)
*/
public Name add(int index, String string) throws InvalidNameException {
try {
names.add(index, new LdapRdn(string));
}
catch (BadLdapGrammarException e) {
throw new InvalidNameException("Failed to parse rdn '" + string + "'");
}
return this;
}
/*
* (non-Javadoc)
*
* @see javax.naming.Name#remove(int)
*/
public Object remove(int arg0) throws InvalidNameException {
LdapRdn rdn = (LdapRdn) names.remove(arg0);
return rdn.getLdapEncoded();
}
/**
* Remove the last part of this DistinguishedName
.
*
* @return the removed {@link LdapRdn}.
*/
public LdapRdn removeLast() {
return (LdapRdn) names.remove(names.size() - 1);
}
/**
* Add a new {@link LdapRdn} using the supplied key and value.
*
* @param key the key of the {@link LdapRdn}.
* @param value the value of the {@link LdapRdn}.
*/
public void add(String key, String value) {
names.add(new LdapRdn(key, value));
}
/**
* Add the supplied {@link LdapRdn} last in the list of Rdns.
*
* @param rdn the {@link LdapRdn} to add.
*/
public void add(LdapRdn rdn) {
names.add(rdn);
}
/**
* Add the supplied {@link LdapRdn} att the specified index.
*
* @param idx the index at which to add the LdapRdn.
* @param rdn the LdapRdn to add.
*/
public void add(int idx, LdapRdn rdn) {
names.add(idx, rdn);
}
/**
* 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() {
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();
}
}