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.api.ldap.codec.standalone;
21  
22  
23  import java.lang.reflect.Constructor;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.Map;
27  
28  import org.apache.directory.api.i18n.I18n;
29  import org.apache.directory.api.ldap.codec.StockCodecFactoryUtil;
30  import org.apache.directory.api.ldap.codec.api.ControlFactory;
31  import org.apache.directory.api.ldap.codec.api.ExtendedOperationFactory;
32  import org.apache.directory.api.ldap.codec.api.IntermediateOperationFactory;
33  import org.apache.directory.api.ldap.codec.api.LdapApiService;
34  import org.apache.directory.api.ldap.codec.osgi.DefaultLdapCodecService;
35  import org.apache.directory.api.ldap.extras.ExtrasCodecFactoryUtil;
36  import org.apache.directory.api.ldap.model.message.Control;
37  import org.apache.directory.api.util.Strings;
38  import org.apache.mina.filter.codec.ProtocolCodecFactory;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  
43  /**
44   * The default {@link org.apache.directory.api.ldap.codec.api.LdapApiService} implementation.
45   * It loads the Controls, ExtendedOperations and IntermediateResponses as defined in the following system parameters :
46   * <ul>
47   *   <li>Controls :
48   *     <ul>
49   *       <li>apacheds.request.controls</li>
50   *       <li>apacheds.response.controls</li>
51   *       <li>default.controls</li>
52   *     </ul>
53   *   </li>
54   *   <li>ExtendedOperations :
55   *     <ul>
56   *       <li>apacheds.extendedOperations</li>
57   *       <li>extra.extendedOperations</li>
58   *     </ul>
59   *   </li>
60   *   <li>IntermediateResponses :
61   *     <ul>
62   *       <li>apacheds.intermediateResponses</li>
63   *     </ul>
64   *   </li>
65   * </ul>
66   *
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   * @version $Rev$, $Date$
69   */
70  public class StandaloneLdapApiService extends DefaultLdapCodecService
71  {
72      /** A logger */
73      private static final Logger LOG = LoggerFactory.getLogger( StandaloneLdapApiService.class );
74  
75      /** The list of request controls to load at startup */
76      public static final String REQUEST_CONTROLS_LIST = "apacheds.request.controls";
77  
78      /** The list of response controls to load at startup */
79      public static final String RESPONSE_CONTROLS_LIST = "apacheds.response.controls";
80  
81      /** The list of extended operations to load at startup */
82      public static final String EXTENDED_OPERATIONS_LIST = "apacheds.extendedOperations";
83  
84      /** The list of Intermediate responses to load at startup */
85      public static final String INTERMEDIATE_RESPONSES_LIST = "apacheds.intermediateResponses";
86  
87      /** The (old) list of default controls to load at startup */
88      private static final String OLD_DEFAULT_CONTROLS_LIST = "default.controls";
89  
90      /** The (old) list of extra extended operations to load at startup */
91      private static final String OLD_EXTRA_EXTENDED_OPERATION_LIST = "extra.extendedOperations";
92      
93      /** The control's type */
94      public enum ControlType
95      {
96          REQUEST( REQUEST_CONTROLS_LIST ),
97          RESPONSE( RESPONSE_CONTROLS_LIST );
98          
99          private String property;
100         
101         ControlType( String property )
102         {
103             this.property = property;
104         }
105         
106         private String getProperty()
107         {
108             return property;
109         }
110     }
111 
112 
113     /**
114      * Creates a new instance of StandaloneLdapCodecService.
115      * <br><br>
116      * The following pom configuration is intended for use by unit test running
117      * tools like Maven's surefire:
118      * <pre>
119      *   &lt;properties&gt;
120      *     &lt;codec.plugin.directory&gt;${project.build.directory}/pluginDirectory&lt;/codec.plugin.directory&gt;
121      *   &lt;/properties&gt;
122      *
123      *   &lt;build&gt;
124      *     &lt;plugins&gt;
125      *       &lt;plugin&gt;
126      *         &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
127      *         &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
128      *         &lt;configuration&gt;
129      *           &lt;systemPropertyVariables&gt;
130      *             &lt;workingDirectory&gt;${basedir}/target&lt;/workingDirectory&gt;
131      *             &lt;felix.cache.rootdir&gt;
132      *               ${project.build.directory}
133      *             &lt;/felix.cache.rootdir&gt;
134      *             &lt;felix.cache.locking&gt;
135      *               true
136      *             &lt;/felix.cache.locking&gt;
137      *             &lt;org.osgi.framework.storage.clean&gt;
138      *               onFirstInit
139      *             &lt;/org.osgi.framework.storage.clean&gt;
140      *             &lt;org.osgi.framework.storage&gt;
141      *               osgi-cache
142      *             &lt;/org.osgi.framework.storage&gt;
143      *             &lt;codec.plugin.directory&gt;
144      *               ${codec.plugin.directory}
145      *             &lt;/codec.plugin.directory&gt;
146      *           &lt;/systemPropertyVariables&gt;
147      *         &lt;/configuration&gt;
148      *       &lt;/plugin&gt;
149      *
150      *       &lt;plugin&gt;
151      *         &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
152      *         &lt;artifactId&gt;maven-dependency-plugin&lt;/artifactId&gt;
153      *         &lt;executions&gt;
154      *           &lt;execution&gt;
155      *             &lt;id&gt;copy&lt;/id&gt;
156      *             &lt;phase&gt;compile&lt;/phase&gt;
157      *             &lt;goals&gt;
158      *               &lt;goal&gt;copy&lt;/goal&gt;
159      *             &lt;/goals&gt;
160      *             &lt;configuration&gt;
161      *               &lt;artifactItems&gt;
162      *                 &lt;artifactItem&gt;
163      *                   &lt;groupId&gt;${project.groupId}&lt;/groupId&gt;
164      *                   &lt;artifactId&gt;api-ldap-extras-codec&lt;/artifactId&gt;
165      *                   &lt;version&gt;${project.version}&lt;/version&gt;
166      *                   &lt;outputDirectory&gt;${codec.plugin.directory}&lt;/outputDirectory&gt;
167      *                 &lt;/artifactItem&gt;
168      *               &lt;/artifactItems&gt;
169      *             &lt;/configuration&gt;
170      *           &lt;/execution&gt;
171      *         &lt;/executions&gt;
172      *       &lt;/plugin&gt;
173      *     &lt;/plugins&gt;
174      *   &lt;/build&gt;
175      * </pre>
176      *
177      * @throws Exception If we had an issue initializing the LDAP service
178      */
179     public StandaloneLdapApiService() throws Exception
180     {
181         this( getControlsFromSystemProperties( ControlType.REQUEST ), 
182             getControlsFromSystemProperties( ControlType.RESPONSE ), 
183             getExtendedOperationsFromSystemProperties(), 
184             getIntermediateResponsesFromSystemProperties() );
185     }
186 
187 
188     /**
189      * Creates a new instance of StandaloneLdapApiService.
190      *
191      * @param requestControls The list of request controls to store
192      * @param responseControls The list of response controls to store
193      * @param extendedOperations The list of extended operations to store
194      * @param intermediateResponses The list of intermediate responsess to store
195      * @throws Exception If we had an issue with one of the two lists
196      */
197     public StandaloneLdapApiService( List<String> requestControls, 
198         List<String> responseControls, List<String> extendedOperations,
199         List<String> intermediateResponses ) throws Exception
200     {
201         StockCodecFactoryUtil.loadStockControls( this );
202         ExtrasCodecFactoryUtil.loadExtrasControls( this );
203         ExtrasCodecFactoryUtil.loadExtrasExtendedOperations( this );
204         ExtrasCodecFactoryUtil.loadExtrasIntermediateResponses( this );
205 
206         // Load the controls
207         loadControls( requestControls, getRequestControlFactories() );
208         loadControls( responseControls, getResponseControlFactories() );
209 
210         // Load the extended operations
211         loadExtendedOperations( extendedOperations );
212 
213         // Load the extended operations
214         loadIntermediateResponse( intermediateResponses );
215 
216         if ( getProtocolCodecFactory() == null )
217         {
218             try
219             {
220                 @SuppressWarnings("unchecked")
221                 Class<? extends ProtocolCodecFactory> clazz = ( Class<? extends ProtocolCodecFactory> )
222                     Class.forName( DEFAULT_PROTOCOL_CODEC_FACTORY );
223                 Constructor<? extends ProtocolCodecFactory> constructor =
224                     clazz.getConstructor( LdapApiService.class );
225 
226                 if ( constructor != null )
227                 {
228                     setProtocolCodecFactory( constructor.newInstance( this ) );
229                 }
230                 else
231                 {
232                     setProtocolCodecFactory( clazz.newInstance() );
233                 }
234             }
235             catch ( Exception cause )
236             {
237                 throw new RuntimeException( I18n.err( I18n.ERR_06000_FAILED_TO_LOAD_DEFAULT_CODEC_FACTORY ), cause );
238             }
239         }
240     }
241 
242 
243     /**
244      * Parses the system properties to obtain the controls list.
245      *
246      * @param type The control's type
247      * @return A list of controls
248      */
249     private static List<String> getControlsFromSystemProperties( ControlType type )
250     {
251         List<String> controlsList = new ArrayList<>();
252 
253         if ( type == ControlType.REQUEST )
254         {            
255             // Loading request controls list from command line properties if it exists
256             String controlsString = System.getProperty( type.getProperty() );
257     
258             if ( !Strings.isEmpty( controlsString ) )
259             {
260                 for ( String control : controlsString.split( "," ) )
261                 {
262                     controlsList.add( control );
263                 }
264             }
265             else
266             {
267                 // Loading old default controls list from command line properties if it exists
268                 String oldDefaultControlsString = System.getProperty( OLD_DEFAULT_CONTROLS_LIST );
269     
270                 if ( !Strings.isEmpty( oldDefaultControlsString ) )
271                 {
272                     for ( String control : oldDefaultControlsString.split( "," ) )
273                     {
274                         controlsList.add( control );
275                     }
276                 }
277             }
278         }
279 
280         return controlsList;
281     }
282 
283 
284     /**
285      * Parses the system properties to obtain the extended operations.
286      * Such extended operations are stored in the <b>apacheds.extendedOperations</b>
287      * and <b>default.extendedOperation.requests</b> system properties.
288      *
289      * @return a list of extended operation
290      */
291     private static List<String> getExtendedOperationsFromSystemProperties()
292     {
293         List<String> extendedOperationsList = new ArrayList<>();
294 
295         // Loading extended operations from command line properties if it exists
296         String defaultExtendedOperationsList = System.getProperty( EXTENDED_OPERATIONS_LIST );
297 
298         if ( !Strings.isEmpty( defaultExtendedOperationsList ) )
299         {
300             for ( String extendedOperation : defaultExtendedOperationsList.split( "," ) )
301             {
302                 extendedOperationsList.add( extendedOperation );
303             }
304         }
305         else
306         {
307             // Loading old extra extended operations list from command line properties if it exists
308             String oldDefaultExtendedOperationsString = System.getProperty( OLD_EXTRA_EXTENDED_OPERATION_LIST );
309 
310             if ( !Strings.isEmpty( oldDefaultExtendedOperationsString ) )
311             {
312                 for ( String extendedOperation : oldDefaultExtendedOperationsString.split( "," ) )
313                 {
314                     extendedOperationsList.add( extendedOperation );
315                 }
316             }
317         }
318 
319         return extendedOperationsList;
320     }
321 
322 
323     /**
324      * Parses the system properties to obtain the intermediate responses.
325      * Such intermediate responses are stored in the <b>apacheds.intermediateResponses</b>
326      * and <b>default.intermediateResponses.requests</b> system properties.
327      *
328      * @return a list of intermediate responses
329      */
330     private static List<String> getIntermediateResponsesFromSystemProperties()
331     {
332         List<String> intermediateResponsesList = new ArrayList<>();
333 
334         // Loading extended operations from command line properties if it exists
335         String defaultIntermediateResponsesList = System.getProperty( INTERMEDIATE_RESPONSES_LIST );
336 
337         if ( !Strings.isEmpty( defaultIntermediateResponsesList ) )
338         {
339             for ( String intermediateResponse : defaultIntermediateResponsesList.split( "," ) )
340             {
341                 intermediateResponsesList.add( intermediateResponse );
342             }
343         }
344 
345         return intermediateResponsesList;
346     }
347 
348 
349     /**
350      * Loads a list of controls from their FQCN.
351      *
352      * @param controlsList The list of controls to load
353      * @param controlFactories The set of control factories already loaded
354      * @throws Exception if a control could not be loaded
355      */
356     private void loadControls( List<String> controlsList, Map<String, ControlFactory<? extends Control>> controlFactories )
357         throws Exception
358     {
359         // Adding all controls
360         if ( !controlsList.isEmpty() )
361         {
362             for ( String controlFQCN : controlsList )
363             {
364                 loadControl( controlFQCN, controlFactories );
365             }
366         }
367     }
368 
369 
370     /**
371      * Loads a control from its FQCN.
372      *
373      * @param controlFQCN The control FQCN
374      * @param controlFactories The set of control factories already loaded
375      * @throws Exception If the control could not be loaded
376      */
377     private void loadControl( String controlFQCN, Map<String, ControlFactory<? extends Control>> controlFactories )
378         throws Exception
379     {
380         if ( controlFactories.containsKey( controlFQCN ) )
381         {
382             if ( LOG.isDebugEnabled() )
383             {
384                 LOG.debug( I18n.msg( I18n.MSG_06003_CONTROL_FACTORY_ALREADY_LOADED, controlFQCN ) );
385             }
386 
387             return;
388         }
389 
390         Class<?>[] types = new Class<?>[]
391             { LdapApiService.class };
392         // note, trimming whitespace doesnt hurt as it is a class name and
393         // helps DI containers that use xml config as xml ignores whitespace
394         @SuppressWarnings("unchecked")
395         Class<? extends ControlFactory<?>> clazz = ( Class<? extends ControlFactory<?>> ) Class
396             .forName( controlFQCN.trim() );
397         Constructor<?> constructor = clazz.getConstructor( types );
398 
399         ControlFactory<?> factory = ( ControlFactory<?> ) constructor.newInstance( this );
400         controlFactories.put( factory.getOid(), factory );
401 
402         if ( LOG.isInfoEnabled() )
403         {
404             LOG.info( I18n.msg( I18n.MSG_06004_REGISTERED_CONTROL_FACTORY, factory.getOid() ) );
405         }
406     }
407 
408 
409     /**
410      * Loads a list of extended operation from their FQCN
411      *
412      * @param extendedOperationsList The list of extended operations to load
413      * @throws Exception If an extended operations cannot be loaded
414      */
415     private void loadExtendedOperations( List<String> extendedOperationsList ) throws Exception
416     {
417         // Adding all extended operations
418         if ( !extendedOperationsList.isEmpty() )
419         {
420             for ( String extendedOperationFQCN : extendedOperationsList )
421             {
422                 loadExtendedRequest( extendedOperationFQCN );
423             }
424         }
425     }
426 
427 
428     /**
429      * Loads an extended request from its FQCN
430      *
431      * @param extendedRequestFQCN The extended operations to load
432      * @throws Exception If the extended operations cannot be loaded
433      */
434     private void loadExtendedRequest( String extendedRequestFQCN ) throws Exception
435     {
436         if ( getExtendedRequestFactories().containsKey( extendedRequestFQCN ) )
437         {
438             if ( LOG.isDebugEnabled() )
439             {
440                 LOG.debug( I18n.msg( I18n.MSG_06005_EXTENDED_OP_FACTORY_ALREADY_LOADED, extendedRequestFQCN ) );
441             }
442 
443             return;
444         }
445 
446         Class<?>[] types = new Class<?>[]
447             { LdapApiService.class };
448 
449         // note, trimming whitespace doesn't hurt as it is a class name and
450         // helps DI containers that use xml config as xml ignores whitespace
451         @SuppressWarnings("unchecked")
452         Class<? extends ExtendedOperationFactory> clazz = ( Class<? extends ExtendedOperationFactory> ) Class
453             .forName( extendedRequestFQCN.trim() );
454         Constructor<?> constructor = clazz.getConstructor( types );
455 
456         ExtendedOperationFactory factory = ( ExtendedOperationFactory ) constructor
457             .newInstance( this );
458         getExtendedRequestFactories().put( factory.getOid(), factory );
459 
460         if ( LOG.isInfoEnabled() )
461         {
462             LOG.info( I18n.msg( I18n.MSG_06001_REGISTERED_EXTENDED_OP_FACTORY, factory.getOid() ) );
463         }
464     }
465 
466 
467     /**
468      * Loads a list of intermediate responses from their FQCN
469      *
470      * @param intermediateResponsesList The list of intermediate response to load
471      * @throws Exception If one of the intermediate response cannot be loaded
472      */
473     private void loadIntermediateResponse( List<String> intermediateResponsesList ) throws Exception
474     {
475         // Adding all extended operations
476         if ( !intermediateResponsesList.isEmpty() )
477         {
478             for ( String intermediateResponseFQCN : intermediateResponsesList )
479             {
480                 loadIntermediateResponse( intermediateResponseFQCN );
481             }
482         }
483     }
484 
485 
486     /**
487      * Loads an intermediate responses from its FQCN
488      *
489      * @param intermediateResponseFQCN The intermediate response to load
490      * @throws Exception If the intermediate response cannot be loaded
491      */
492     private void loadIntermediateResponse( String intermediateResponseFQCN ) throws Exception
493     {
494         if ( getIntermediateResponseFactories().containsKey( intermediateResponseFQCN ) )
495         {
496             if ( LOG.isDebugEnabled() )
497             {
498                 LOG.debug( I18n.msg( I18n.MSG_06006_INTERMEDIATE_FACTORY_ALREADY_LOADED, intermediateResponseFQCN ) );
499             }
500 
501             return;
502         }
503 
504         Class<?>[] types = new Class<?>[]
505             {};
506 
507         // note, trimming whitespace doesn't hurt as it is a class name and
508         // helps DI containers that use xml config as xml ignores whitespace
509         @SuppressWarnings("unchecked")
510         Class<? extends IntermediateOperationFactory> clazz = ( Class<? extends IntermediateOperationFactory> ) Class
511             .forName( intermediateResponseFQCN.trim() );
512         Constructor<?> constructor = clazz.getConstructor( types );
513 
514         IntermediateOperationFactory factory = ( IntermediateOperationFactory ) constructor
515             .newInstance();
516         getIntermediateResponseFactories().put( factory.getOid(), factory );
517 
518         if ( LOG.isInfoEnabled() )
519         {
520             LOG.info( I18n.msg( I18n.MSG_06007_REGISTRED_INTERMEDIATE_RESP_FACTORY, factory.getOid() ) );
521         }
522     }
523 }