001/*
002 *   Licensed to the Apache Software Foundation (ASF) under one
003 *   or more contributor license agreements.  See the NOTICE file
004 *   distributed with this work for additional information
005 *   regarding copyright ownership.  The ASF licenses this file
006 *   to you under the Apache License, Version 2.0 (the
007 *   "License"); you may not use this file except in compliance
008 *   with the License.  You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 *   Unless required by applicable law or agreed to in writing,
013 *   software distributed under the License is distributed on an
014 *   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *   KIND, either express or implied.  See the License for the
016 *   specific language governing permissions and limitations
017 *   under the License.
018 *
019 */
020package org.apache.directory.ldap.client.template;
021
022
023import java.util.ArrayList;
024import java.util.List;
025
026import org.apache.directory.api.i18n.I18n;
027import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponse;
028import org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyResponseImpl;
029import org.apache.directory.api.ldap.model.entry.Attribute;
030import org.apache.directory.api.ldap.model.entry.Entry;
031import org.apache.directory.api.ldap.model.entry.Value;
032import org.apache.directory.api.ldap.model.exception.LdapException;
033import org.apache.directory.api.ldap.model.message.AddRequest;
034import org.apache.directory.api.ldap.model.message.AddResponse;
035import org.apache.directory.api.ldap.model.message.BindRequest;
036import org.apache.directory.api.ldap.model.message.BindRequestImpl;
037import org.apache.directory.api.ldap.model.message.DeleteRequest;
038import org.apache.directory.api.ldap.model.message.DeleteResponse;
039import org.apache.directory.api.ldap.model.message.ModifyRequest;
040import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
041import org.apache.directory.api.ldap.model.message.ModifyResponse;
042import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
043import org.apache.directory.api.ldap.model.message.ResultResponse;
044import org.apache.directory.api.ldap.model.message.SearchRequest;
045import org.apache.directory.api.ldap.model.message.SearchScope;
046import org.apache.directory.api.ldap.model.name.Dn;
047import org.apache.directory.ldap.client.api.EntryCursorImpl;
048import org.apache.directory.ldap.client.api.LdapConnection;
049import org.apache.directory.ldap.client.api.LdapConnectionPool;
050import org.apache.directory.ldap.client.api.search.FilterBuilder;
051import org.apache.directory.ldap.client.template.exception.LdapRequestUnsuccessfulException;
052import org.apache.directory.ldap.client.template.exception.LdapRuntimeException;
053import org.apache.directory.ldap.client.template.exception.PasswordException;
054import org.slf4j.Logger;
055import org.slf4j.LoggerFactory;
056
057
058/**
059 * A facade for LDAP operations that handles all of the boiler plate code for 
060 * you allowing more concise operations through the use of callbacks.
061 *
062 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
063 * 
064 * @see <a href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a>
065 */
066public class LdapConnectionTemplate implements LdapConnectionOperations, ModelFactory
067{
068    private static final Logger LOG = LoggerFactory.getLogger( LdapConnectionTemplate.class );
069    private static final EntryMapper<Dn> DN_ENTRY_MAPPER = new EntryMapper<Dn>()
070    {
071        @Override
072        public Dn map( Entry entry ) throws LdapException
073        {
074            return entry.getDn();
075        }
076    };
077
078    private LdapConnectionPool connectionPool;
079    private final PasswordPolicyResponse passwordPolicyRequestControl;
080    private PasswordPolicyResponder passwordPolicyResponder;
081    private ModelFactory modelFactory;
082
083
084    /**
085     * Creates a new instance of LdapConnectionTemplate.
086     *
087     * @param connectionPool The pool to obtain connections from.
088     */
089    public LdapConnectionTemplate( LdapConnectionPool connectionPool )
090    {
091        if ( LOG.isDebugEnabled() )
092        {
093            LOG.debug( I18n.msg( I18n.MSG_04174_CREATING_NEW_CONNECTION_TEMPLATE ) );
094        }
095        
096        this.connectionPool = connectionPool;
097        this.passwordPolicyRequestControl = new PasswordPolicyResponseImpl();
098        this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
099            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}