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;
026
027import org.apache.directory.api.ldap.codec.api.ControlFactory;
028import org.apache.directory.api.ldap.codec.api.ExtendedOperationFactory;
029import org.apache.directory.api.ldap.codec.api.LdapApiService;
030import org.apache.directory.api.ldap.codec.osgi.DefaultLdapCodecService;
031import org.apache.directory.api.util.Strings;
032import org.apache.mina.filter.codec.ProtocolCodecFactory;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036
037/**
038 * The default {@link org.apache.directory.api.ldap.codec.api.LdapApiService} implementation.
039 * It loads the Controls and ExtendedOperations as defined in the following system parameters :
040 * <ul>
041 *   <li>Controls :
042 *     <ul>
043 *       <li>apacheds.controls</li>
044 *       <li>default.controls</li>
045 *     </ul>
046 *   </li>
047 *   <li>ExtendedOperations :
048 *     <ul>
049 *       <li>apacheds.extendedOperations</li>
050 *       <li>default.extendedOperation.responses</li>
051 *       <li>extra.extendedOperations</li>
052 *     </ul>
053 *   </li>
054 * </ul>
055 *
056 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
057 * @version $Rev$, $Date$
058 */
059public class StandaloneLdapApiService extends DefaultLdapCodecService
060{
061    /** A logger */
062    private static final Logger LOG = LoggerFactory.getLogger( StandaloneLdapApiService.class );
063
064    /** The list of controls to load at startup */
065    public static final String CONTROLS_LIST = "apacheds.controls";
066
067    /** The list of extended operations to load at startup */
068    public static final String EXTENDED_OPERATIONS_LIST = "apacheds.extendedOperations";
069
070    /** The (old) list of default controls to load at startup */
071    private static final String OLD_DEFAULT_CONTROLS_LIST = "default.controls";
072
073    /** The (old) list of extra extended operations to load at startup */
074    private static final String OLD_EXTRA_EXTENDED_OPERATION_LIST = "extra.extendedOperations";
075
076
077    /**
078     * Creates a new instance of StandaloneLdapCodecService.
079     * <br><br>
080     * The following pom configuration is intended for use by unit test running 
081     * tools like Maven's surefire:
082     * <pre>
083     *   &lt;properties&gt;
084     *     &lt;codec.plugin.directory&gt;${project.build.directory}/pluginDirectory&lt;/codec.plugin.directory&gt;
085     *   &lt;/properties&gt;
086     * 
087     *   &lt;build&gt;
088     *     &lt;plugins&gt;
089     *       &lt;plugin&gt;
090     *         &lt;artifactId&gt;maven-surefire-plugin&lt;/artifactId&gt;
091     *         &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
092     *         &lt;configuration&gt;
093     *           &lt;systemPropertyVariables&gt;
094     *             &lt;workingDirectory&gt;${basedir}/target&lt;/workingDirectory&gt;
095     *             &lt;felix.cache.rootdir&gt;
096     *               ${project.build.directory}
097     *             &lt;/felix.cache.rootdir&gt;
098     *             &lt;felix.cache.locking&gt;
099     *               true
100     *             &lt;/felix.cache.locking&gt;
101     *             &lt;org.osgi.framework.storage.clean&gt;
102     *               onFirstInit
103     *             &lt;/org.osgi.framework.storage.clean&gt;
104     *             &lt;org.osgi.framework.storage&gt;
105     *               osgi-cache
106     *             &lt;/org.osgi.framework.storage&gt;
107     *             &lt;codec.plugin.directory&gt;
108     *               ${codec.plugin.directory}
109     *             &lt;/codec.plugin.directory&gt;
110     *           &lt;/systemPropertyVariables&gt;
111     *         &lt;/configuration&gt;
112     *       &lt;/plugin&gt;
113     *       
114     *       &lt;plugin&gt;
115     *         &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
116     *         &lt;artifactId&gt;maven-dependency-plugin&lt;/artifactId&gt;
117     *         &lt;executions&gt;
118     *           &lt;execution&gt;
119     *             &lt;id&gt;copy&lt;/id&gt;
120     *             &lt;phase&gt;compile&lt;/phase&gt;
121     *             &lt;goals&gt;
122     *               &lt;goal&gt;copy&lt;/goal&gt;
123     *             &lt;/goals&gt;
124     *             &lt;configuration&gt;
125     *               &lt;artifactItems&gt;
126     *                 &lt;artifactItem&gt;
127     *                   &lt;groupId&gt;${project.groupId}&lt;/groupId&gt;
128     *                   &lt;artifactId&gt;api-ldap-extras-codec&lt;/artifactId&gt;
129     *                   &lt;version&gt;${project.version}&lt;/version&gt;
130     *                   &lt;outputDirectory&gt;${codec.plugin.directory}&lt;/outputDirectory&gt;
131     *                 &lt;/artifactItem&gt;
132     *               &lt;/artifactItems&gt;
133     *             &lt;/configuration&gt;
134     *           &lt;/execution&gt;
135     *         &lt;/executions&gt;
136     *       &lt;/plugin&gt;
137     *     &lt;/plugins&gt;
138     *   &lt;/build&gt;
139     * </pre>
140     * 
141     * @throws Exception If we had an issue initializing the LDAP service
142     */
143    public StandaloneLdapApiService() throws Exception
144    {
145        this( getControlsFromSystemProperties(), getExtendedOperationsFromSystemProperties() );
146    }
147
148
149    /**
150     * Creates a new instance of StandaloneLdapApiService.
151     *
152     * @param controls The list of controls to store
153     * @param extendedOperations The list of extended operations to store
154     * @throws Exception If we had an issue with one of the two lists
155     */
156    public StandaloneLdapApiService( List<String> controls, List<String> extendedOperations ) throws Exception
157    {
158        CodecFactoryUtil.loadStockControls( getControlFactories(), this );
159
160        CodecFactoryUtil.loadStockExtendedOperations( getExtendedOperationsFactories(), this );
161
162        // Load the controls
163        loadControls( controls );
164
165        // Load the extended operations
166        loadExtendedOperations( extendedOperations );
167
168        if ( getProtocolCodecFactory() == null )
169        {
170            try
171            {
172                @SuppressWarnings("unchecked")
173                Class<? extends ProtocolCodecFactory> clazz = ( Class<? extends ProtocolCodecFactory> )
174                    Class.forName( DEFAULT_PROTOCOL_CODEC_FACTORY );
175                Constructor<? extends ProtocolCodecFactory> constructor =
176                    clazz.getConstructor( LdapApiService.class );
177                if ( constructor != null )
178                {
179                    setProtocolCodecFactory( constructor.newInstance( this ) );
180                }
181                else
182                {
183                    setProtocolCodecFactory( clazz.newInstance() );
184                }
185            }
186            catch ( Exception cause )
187            {
188                throw new RuntimeException( "Failed to load default codec factory.", cause );
189            }
190        }
191    }
192
193
194    /**
195     * Parses the system properties to obtain the controls list.
196     * 
197     * @throws Exception
198     */
199    private static List<String> getControlsFromSystemProperties() throws Exception
200    {
201        List<String> controlsList = new ArrayList<>();
202
203        // Loading controls list from command line properties if it exists
204        String controlsString = System.getProperty( CONTROLS_LIST );
205
206        if ( !Strings.isEmpty( controlsString ) )
207        {
208            for ( String control : controlsString.split( "," ) )
209            {
210                controlsList.add( control );
211            }
212        }
213        else
214        {
215            // Loading old default controls list from command line properties if it exists
216            String oldDefaultControlsString = System.getProperty( OLD_DEFAULT_CONTROLS_LIST );
217
218            if ( !Strings.isEmpty( oldDefaultControlsString ) )
219            {
220                for ( String control : oldDefaultControlsString.split( "," ) )
221                {
222                    controlsList.add( control );
223                }
224            }
225        }
226
227        return controlsList;
228    }
229
230
231    /**
232     * Parses the system properties to obtain the extended operations.
233     * Such extended operations are stored in the <b>apacheds.extendedOperations</b>
234     * and <b>default.extendedOperation.requests</b> system properties.
235     */
236    private static List<String> getExtendedOperationsFromSystemProperties() throws Exception
237    {
238        List<String> extendedOperationsList = new ArrayList<>();
239
240        // Loading extended operations from command line properties if it exists
241        String defaultExtendedOperationsList = System.getProperty( EXTENDED_OPERATIONS_LIST );
242
243        if ( !Strings.isEmpty( defaultExtendedOperationsList ) )
244        {
245            for ( String extendedOperation : defaultExtendedOperationsList.split( "," ) )
246            {
247                extendedOperationsList.add( extendedOperation );
248            }
249        }
250        else
251        {
252            // Loading old extra extended operations list from command line properties if it exists
253            String oldDefaultControlsString = System.getProperty( OLD_EXTRA_EXTENDED_OPERATION_LIST );
254
255            if ( !Strings.isEmpty( oldDefaultControlsString ) )
256            {
257                for ( String extendedOperation : oldDefaultControlsString.split( "," ) )
258                {
259                    extendedOperationsList.add( extendedOperation );
260                }
261            }
262        }
263
264        return extendedOperationsList;
265    }
266
267
268    /**
269     * Loads a list of controls from their FQCN.
270     */
271    private void loadControls( List<String> controlsList ) throws Exception
272    {
273        // Adding all controls
274        if ( !controlsList.isEmpty() )
275        {
276            for ( String controlFQCN : controlsList )
277            {
278                loadControl( controlFQCN );
279            }
280        }
281    }
282
283
284    /**
285     * Loads a control from its FQCN.
286     */
287    private void loadControl( String controlFQCN ) throws Exception
288    {
289        if ( getControlFactories().containsKey( controlFQCN ) )
290        {
291            LOG.debug( "Factory for control {} was already loaded", controlFQCN );
292            return;
293        }
294
295        Class<?>[] types = new Class<?>[]
296            { LdapApiService.class };
297        // note, trimming whitespace doesnt hurt as it is a class name and
298        // helps DI containers that use xml config as xml ignores whitespace
299        @SuppressWarnings("unchecked")
300        Class<? extends ControlFactory<?>> clazz = ( Class<? extends ControlFactory<?>> ) Class
301            .forName( controlFQCN.trim() );
302        Constructor<?> constructor = clazz.getConstructor( types );
303
304        ControlFactory<?> factory = ( ControlFactory<?> ) constructor.newInstance( new Object[]
305            { this } );
306        getControlFactories().put( factory.getOid(), factory );
307
308        LOG.info( "Registered control factory: {}", factory.getOid() );
309    }
310
311
312    /**
313     * Loads a list of extended operation from their FQCN
314     */
315    private void loadExtendedOperations( List<String> extendedOperationsList ) throws Exception
316    {
317        // Adding all extended operations
318        if ( !extendedOperationsList.isEmpty() )
319        {
320            for ( String extendedOperationFQCN : extendedOperationsList )
321            {
322                loadExtendedOperation( extendedOperationFQCN );
323            }
324        }
325    }
326
327
328    /**
329     * Loads an of extended operations from its FQCN
330     */
331    private void loadExtendedOperation( String extendedOperationFQCN ) throws Exception
332    {
333        if ( getExtendedOperationsFactories().containsKey( extendedOperationFQCN ) )
334        {
335            LOG.debug( "Factory for extended operation {} was already loaded", extendedOperationFQCN );
336            return;
337        }
338
339        Class<?>[] types = new Class<?>[]
340            { LdapApiService.class };
341
342        // note, trimming whitespace doesn't hurt as it is a class name and
343        // helps DI containers that use xml config as xml ignores whitespace
344        @SuppressWarnings("unchecked")
345        Class<? extends ExtendedOperationFactory> clazz = ( Class<? extends ExtendedOperationFactory> ) Class
346            .forName( extendedOperationFQCN.trim() );
347        Constructor<?> constructor = clazz.getConstructor( types );
348
349        ExtendedOperationFactory factory = ( ExtendedOperationFactory ) constructor
350            .newInstance( new Object[]
351                { this } );
352        getExtendedOperationsFactories().put( factory.getOid(), factory );
353
354        LOG.info( "Registered pre-bundled extended operation factory: {}", factory.getOid() );
355    }
356}