View Javadoc

1   /*
2    * Copyright 2005-2008 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 org.springframework.ldap.core;
18  
19  import java.util.ArrayList;
20  import java.util.Hashtable;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.SortedSet;
24  import java.util.TreeSet;
25  
26  import javax.naming.Context;
27  import javax.naming.Name;
28  import javax.naming.NameNotFoundException;
29  import javax.naming.NameParser;
30  import javax.naming.NamingEnumeration;
31  import javax.naming.NamingException;
32  import javax.naming.directory.Attribute;
33  import javax.naming.directory.Attributes;
34  import javax.naming.directory.BasicAttribute;
35  import javax.naming.directory.BasicAttributes;
36  import javax.naming.directory.DirContext;
37  import javax.naming.directory.ModificationItem;
38  import javax.naming.directory.SearchControls;
39  
40  import org.apache.commons.lang.ArrayUtils;
41  import org.apache.commons.lang.builder.EqualsBuilder;
42  import org.apache.commons.lang.builder.HashCodeBuilder;
43  import org.apache.commons.logging.Log;
44  import org.apache.commons.logging.LogFactory;
45  import org.springframework.ldap.NoSuchAttributeException;
46  import org.springframework.ldap.support.LdapUtils;
47  import org.springframework.util.StringUtils;
48  
49  /**
50   * Adapter that implements the interesting methods of the DirContext interface.
51   * In particular it contains utility methods for getting and setting attributes.
52   * Using the
53   * {@link org.springframework.ldap.core.support.DefaultDirObjectFactory} in your
54   * <code>ContextSource</code> (which is the default) you will receive instances
55   * of this class from searches and lookups. This can be particularly useful when
56   * updating data, since this class implements
57   * {@link AttributeModificationsAware}, providing a
58   * {@link #getModificationItems()} method. When in update mode, an object of
59   * this class keeps track of the changes made to its attributes, making them
60   * available as an array of <code>ModificationItem</code> objects, suitable as
61   * input to {@link LdapTemplate#modifyAttributes(DirContextOperations)}.
62   * 
63   * @see #setAttributeValue(String, Object)
64   * @see #setAttributeValues(String, Object[])
65   * @see #getStringAttribute(String)
66   * @see #getStringAttributes(String)
67   * @see #getObjectAttribute(String)
68   * @see #addAttributeValue(String, Object)
69   * @see #removeAttributeValue(String, Object)
70   * @see #setUpdateMode(boolean)
71   * @see #isUpdateMode()
72   * 
73   * @author Magnus Robertsson
74   * @author Andreas Ronge
75   * @author Adam Skogman
76   * @author Mattias Hellborg Arthursson
77   */
78  public class DirContextAdapter implements DirContextOperations {
79  
80  	private static final boolean DONT_ADD_IF_DUPLICATE_EXISTS = false;
81  
82  	private static final String EMPTY_STRING = "";
83  
84  	private static final boolean ORDER_DOESNT_MATTER = false;
85  
86  	private static Log log = LogFactory.getLog(DirContextAdapter.class);
87  
88  	private final Attributes originalAttrs;
89  
90  	private DistinguishedName dn;
91  
92  	private DistinguishedName base;
93  
94  	private boolean updateMode = false;
95  
96  	private Attributes updatedAttrs;
97  
98  	private String referralUrl;
99  
100 	/**
101 	 * Default constructor.
102 	 */
103 	public DirContextAdapter() {
104 		this(null, null, null);
105 	}
106 
107 	/**
108 	 * Create a new DirContextAdapter from the supplied DN String.
109 	 * @param dnString the DN string. Must be syntactically correct, or an
110 	 * exception will be thrown.
111 	 */
112 	public DirContextAdapter(String dnString) {
113 		this(new DistinguishedName(dnString));
114 	}
115 
116 	/**
117 	 * Create a new adapter from the supplied dn.
118 	 * 
119 	 * @param dn the dn.
120 	 */
121 	public DirContextAdapter(Name dn) {
122 		this(null, dn);
123 	}
124 
125 	/**
126 	 * Create a new adapter from the supplied attributes and dn.
127 	 * 
128 	 * @param attrs the attributes.
129 	 * @param dn the dn.
130 	 */
131 	public DirContextAdapter(Attributes attrs, Name dn) {
132 		this(attrs, dn, null);
133 	}
134 
135 	/**
136 	 * Create a new adapter from the supplied attributes, dn, and base.
137 	 * 
138 	 * @param attrs the attributes.
139 	 * @param dn the dn.
140 	 * @param base the base name.
141 	 */
142 	public DirContextAdapter(Attributes attrs, Name dn, Name base) {
143 		this(attrs, dn, base, null);
144 	}
145 
146 	/**
147 	 * Create a new adapter from the supplied attributes, dn, base, and referral
148 	 * url.
149 	 * @param attrs the attributes.
150 	 * @param dn the dn.
151 	 * @param base the base.
152 	 * @param referralUrl the referral url (if this instance results from a
153 	 * referral).
154 	 */
155 	public DirContextAdapter(Attributes attrs, Name dn, Name base,
156 			String referralUrl) {
157 		if (attrs != null) {
158 			this.originalAttrs = attrs;
159 		}
160 		else {
161 			this.originalAttrs = new BasicAttributes(true);
162 		}
163 		if (dn != null) {
164 			this.dn = new DistinguishedName(dn);
165 		}
166 		else {
167 			this.dn = new DistinguishedName();
168 		}
169 		if (base != null) {
170 			this.base = new DistinguishedName(base);
171 		}
172 		else {
173 			this.base = new DistinguishedName();
174 		}
175 		if (referralUrl != null) {
176 			this.referralUrl = referralUrl;
177 		}
178 		else {
179 			this.referralUrl = EMPTY_STRING;
180 		}
181 	}
182 
183 	/**
184 	 * Constructor for cloning an existing adapter.
185 	 * 
186 	 * @param master The adapter to be copied.
187 	 */
188 	protected DirContextAdapter(DirContextAdapter master) {
189 		this.originalAttrs = (Attributes) master.originalAttrs.clone();
190 		this.dn = master.dn;
191 		this.updatedAttrs = (Attributes) master.updatedAttrs.clone();
192 		this.updateMode = master.updateMode;
193 	}
194 
195 	/**
196 	 * Sets the update mode. The update mode should be <code>false</code> for a
197 	 * new entry and <code>true</code> for an existing entry that is being
198 	 * updated.
199 	 * 
200 	 * @param mode Update mode.
201 	 */
202 	public void setUpdateMode(boolean mode) {
203 		this.updateMode = mode;
204 		if (updateMode) {
205 			updatedAttrs = new BasicAttributes(true);
206 		}
207 	}
208 
209 	/*
210 	 * @see org.springframework.ldap.support.DirContextOperations#isUpdateMode()
211 	 */
212 	public boolean isUpdateMode() {
213 		return updateMode;
214 	}
215 
216 	/*
217 	 * @seeorg.springframework.ldap.support.DirContextOperations#
218 	 * getNamesOfModifiedAttributes()
219 	 */
220 	public String[] getNamesOfModifiedAttributes() {
221 
222 		List tmpList = new ArrayList();
223 
224 		NamingEnumeration attributesEnumeration;
225 		if (isUpdateMode()) {
226 			attributesEnumeration = updatedAttrs.getAll();
227 		}
228 		else {
229 			attributesEnumeration = originalAttrs.getAll();
230 		}
231 
232 		try {
233 			while (attributesEnumeration.hasMore()) {
234 				Attribute oneAttribute = (Attribute) attributesEnumeration
235 						.next();
236 				tmpList.add(oneAttribute.getID());
237 			}
238 		}
239 		catch (NamingException e) {
240 			throw LdapUtils.convertLdapException(e);
241 		}
242 		finally {
243 			closeNamingEnumeration(attributesEnumeration);
244 		}
245 
246 		return (String[]) tmpList.toArray(new String[0]);
247 	}
248 
249 	private void closeNamingEnumeration(NamingEnumeration enumeration) {
250 		try {
251 			if (enumeration != null) {
252 				enumeration.close();
253 			}
254 		}
255 		catch (NamingException e) {
256 			// Never mind this
257 		}
258 	}
259 
260 	/*
261 	 * @seeorg.springframework.ldap.support.AttributeModificationsAware#
262 	 * getModificationItems()
263 	 */
264 	public ModificationItem[] getModificationItems() {
265 		if (!updateMode) {
266 			return new ModificationItem[0];
267 		}
268 
269 		List tmpList = new LinkedList();
270 		NamingEnumeration attributesEnumeration = null;
271 		try {
272 			attributesEnumeration = updatedAttrs.getAll();
273 
274 			// find attributes that have been changed, removed or added
275 			while (attributesEnumeration.hasMore()) {
276 				Attribute oneAttr = (Attribute) attributesEnumeration.next();
277 
278 				collectModifications(oneAttr, tmpList);
279 			}
280 		}
281 		catch (NamingException e) {
282 			throw LdapUtils.convertLdapException(e);
283 		}
284 		finally {
285 			closeNamingEnumeration(attributesEnumeration);
286 		}
287 
288 		if (log.isDebugEnabled()) {
289 			log.debug("Number of modifications:" + tmpList.size());
290 		}
291 
292 		return (ModificationItem[]) tmpList
293 				.toArray(new ModificationItem[tmpList.size()]);
294 	}
295 
296 	/**
297 	 * Collect all modifications for the changed attribute. If no changes have
298 	 * been made, return immediately. If modifications have been made, and the
299 	 * original size as well as the updated size of the attribute is 1, replace
300 	 * the attribute. If the size of the updated attribute is 0, remove the
301 	 * attribute. Otherwise, the attribute is a multi-value attribute; if it's
302 	 * an ordered one it should be replaced in its entirety to preserve the new
303 	 * ordering, if not all modifications to the original value (removals and
304 	 * additions) will be collected individually.
305 	 * 
306 	 * @param changedAttr the value of the changed attribute.
307 	 * @param modificationList the list in which to add the modifications.
308 	 * @throws NamingException if thrown by called Attribute methods.
309 	 */
310 	private void collectModifications(Attribute changedAttr,
311 			List modificationList) throws NamingException {
312 		Attribute currentAttribute = originalAttrs.get(changedAttr.getID());
313 
314 		if (changedAttr.equals(currentAttribute)) {
315 			// No changes
316 			return;
317 		}
318 		else if (currentAttribute != null && currentAttribute.size() == 1
319 				&& changedAttr.size() == 1) {
320 			// Replace single-vale attribute.
321 			modificationList.add(new ModificationItem(
322 					DirContext.REPLACE_ATTRIBUTE, changedAttr));
323 		}
324 		else if (changedAttr.size() == 0 && currentAttribute != null) {
325 			// Attribute has been removed.
326 			modificationList.add(new ModificationItem(
327 					DirContext.REMOVE_ATTRIBUTE, changedAttr));
328 		}
329 		else if ((currentAttribute == null || currentAttribute.size() == 0)
330 				&& changedAttr.size() > 0) {
331 			// Attribute has been added.
332 			modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE,
333 					changedAttr));
334 		}
335 		else if (changedAttr.size() > 0 && changedAttr.isOrdered()) {
336 			// This is a multivalue attribute and it is ordered - the original
337 			// value should be replaced with the new values so that the ordering
338 			// is preserved.
339 			modificationList.add(new ModificationItem(
340 					DirContext.REPLACE_ATTRIBUTE, changedAttr));
341 		}
342 		else if (changedAttr.size() > 0) {
343 			// Change of multivalue Attribute. Collect additions and removals
344 			// individually.
345 			List myModifications = new LinkedList();
346 			collectModifications(currentAttribute, changedAttr, myModifications);
347 
348 			if (myModifications.isEmpty()) {
349 				// This means that the attributes are not equal, but the
350 				// actual values are the same - thus the order must have
351 				// changed. This should result in a REPLACE_ATTRIBUTE operation.
352 				myModifications.add(new ModificationItem(
353 						DirContext.REPLACE_ATTRIBUTE, changedAttr));
354 			}
355 
356 			modificationList.addAll(myModifications);
357 		}
358 	}
359 
360 	private void collectModifications(Attribute originalAttr,
361 			Attribute changedAttr, List modificationList)
362 			throws NamingException {
363 
364 		Attribute originalClone = (Attribute) originalAttr.clone();
365 		Attribute addedValuesAttribute = new BasicAttribute(originalAttr
366 				.getID());
367 
368 		for (int i = 0; i < changedAttr.size(); i++) {
369 			Object attributeValue = changedAttr.get(i);
370 			if (!originalClone.remove(attributeValue)) {
371 				addedValuesAttribute.add(attributeValue);
372 			}
373 		}
374 
375 		// We have now traversed and removed all values from the original that
376 		// were also present in the new values. The remaining values in the
377 		// original must be the ones that were removed.
378 		if (originalClone.size() > 0) {
379 			modificationList.add(new ModificationItem(
380 					DirContext.REMOVE_ATTRIBUTE, originalClone));
381 		}
382 
383 		if (addedValuesAttribute.size() > 0) {
384 			modificationList.add(new ModificationItem(DirContext.ADD_ATTRIBUTE,
385 					addedValuesAttribute));
386 		}
387 	}
388 
389 	/**
390 	 * returns true if the attribute is empty. It is empty if a == null, size ==
391 	 * 0 or get() == null or an exception if thrown when accessing the get
392 	 * method
393 	 */
394 	private boolean isEmptyAttribute(Attribute a) {
395 		try {
396 			return (a == null || a.size() == 0 || a.get() == null);
397 		}
398 		catch (NamingException e) {
399 			return true;
400 		}
401 	}
402 
403 	/**
404 	 * Compare the existing attribute <code>name</code> with the values on the
405 	 * array <code>values</code>. The order of the array must be the same order
406 	 * as the existing multivalued attribute.
407 	 * <p>
408 	 * Also handles the case where the values have been reset to the original
409 	 * values after a previous change. For example, changing
410 	 * <code>[a,b,c]</code> to <code>[a,b]</code> and then back to
411 	 * <code>[a,b,c]</code> again must result in this method returning
412 	 * <code>true</code> so the first change can be overwritten with the latest
413 	 * change.
414 	 * 
415 	 * @param name Name of the original multi-valued attribute.
416 	 * @param values Array of values to check if they have been changed.
417 	 * @return true if there has been a change compared to original attribute,
418 	 * or a previous update
419 	 */
420 	private boolean isChanged(String name, Object[] values, boolean orderMatters) {
421 
422 		Attribute orig = originalAttrs.get(name);
423 		Attribute prev = updatedAttrs.get(name);
424 
425 		// values == null and values.length == 0 is treated the same way
426 		boolean emptyNewValue = (values == null || values.length == 0);
427 
428 		// Setting to empty ---------------------
429 		if (emptyNewValue) {
430 			// FALSE: if both are null, it is not changed (both don't exist)
431 			// TRUE: if new value is null and old value exists (should be
432 			// removed)
433 			// TODO Also include prev in null check
434 			// TODO Also check if there is a single null element
435 			if (orig != null) {
436 				return true;
437 			}
438 			return false;
439 		}
440 
441 		// NOT setting to empty -------------------
442 
443 		// TRUE if existing value is null
444 		if (orig == null) {
445 			return true;
446 		}
447 
448 		// TRUE if different length compared to original attributes
449 		if (orig.size() != values.length) {
450 			return true;
451 		}
452 
453 		// TRUE if different length compared to previously updated attributes
454 		if (prev != null && prev.size() != values.length) {
455 			return true;
456 		}
457 
458 		// Check contents of arrays
459 
460 		// Order DOES matter, e.g. first names
461 		try {
462 			for (int i = 0; i < orig.size(); i++) {
463 				Object obj = orig.get(i);
464 				// TRUE if one value is not equal
465 				if (!(obj instanceof String)) {
466 					return true;
467 				}
468 				if (orderMatters) {
469 					// check only the string with same index
470 					if (!values[i].equals(obj)) {
471 						return true;
472 					}
473 				}
474 				else {
475 					// check all strings
476 					if (!ArrayUtils.contains(values, obj)) {
477 						return true;
478 					}
479 				}
480 			}
481 
482 		}
483 		catch (NamingException e) {
484 			// TRUE if we can't access the value
485 			return true;
486 		}
487 
488 		if (prev != null) {
489 			// Also check against updatedAttrs, since there might have been
490 			// a previous update
491 			try {
492 				for (int i = 0; i < prev.size(); i++) {
493 					Object obj = prev.get(i);
494 					// TRUE if one value is not equal
495 					if (!(obj instanceof String)) {
496 						return true;
497 					}
498 					if (orderMatters) {
499 						// check only the string with same index
500 						if (!values[i].equals(obj)) {
501 							return true;
502 						}
503 					}
504 					else {
505 						// check all strings
506 						if (!ArrayUtils.contains(values, obj)) {
507 							return true;
508 						}
509 					}
510 				}
511 
512 			}
513 			catch (NamingException e) {
514 				// TRUE if we can't access the value
515 				return true;
516 			}
517 		}
518 		// FALSE since we have compared all values
519 		return false;
520 	}
521 
522 	/**
523 	 * Checks if an entry has a specific attribute.
524 	 * 
525 	 * This method simply calls exists(String) with the attribute name.
526 	 * 
527 	 * @param attr the attribute to check.
528 	 * @return true if attribute exists in entry.
529 	 */
530 	protected final boolean exists(Attribute attr) {
531 		return exists(attr.getID());
532 	}
533 
534 	/**
535 	 * Checks if the attribute exists in this entry, either it was read or it
536 	 * has been added and update() has been called.
537 	 * 
538 	 * @param attrId id of the attribute to check.
539 	 * @return true if the attribute exists in the entry.
540 	 */
541 	protected final boolean exists(String attrId) {
542 		return originalAttrs.get(attrId) != null;
543 	}
544 
545 	/*
546 	 * @see
547 	 * org.springframework.ldap.support.DirContextOperations#getStringAttribute
548 	 * (java.lang.String)
549 	 */
550 	public String getStringAttribute(String name) {
551 		return (String) getObjectAttribute(name);
552 	}
553 
554 	/*
555 	 * @see
556 	 * org.springframework.ldap.support.DirContextOperations#getObjectAttribute
557 	 * (java.lang.String)
558 	 */
559 	public Object getObjectAttribute(String name) {
560 		Attribute oneAttr = originalAttrs.get(name);
561 		if (oneAttr == null) {
562 			return null;
563 		}
564 		try {
565 			return oneAttr.get();
566 		}
567 		catch (NamingException e) {
568 			throw LdapUtils.convertLdapException(e);
569 		}
570 	}
571 
572 	/*
573 	 * @see
574 	 * org.springframework.ldap.support.DirContextOperations#setAttributeValue
575 	 * (java.lang.String, java.lang.Object)
576 	 */
577 	public void setAttributeValue(String name, Object value) {
578 		// new entry
579 		if (!updateMode && value != null) {
580 			originalAttrs.put(name, value);
581 		}
582 
583 		// updating entry
584 		if (updateMode) {
585 			BasicAttribute attribute = new BasicAttribute(name);
586 			if (value != null) {
587 				attribute.add(value);
588 			}
589 			updatedAttrs.put(attribute);
590 		}
591 	}
592 
593 	/*
594 	 * (non-Javadoc)
595 	 * 
596 	 * @see
597 	 * org.springframework.ldap.core.DirContextOperations#addAttributeValue(
598 	 * java.lang.String, java.lang.Object)
599 	 */
600 	public void addAttributeValue(String name, Object value) {
601 		addAttributeValue(name, value, DONT_ADD_IF_DUPLICATE_EXISTS);
602 	}
603 
604 	public void addAttributeValue(String name, Object value,
605 			boolean addIfDuplicateExists) {
606 		if (!updateMode && value != null) {
607 			Attribute attr = originalAttrs.get(name);
608 			if (attr == null) {
609 				originalAttrs.put(name, value);
610 			}
611 			else {
612 				attr.add(value);
613 			}
614 		}
615 		else if (updateMode) {
616 			Attribute attr = updatedAttrs.get(name);
617 			if (attr == null) {
618 				if (originalAttrs.get(name) == null) {
619 					// No match in the original attributes -
620 					// add a new Attribute to updatedAttrs
621 					updatedAttrs.put(name, value);
622 				}
623 				else {
624 					// The attribute exists in the original attributes - clone
625 					// that and add the new entry to it
626 					attr = (Attribute) originalAttrs.get(name).clone();
627 					if (addIfDuplicateExists || !attr.contains(value)) {
628 						attr.add(value);
629 					}
630 					updatedAttrs.put(attr);
631 				}
632 			}
633 			else {
634 				attr.add(value);
635 			}
636 		}
637 	}
638 
639 	/*
640 	 * (non-Javadoc)
641 	 * 
642 	 * @see
643 	 * org.springframework.ldap.core.DirContextOperations#removeAttributeValue
644 	 * (java.lang.String, java.lang.Object)
645 	 */
646 	public void removeAttributeValue(String name, Object value) {
647 		if (!updateMode && value != null) {
648 			Attribute attr = originalAttrs.get(name);
649 			if (attr != null) {
650 				attr.remove(value);
651 				if (attr.size() == 0) {
652 					originalAttrs.remove(name);
653 				}
654 			}
655 		}
656 		else if (updateMode) {
657 			Attribute attr = updatedAttrs.get(name);
658 			if (attr == null) {
659 				if (originalAttrs.get(name) != null) {
660 					attr = (Attribute) originalAttrs.get(name).clone();
661 					attr.remove(value);
662 					updatedAttrs.put(attr);
663 				}
664 			}
665 			else {
666 				attr.remove(value);
667 			}
668 		}
669 	}
670 
671 	/*
672 	 * @see
673 	 * org.springframework.ldap.support.DirContextOperations#setAttributeValues
674 	 * (java.lang.String, java.lang.Object[])
675 	 */
676 	public void setAttributeValues(String name, Object[] values) {
677 		setAttributeValues(name, values, ORDER_DOESNT_MATTER);
678 	}
679 
680 	/*
681 	 * @see
682 	 * org.springframework.ldap.support.DirContextOperations#setAttributeValues
683 	 * (java.lang.String, java.lang.Object[], boolean)
684 	 */
685 	public void setAttributeValues(String name, Object[] values,
686 			boolean orderMatters) {
687 		Attribute a = new BasicAttribute(name, orderMatters);
688 
689 		for (int i = 0; values != null && i < values.length; i++) {
690 			a.add(values[i]);
691 		}
692 
693 		// only change the original attribute if not in update mode
694 		if (!updateMode && values != null && values.length > 0) {
695 			// don't save empty arrays
696 			originalAttrs.put(a);
697 		}
698 
699 		// possible to set an already existing attribute to an empty array
700 		if (updateMode && isChanged(name, values, orderMatters)) {
701 			updatedAttrs.put(a);
702 		}
703 	}
704 
705 	/*
706 	 * @see org.springframework.ldap.support.DirContextOperations#update()
707 	 */
708 	public void update() {
709 		NamingEnumeration attributesEnumeration = null;
710 
711 		try {
712 			attributesEnumeration = updatedAttrs.getAll();
713 
714 			// find what to update
715 			while (attributesEnumeration.hasMore()) {
716 				Attribute a = (Attribute) attributesEnumeration.next();
717 
718 				// if it does not exist it should be added
719 				if (isEmptyAttribute(a)) {
720 					originalAttrs.remove(a.getID());
721 				}
722 				else {
723 					// Otherwise it should be set.
724 					originalAttrs.put(a);
725 				}
726 			}
727 		}
728 		catch (NamingException e) {
729 			throw LdapUtils.convertLdapException(e);
730 		}
731 		finally {
732 			closeNamingEnumeration(attributesEnumeration);
733 		}
734 
735 		// Reset the attributes to be updated
736 		updatedAttrs = new BasicAttributes(true);
737 	}
738 
739 	/*
740 	 * @see
741 	 * org.springframework.ldap.core.DirContextOperations#getStringAttributes
742 	 * (java.lang.String)
743 	 */
744 	public String[] getStringAttributes(String name) {
745 		try {
746 			return (String[]) collectAttributeValuesAsList(name).toArray(
747 					new String[0]);
748 		}
749 		catch (NoSuchAttributeException e) {
750 			// The attribute does not exist - contract says to return null.
751 			return null;
752 		}
753 	}
754 
755 	/*
756 	 * (non-Javadoc)
757 	 * 
758 	 * @see
759 	 * org.springframework.ldap.core.DirContextOperations#getObjectAttributes
760 	 * (java.lang.String)
761 	 */
762 	public Object[] getObjectAttributes(String name) {
763 		try {
764 			return collectAttributeValuesAsList(name).toArray(new Object[0]);
765 		}
766 		catch (NoSuchAttributeException e) {
767 			// The attribute does not exist - contract says to return null.
768 			return null;
769 		}
770 	}
771 
772 	private List collectAttributeValuesAsList(String name) {
773 		List list = new LinkedList();
774 		LdapUtils.collectAttributeValues(originalAttrs, name, list);
775 		return list;
776 	}
777 
778 	/*
779 	 * @seeorg.springframework.ldap.support.DirContextOperations#
780 	 * getAttributeSortedStringSet(java.lang.String)
781 	 */
782 	public SortedSet getAttributeSortedStringSet(String name) {
783 		try {
784 			TreeSet attrSet = new TreeSet();
785 			LdapUtils.collectAttributeValues(originalAttrs, name, attrSet);
786 			return attrSet;
787 		}
788 		catch (NoSuchAttributeException e) {
789 			// The attribute does not exist - contract says to return null.
790 			return null;
791 		}
792 	}
793 
794 	/**
795 	 * Set the supplied attribute.
796 	 * 
797 	 * @param attribute the attribute to set.
798 	 */
799 	public void setAttribute(Attribute attribute) {
800 		if (!updateMode) {
801 			originalAttrs.put(attribute);
802 		}
803 		else {
804 			updatedAttrs.put(attribute);
805 		}
806 	}
807 
808 	/**
809 	 * Get all attributes.
810 	 * 
811 	 * @return all attributes.
812 	 */
813 	public Attributes getAttributes() {
814 		return originalAttrs;
815 	}
816 
817 	/**
818 	 * @see javax.naming.directory.DirContext#getAttributes(Name)
819 	 */
820 	public Attributes getAttributes(Name name) throws NamingException {
821 		return getAttributes(name.toString());
822 	}
823 
824 	/**
825 	 * @see javax.naming.directory.DirContext#getAttributes(String)
826 	 */
827 	public Attributes getAttributes(String name) throws NamingException {
828 		if (StringUtils.hasLength(name)) {
829 			throw new NameNotFoundException();
830 		}
831 		return (Attributes) originalAttrs.clone();
832 	}
833 
834 	/**
835 	 * @see javax.naming.directory.DirContext#getAttributes(Name, String[])
836 	 */
837 	public Attributes getAttributes(Name name, String[] attrIds)
838 			throws NamingException {
839 		return getAttributes(name.toString(), attrIds);
840 	}
841 
842 	/**
843 	 * @see javax.naming.directory.DirContext#getAttributes(String, String[])
844 	 */
845 	public Attributes getAttributes(String name, String[] attrIds)
846 			throws NamingException {
847 		if (StringUtils.hasLength(name)) {
848 			throw new NameNotFoundException();
849 		}
850 
851 		Attributes a = new BasicAttributes(true);
852 		Attribute target;
853 		for (int i = 0; i < attrIds.length; i++) {
854 			target = originalAttrs.get(attrIds[i]);
855 			if (target != null) {
856 				a.put(target);
857 			}
858 		}
859 
860 		return a;
861 	}
862 
863 	/**
864 	 * @see javax.naming.directory.DirContext#modifyAttributes(javax.naming.Name,
865 	 * int, javax.naming.directory.Attributes)
866 	 */
867 	public void modifyAttributes(Name name, int modOp, Attributes attrs)
868 			throws NamingException {
869 		throw new UnsupportedOperationException("Not implemented.");
870 	}
871 
872 	/**
873 	 * @see javax.naming.directory.DirContext#modifyAttributes(String, int,
874 	 * Attributes)
875 	 */
876 	public void modifyAttributes(String name, int modOp, Attributes attrs)
877 			throws NamingException {
878 		throw new UnsupportedOperationException("Not implemented.");
879 	}
880 
881 	/**
882 	 * @see javax.naming.directory.DirContext#modifyAttributes(Name,
883 	 * ModificationItem[])
884 	 */
885 	public void modifyAttributes(Name name, ModificationItem[] mods)
886 			throws NamingException {
887 		throw new UnsupportedOperationException("Not implemented.");
888 	}
889 
890 	/**
891 	 * @see javax.naming.directory.DirContext#modifyAttributes(String,
892 	 * ModificationItem[])
893 	 */
894 	public void modifyAttributes(String name, ModificationItem[] mods)
895 			throws NamingException {
896 		throw new UnsupportedOperationException("Not implemented.");
897 	}
898 
899 	/**
900 	 * @see javax.naming.directory.DirContext#bind(Name, Object, Attributes)
901 	 */
902 	public void bind(Name name, Object obj, Attributes attrs)
903 			throws NamingException {
904 		throw new UnsupportedOperationException("Not implemented.");
905 	}
906 
907 	/**
908 	 * @see javax.naming.directory.DirContext#bind(String, Object, Attributes)
909 	 */
910 	public void bind(String name, Object obj, Attributes attrs)
911 			throws NamingException {
912 		throw new UnsupportedOperationException("Not implemented.");
913 	}
914 
915 	/**
916 	 * @see javax.naming.directory.DirContext#rebind(Name, Object, Attributes)
917 	 */
918 	public void rebind(Name name, Object obj, Attributes attrs)
919 			throws NamingException {
920 		throw new UnsupportedOperationException("Not implemented.");
921 	}
922 
923 	/**
924 	 * @see javax.naming.directory.DirContext#rebind(String, Object, Attributes)
925 	 */
926 	public void rebind(String name, Object obj, Attributes attrs)
927 			throws NamingException {
928 		throw new UnsupportedOperationException("Not implemented.");
929 	}
930 
931 	/**
932 	 * @see javax.naming.directory.DirContext#createSubcontext(Name, Attributes)
933 	 */
934 	public DirContext createSubcontext(Name name, Attributes attrs)
935 			throws NamingException {
936 		throw new UnsupportedOperationException("Not implemented.");
937 	}
938 
939 	/**
940 	 * @see javax.naming.directory.DirContext#createSubcontext(String,
941 	 * Attributes)
942 	 */
943 	public DirContext createSubcontext(String name, Attributes attrs)
944 			throws NamingException {
945 		throw new UnsupportedOperationException("Not implemented.");
946 	}
947 
948 	/**
949 	 * @see javax.naming.directory.DirContext#getSchema(Name)
950 	 */
951 	public DirContext getSchema(Name name) throws NamingException {
952 		throw new UnsupportedOperationException("Not implemented.");
953 	}
954 
955 	/**
956 	 * @see javax.naming.directory.DirContext#getSchema(String)
957 	 */
958 	public DirContext getSchema(String name) throws NamingException {
959 		throw new UnsupportedOperationException("Not implemented.");
960 	}
961 
962 	/**
963 	 * @see javax.naming.directory.DirContext#getSchemaClassDefinition(Name)
964 	 */
965 	public DirContext getSchemaClassDefinition(Name name)
966 			throws NamingException {
967 		throw new UnsupportedOperationException("Not implemented.");
968 	}
969 
970 	/**
971 	 * @see javax.naming.directory.DirContext#getSchemaClassDefinition(String)
972 	 */
973 	public DirContext getSchemaClassDefinition(String name)
974 			throws NamingException {
975 		throw new UnsupportedOperationException("Not implemented.");
976 	}
977 
978 	/**
979 	 * @see javax.naming.directory.DirContext#search(Name, Attributes, String[])
980 	 */
981 	public NamingEnumeration search(Name name, Attributes matchingAttributes,
982 			String[] attributesToReturn) throws NamingException {
983 		throw new UnsupportedOperationException("Not implemented.");
984 	}
985 
986 	/**
987 	 * @see javax.naming.directory.DirContext#search(String, Attributes,
988 	 * String[])
989 	 */
990 	public NamingEnumeration search(String name, Attributes matchingAttributes,
991 			String[] attributesToReturn) throws NamingException {
992 		throw new UnsupportedOperationException("Not implemented.");
993 	}
994 
995 	/**
996 	 * @see javax.naming.directory.DirContext#search(Name, Attributes)
997 	 */
998 	public NamingEnumeration search(Name name, Attributes matchingAttributes)
999 			throws NamingException {
1000 		throw new UnsupportedOperationException("Not implemented.");
1001 	}
1002 
1003 	/**
1004 	 * @see javax.naming.directory.DirContext#search(String, Attributes)
1005 	 */
1006 	public NamingEnumeration search(String name, Attributes matchingAttributes)
1007 			throws NamingException {
1008 		throw new UnsupportedOperationException("Not implemented.");
1009 	}
1010 
1011 	/**
1012 	 * @see javax.naming.directory.DirContext#search(Name, String,
1013 	 * SearchControls)
1014 	 */
1015 	public NamingEnumeration search(Name name, String filter,
1016 			SearchControls cons) throws NamingException {
1017 		throw new UnsupportedOperationException("Not implemented.");
1018 	}
1019 
1020 	/**
1021 	 * @see javax.naming.directory.DirContext#search(String, String,
1022 	 * SearchControls)
1023 	 */
1024 	public NamingEnumeration search(String name, String filter,
1025 			SearchControls cons) throws NamingException {
1026 		throw new UnsupportedOperationException("Not implemented.");
1027 	}
1028 
1029 	/**
1030 	 * @see javax.naming.directory.DirContext#search(Name, String, Object[],
1031 	 * SearchControls)
1032 	 */
1033 	public NamingEnumeration search(Name name, String filterExpr,
1034 			Object[] filterArgs, SearchControls cons) throws NamingException {
1035 		throw new UnsupportedOperationException("Not implemented.");
1036 	}
1037 
1038 	/**
1039 	 * @see javax.naming.directory.DirContext#search(String, String, Object[],
1040 	 * SearchControls)
1041 	 */
1042 	public NamingEnumeration search(String name, String filterExpr,
1043 			Object[] filterArgs, SearchControls cons) throws NamingException {
1044 		throw new UnsupportedOperationException("Not implemented.");
1045 	}
1046 
1047 	/**
1048 	 * @see javax.naming.Context#lookup(Name)
1049 	 */
1050 	public Object lookup(Name name) throws NamingException {
1051 		throw new UnsupportedOperationException("Not implemented.");
1052 	}
1053 
1054 	/**
1055 	 * @see javax.naming.Context#lookup(String)
1056 	 */
1057 	public Object lookup(String name) throws NamingException {
1058 		throw new UnsupportedOperationException("Not implemented.");
1059 	}
1060 
1061 	/**
1062 	 * @see javax.naming.Context#bind(Name, Object)
1063 	 */
1064 	public void bind(Name name, Object obj) throws NamingException {
1065 		throw new UnsupportedOperationException("Not implemented.");
1066 	}
1067 
1068 	/**
1069 	 * @see javax.naming.Context#bind(String, Object)
1070 	 */
1071 	public void bind(String name, Object obj) throws NamingException {
1072 		throw new UnsupportedOperationException("Not implemented.");
1073 	}
1074 
1075 	/**
1076 	 * @see javax.naming.Context#rebind(Name, Object)
1077 	 */
1078 	public void rebind(Name name, Object obj) throws NamingException {
1079 		throw new UnsupportedOperationException("Not implemented.");
1080 	}
1081 
1082 	/**
1083 	 * @see javax.naming.Context#rebind(String, Object)
1084 	 */
1085 	public void rebind(String name, Object obj) throws NamingException {
1086 		throw new UnsupportedOperationException("Not implemented.");
1087 	}
1088 
1089 	/**
1090 	 * @see javax.naming.Context#unbind(Name)
1091 	 */
1092 	public void unbind(Name name) throws NamingException {
1093 		throw new UnsupportedOperationException("Not implemented.");
1094 	}
1095 
1096 	/**
1097 	 * @see javax.naming.Context#unbind(String)
1098 	 */
1099 	public void unbind(String name) throws NamingException {
1100 		throw new UnsupportedOperationException("Not implemented.");
1101 	}
1102 
1103 	/**
1104 	 * @see javax.naming.Context#rename(Name, Name)
1105 	 */
1106 	public void rename(Name oldName, Name newName) throws NamingException {
1107 		throw new UnsupportedOperationException("Not implemented.");
1108 	}
1109 
1110 	/**
1111 	 * @see javax.naming.Context#rename(String, String)
1112 	 */
1113 	public void rename(String oldName, String newName) throws NamingException {
1114 		throw new UnsupportedOperationException("Not implemented.");
1115 	}
1116 
1117 	/**
1118 	 * @see javax.naming.Context#list(Name)
1119 	 */
1120 	public NamingEnumeration list(Name name) throws NamingException {
1121 		throw new UnsupportedOperationException("Not implemented.");
1122 	}
1123 
1124 	/**
1125 	 * @see javax.naming.Context#list(String)
1126 	 */
1127 	public NamingEnumeration list(String name) throws NamingException {
1128 		throw new UnsupportedOperationException("Not implemented.");
1129 	}
1130 
1131 	/**
1132 	 * @see javax.naming.Context#listBindings(Name)
1133 	 */
1134 	public NamingEnumeration listBindings(Name name) throws NamingException {
1135 		throw new UnsupportedOperationException("Not implemented.");
1136 	}
1137 
1138 	/**
1139 	 * @see javax.naming.Context#listBindings(String)
1140 	 */
1141 	public NamingEnumeration listBindings(String name) throws NamingException {
1142 		throw new UnsupportedOperationException("Not implemented.");
1143 	}
1144 
1145 	/**
1146 	 * @see javax.naming.Context#destroySubcontext(Name)
1147 	 */
1148 	public void destroySubcontext(Name name) throws NamingException {
1149 		throw new UnsupportedOperationException("Not implemented.");
1150 	}
1151 
1152 	/**
1153 	 * @see javax.naming.Context#destroySubcontext(String)
1154 	 */
1155 	public void destroySubcontext(String name) throws NamingException {
1156 		throw new UnsupportedOperationException("Not implemented.");
1157 	}
1158 
1159 	/**
1160 	 * @see javax.naming.Context#createSubcontext(Name)
1161 	 */
1162 	public Context createSubcontext(Name name) throws NamingException {
1163 		throw new UnsupportedOperationException("Not implemented.");
1164 	}
1165 
1166 	/**
1167 	 * @see javax.naming.Context#createSubcontext(String)
1168 	 */
1169 	public Context createSubcontext(String name) throws NamingException {
1170 		throw new UnsupportedOperationException("Not implemented.");
1171 	}
1172 
1173 	/**
1174 	 * @see javax.naming.Context#lookupLink(Name)
1175 	 */
1176 	public Object lookupLink(Name name) throws NamingException {
1177 		throw new UnsupportedOperationException("Not implemented.");
1178 	}
1179 
1180 	/**
1181 	 * @see javax.naming.Context#lookupLink(String)
1182 	 */
1183 	public Object lookupLink(String name) throws NamingException {
1184 		throw new UnsupportedOperationException("Not implemented.");
1185 	}
1186 
1187 	/**
1188 	 * @see javax.naming.Context#getNameParser(Name)
1189 	 */
1190 	public NameParser getNameParser(Name name) throws NamingException {
1191 		throw new UnsupportedOperationException("Not implemented.");
1192 	}
1193 
1194 	/**
1195 	 * @see javax.naming.Context#getNameParser(String)
1196 	 */
1197 	public NameParser getNameParser(String name) throws NamingException {
1198 		throw new UnsupportedOperationException("Not implemented.");
1199 	}
1200 
1201 	/**
1202 	 * @see javax.naming.Context#composeName(Name, Name)
1203 	 */
1204 	public Name composeName(Name name, Name prefix) throws NamingException {
1205 		throw new UnsupportedOperationException("Not implemented.");
1206 	}
1207 
1208 	/**
1209 	 * @see javax.naming.Context#composeName(String, String)
1210 	 */
1211 	public String composeName(String name, String prefix)
1212 			throws NamingException {
1213 		throw new UnsupportedOperationException("Not implemented.");
1214 	}
1215 
1216 	/**
1217 	 * @see javax.naming.Context#addToEnvironment(String, Object)
1218 	 */
1219 	public Object addToEnvironment(String propName, Object propVal)
1220 			throws NamingException {
1221 		throw new UnsupportedOperationException("Not implemented.");
1222 	}
1223 
1224 	/**
1225 	 * @see javax.naming.Context#removeFromEnvironment(String)
1226 	 */
1227 	public Object removeFromEnvironment(String propName) throws NamingException {
1228 		throw new UnsupportedOperationException("Not implemented.");
1229 	}
1230 
1231 	/**
1232 	 * @see javax.naming.Context#getEnvironment()
1233 	 */
1234 	public Hashtable getEnvironment() throws NamingException {
1235 		throw new UnsupportedOperationException("Not implemented.");
1236 	}
1237 
1238 	/**
1239 	 * @see javax.naming.Context#close()
1240 	 */
1241 	public void close() throws NamingException {
1242 		throw new UnsupportedOperationException("Not implemented.");
1243 	}
1244 
1245 	/**
1246 	 * @see javax.naming.Context#getNameInNamespace()
1247 	 */
1248 	public String getNameInNamespace() {
1249 		DistinguishedName result = new DistinguishedName(dn);
1250 		result.prepend(base);
1251 		return result.toString();
1252 	}
1253 
1254 	/*
1255 	 * (non-Javadoc)
1256 	 * 
1257 	 * @see org.springframework.ldap.support.DirContextOperations#getDn()
1258 	 */
1259 	public Name getDn() {
1260 		return new DistinguishedName(dn);
1261 	}
1262 
1263 	/*
1264 	 * (non-Javadoc)
1265 	 * 
1266 	 * @see
1267 	 * org.springframework.ldap.support.DirContextOperations#setDn(javax.naming
1268 	 * .Name)
1269 	 */
1270 	public final void setDn(Name dn) {
1271 		if (!updateMode) {
1272 			this.dn = new DistinguishedName(dn.toString());
1273 		}
1274 		else {
1275 			throw new IllegalStateException(
1276 					"Not possible to call setDn() on a DirContextAdapter in update mode");
1277 		}
1278 
1279 	}
1280 
1281 	/**
1282 	 * @see java.lang.Object#equals(java.lang.Object)
1283 	 */
1284 	public boolean equals(Object obj) {
1285 		// A subclass with identical values should NOT be considered equal.
1286 		// EqualsBuilder in commons-lang cannot handle subclasses correctly.
1287 		if (obj == null || obj.getClass() != this.getClass()) {
1288 			return false;
1289 		}
1290 		return EqualsBuilder.reflectionEquals(this, obj);
1291 	}
1292 
1293 	/**
1294 	 * @see Object#hashCode()
1295 	 */
1296 	public int hashCode() {
1297 		return HashCodeBuilder.reflectionHashCode(this);
1298 	}
1299 
1300 	/**
1301 	 * @see java.lang.Object#toString()
1302 	 */
1303 	public String toString() {
1304 		StringBuffer buf = new StringBuffer();
1305 		buf.append(getClass().getName());
1306 		buf.append(":");
1307 		if (dn != null) {
1308 			buf.append(" dn=" + dn);
1309 		}
1310 		buf.append(" {");
1311 
1312 		try {
1313 			for (NamingEnumeration i = originalAttrs.getAll(); i.hasMore();) {
1314 				Attribute attribute = (Attribute) i.next();
1315 				if (attribute.size() == 1) {
1316 					buf.append(attribute.getID());
1317 					buf.append('=');
1318 					buf.append(attribute.get());
1319 				}
1320 				else {
1321 					for (int j = 0; j < attribute.size(); j++) {
1322 						if (j > 0) {
1323 							buf.append(", ");
1324 						}
1325 						buf.append(attribute.getID());
1326 						buf.append('[');
1327 						buf.append(j);
1328 						buf.append("]=");
1329 						buf.append(attribute.get(j));
1330 					}
1331 				}
1332 
1333 				if (i.hasMore()) {
1334 					buf.append(", ");
1335 				}
1336 			}
1337 		}
1338 		catch (NamingException e) {
1339 			log.warn("Error in toString()");
1340 		}
1341 		buf.append('}');
1342 
1343 		return buf.toString();
1344 	}
1345 
1346 	/*
1347 	 * (non-Javadoc)
1348 	 * 
1349 	 * @see org.springframework.ldap.core.DirContextOperations#getReferralUrl()
1350 	 */
1351 	public String getReferralUrl() {
1352 		return referralUrl;
1353 	}
1354 
1355 	/*
1356 	 * (non-Javadoc)
1357 	 * 
1358 	 * @see org.springframework.ldap.core.DirContextOperations#isReferral()
1359 	 */
1360 	public boolean isReferral() {
1361 		return StringUtils.hasLength(referralUrl);
1362 	}
1363 
1364 }