View Javadoc
1   /*
2    * Copyright 2002-2005 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package net.sf.ldaptemplate.support;
18  
19  import java.util.Collections;
20  import java.util.Enumeration;
21  import java.util.Iterator;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.ListIterator;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  import javax.naming.InvalidNameException;
29  import javax.naming.Name;
30  
31  import net.sf.ldaptemplate.BadLdapGrammarException;
32  
33  import org.apache.commons.lang.StringUtils;
34  
35  /***
36   * Default implementation of a Name corresponding to an LDAP path. A
37   * DistinguishedName implementation is included in JDK1.5 (LdapName), but not in
38   * prior releases, and this implementation is intended if a prior implementation
39   * is used.
40   * 
41   * An DistinguishedName is particularly useful when building or modifying an
42   * Ldap path dynamically, as escaping will be taken care of.
43   * 
44   * A path is split into several names. The Name interface specifies that the
45   * most significant part be in position 0, i.e.
46   * 
47   * The path: uid=adam.skogman, ou=People, ou=EU Name[0]: ou=EU Name[1]:
48   * ou=People Name[2]: uid=adam.skogman
49   * 
50   * Useful for parsing and building LDAP paths.
51   * 
52   * <pre>
53   * DistinguishedName path = new DistinguishedName();
54   * path.addLast(&quot;cn&quot;, entry.getUid());
55   * path.addLast(&quot;ou&quot;, &quot;users&quot;);
56   * path.append(new DistinguishedName(helpdesk.getSomeSuffix()));
57   * String dn = path.toString();
58   * </pre>
59   * 
60   * TODO: Implement compareTo().
61   * 
62   * @author Adam Skogman
63   * @author Mattias Arthursson
64   */
65  public class DistinguishedName implements Name {
66      private static final long serialVersionUID = 3514344371999042586L;
67  
68      public static final DistinguishedName EMPTY_PATH = new DistinguishedName();
69  
70      private LinkedList names;
71  
72      protected static final Pattern NAME_PATTERN = Pattern
73              .compile("(.*?[^////])(,|;|$)");
74  
75      /***
76       * Construct a new DistinguishedName with no components.
77       */
78      public DistinguishedName() {
79          names = new LinkedList();
80      }
81  
82      /***
83       * Construct a new DistinguishedName from a String.
84       * 
85       * @param path
86       *            a String corresponding to a (syntactically) valid LDAP path.
87       */
88      public DistinguishedName(String path) {
89          parse(path);
90      }
91  
92      /***
93       * Construct a new DistinguishedName from the supplied List of LdapRdn
94       * objects.
95       * 
96       * @param list
97       *            the components that this instance will consist of.
98       */
99      public DistinguishedName(LinkedList list) {
100         this.names = list;
101     }
102 
103     /***
104      * Construct a new DistinguishedName from the supplied Name. The parts of
105      * the supplied Name must be syntactically correct LdapRdns.
106      * 
107      * @param name
108      *            the Name to construct a new DistinguishedName from.
109      */
110     public DistinguishedName(Name name) {
111         names = new LinkedList();
112         for (int i = 0; i < name.size(); i++) {
113             names.add(new LdapRdn(name.get(i)));
114         }
115     }
116 
117     /***
118      * Parse the supplied String and make this instance represent the
119      * corresponding distinguished name.
120      * 
121      * @param path
122      *            the LDAP path to parse.
123      */
124     protected void parse(String path) {
125         names = new LinkedList();
126 
127         if (!StringUtils.isBlank(path)) {
128 
129             Matcher matcher = NAME_PATTERN.matcher(path);
130 
131             while (matcher.find()) {
132                 String rdnString = matcher.group(1);
133                 LdapRdn name = new LdapRdn(rdnString);
134                 names.add(0, name);
135             }
136         }
137     }
138 
139     /***
140      * Get the LdapRdn at a specified position.
141      * 
142      * @param index
143      *            the LdapRdn to retrieve.
144      * @return the LdapRdn at the requested position.
145      */
146     public LdapRdn getLdapRdn(int index) {
147         return (LdapRdn) names.get(index);
148     }
149 
150     /***
151      * Get the name list.
152      * 
153      * @return the list of LdapRdns that this DistinguishedName consists of.
154      */
155     public LinkedList getNames() {
156         return names;
157     }
158 
159     /***
160      * Get the String representation of this DistinguishedName.
161      * 
162      * @return a syntactically correct, escaped String representation of the
163      *         DistinguishedName.
164      */
165     public String toString() {
166         return encode();
167     }
168 
169     /***
170      * Builds a complete LDAP path, ldap encoded, useful as a DN
171      * 
172      * Always uses lowercase, always separates with ", " i.e. comma and a space.
173      * 
174      * @return the LDAP path.
175      */
176     public String encode() {
177 
178         // empty path
179         if (names.size() == 0)
180             return "";
181 
182         StringBuffer buffer = new StringBuffer(256);
183 
184         ListIterator i = names.listIterator(names.size());
185         while (i.hasPrevious()) {
186             LdapRdn rdn = (LdapRdn) i.previous();
187             buffer.append(rdn.getLdapEncoded());
188 
189             // add comma, except in last iteration
190             if (i.hasPrevious())
191                 buffer.append(", ");
192         }
193 
194         return buffer.toString();
195 
196     }
197 
198     /***
199      * Builds a complete LDAP path, ldap and url encoded. Separates only with
200      * ",".
201      * 
202      * @return the LDAP path, for use in an url.
203      */
204     public String toUrl() {
205         StringBuffer buffer = new StringBuffer(256);
206 
207         for (int i = names.size() - 1; i >= 0; i--) {
208             LdapRdn n = (LdapRdn) names.get(i);
209             buffer.append(n.encodeUrl());
210             if (i > 0) {
211                 buffer.append(",");
212             }
213         }
214         return buffer.toString();
215     }
216 
217     /***
218      * Determines if a ldap path contains another path.
219      * 
220      * @param path
221      *            the path to check.
222      * @return true if the supplied path is conained in this instance, false
223      *         otherwise.
224      */
225     public boolean contains(DistinguishedName path) {
226 
227         List shortlist = path.getNames();
228 
229         // this path must be at least as long
230         if (getNames().size() < shortlist.size())
231             return false;
232 
233         // must have names
234         if (shortlist.size() == 0)
235             return false;
236 
237         Iterator longiter = getNames().iterator();
238         Iterator shortiter = shortlist.iterator();
239 
240         LdapRdn longname = (LdapRdn) longiter.next();
241         LdapRdn shortname = (LdapRdn) shortiter.next();
242 
243         // find first match
244         while (!longname.equals(shortname) && longiter.hasNext()) {
245             longname = (LdapRdn) longiter.next();
246         }
247 
248         // Done?
249         if (!shortiter.hasNext() && longname.equals(shortname))
250             return true;
251         if (!longiter.hasNext())
252             return false;
253 
254         // compare
255         while (longname.equals(shortname) && longiter.hasNext()
256                 && shortiter.hasNext()) {
257             longname = (LdapRdn) longiter.next();
258             shortname = (LdapRdn) shortiter.next();
259         }
260 
261         // Done
262         if (!shortiter.hasNext() && longname.equals(shortname))
263             return true;
264         else
265             return false;
266 
267     }
268 
269     /***
270      * Add a LDAP path first
271      * 
272      * @param path
273      */
274     public void append(DistinguishedName path) {
275         getNames().addAll(path.getNames());
276     }
277 
278     /***
279      * Add a LDAP path first
280      * 
281      * @param path
282      */
283     public void prepend(DistinguishedName path) {
284         ListIterator i = path.getNames().listIterator(path.getNames().size());
285         while (i.hasPrevious()) {
286             getNames().addFirst(i.previous());
287         }
288     }
289 
290     /***
291      * Remove the first part of this DistinguishedName.
292      * 
293      * @return the removed entry.
294      */
295     public LdapRdn removeFirst() {
296         return (LdapRdn) getNames().removeFirst();
297     }
298 
299     /***
300      * Remove the supplied path from the beginning of this DistinguishedName if
301      * this instance starts with <path>. Useful for stripping base path suffix
302      * from a DistinguishedName.
303      * 
304      * @param path
305      *            the path to remove from the beginning of this instance.
306      */
307     public void removeFirst(Name path) {
308         if (path != null && this.startsWith(path)) {
309             for (int i = 0; i < path.size(); i++)
310                 this.removeFirst();
311         }
312     }
313 
314     /***
315      * @see java.lang.Object#clone()
316      */
317     public Object clone() {
318 
319         // just duplicate the list, the rdns are immutable.
320         LinkedList list = new LinkedList(getNames());
321 
322         return new DistinguishedName(list);
323     }
324 
325     /***
326      * @see java.lang.Object#equals(java.lang.Object)
327      */
328     public boolean equals(Object obj) {
329         // A subclass with identical values should NOT be considered equal.
330         // EqualsBuilder in commons-lang cannot handle subclasses correctly.
331         if (obj == null || obj.getClass() != this.getClass()) {
332             return false;
333         }
334 
335         DistinguishedName name = (DistinguishedName) obj;
336 
337         // compare the lists
338         return getNames().equals(name.getNames());
339     }
340 
341     /***
342      * @see java.lang.Object#hashCode()
343      */
344     public int hashCode() {
345         return this.getClass().hashCode() ^ getNames().hashCode();
346     }
347 
348     public int compareTo(Object o) {
349         // TODO: Implement this.
350         return 0;
351     }
352 
353     public int size() {
354         return names.size();
355     }
356 
357     public boolean isEmpty() {
358         return names.size() == 0;
359     }
360 
361     /*
362      * (non-Javadoc)
363      * 
364      * @see javax.naming.Name#getAll()
365      */
366     public Enumeration getAll() {
367         LinkedList strings = new LinkedList();
368         for (Iterator iter = names.iterator(); iter.hasNext();) {
369             LdapRdn rdn = (LdapRdn) iter.next();
370             strings.add(rdn.getLdapEncoded());
371         }
372 
373         return Collections.enumeration(strings);
374     }
375 
376     /*
377      * (non-Javadoc)
378      * 
379      * @see javax.naming.Name#get(int)
380      */
381     public String get(int index) {
382         LdapRdn rdn = (LdapRdn) names.get(index);
383         return rdn.getLdapEncoded();
384     }
385 
386     /*
387      * (non-Javadoc)
388      * 
389      * @see javax.naming.Name#getPrefix(int)
390      */
391     public Name getPrefix(int index) {
392         LinkedList newNames = new LinkedList();
393         for (int i = 0; i < index; i++) {
394             newNames.add(names.get(i));
395         }
396 
397         return new DistinguishedName(newNames);
398     }
399 
400     /*
401      * (non-Javadoc)
402      * 
403      * @see javax.naming.Name#getSuffix(int)
404      */
405     public Name getSuffix(int index) {
406         if (index > names.size()) {
407             throw new ArrayIndexOutOfBoundsException();
408         }
409 
410         LinkedList newNames = new LinkedList();
411         for (int i = index; i < names.size(); i++) {
412             newNames.add(names.get(i));
413         }
414 
415         return new DistinguishedName(newNames);
416     }
417 
418     /*
419      * (non-Javadoc)
420      * 
421      * @see javax.naming.Name#startsWith(javax.naming.Name)
422      */
423     public boolean startsWith(Name name) {
424         if (name.size() == 0) {
425             return false;
426         }
427 
428         DistinguishedName start = null;
429         if (name instanceof DistinguishedName) {
430             start = (DistinguishedName) name;
431         } else {
432             return false;
433         }
434 
435         if (start.size() > this.size()) {
436             return false;
437         }
438 
439         Iterator longiter = names.iterator();
440         Iterator shortiter = start.getNames().iterator();
441 
442         while (shortiter.hasNext()) {
443             Object longname = longiter.next();
444             Object shortname = shortiter.next();
445 
446             if (!longname.equals(shortname)) {
447                 return false;
448             }
449         }
450 
451         // All names in shortiter matched.
452         return true;
453     }
454 
455     /***
456      * Determines if this ldap path ends with a certian path.
457      * 
458      * If the argument path is empty (no names in path) this methid will return
459      * false.
460      * 
461      * @param name
462      *            The suffix to check for
463      * 
464      */
465     public boolean endsWith(Name name) {
466         DistinguishedName path = null;
467         if (name instanceof DistinguishedName) {
468             path = (DistinguishedName) name;
469         } else {
470             return false;
471         }
472 
473         List shortlist = path.getNames();
474 
475         // this path must be at least as long
476         if (getNames().size() < shortlist.size())
477             return false;
478 
479         // must have names
480         if (shortlist.size() == 0)
481             return false;
482 
483         ListIterator longiter = getNames().listIterator(getNames().size());
484         ListIterator shortiter = shortlist.listIterator(shortlist.size());
485 
486         while (shortiter.hasPrevious()) {
487             LdapRdn longname = (LdapRdn) longiter.previous();
488             LdapRdn shortname = (LdapRdn) shortiter.previous();
489 
490             if (!longname.equals(shortname))
491                 return false;
492         }
493 
494         // if short list ended, all were equal
495         return true;
496 
497     }
498 
499     /*
500      * (non-Javadoc)
501      * 
502      * @see javax.naming.Name#addAll(javax.naming.Name)
503      */
504     public Name addAll(Name name) throws InvalidNameException {
505         return addAll(names.size(), name);
506     }
507 
508     /*
509      * (non-Javadoc)
510      * 
511      * @see javax.naming.Name#addAll(int, javax.naming.Name)
512      */
513     public Name addAll(int arg0, Name name) throws InvalidNameException {
514         DistinguishedName distinguishedName = null;
515         try {
516             distinguishedName = (DistinguishedName) name;
517         } catch (ClassCastException e) {
518             throw new InvalidNameException("Invalid name type");
519         }
520 
521         names.addAll(arg0, distinguishedName.getNames());
522         return this;
523     }
524 
525     /*
526      * (non-Javadoc)
527      * 
528      * @see javax.naming.Name#add(java.lang.String)
529      */
530     public Name add(String string) throws InvalidNameException {
531         return add(names.size(), string);
532     }
533 
534     /*
535      * (non-Javadoc)
536      * 
537      * @see javax.naming.Name#add(int, java.lang.String)
538      */
539     public Name add(int index, String string) throws InvalidNameException {
540         try {
541             names.add(index, new LdapRdn(string));
542         } catch (BadLdapGrammarException e) {
543             throw new InvalidNameException("Failed to parse rdn '" + string
544                     + "'");
545         }
546         return this;
547     }
548 
549     /*
550      * (non-Javadoc)
551      * 
552      * @see javax.naming.Name#remove(int)
553      */
554     public Object remove(int arg0) throws InvalidNameException {
555         LdapRdn rdn = (LdapRdn) names.remove(arg0);
556         return rdn.getLdapEncoded();
557     }
558 
559     /***
560      * Remove the ldast part of this DistinguishedName.
561      * 
562      * @return the removed LdapRdn.
563      */
564     public LdapRdn removeLast() {
565         return (LdapRdn) getNames().removeLast();
566     }
567 
568     public void add(String key, String value) {
569         names.add(new LdapRdn(key, value));
570     }
571 }