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 *     http://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.api.ldap.codec.standalone;
021
022
023import java.lang.reflect.Constructor;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.directory.api.i18n.I18n;
029import org.apache.directory.api.ldap.codec.StockCodecFactoryUtil;
030import org.apache.directory.api.ldap.codec.api.ControlFactory;
031import org.apache.directory.api.ldap.codec.api.ExtendedOperationFactory;
032import org.apache.directory.api.ldap.codec.api.IntermediateOperationFactory;
033import org.apache.directory.api.ldap.codec.api.LdapApiService;
034import org.apache.directory.api.ldap.codec.osgi.DefaultLdapCodecService;
035import org.apache.directory.api.ldap.extras.ExtrasCodecFactoryUtil;
036import org.apache.directory.api.ldap.model.message.Control;
037import org.apache.directory.api.util.Strings;
038import org.apache.mina.filter.codec.ProtocolCodecFactory;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * The default {@link org.apache.directory.api.ldap.codec.api.LdapApiService} implementation.
045 * It loads the Controls, ExtendedOperations and IntermediateResponses as defined in the following system parameters :
046 * <ul>
047 *   <li>Controls :
048 *     <ul>
049 *       <li>apacheds.request.controls</li>
050 *       <li>apacheds.response.controls</li>
051 *       <li>default.controls</li>
052 *     </ul>
053 *   </li>
054 *   <li>ExtendedOperations :
055 *     <ul>
056 *       <li>apacheds.extendedOperations</li>
057 *       <li>extra.extendedOperations</li>
058 *     </ul>
059 *   </li>
060 *   <li>IntermediateResponses :
061 *     <ul>
062 *       <li>apacheds.intermediateResponses</li>
063 *     </ul>
064 *   </li>
065 * </ul>
066 *
067 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
068 * @version $Rev$, $Date$
069 */
070public class StandaloneLdapApiService extends DefaultLdapCodecService
071{
072    /** A logger */
073    private static final Logger LOG = LoggerFactory.getLogger( StandaloneLdapApiService.class );
074
075    /** The list of request controls to load at startup */
076    public static final String REQUEST_CONTROLS_LIST = "apacheds.request.controls";
077
078    /** The list of response controls to load at startup */
079    public static final String RESPONSE_CONTROLS_LIST = "apacheds.response.controls";
080
081    /** The list of extended operations to load at startup */
082    public static final String EXTENDED_OPERATIONS_LIST = "apacheds.extendedOperations";
083
084    /** The list of Intermediate responses to load at startup */
085    public static final String INTERMEDIATE_RESPONSES_LIST = "apacheds.intermediateResponses";
086
087    /** The (old) list of default controls to load at startup */
088    private static final String OLD_DEFAULT_CONTROLS_LIST = "default.controls";
089
090    /** The (old) list of extra extended operations to load at startup */
091    private static final String OLD_EXTRA_EXTENDED_OPERATION_LIST = "extra.extendedOperations";
092    
093    /** The control's type */
094    public enum ControlType
095    {
096        REQUEST( REQUEST_CONTROLS_LIST ),
097        RESPONSE( RESPONSE_CONTROLS_LIST );
098        
099        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}