View Javadoc
1   /*
2    *   Licensed to the Apache Software Foundation (ASF) under one
3    *   or more contributor license agreements.  See the NOTICE file
4    *   distributed with this work for additional information
5    *   regarding copyright ownership.  The ASF licenses this file
6    *   to you under the Apache License, Version 2.0 (the
7    *   "License"); you may not use this file except in compliance
8    *   with the License.  You may obtain a copy of the License at
9    *
10   *     https://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing,
13   *   software distributed under the License is distributed on an
14   *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *   KIND, either express or implied.  See the License for the
16   *   specific language governing permissions and limitations
17   *   under the License.
18   *
19   */
20  package org.apache.directory.ldap.client.template;
21  
22  
23  import java.util.ArrayList;
24  import java.util.List;
25  
26  import org.apache.directory.api.i18n.I18n;
27  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse;
28  import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl;
29  import org.apache.directory.api.ldap.model.entry.Attribute;
30  import org.apache.directory.api.ldap.model.entry.Entry;
31  import org.apache.directory.api.ldap.model.entry.Value;
32  import org.apache.directory.api.ldap.model.exception.LdapException;
33  import org.apache.directory.api.ldap.model.message.AddRequest;
34  import org.apache.directory.api.ldap.model.message.AddResponse;
35  import org.apache.directory.api.ldap.model.message.BindRequest;
36  import org.apache.directory.api.ldap.model.message.BindRequestImpl;
37  import org.apache.directory.api.ldap.model.message.DeleteRequest;
38  import org.apache.directory.api.ldap.model.message.DeleteResponse;
39  import org.apache.directory.api.ldap.model.message.ModifyRequest;
40  import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
41  import org.apache.directory.api.ldap.model.message.ModifyResponse;
42  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
43  import org.apache.directory.api.ldap.model.message.ResultResponse;
44  import org.apache.directory.api.ldap.model.message.SearchRequest;
45  import org.apache.directory.api.ldap.model.message.SearchScope;
46  import org.apache.directory.api.ldap.model.name.Dn;
47  import org.apache.directory.ldap.client.api.EntryCursorImpl;
48  import org.apache.directory.ldap.client.api.LdapConnection;
49  import org.apache.directory.ldap.client.api.LdapConnectionPool;
50  import org.apache.directory.ldap.client.api.search.FilterBuilder;
51  import org.apache.directory.ldap.client.template.exception.LdapRequestUnsuccessfulException;
52  import org.apache.directory.ldap.client.template.exception.LdapRuntimeException;
53  import org.apache.directory.ldap.client.template.exception.PasswordException;
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  
58  /**
59   * A facade for LDAP operations that handles all of the boiler plate code for 
60   * you allowing more concise operations through the use of callbacks.
61   *
62   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
63   * 
64   * @see <a href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a>
65   */
66  public class LdapConnectionTemplate implements LdapConnectionOperations, ModelFactory
67  {
68      private static final Logger LOG = LoggerFactory.getLogger( LdapConnectionTemplate.class );
69      private static final EntryMapper<Dn> DN_ENTRY_MAPPER = new EntryMapper<Dn>()
70      {
71          @Override
72          public Dn map( Entry entry ) throws LdapException
73          {
74              return entry.getDn();
75          }
76      };
77  
78      private LdapConnectionPool connectionPool;
79      private final PasswordPolicyResponse passwordPolicyRequestControl;
80      private PasswordPolicyResponder passwordPolicyResponder;
81      private ModelFactory modelFactory;
82  
83  
84      /**
85       * Creates a new instance of LdapConnectionTemplate.
86       *
87       * @param connectionPool The pool to obtain connections from.
88       */
89      public LdapConnectionTemplate( LdapConnectionPool connectionPool )
90      {
91          if ( LOG.isDebugEnabled() )
92          {
93              LOG.debug( I18n.msg( I18n.MSG_04174_CREATING_NEW_CONNECTION_TEMPLATE ) );
94          }
95          
96          this.connectionPool = connectionPool;
97          this.passwordPolicyRequestControl = new PasswordPolicyResponseImpl();
98          this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
99              connectionPool.getLdapApiService() );
100         this.modelFactory = new ModelFactoryImpl();
101     }
102 
103 
104     /**
105      * {@inheritDoc}
106      */
107     @Override
108     public AddResponse add( Dn dn, final Attribute... attributes )
109     {
110         return add( dn,
111             new RequestBuilder<AddRequest>()
112             {
113                 @Override
114                 public void buildRequest( AddRequest request ) throws LdapException
115                 {
116                     request.getEntry().add( attributes );
117                 }
118             } );
119     }
120 
121 
122     /**
123      * {@inheritDoc}
124      */
125     @Override
126     public AddResponse add( Dn dn, RequestBuilder<AddRequest> requestBuilder )
127     {
128         AddRequest addRequest = newAddRequest( newEntry( dn ) );
129         try
130         {
131             requestBuilder.buildRequest( addRequest );
132         }
133         catch ( LdapException e )
134         {
135             throw new LdapRuntimeException( e );
136         }
137         return add( addRequest );
138     }
139 
140 
141     /**
142      * {@inheritDoc}
143      */
144     @Override
145     public AddResponse add( AddRequest addRequest )
146     {
147         LdapConnection connection = null;
148         try
149         {
150             connection = connectionPool.getConnection();
151             return connection.add( addRequest );
152         }
153         catch ( LdapException e )
154         {
155             throw new LdapRuntimeException( e );
156         }
157         finally
158         {
159             returnLdapConnection( connection );
160         }
161     }
162 
163 
164     /**
165      * {@inheritDoc}
166      */
167     @Override
168     public PasswordWarning authenticate( String baseDn, String filter, SearchScope scope, char[] password )
169         throws PasswordException
170     {
171         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
172     }
173 
174 
175     /**
176      * {@inheritDoc}
177      */
178     @Override
179     public PasswordWarning authenticate( Dn baseDn, String filter, SearchScope scope, char[] password )
180         throws PasswordException
181     {
182         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
183     }
184 
185 
186     /**
187      * {@inheritDoc}
188      */
189     @Override
190     public PasswordWarning authenticate( SearchRequest searchRequest, char[] password ) throws PasswordException
191     {
192         Dn userDn = searchFirst( searchRequest, DN_ENTRY_MAPPER );
193         if ( userDn == null )
194         {
195             throw new PasswordException().setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
196         }
197 
198         return authenticate( userDn, password );
199     }
200 
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public PasswordWarning authenticate( Dn userDn, char[] password ) throws PasswordException
207     {
208         LdapConnection connection = null;
209         try
210         {
211             connection = connectionPool.getConnection();
212             return authenticateConnection( connection, userDn, password );
213         }
214         catch ( LdapException e )
215         {
216             throw new LdapRuntimeException( e );
217         }
218         finally
219         {
220             returnLdapConnection( connection );
221         }
222     }
223 
224 
225     private PasswordWarning authenticateConnection( final LdapConnection connection,
226         final Dn userDn, final char[] password ) throws PasswordException
227     {
228         return passwordPolicyResponder.process(
229             new PasswordPolicyOperation()
230             {
231                 @Override
232                 public ResultResponse process() throws LdapException
233                 {
234                     MemoryClearingBuffer passwordBuffer = MemoryClearingBuffer.newInstance( password );
235                     try
236                     {
237                         BindRequest bindRequest = new BindRequestImpl()
238                             .setDn( userDn )
239                             .setCredentials( passwordBuffer.getBytes() )
240                             .addControl( passwordPolicyRequestControl );
241 
242                         return connection.bind( bindRequest );
243                     }
244                     finally
245                     {
246                         passwordBuffer.clear();
247                     }
248                 }
249             } );
250     }
251 
252 
253     /**
254      * {@inheritDoc}
255      */
256     @Override
257     public DeleteResponse delete( Dn dn )
258     {
259         return delete( dn, null );
260     }
261 
262 
263     /**
264      * {@inheritDoc}
265      */
266     @Override
267     public DeleteResponse delete( Dn dn, RequestBuilder<DeleteRequest> requestBuilder )
268     {
269         DeleteRequest deleteRequest = newDeleteRequest( dn );
270         if ( requestBuilder != null )
271         {
272             try
273             {
274                 requestBuilder.buildRequest( deleteRequest );
275             }
276             catch ( LdapException e )
277             {
278                 throw new LdapRuntimeException( e );
279             }
280         }
281         return delete( deleteRequest );
282     }
283 
284 
285     /**
286      * {@inheritDoc}
287      */
288     @Override
289     public DeleteResponse delete( DeleteRequest deleteRequest )
290     {
291         LdapConnection connection = null;
292         try
293         {
294             connection = connectionPool.getConnection();
295             return connection.delete( deleteRequest );
296         }
297         catch ( LdapException e )
298         {
299             throw new LdapRuntimeException( e );
300         }
301         finally
302         {
303             returnLdapConnection( connection );
304         }
305     }
306 
307 
308     /**
309      * {@inheritDoc}
310      */
311     @Override
312     public <T> T execute( ConnectionCallback<T> connectionCallback )
313     {
314         LdapConnection connection = null;
315         try
316         {
317             connection = connectionPool.getConnection();
318             return connectionCallback.doWithConnection( connection );
319         }
320         catch ( LdapException e )
321         {
322             throw new LdapRuntimeException( e );
323         }
324         finally
325         {
326             returnLdapConnection( connection );
327         }
328     }
329 
330 
331     /**
332      * {@inheritDoc}
333      */
334     @Override
335     public <T> T lookup( Dn dn, EntryMapper<T> entryMapper )
336     {
337         return lookup( dn, null, entryMapper );
338     }
339 
340 
341     /**
342      * {@inheritDoc}
343      */
344     @Override
345     public <T> T lookup( Dn dn, String[] attributes, EntryMapper<T> entryMapper )
346     {
347         LdapConnection connection = null;
348         try
349         {
350             connection = connectionPool.getConnection();
351             Entry entry = attributes == null
352                 ? connection.lookup( dn )
353                 : connection.lookup( dn, attributes );
354             return entry == null ? null : entryMapper.map( entry );
355         }
356         catch ( LdapException e )
357         {
358             throw new LdapRuntimeException( e );
359         }
360         finally
361         {
362             returnLdapConnection( connection );
363         }
364     }
365 
366 
367     private void modifyPassword( final LdapConnection connection, final Dn userDn,
368         final char[] newPassword ) throws PasswordException
369     {
370         passwordPolicyResponder.process(
371             new PasswordPolicyOperation()
372             {
373                 @Override
374                 public ResultResponse process() throws PasswordException, LdapException
375                 {
376                     // Can't use Password Modify:
377                     // https://issues.apache.org/jira/browse/DIRSERVER-1935
378                     // So revert to regular Modify
379                     MemoryClearingBuffer newPasswordBuffer = MemoryClearingBuffer.newInstance( newPassword );
380                     try
381                     {
382                         ModifyRequest modifyRequest = new ModifyRequestImpl()
383                             .setName( userDn )
384                             .replace( "userPassword", newPasswordBuffer.getComputedBytes() )
385                             .addControl( passwordPolicyRequestControl );
386 
387                         return connection.modify( modifyRequest );
388                     }
389                     finally
390                     {
391                         newPasswordBuffer.clear();
392                     }
393                 }
394             } );
395 
396     }
397 
398 
399     /**
400      * {@inheritDoc}
401      */
402     @Override
403     public void modifyPassword( Dn userDn, char[] newPassword )
404         throws PasswordException
405     {
406         modifyPassword( userDn, null, newPassword, true );
407     }
408 
409 
410     /**
411      * {@inheritDoc}
412      */
413     @Override
414     public void modifyPassword( Dn userDn, char[] oldPassword,
415         char[] newPassword ) throws PasswordException
416     {
417         modifyPassword( userDn, oldPassword, newPassword, false );
418     }
419 
420 
421     /**
422      * {@inheritDoc}
423      */
424     @Override
425     public void modifyPassword( Dn userDn, char[] oldPassword,
426         char[] newPassword, boolean asAdmin ) throws PasswordException
427     {
428         LdapConnection connection = null;
429         try
430         {
431             connection = connectionPool.getConnection();
432             if ( !asAdmin )
433             {
434                 authenticateConnection( connection, userDn, oldPassword );
435             }
436 
437             modifyPassword( connection, userDn, newPassword );
438         }
439         catch ( LdapException e )
440         {
441             throw new LdapRuntimeException( e );
442         }
443         finally
444         {
445             returnLdapConnection( connection );
446         }
447     }
448 
449 
450     /**
451      * {@inheritDoc}
452      */
453     @Override
454     public ModifyResponse modify( Dn dn, RequestBuilder<ModifyRequest> requestBuilder )
455     {
456         ModifyRequest modifyRequest = newModifyRequest( dn );
457         try
458         {
459             requestBuilder.buildRequest( modifyRequest );
460         }
461         catch ( LdapException e )
462         {
463             throw new LdapRuntimeException( e );
464         }
465         return modify( modifyRequest );
466     }
467 
468 
469     /**
470      * {@inheritDoc}
471      */
472     @Override
473     public ModifyResponse modify( ModifyRequest modifyRequest )
474     {
475         LdapConnection connection = null;
476         try
477         {
478             connection = connectionPool.getConnection();
479             return connection.modify( modifyRequest );
480         }
481         catch ( LdapException e )
482         {
483             throw new LdapRuntimeException( e );
484         }
485         finally
486         {
487             returnLdapConnection( connection );
488         }
489     }
490 
491 
492     /**
493      * {@inheritDoc}
494      */
495     @Override
496     public AddRequest newAddRequest( Entry entry )
497     {
498         return modelFactory.newAddRequest( entry );
499     }
500 
501 
502     /**
503      * {@inheritDoc}
504      */
505     @Override
506     public Attribute newAttribute( String name )
507     {
508         return modelFactory.newAttribute( name );
509     }
510 
511 
512     /**
513      * {@inheritDoc}
514      */
515     public Attribute newAttribute( String name, byte[]... values )
516     {
517         return modelFactory.newAttribute( name, values );
518     }
519 
520 
521     /**
522      * {@inheritDoc}
523      */
524     @Override
525     public Attribute newAttribute( String name, String... values )
526     {
527         return modelFactory.newAttribute( name, values );
528     }
529 
530 
531     /**
532      * {@inheritDoc}
533      */
534     @Override
535     public Attribute newAttribute( String name, Value... values )
536     {
537         return modelFactory.newAttribute( name, values );
538     }
539 
540 
541     /**
542      * {@inheritDoc}
543      */
544     @Override
545     public DeleteRequest newDeleteRequest( Dn dn )
546     {
547         return modelFactory.newDeleteRequest( dn );
548     }
549 
550 
551     /**
552      * {@inheritDoc}
553      */
554     @Override
555     public Dn newDn( String dn )
556     {
557         return modelFactory.newDn( dn );
558     }
559 
560 
561     /**
562      * {@inheritDoc}
563      */
564     @Override
565     public Entry newEntry( String dn )
566     {
567         return modelFactory.newEntry( dn );
568     }
569 
570 
571     /**
572      * {@inheritDoc}
573      */
574     @Override
575     public Entry newEntry( Dn dn )
576     {
577         return modelFactory.newEntry( dn );
578     }
579 
580 
581     /**
582      * {@inheritDoc}
583      */
584     @Override
585     public ModifyRequest newModifyRequest( String dn )
586     {
587         return modelFactory.newModifyRequest( dn );
588     }
589 
590 
591     /**
592      * {@inheritDoc}
593      */
594     @Override
595     public ModifyRequest newModifyRequest( Dn dn )
596     {
597         return modelFactory.newModifyRequest( dn );
598     }
599 
600 
601     /**
602      * {@inheritDoc}
603      */
604     @Override
605     public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope )
606     {
607         return modelFactory.newSearchRequest( baseDn, filter, scope );
608     }
609 
610 
611     /**
612      * {@inheritDoc}
613      */
614     @Override
615     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope )
616     {
617         return modelFactory.newSearchRequest( baseDn, filter, scope );
618     }
619 
620 
621     /**
622      * {@inheritDoc}
623      */
624     @Override
625     public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope )
626     {
627         return modelFactory.newSearchRequest( baseDn, filter, scope );
628     }
629 
630 
631     /**
632      * {@inheritDoc}
633      */
634     @Override
635     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope )
636     {
637         return modelFactory.newSearchRequest( baseDn, filter, scope );
638     }
639 
640 
641     /**
642      * {@inheritDoc}
643      */
644     @Override
645     public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
646     {
647         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
648     }
649 
650 
651     /**
652      * {@inheritDoc}
653      */
654     @Override
655     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope, String... attributes )
656     {
657         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
658     }
659 
660 
661     /**
662      * {@inheritDoc}
663      */
664     @Override
665     public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
666     {
667         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
668     }
669 
670 
671     /**
672      * {@inheritDoc}
673      */
674     @Override
675     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope, String... attributes )
676     {
677         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
678     }
679 
680 
681     /**
682      * {@inheritDoc}
683      */
684     @Override
685     public <T extends ResultResponse> T responseOrException( T response )
686     {
687         if ( ResultCodeEnum.SUCCESS != response.getLdapResult().getResultCode() )
688         {
689             throw new LdapRequestUnsuccessfulException( response );
690         }
691         return response;
692     }
693 
694 
695     private void returnLdapConnection( LdapConnection connection )
696     {
697         if ( connection != null )
698         {
699             try
700             {
701                 connectionPool.releaseConnection( connection );
702             }
703             catch ( LdapException e )
704             {
705                 throw new LdapRuntimeException( e );
706             }
707         }
708     }
709 
710 
711     /**
712      * {@inheritDoc}
713      */
714     @Override
715     public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
716         EntryMapper<T> entryMapper )
717     {
718         return search(
719             modelFactory.newSearchRequest( baseDn, filter, scope ),
720             entryMapper );
721     }
722 
723 
724     /**
725      * {@inheritDoc}
726      */
727     @Override
728     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
729         EntryMapper<T> entryMapper )
730     {
731         return search(
732             modelFactory.newSearchRequest( baseDn, filter, scope ),
733             entryMapper );
734     }
735 
736 
737     /**
738      * {@inheritDoc}
739      */
740     @Override
741     public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
742         EntryMapper<T> entryMapper )
743     {
744         return search(
745             modelFactory.newSearchRequest( baseDn, filter, scope ),
746             entryMapper );
747     }
748 
749 
750     /**
751      * {@inheritDoc}
752      */
753     @Override
754     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
755         EntryMapper<T> entryMapper )
756     {
757         return search(
758             modelFactory.newSearchRequest( baseDn, filter, scope ),
759             entryMapper );
760     }
761 
762 
763     /**
764      * {@inheritDoc}
765      */
766     @Override
767     public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
768         String[] attributes, EntryMapper<T> entryMapper )
769     {
770         return search(
771             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
772             entryMapper );
773     }
774 
775 
776     /**
777      * {@inheritDoc}
778      */
779     @Override
780     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
781         String[] attributes, EntryMapper<T> entryMapper )
782     {
783         return search(
784             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
785             entryMapper );
786     }
787 
788 
789     /**
790      * {@inheritDoc}
791      */
792     @Override
793     public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
794         String[] attributes, EntryMapper<T> entryMapper )
795     {
796         return search(
797             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
798             entryMapper );
799     }
800 
801 
802     /**
803      * {@inheritDoc}
804      */
805     @Override
806     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
807         String[] attributes, EntryMapper<T> entryMapper )
808     {
809         return search(
810             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
811             entryMapper );
812     }
813 
814 
815     /**
816      * {@inheritDoc}
817      */
818     @Override
819     public <T> List<T> search( SearchRequest searchRequest,
820         EntryMapper<T> entryMapper )
821     {
822         List<T> entries = new ArrayList<>();
823 
824         LdapConnection connection = null;
825         try
826         {
827             connection = connectionPool.getConnection();
828 
829             for ( Entry entry : new EntryCursorImpl( connection.search( searchRequest ) ) )
830             {
831                 entries.add( entryMapper.map( entry ) );
832             }
833         }
834         catch ( LdapException e )
835         {
836             throw new LdapRuntimeException( e );
837         }
838         finally
839         {
840             returnLdapConnection( connection );
841         }
842 
843         return entries;
844     }
845 
846 
847     /**
848      * {@inheritDoc}
849      */
850     @Override
851     public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
852         EntryMapper<T> entryMapper )
853     {
854         return searchFirst(
855             modelFactory.newSearchRequest( baseDn, filter, scope ),
856             entryMapper );
857     }
858 
859 
860     /**
861      * {@inheritDoc}
862      */
863     @Override
864     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
865         EntryMapper<T> entryMapper )
866     {
867         return searchFirst(
868             modelFactory.newSearchRequest( baseDn, filter, scope ),
869             entryMapper );
870     }
871 
872 
873     /**
874      * {@inheritDoc}
875      */
876     @Override
877     public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
878         EntryMapper<T> entryMapper )
879     {
880         return searchFirst(
881             modelFactory.newSearchRequest( baseDn, filter, scope ),
882             entryMapper );
883     }
884 
885 
886     /**
887      * {@inheritDoc}
888      */
889     @Override
890     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
891         EntryMapper<T> entryMapper )
892     {
893         return searchFirst(
894             modelFactory.newSearchRequest( baseDn, filter, scope ),
895             entryMapper );
896     }
897 
898 
899     /**
900      * {@inheritDoc}
901      */
902     @Override
903     public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
904         String[] attributes, EntryMapper<T> entryMapper )
905     {
906         return searchFirst(
907             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
908             entryMapper );
909     }
910 
911 
912     /**
913      * {@inheritDoc}
914      */
915     @Override
916     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
917         String[] attributes, EntryMapper<T> entryMapper )
918     {
919         return searchFirst(
920             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
921             entryMapper );
922     }
923 
924 
925     /**
926      * {@inheritDoc}
927      */
928     @Override
929     public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
930         String[] attributes, EntryMapper<T> entryMapper )
931     {
932         return searchFirst(
933             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
934             entryMapper );
935     }
936 
937 
938     /**
939      * {@inheritDoc}
940      */
941     @Override
942     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
943         String[] attributes, EntryMapper<T> entryMapper )
944     {
945         return searchFirst(
946             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
947             entryMapper );
948     }
949 
950 
951     /**
952      * {@inheritDoc}
953      */
954     @Override
955     public <T> T searchFirst( SearchRequest searchRequest,
956         EntryMapper<T> entryMapper )
957     {
958         // in case the caller did not set size limit, we cache original value,
959         // set to 1, then set back to original value before returning...
960         long originalSizeLimit = searchRequest.getSizeLimit();
961         try
962         {
963             searchRequest.setSizeLimit( 1 );
964             List<T> entries = search( searchRequest, entryMapper );
965             return entries.isEmpty() ? null : entries.get( 0 );
966         }
967         finally
968         {
969             searchRequest.setSizeLimit( originalSizeLimit );
970         }
971     }
972 
973 
974     /**
975      * Sets the <code>modelFactory</code> implementation for this facade.
976      *
977      * @param modelFactory The model factory implementation
978      */
979     public void setModelFactory( ModelFactory modelFactory )
980     {
981         this.modelFactory = modelFactory;
982     }
983 
984 
985     /**
986      * Sets the <code>passwordPolicyResponder</code> implementation for this
987      * facade.
988      *
989      * @param passwordPolicyResponder The password policy responder 
990      * implementation
991      */
992     public void setPasswordPolicyResponder( PasswordPolicyResponder passwordPolicyResponder )
993     {
994         this.passwordPolicyResponder = passwordPolicyResponder;
995     }
996 }