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   *     http://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.ldap.extras.controls.ppolicy_impl.PasswordPolicyDecorator;
27  import org.apache.directory.api.ldap.model.entry.Attribute;
28  import org.apache.directory.api.ldap.model.entry.Entry;
29  import org.apache.directory.api.ldap.model.entry.Value;
30  import org.apache.directory.api.ldap.model.exception.LdapException;
31  import org.apache.directory.api.ldap.model.message.AddRequest;
32  import org.apache.directory.api.ldap.model.message.AddResponse;
33  import org.apache.directory.api.ldap.model.message.BindRequest;
34  import org.apache.directory.api.ldap.model.message.BindRequestImpl;
35  import org.apache.directory.api.ldap.model.message.DeleteRequest;
36  import org.apache.directory.api.ldap.model.message.DeleteResponse;
37  import org.apache.directory.api.ldap.model.message.ModifyRequest;
38  import org.apache.directory.api.ldap.model.message.ModifyRequestImpl;
39  import org.apache.directory.api.ldap.model.message.ModifyResponse;
40  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
41  import org.apache.directory.api.ldap.model.message.ResultResponse;
42  import org.apache.directory.api.ldap.model.message.SearchRequest;
43  import org.apache.directory.api.ldap.model.message.SearchScope;
44  import org.apache.directory.api.ldap.model.name.Dn;
45  import org.apache.directory.ldap.client.api.EntryCursorImpl;
46  import org.apache.directory.ldap.client.api.LdapConnection;
47  import org.apache.directory.ldap.client.api.LdapConnectionPool;
48  import org.apache.directory.ldap.client.api.search.FilterBuilder;
49  import org.apache.directory.ldap.client.template.exception.LdapRequestUnsuccessfulException;
50  import org.apache.directory.ldap.client.template.exception.LdapRuntimeException;
51  import org.apache.directory.ldap.client.template.exception.PasswordException;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  
56  /**
57   * A facade for LDAP operations that handles all of the boiler plate code for 
58   * you allowing more concise operations through the use of callbacks.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   * 
62   * @see <a href="http://en.wikipedia.org/wiki/Template_method_pattern">Template method pattern</a>
63   */
64  public class LdapConnectionTemplate implements LdapConnectionOperations, ModelFactory
65  {
66      private static Logger logger = LoggerFactory.getLogger( LdapConnectionTemplate.class );
67      private static final EntryMapper<Dn> dnEntryMapper = new EntryMapper<Dn>()
68      {
69          @Override
70          public Dn map( Entry entry ) throws LdapException
71          {
72              return entry.getDn();
73          }
74      };
75  
76      private LdapConnectionPool connectionPool;
77      private final PasswordPolicyDecorator passwordPolicyRequestControl;
78      private PasswordPolicyResponder passwordPolicyResponder;
79      private ModelFactory modelFactory;
80  
81  
82      /**
83       * Creates a new instance of LdapConnectionTemplate.
84       *
85       * @param connectionPool The pool to obtain connections from.
86       */
87      public LdapConnectionTemplate( LdapConnectionPool connectionPool )
88      {
89          logger.debug( "creating new connection template from connectionPool" );
90          this.connectionPool = connectionPool;
91          this.passwordPolicyRequestControl = new PasswordPolicyDecorator(
92              connectionPool.getLdapApiService() );
93          this.passwordPolicyResponder = new PasswordPolicyResponderImpl(
94              connectionPool.getLdapApiService() );
95          this.modelFactory = new ModelFactoryImpl();
96      }
97  
98  
99      @Override
100     public AddResponse add( Dn dn, final Attribute... attributes )
101     {
102         return add( dn,
103             new RequestBuilder<AddRequest>()
104             {
105                 @Override
106                 public void buildRequest( AddRequest request ) throws LdapException
107                 {
108                     request.getEntry().add( attributes );
109                 }
110             } );
111     }
112 
113 
114     @Override
115     public AddResponse add( Dn dn, RequestBuilder<AddRequest> requestBuilder )
116     {
117         AddRequest addRequest = newAddRequest( newEntry( dn ) );
118         try
119         {
120             requestBuilder.buildRequest( addRequest );
121         }
122         catch ( LdapException e )
123         {
124             throw new LdapRuntimeException( e );
125         }
126         return add( addRequest );
127     }
128 
129 
130     @Override
131     public AddResponse add( AddRequest addRequest )
132     {
133         LdapConnection connection = null;
134         try
135         {
136             connection = connectionPool.getConnection();
137             return connection.add( addRequest );
138         }
139         catch ( LdapException e )
140         {
141             throw new LdapRuntimeException( e );
142         }
143         finally
144         {
145             returnLdapConnection( connection );
146         }
147     }
148 
149 
150     @Override
151     public PasswordWarning authenticate( String baseDn, String filter, SearchScope scope, char[] password )
152         throws PasswordException
153     {
154         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
155     }
156 
157 
158     @Override
159     public PasswordWarning authenticate( Dn baseDn, String filter, SearchScope scope, char[] password )
160         throws PasswordException
161     {
162         return authenticate( newSearchRequest( baseDn, filter, scope ), password );
163     }
164 
165 
166     @Override
167     public PasswordWarning authenticate( SearchRequest searchRequest, char[] password ) throws PasswordException
168     {
169         Dn userDn = searchFirst( searchRequest, dnEntryMapper );
170         if ( userDn == null )
171         {
172             throw new PasswordException().setResultCode( ResultCodeEnum.INVALID_CREDENTIALS );
173         }
174 
175         return authenticate( userDn, password );
176     }
177 
178 
179     @Override
180     public PasswordWarning authenticate( Dn userDn, char[] password ) throws PasswordException
181     {
182         LdapConnection connection = null;
183         try
184         {
185             connection = connectionPool.getConnection();
186             return authenticateConnection( connection, userDn, password );
187         }
188         catch ( LdapException e )
189         {
190             throw new LdapRuntimeException( e );
191         }
192         finally
193         {
194             returnLdapConnection( connection );
195         }
196     }
197 
198 
199     private PasswordWarning authenticateConnection( final LdapConnection connection,
200         final Dn userDn, final char[] password ) throws PasswordException
201     {
202         return passwordPolicyResponder.process(
203             new PasswordPolicyOperation()
204             {
205                 @Override
206                 public ResultResponse process() throws LdapException
207                 {
208                     MemoryClearingBuffer passwordBuffer = MemoryClearingBuffer.newInstance( password );
209                     try
210                     {
211                         BindRequest bindRequest = new BindRequestImpl()
212                             .setDn( userDn )
213                             .setCredentials( passwordBuffer.getBytes() )
214                             .addControl( passwordPolicyRequestControl );
215 
216                         return connection.bind( bindRequest );
217                     }
218                     finally
219                     {
220                         passwordBuffer.clear();
221                     }
222                 }
223             } );
224     }
225 
226 
227     @Override
228     public DeleteResponse delete( Dn dn )
229     {
230         return delete( dn, null );
231     }
232 
233 
234     @Override
235     public DeleteResponse delete( Dn dn, RequestBuilder<DeleteRequest> requestBuilder )
236     {
237         DeleteRequest deleteRequest = newDeleteRequest( dn );
238         if ( requestBuilder != null )
239         {
240             try
241             {
242                 requestBuilder.buildRequest( deleteRequest );
243             }
244             catch ( LdapException e )
245             {
246                 throw new LdapRuntimeException( e );
247             }
248         }
249         return delete( deleteRequest );
250     }
251 
252 
253     @Override
254     public DeleteResponse delete( DeleteRequest deleteRequest )
255     {
256         LdapConnection connection = null;
257         try
258         {
259             connection = connectionPool.getConnection();
260             return connection.delete( deleteRequest );
261         }
262         catch ( LdapException e )
263         {
264             throw new LdapRuntimeException( e );
265         }
266         finally
267         {
268             returnLdapConnection( connection );
269         }
270     }
271 
272 
273     @Override
274     public <T> T execute( ConnectionCallback<T> connectionCallback )
275     {
276         LdapConnection connection = null;
277         try
278         {
279             connection = connectionPool.getConnection();
280             return connectionCallback.doWithConnection( connection );
281         }
282         catch ( LdapException e )
283         {
284             throw new LdapRuntimeException( e );
285         }
286         finally
287         {
288             returnLdapConnection( connection );
289         }
290     }
291 
292 
293     @Override
294     public <T> T lookup( Dn dn, EntryMapper<T> entryMapper )
295     {
296         return lookup( dn, null, entryMapper );
297     }
298 
299 
300     @Override
301     public <T> T lookup( Dn dn, String[] attributes, EntryMapper<T> entryMapper )
302     {
303         LdapConnection connection = null;
304         try
305         {
306             connection = connectionPool.getConnection();
307             Entry entry = attributes == null
308                 ? connection.lookup( dn )
309                 : connection.lookup( dn, attributes );
310             return entry == null ? null : entryMapper.map( entry );
311         }
312         catch ( LdapException e )
313         {
314             throw new LdapRuntimeException( e );
315         }
316         finally
317         {
318             returnLdapConnection( connection );
319         }
320     }
321 
322 
323     private void modifyPassword( final LdapConnection connection, final Dn userDn,
324         final char[] newPassword ) throws PasswordException
325     {
326         passwordPolicyResponder.process(
327             new PasswordPolicyOperation()
328             {
329                 @Override
330                 public ResultResponse process() throws PasswordException, LdapException
331                 {
332                     // Can't use Password Modify:
333                     // https://issues.apache.org/jira/browse/DIRSERVER-1935
334                     // So revert to regular Modify
335                     MemoryClearingBuffer newPasswordBuffer = MemoryClearingBuffer.newInstance( newPassword );
336                     try
337                     {
338                         ModifyRequest modifyRequest = new ModifyRequestImpl()
339                             .setName( userDn )
340                             .replace( "userPassword", newPasswordBuffer.getComputedBytes() )
341                             .addControl( passwordPolicyRequestControl );
342 
343                         return connection.modify( modifyRequest );
344                     }
345                     finally
346                     {
347                         newPasswordBuffer.clear();
348                     }
349                 }
350             } );
351 
352     }
353 
354 
355     @Override
356     public void modifyPassword( Dn userDn, char[] newPassword )
357         throws PasswordException
358     {
359         modifyPassword( userDn, null, newPassword, true );
360     }
361 
362 
363     @Override
364     public void modifyPassword( Dn userDn, char[] oldPassword,
365         char[] newPassword ) throws PasswordException
366     {
367         modifyPassword( userDn, oldPassword, newPassword, false );
368     }
369 
370 
371     @Override
372     public void modifyPassword( Dn userDn, char[] oldPassword,
373         char[] newPassword, boolean asAdmin ) throws PasswordException
374     {
375         LdapConnection connection = null;
376         try
377         {
378             connection = connectionPool.getConnection();
379             if ( !asAdmin )
380             {
381                 authenticateConnection( connection, userDn, oldPassword );
382             }
383 
384             modifyPassword( connection, userDn, newPassword );
385         }
386         catch ( LdapException e )
387         {
388             throw new LdapRuntimeException( e );
389         }
390         finally
391         {
392             returnLdapConnection( connection );
393         }
394     }
395 
396 
397     @Override
398     public ModifyResponse modify( Dn dn, RequestBuilder<ModifyRequest> requestBuilder )
399     {
400         ModifyRequest modifyRequest = newModifyRequest( dn );
401         try
402         {
403             requestBuilder.buildRequest( modifyRequest );
404         }
405         catch ( LdapException e )
406         {
407             throw new LdapRuntimeException( e );
408         }
409         return modify( modifyRequest );
410     }
411 
412 
413     @Override
414     public ModifyResponse modify( ModifyRequest modifyRequest )
415     {
416         LdapConnection connection = null;
417         try
418         {
419             connection = connectionPool.getConnection();
420             return connection.modify( modifyRequest );
421         }
422         catch ( LdapException e )
423         {
424             throw new LdapRuntimeException( e );
425         }
426         finally
427         {
428             returnLdapConnection( connection );
429         }
430     }
431 
432 
433     @Override
434     public AddRequest newAddRequest( Entry entry )
435     {
436         return modelFactory.newAddRequest( entry );
437     }
438 
439 
440     @Override
441     public Attribute newAttribute( String name, byte[]... values )
442     {
443         return modelFactory.newAttribute( name, values );
444     }
445 
446 
447     @Override
448     public Attribute newAttribute( String name, String... values )
449     {
450         return modelFactory.newAttribute( name, values );
451     }
452 
453 
454     @Override
455     public Attribute newAttribute( String name, Value<?>... values )
456     {
457         return modelFactory.newAttribute( name, values );
458     }
459 
460 
461     @Override
462     public DeleteRequest newDeleteRequest( Dn dn )
463     {
464         return modelFactory.newDeleteRequest( dn );
465     }
466 
467 
468     @Override
469     public Dn newDn( String dn )
470     {
471         return modelFactory.newDn( dn );
472     }
473 
474 
475     @Override
476     public Entry newEntry( String dn )
477     {
478         return modelFactory.newEntry( dn );
479     }
480 
481 
482     @Override
483     public Entry newEntry( Dn dn )
484     {
485         return modelFactory.newEntry( dn );
486     }
487 
488 
489     @Override
490     public ModifyRequest newModifyRequest( String dn )
491     {
492         return modelFactory.newModifyRequest( dn );
493     }
494 
495 
496     @Override
497     public ModifyRequest newModifyRequest( Dn dn )
498     {
499         return modelFactory.newModifyRequest( dn );
500     }
501 
502 
503     @Override
504     public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope )
505     {
506         return modelFactory.newSearchRequest( baseDn, filter, scope );
507     }
508 
509 
510     @Override
511     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope )
512     {
513         return modelFactory.newSearchRequest( baseDn, filter, scope );
514     }
515 
516 
517     @Override
518     public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope )
519     {
520         return modelFactory.newSearchRequest( baseDn, filter, scope );
521     }
522 
523 
524     @Override
525     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope )
526     {
527         return modelFactory.newSearchRequest( baseDn, filter, scope );
528     }
529 
530 
531     @Override
532     public SearchRequest newSearchRequest( String baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
533     {
534         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
535     }
536 
537 
538     @Override
539     public SearchRequest newSearchRequest( String baseDn, String filter, SearchScope scope, String... attributes )
540     {
541         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
542     }
543 
544 
545     @Override
546     public SearchRequest newSearchRequest( Dn baseDn, FilterBuilder filter, SearchScope scope, String... attributes )
547     {
548         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
549     }
550 
551 
552     @Override
553     public SearchRequest newSearchRequest( Dn baseDn, String filter, SearchScope scope, String... attributes )
554     {
555         return modelFactory.newSearchRequest( baseDn, filter, scope, attributes );
556     }
557 
558 
559     @Override
560     public <T extends ResultResponse> T responseOrException( T response )
561     {
562         if ( ResultCodeEnum.SUCCESS != response.getLdapResult().getResultCode() )
563         {
564             throw new LdapRequestUnsuccessfulException( response );
565         }
566         return response;
567     }
568 
569 
570     private void returnLdapConnection( LdapConnection connection )
571     {
572         if ( connection != null )
573         {
574             try
575             {
576                 connectionPool.releaseConnection( connection );
577             }
578             catch ( LdapException e )
579             {
580                 throw new LdapRuntimeException( e );
581             }
582         }
583     }
584 
585 
586     @Override
587     public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
588         EntryMapper<T> entryMapper )
589     {
590         return search(
591             modelFactory.newSearchRequest( baseDn, filter, scope ),
592             entryMapper );
593     }
594 
595 
596     @Override
597     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
598         EntryMapper<T> entryMapper )
599     {
600         return search(
601             modelFactory.newSearchRequest( baseDn, filter, scope ),
602             entryMapper );
603     }
604 
605 
606     @Override
607     public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
608         EntryMapper<T> entryMapper )
609     {
610         return search(
611             modelFactory.newSearchRequest( baseDn, filter, scope ),
612             entryMapper );
613     }
614 
615 
616     @Override
617     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
618         EntryMapper<T> entryMapper )
619     {
620         return search(
621             modelFactory.newSearchRequest( baseDn, filter, scope ),
622             entryMapper );
623     }
624 
625 
626     @Override
627     public <T> List<T> search( String baseDn, FilterBuilder filter, SearchScope scope,
628         String[] attributes, EntryMapper<T> entryMapper )
629     {
630         return search(
631             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
632             entryMapper );
633     }
634 
635 
636     @Override
637     public <T> List<T> search( String baseDn, String filter, SearchScope scope,
638         String[] attributes, EntryMapper<T> entryMapper )
639     {
640         return search(
641             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
642             entryMapper );
643     }
644 
645 
646     @Override
647     public <T> List<T> search( Dn baseDn, FilterBuilder filter, SearchScope scope,
648         String[] attributes, EntryMapper<T> entryMapper )
649     {
650         return search(
651             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
652             entryMapper );
653     }
654 
655 
656     @Override
657     public <T> List<T> search( Dn baseDn, String filter, SearchScope scope,
658         String[] attributes, EntryMapper<T> entryMapper )
659     {
660         return search(
661             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
662             entryMapper );
663     }
664 
665 
666     @Override
667     public <T> List<T> search( SearchRequest searchRequest,
668         EntryMapper<T> entryMapper )
669     {
670         List<T> entries = new ArrayList<T>();
671 
672         LdapConnection connection = null;
673         try
674         {
675             connection = connectionPool.getConnection();
676 
677             for ( Entry entry : new EntryCursorImpl( connection.search( searchRequest ) ) )
678             {
679                 entries.add( entryMapper.map( entry ) );
680             }
681         }
682         catch ( LdapException e )
683         {
684             throw new LdapRuntimeException( e );
685         }
686         finally
687         {
688             returnLdapConnection( connection );
689         }
690 
691         return entries;
692     }
693 
694 
695     @Override
696     public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
697         EntryMapper<T> entryMapper )
698     {
699         return searchFirst(
700             modelFactory.newSearchRequest( baseDn, filter, scope ),
701             entryMapper );
702     }
703 
704 
705     @Override
706     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
707         EntryMapper<T> entryMapper )
708     {
709         return searchFirst(
710             modelFactory.newSearchRequest( baseDn, filter, scope ),
711             entryMapper );
712     }
713 
714 
715     @Override
716     public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
717         EntryMapper<T> entryMapper )
718     {
719         return searchFirst(
720             modelFactory.newSearchRequest( baseDn, filter, scope ),
721             entryMapper );
722     }
723 
724 
725     @Override
726     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
727         EntryMapper<T> entryMapper )
728     {
729         return searchFirst(
730             modelFactory.newSearchRequest( baseDn, filter, scope ),
731             entryMapper );
732     }
733 
734 
735     @Override
736     public <T> T searchFirst( String baseDn, FilterBuilder filter, SearchScope scope,
737         String[] attributes, EntryMapper<T> entryMapper )
738     {
739         return searchFirst(
740             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
741             entryMapper );
742     }
743 
744 
745     @Override
746     public <T> T searchFirst( String baseDn, String filter, SearchScope scope,
747         String[] attributes, EntryMapper<T> entryMapper )
748     {
749         return searchFirst(
750             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
751             entryMapper );
752     }
753 
754 
755     @Override
756     public <T> T searchFirst( Dn baseDn, FilterBuilder filter, SearchScope scope,
757         String[] attributes, EntryMapper<T> entryMapper )
758     {
759         return searchFirst(
760             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
761             entryMapper );
762     }
763 
764 
765     @Override
766     public <T> T searchFirst( Dn baseDn, String filter, SearchScope scope,
767         String[] attributes, EntryMapper<T> entryMapper )
768     {
769         return searchFirst(
770             modelFactory.newSearchRequest( baseDn, filter, scope, attributes ),
771             entryMapper );
772     }
773 
774 
775     @Override
776     public <T> T searchFirst( SearchRequest searchRequest,
777         EntryMapper<T> entryMapper )
778     {
779         // in case the caller did not set size limit, we cache original value,
780         // set to 1, then set back to original value before returning...
781         long originalSizeLimit = searchRequest.getSizeLimit();
782         try
783         {
784             searchRequest.setSizeLimit( 1 );
785             List<T> entries = search( searchRequest, entryMapper );
786             return entries.isEmpty() ? null : entries.get( 0 );
787         }
788         finally
789         {
790             searchRequest.setSizeLimit( originalSizeLimit );
791         }
792     }
793 
794 
795     /**
796      * Sets the <code>modelFactory</code> implementation for this facade.
797      *
798      * @param modelFactory The model factory implementation
799      */
800     public void setModelFactory( ModelFactory modelFactory )
801     {
802         this.modelFactory = modelFactory;
803     }
804 
805 
806     /**
807      * Sets the <code>passwordPolicyResponder</code> implementation for this
808      * facade.
809      *
810      * @param passwordPolicyResponder The password policy responder 
811      * implementation
812      */
813     public void setPasswordPolicyResponder( PasswordPolicyResponder passwordPolicyResponder )
814     {
815         this.passwordPolicyResponder = passwordPolicyResponder;
816     }
817 }