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  package net.sf.ldaptemplate;
17  
18  import java.util.List;
19  
20  import javax.naming.Binding;
21  import javax.naming.Name;
22  import javax.naming.NameNotFoundException;
23  import javax.naming.NamingEnumeration;
24  import javax.naming.NamingException;
25  import javax.naming.PartialResultException;
26  import javax.naming.directory.Attributes;
27  import javax.naming.directory.DirContext;
28  import javax.naming.directory.ModificationItem;
29  import javax.naming.directory.SearchControls;
30  import javax.naming.directory.SearchResult;
31  
32  import net.sf.ldaptemplate.support.DistinguishedName;
33  
34  import org.apache.commons.lang.Validate;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.springframework.beans.factory.InitializingBean;
38  import org.springframework.dao.DataAccessException;
39  
40  /***
41   * Executes core LDAP functionality and helps to avoid common errors, relieving
42   * the user of the burden of looking up contexts, looping through
43   * NamingEnumerations and closing contexts.
44   * <p>
45   * <b>Note for Active Directory (AD) users:</b> AD servers are apparently
46   * unable to handle referrals automatically, which causes a
47   * <code>PartialResultException</code> to be thrown whenever a referral is
48   * encountered in a search. To avoid this, set the
49   * <code>ignorePartialResultException</code> property to <code>true</code>.
50   * There is currently no way of manually handling these referrals in the form of
51   * <code>ReferralException</code>, i.e. either you get the exception (and
52   * your results are lost) or all referrals are ignored (if the server is unable
53   * to handle them properly. Neither is there any simple way to get notified that
54   * a <code>PartialResultException</code> has been ignored (other than in the
55   * log).
56   * 
57   * @see net.sf.ldaptemplate.ContextSource
58   * 
59   * @author Mattias Arthursson
60   * @author Ulrik Sandberg
61   */
62  public class LdapTemplate implements LdapOperations, InitializingBean {
63  
64      private static final Log log = LogFactory.getLog(LdapTemplate.class);
65  
66      private static final int DEFAULT_SEARCH_SCOPE = SearchControls.SUBTREE_SCOPE;
67  
68      private static final boolean DONT_RETURN_OBJ_FLAG = false;
69  
70      private static final boolean RETURN_OBJ_FLAG = true;
71  
72      private ContextSource contextSource;
73  
74      private NamingExceptionTranslator exceptionTranslator = new DefaultNamingExceptionTranslator();
75  
76      private boolean ignorePartialResultException = false;
77  
78      /***
79       * Constructor for bean usage.
80       */
81      public LdapTemplate() {
82      }
83  
84      /***
85       * Constructor to setup instance directly.
86       * 
87       * @param contextSource
88       *            the ContextSource to use.
89       */
90      public LdapTemplate(ContextSource contextSource) {
91          this.contextSource = contextSource;
92      }
93  
94      /***
95       * Set the ContextSource. Call this method when the default constructor has
96       * been used.
97       * 
98       * @param contextSource
99       *            the ContextSource.
100      */
101     public void setContextSource(ContextSource contextSource) {
102         this.contextSource = contextSource;
103     }
104 
105     /***
106      * Specify whether <code>PartialResultException</code> should be ignored
107      * in searches. AD servers typically have a problem with referrals. Normally
108      * a referral should be followed automatically, but this does not seem to
109      * work with AD servers. The problem manifests itself with a a
110      * <code>PartialResultException</code> being thrown when a referral is
111      * encountered by the server. Setting this property to <code>true</code>
112      * presents a workaround to this problem by causing
113      * <code>PartialResultException</code> to be ignored, so that the search
114      * method returns normally. Default value of this parameter is
115      * <code>false</code>.
116      * 
117      * @param ignore
118      *            <code>true</code> if <code>PartialResultException</code>
119      *            should be ignored in searches, <code>false</code> otherwise.
120      *            Default is <code>false</code>.
121      */
122     public void setIgnorePartialResultException(boolean ignore) {
123         this.ignorePartialResultException = ignore;
124     }
125 
126     /*
127      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
128      *      java.lang.String, int, boolean,
129      *      net.sf.ldaptemplate.SearchResultCallbackHandler)
130      */
131     public void search(Name base, String filter, int searchScope,
132             boolean returningObjFlag, SearchResultCallbackHandler handler) {
133 
134         search(base, filter, getDefaultSearchControls(searchScope,
135                 returningObjFlag), handler);
136     }
137 
138     /*
139      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
140      *      java.lang.String, int, boolean,
141      *      net.sf.ldaptemplate.SearchResultCallbackHandler)
142      */
143     public void search(String base, String filter, int searchScope,
144             boolean returningObjFlag, SearchResultCallbackHandler handler)
145             throws DataAccessException {
146 
147         search(base, filter, getDefaultSearchControls(searchScope,
148                 returningObjFlag), handler);
149     }
150 
151     /*
152      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
153      *      java.lang.String, javax.naming.directory.SearchControls,
154      *      net.sf.ldaptemplate.SearchResultCallbackHandler)
155      */
156     public void search(final Name base, final String filter,
157             final SearchControls controls, SearchResultCallbackHandler handler) {
158 
159         // Create a SearchExecutor to perform the search.
160         SearchExecutor se = new SearchExecutor() {
161             public NamingEnumeration executeSearch(DirContext ctx)
162                     throws NamingException {
163                 return ctx.search(base, filter, controls);
164             }
165         };
166 
167         search(se, handler);
168     }
169 
170     /*
171      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
172      *      java.lang.String, javax.naming.directory.SearchControls,
173      *      net.sf.ldaptemplate.SearchResultCallbackHandler)
174      */
175     public void search(final String base, final String filter,
176             final SearchControls controls, SearchResultCallbackHandler handler) {
177 
178         // Create a SearchExecutor to perform the search.
179         SearchExecutor se = new SearchExecutor() {
180             public NamingEnumeration executeSearch(DirContext ctx)
181                     throws NamingException {
182                 return ctx.search(base, filter, controls);
183             }
184         };
185 
186         search(se, handler);
187     }
188 
189     /*
190      * @see net.sf.ldaptemplate.LdapOperations#doSearch(net.sf.ldaptemplate.LdapTemplate.SearchExecutor,
191      *      net.sf.ldaptemplate.SearchResultCallbackHandler)
192      */
193     public void search(SearchExecutor se, SearchResultCallbackHandler handler) {
194         DirContext ctx = contextSource.getReadOnlyContext();
195 
196         NamingEnumeration results = null;
197         try {
198             results = se.executeSearch(ctx);
199 
200             while (results.hasMore()) {
201                 SearchResult searchResult = (SearchResult) results.next();
202                 handler.handleSearchResult(searchResult);
203             }
204         } catch (NameNotFoundException e) {
205             // The base context was not found, which basically means
206             // that the search did not return any results. Just clean up and
207             // exit.
208         } catch (PartialResultException e) {
209             // Workaround for AD servers not handling referrals correctly.
210             if (ignorePartialResultException) {
211                 log.debug("PartialResultException encountered and ignored", e);
212             } else {
213                 throw getExceptionTranslator().translate(e);
214             }
215         } catch (NamingException e) {
216             throw getExceptionTranslator().translate(e);
217         } finally {
218             closeContextAndNamingEnumeration(ctx, results);
219         }
220     }
221 
222     /*
223      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
224      *      java.lang.String, net.sf.ldaptemplate.SearchResultCallbackHandler)
225      */
226     public void search(Name base, String filter,
227             SearchResultCallbackHandler handler) throws DataAccessException {
228 
229         search(base, filter, getDefaultSearchControls(DEFAULT_SEARCH_SCOPE,
230                 DONT_RETURN_OBJ_FLAG), handler);
231     }
232 
233     /*
234      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
235      *      java.lang.String, net.sf.ldaptemplate.SearchResultCallbackHandler)
236      */
237     public void search(String base, String filter,
238             SearchResultCallbackHandler handler) throws DataAccessException {
239 
240         search(base, filter, getDefaultSearchControls(DEFAULT_SEARCH_SCOPE,
241                 DONT_RETURN_OBJ_FLAG), handler);
242     }
243 
244     /*
245      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
246      *      java.lang.String, int, net.sf.ldaptemplate.AttributesMapper)
247      */
248     public List search(Name base, String filter, int searchScope,
249             AttributesMapper mapper) {
250 
251         return search(base, filter, getDefaultSearchControls(searchScope,
252                 DONT_RETURN_OBJ_FLAG), mapper);
253     }
254 
255     /*
256      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
257      *      java.lang.String, int, net.sf.ldaptemplate.AttributesMapper)
258      */
259     public List search(String base, String filter, int searchScope,
260             AttributesMapper mapper) throws DataAccessException {
261 
262         return search(base, filter, getDefaultSearchControls(searchScope,
263                 DONT_RETURN_OBJ_FLAG), mapper);
264     }
265 
266     /*
267      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
268      *      java.lang.String, net.sf.ldaptemplate.AttributesMapper)
269      */
270     public List search(Name base, String filter, AttributesMapper mapper)
271             throws DataAccessException {
272 
273         return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper);
274     }
275 
276     /*
277      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
278      *      java.lang.String, net.sf.ldaptemplate.AttributesMapper)
279      */
280     public List search(String base, String filter, AttributesMapper mapper)
281             throws DataAccessException {
282 
283         return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper);
284     }
285 
286     /*
287      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
288      *      java.lang.String, int, net.sf.ldaptemplate.ContextMapper)
289      */
290     public List search(Name base, String filter, int searchScope,
291             ContextMapper mapper) {
292 
293         return search(base, filter, getDefaultSearchControls(searchScope,
294                 RETURN_OBJ_FLAG), mapper);
295     }
296 
297     /*
298      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
299      *      java.lang.String, int, net.sf.ldaptemplate.ContextMapper)
300      */
301     public List search(String base, String filter, int searchScope,
302             ContextMapper mapper) throws DataAccessException {
303 
304         return search(base, filter, getDefaultSearchControls(searchScope,
305                 RETURN_OBJ_FLAG), mapper);
306     }
307 
308     /*
309      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
310      *      java.lang.String, net.sf.ldaptemplate.ContextMapper)
311      */
312     public List search(Name base, String filter, ContextMapper mapper)
313             throws DataAccessException {
314 
315         return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper);
316     }
317 
318     /*
319      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
320      *      java.lang.String, net.sf.ldaptemplate.ContextMapper)
321      */
322     public List search(String base, String filter, ContextMapper mapper)
323             throws DataAccessException {
324 
325         return search(base, filter, DEFAULT_SEARCH_SCOPE, mapper);
326     }
327 
328     /*
329      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
330      *      java.lang.String, javax.naming.directory.SearchControls,
331      *      net.sf.ldaptemplate.ContextMapper)
332      */
333     public List search(String base, String filter, SearchControls controls,
334             ContextMapper mapper) {
335 
336         assureReturnObjFlagSet(controls);
337         ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(
338                 mapper);
339         search(base, filter, controls, handler);
340 
341         return handler.getList();
342     }
343 
344     /*
345      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
346      *      java.lang.String, javax.naming.directory.SearchControls,
347      *      net.sf.ldaptemplate.ContextMapper)
348      */
349     public List search(Name base, String filter, SearchControls controls,
350             ContextMapper mapper) {
351 
352         assureReturnObjFlagSet(controls);
353         ContextMapperCallbackHandler handler = new ContextMapperCallbackHandler(
354                 mapper);
355         search(base, filter, controls, handler);
356 
357         return handler.getList();
358     }
359 
360     /*
361      * @see net.sf.ldaptemplate.LdapOperations#search(javax.naming.Name,
362      *      java.lang.String, javax.naming.directory.SearchControls,
363      *      net.sf.ldaptemplate.AttributesMapper)
364      */
365     public List search(Name base, String filter, SearchControls controls,
366             AttributesMapper mapper) {
367 
368         AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(
369                 mapper);
370         search(base, filter, controls, handler);
371 
372         return handler.getList();
373     }
374 
375     /*
376      * @see net.sf.ldaptemplate.LdapOperations#search(java.lang.String,
377      *      java.lang.String, javax.naming.directory.SearchControls,
378      *      net.sf.ldaptemplate.AttributesMapper)
379      */
380     public List search(String base, String filter, SearchControls controls,
381             AttributesMapper mapper) {
382 
383         AttributesMapperCallbackHandler handler = new AttributesMapperCallbackHandler(
384                 mapper);
385         search(base, filter, controls, handler);
386 
387         return handler.getList();
388     }
389 
390     /*
391      * @see net.sf.ldaptemplate.LdapOperations#executeReadOnly(net.sf.ldaptemplate.ContextExecutor)
392      */
393     public Object executeReadOnly(ContextExecutor ce) {
394         DirContext ctx = contextSource.getReadOnlyContext();
395         return executeWithContext(ce, ctx);
396     }
397 
398     /*
399      * @see net.sf.ldaptemplate.LdapOperations#executeReadWrite(net.sf.ldaptemplate.ContextExecutor)
400      */
401     public Object executeReadWrite(ContextExecutor ce) {
402         DirContext ctx = contextSource.getReadWriteContext();
403         return executeWithContext(ce, ctx);
404     }
405 
406     private Object executeWithContext(ContextExecutor ce, DirContext ctx) {
407         try {
408             return ce.executeWithContext(ctx);
409         } catch (NamingException e) {
410             throw getExceptionTranslator().translate(e);
411         } finally {
412             closeContext(ctx);
413         }
414     }
415 
416     /*
417      * @see net.sf.ldaptemplate.LdapOperations#lookup(javax.naming.Name)
418      */
419     public Object lookup(final Name dn) {
420         return executeReadOnly(new ContextExecutor() {
421             public Object executeWithContext(DirContext ctx)
422                     throws NamingException {
423                 return ctx.lookup(dn);
424             }
425         });
426     }
427 
428     /*
429      * @see net.sf.ldaptemplate.LdapOperations#lookup(java.lang.String)
430      */
431     public Object lookup(final String dn) throws DataAccessException {
432         return executeReadOnly(new ContextExecutor() {
433             public Object executeWithContext(DirContext ctx)
434                     throws NamingException {
435                 return ctx.lookup(dn);
436             }
437         });
438     }
439 
440     /*
441      * @see net.sf.ldaptemplate.LdapOperations#lookup(javax.naming.Name,
442      *      net.sf.ldaptemplate.AttributesMapper)
443      */
444     public Object lookup(final Name dn, final AttributesMapper mapper) {
445         return executeReadOnly(new ContextExecutor() {
446             public Object executeWithContext(DirContext ctx)
447                     throws NamingException {
448                 Attributes attributes = ctx.getAttributes(dn);
449                 return mapper.mapFromAttributes(attributes);
450             }
451         });
452     }
453 
454     /*
455      * @see net.sf.ldaptemplate.LdapOperations#lookup(java.lang.String,
456      *      net.sf.ldaptemplate.AttributesMapper)
457      */
458     public Object lookup(final String dn, final AttributesMapper mapper)
459             throws DataAccessException {
460 
461         return executeReadOnly(new ContextExecutor() {
462             public Object executeWithContext(DirContext ctx)
463                     throws NamingException {
464                 Attributes attributes = ctx.getAttributes(dn);
465                 return mapper.mapFromAttributes(attributes);
466             }
467         });
468     }
469 
470     /*
471      * @see net.sf.ldaptemplate.LdapOperations#lookup(javax.naming.Name,
472      *      net.sf.ldaptemplate.ContextMapper)
473      */
474     public Object lookup(final Name dn, final ContextMapper mapper) {
475         return executeReadOnly(new ContextExecutor() {
476             public Object executeWithContext(DirContext ctx)
477                     throws NamingException {
478                 Object object = ctx.lookup(dn);
479                 return mapper.mapFromContext(object);
480             }
481         });
482     }
483 
484     /*
485      * @see net.sf.ldaptemplate.LdapOperations#lookup(java.lang.String,
486      *      net.sf.ldaptemplate.ContextMapper)
487      */
488     public Object lookup(final String dn, final ContextMapper mapper)
489             throws DataAccessException {
490 
491         return executeReadOnly(new ContextExecutor() {
492             public Object executeWithContext(DirContext ctx)
493                     throws NamingException {
494                 Object object = ctx.lookup(dn);
495                 return mapper.mapFromContext(object);
496             }
497         });
498     }
499 
500     /*
501      * @see net.sf.ldaptemplate.LdapOperations#modifyAttributes(javax.naming.Name,
502      *      javax.naming.directory.ModificationItem[])
503      */
504     public void modifyAttributes(final Name dn, final ModificationItem[] mods) {
505         executeReadWrite(new ContextExecutor() {
506             public Object executeWithContext(DirContext ctx)
507                     throws NamingException {
508                 ctx.modifyAttributes(dn, mods);
509                 return null;
510             }
511         });
512     }
513 
514     /*
515      * @see net.sf.ldaptemplate.LdapOperations#modifyAttributes(java.lang.String,
516      *      javax.naming.directory.ModificationItem[])
517      */
518     public void modifyAttributes(final String dn, final ModificationItem[] mods)
519             throws DataAccessException {
520 
521         executeReadWrite(new ContextExecutor() {
522             public Object executeWithContext(DirContext ctx)
523                     throws NamingException {
524                 ctx.modifyAttributes(dn, mods);
525                 return null;
526             }
527         });
528     }
529 
530     /*
531      * @see net.sf.ldaptemplate.LdapOperations#bind(javax.naming.Name,
532      *      java.lang.Object, javax.naming.directory.Attributes)
533      */
534     public void bind(final Name dn, final Object obj,
535             final Attributes attributes) {
536 
537         executeReadWrite(new ContextExecutor() {
538             public Object executeWithContext(DirContext ctx)
539                     throws NamingException {
540                 ctx.bind(dn, obj, attributes);
541                 return null;
542             }
543         });
544     }
545 
546     /*
547      * @see net.sf.ldaptemplate.LdapOperations#bind(java.lang.String,
548      *      java.lang.Object, javax.naming.directory.Attributes)
549      */
550     public void bind(final String dn, final Object obj,
551             final Attributes attributes) throws DataAccessException {
552 
553         executeReadWrite(new ContextExecutor() {
554             public Object executeWithContext(DirContext ctx)
555                     throws NamingException {
556                 ctx.bind(dn, obj, attributes);
557                 return null;
558             }
559         });
560     }
561 
562     /*
563      * @see net.sf.ldaptemplate.LdapOperations#unbind(javax.naming.Name)
564      */
565     public void unbind(final Name dn) {
566         doUnbind(dn);
567     }
568 
569     /*
570      * @see net.sf.ldaptemplate.LdapOperations#unbind(java.lang.String)
571      */
572     public void unbind(final String dn) throws DataAccessException {
573         doUnbind(dn);
574     }
575 
576     /*
577      * @see net.sf.ldaptemplate.LdapOperations#unbind(javax.naming.Name,
578      *      boolean)
579      */
580     public void unbind(final Name dn, boolean recursive)
581             throws DataAccessException {
582         if (!recursive) {
583             doUnbind(dn);
584         } else {
585             doUnbindRecursively(dn);
586         }
587     }
588 
589     /*
590      * @see net.sf.ldaptemplate.LdapOperations#unbind(java.lang.String, boolean)
591      */
592     public void unbind(final String dn, boolean recursive)
593             throws DataAccessException {
594         if (!recursive) {
595             doUnbind(dn);
596         } else {
597             doUnbindRecursively(dn);
598         }
599     }
600 
601     private void doUnbind(final Name dn) throws DataAccessException {
602         executeReadWrite(new ContextExecutor() {
603             public Object executeWithContext(DirContext ctx)
604                     throws NamingException {
605                 ctx.unbind(dn);
606                 return null;
607             }
608         });
609     }
610 
611     private void doUnbind(final String dn) throws DataAccessException {
612         executeReadWrite(new ContextExecutor() {
613             public Object executeWithContext(DirContext ctx)
614                     throws NamingException {
615                 ctx.unbind(dn);
616                 return null;
617             }
618         });
619     }
620 
621     private void doUnbindRecursively(final Name dn) throws DataAccessException {
622         executeReadWrite(new ContextExecutor() {
623             public Object executeWithContext(DirContext ctx)
624                     throws NamingException {
625                 deleteRecursively(ctx, new DistinguishedName(dn));
626                 return null;
627             }
628         });
629     }
630 
631     private void doUnbindRecursively(final String dn)
632             throws DataAccessException {
633         executeReadWrite(new ContextExecutor() {
634             public Object executeWithContext(DirContext ctx)
635                     throws NamingException {
636                 deleteRecursively(ctx, new DistinguishedName(dn));
637                 return null;
638             }
639         });
640     }
641 
642     /***
643      * Delete all subcontexts including the current one recursively.
644      * 
645      * @param ctx
646      *            The context to use for deleting.
647      * @param name
648      *            The starting point to delete recursively.
649      * @throws DataAccessException
650      *             if any error occurs
651      */
652     protected void deleteRecursively(DirContext ctx, DistinguishedName name)
653             throws DataAccessException {
654 
655         NamingEnumeration enumeration = null;
656         try {
657             enumeration = ctx.listBindings(name);
658             while (enumeration.hasMore()) {
659                 Binding binding = (Binding) enumeration.next();
660                 DistinguishedName childName = new DistinguishedName(binding
661                         .getName());
662                 childName.prepend((DistinguishedName) name);
663                 deleteRecursively(ctx, childName);
664             }
665             ctx.unbind(name);
666             if (log.isDebugEnabled()) {
667                 log.debug("Entry " + name + " deleted");
668             }
669         } catch (NamingException e) {
670             throw exceptionTranslator.translate(e);
671         } finally {
672             try {
673                 enumeration.close();
674             } catch (Exception e) {
675                 // Never mind this
676             }
677         }
678     }
679 
680     /*
681      * @see net.sf.ldaptemplate.LdapOperations#rebind(javax.naming.Name,
682      *      java.lang.Object, javax.naming.directory.Attributes)
683      */
684     public void rebind(final Name dn, final Object obj,
685             final Attributes attributes) throws DataAccessException {
686 
687         executeReadWrite(new ContextExecutor() {
688             public Object executeWithContext(DirContext ctx)
689                     throws NamingException {
690                 ctx.rebind(dn, obj, attributes);
691                 return null;
692             }
693         });
694     }
695 
696     /*
697      * @see net.sf.ldaptemplate.LdapOperations#rebind(java.lang.String,
698      *      java.lang.Object, javax.naming.directory.Attributes)
699      */
700     public void rebind(final String dn, final Object obj,
701             final Attributes attributes) throws DataAccessException {
702 
703         executeReadWrite(new ContextExecutor() {
704             public Object executeWithContext(DirContext ctx)
705                     throws NamingException {
706                 ctx.rebind(dn, obj, attributes);
707                 return null;
708             }
709         });
710     }
711 
712     /*
713      * @see net.sf.ldaptemplate.LdapOperations#rename(javax.naming.Name,
714      *      javax.naming.Name)
715      */
716     public void rename(final Name oldDn, final Name newDn)
717             throws DataAccessException {
718 
719         executeReadWrite(new ContextExecutor() {
720             public Object executeWithContext(DirContext ctx)
721                     throws NamingException {
722                 ctx.rename(oldDn, newDn);
723                 return null;
724             }
725         });
726     }
727 
728     /*
729      * @see net.sf.ldaptemplate.LdapOperations#rename(java.lang.String,
730      *      java.lang.String)
731      */
732     public void rename(final String oldDn, final String newDn)
733             throws DataAccessException {
734 
735         executeReadWrite(new ContextExecutor() {
736             public Object executeWithContext(DirContext ctx)
737                     throws NamingException {
738                 ctx.rename(oldDn, newDn);
739                 return null;
740             }
741         });
742     }
743 
744     /*
745      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
746      */
747     public void afterPropertiesSet() throws Exception {
748         if (contextSource == null) {
749             throw new IllegalArgumentException(
750                     "Property 'contextSource' must be set.");
751         }
752     }
753 
754     private void closeContextAndNamingEnumeration(DirContext ctx,
755             NamingEnumeration results) {
756 
757         closeNamingEnumeration(results);
758         closeContext(ctx);
759     }
760 
761     /***
762      * Close the supplied DirContext if it is not null. Swallow any exceptions,
763      * as this is only for cleanup.
764      * 
765      * @param ctx
766      *            the context to close.
767      */
768     private void closeContext(DirContext ctx) {
769         if (ctx != null) {
770             try {
771                 ctx.close();
772             } catch (Exception e) {
773                 // Never mind this.
774             }
775         }
776     }
777 
778     /***
779      * Close the supplied NamingEnumeration if it is not null. Swallow any
780      * exceptions, as this is only for cleanup.
781      * 
782      * @param results
783      *            the NamingEnumeration to close.
784      */
785     private void closeNamingEnumeration(NamingEnumeration results) {
786         if (results != null) {
787             try {
788                 results.close();
789             } catch (Exception e) {
790                 // Never mind this.
791             }
792         }
793     }
794 
795     /***
796      * Get the NamingExceptionTranslator that will be used by this instance. If
797      * no exceptionTranslator has been set, a default instance will be created.
798      * 
799      * @return the NamingExceptionTranslator to be used by this instance.
800      */
801     public NamingExceptionTranslator getExceptionTranslator() {
802         return exceptionTranslator;
803     }
804 
805     /***
806      * Set the NamingExceptionTranslator to be used by this instance.
807      * 
808      * @param exceptionTranslator
809      *            the NamingExceptionTranslator to use.
810      */
811     public void setExceptionTranslator(
812             NamingExceptionTranslator exceptionTranslator) {
813 
814         this.exceptionTranslator = exceptionTranslator;
815     }
816 
817     private SearchControls getDefaultSearchControls(int searchScope,
818             boolean returningObjFlag) {
819 
820         SearchControls controls = new SearchControls();
821         controls.setSearchScope(searchScope);
822         controls.setReturningObjFlag(returningObjFlag);
823         return controls;
824     }
825 
826     /***
827      * Make sure the returnObjFlag is set in the supplied SearchControls. Set it
828      * and log if it's not set.
829      * 
830      * @param controls
831      *            the SearchControls to check.
832      */
833     private void assureReturnObjFlagSet(SearchControls controls) {
834         Validate.notNull(controls);
835         if (!controls.getReturningObjFlag()) {
836             log.info("The returnObjFlag of supplied SearchControls is not set"
837                     + " but a ContextMapper is used - setting flag to true");
838             controls.setReturningObjFlag(true);
839         }
840     }
841 
842     /***
843      * A CollectingSearchResultCallbackHandler to wrap an AttributesMapper. That
844      * is, the found objects are extracted from all SearchResults, and then
845      * passed to the specified AttributesMapper for translation. This class
846      * needs to be nested, since we want to be able to get hold of the exception
847      * translator of this instance.
848      * 
849      * @author Mattias Arthursson
850      * @author Ulrik Sandberg
851      */
852     public class AttributesMapperCallbackHandler extends
853             CollectingSearchResultCallbackHandler {
854         private AttributesMapper mapper;
855 
856         public AttributesMapperCallbackHandler(AttributesMapper mapper) {
857             this.mapper = mapper;
858         }
859 
860         protected Object getObjectFromResult(SearchResult searchResult) {
861             Attributes attributes = searchResult.getAttributes();
862             try {
863                 return mapper.mapFromAttributes(attributes);
864             } catch (NamingException e) {
865                 throw getExceptionTranslator().translate(e);
866             }
867         }
868     }
869 
870     /***
871      * A CollectingSearchResultCallbackHandler to wrap a ContextMapper. That is,
872      * the found objects are extracted from all SearchResults, and then passed
873      * to the specified ContextMapper for translation.
874      * 
875      * @author Mattias Arthursson
876      */
877     public class ContextMapperCallbackHandler extends
878             CollectingSearchResultCallbackHandler {
879         private ContextMapper mapper;
880 
881         public ContextMapperCallbackHandler(ContextMapper mapper) {
882             this.mapper = mapper;
883         }
884 
885         protected Object getObjectFromResult(SearchResult searchResult) {
886             Object object = searchResult.getObject();
887             if (object == null) {
888                 throw new EntryNotFoundException(
889                         "SearchResult did not contain any object.");
890             }
891             return mapper.mapFromContext(object);
892         }
893     }
894 }