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 */
019package org.apache.directory.server.core.integ;
020
021
022import java.lang.reflect.Constructor;
023import java.lang.reflect.Method;
024
025import org.apache.commons.pool2.PooledObjectFactory;
026import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
027import org.apache.directory.api.ldap.codec.api.DefaultConfigurableBinaryAttributeDetector;
028import org.apache.directory.api.util.Network;
029import org.apache.directory.ldap.client.api.LdapConnection;
030import org.apache.directory.ldap.client.api.LdapConnectionConfig;
031import org.apache.directory.ldap.client.api.LdapConnectionFactory;
032import org.apache.directory.ldap.client.api.LdapConnectionPool;
033import org.apache.directory.ldap.client.api.LdapConnectionValidator;
034import org.apache.directory.ldap.client.template.LdapConnectionTemplate;
035import org.apache.directory.server.annotations.CreateLdapConnectionPool;
036import org.apache.directory.server.ldap.LdapServer;
037import org.junit.rules.TestRule;
038import org.junit.runner.Description;
039import org.junit.runners.model.Statement;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043
044/**
045 * A {@link TestRule} for creating connection pools.
046 *
047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048 */
049public class CreateLdapConnectionPoolRule extends CreateLdapServerRule
050{
051    private static final Logger LOG = LoggerFactory.getLogger( CreateLdapConnectionPoolRule.class );
052    private CreateLdapConnectionPoolRule classCreateLdapConnectionPoolRule;
053    private CreateLdapConnectionPool createLdapConnectionPool;
054    private LdapConnectionPool ldapConnectionPool;
055    private LdapConnectionFactory ldapConnectionFactory;
056    private LdapConnectionTemplate ldapConnectionTemplate;
057    private PooledObjectFactory<LdapConnection> poolableLdapConnectionFactory;
058
059
060    public CreateLdapConnectionPoolRule()
061    {
062        this( null );
063    }
064
065
066    public CreateLdapConnectionPoolRule(
067        CreateLdapConnectionPoolRule classCreateLdapConnectionPoolRule )
068    {
069        super( classCreateLdapConnectionPoolRule );
070        this.classCreateLdapConnectionPoolRule = classCreateLdapConnectionPoolRule;
071    }
072
073
074    @Override
075    public Statement apply( final Statement base, final Description description )
076    {
077        return super.apply( buildStatement( base, description ), description );
078    }
079
080
081    private Statement buildStatement( final Statement base, final Description description )
082    {
083        createLdapConnectionPool = description.getAnnotation( CreateLdapConnectionPool.class );
084        if ( createLdapConnectionPool == null )
085        {
086            return new Statement()
087            {
088                @Override
089                public void evaluate() throws Throwable
090                {
091                    LdapServer ldapServer = getLdapServer();
092                    if ( classCreateLdapConnectionPoolRule != null
093                        && classCreateLdapConnectionPoolRule.getLdapServer() != ldapServer )
094                    {
095                        LOG.trace( "Creating connection pool to new ldap server" );
096
097                        LdapConnectionPool oldLdapConnectionPool = ldapConnectionPool;
098                        LdapConnectionTemplate oldLdapConnectionTemplate = ldapConnectionTemplate;
099
100                        Class<? extends PooledObjectFactory<LdapConnection>> factoryClass =
101                                classCreateLdapConnectionPoolRule.createLdapConnectionPool.factoryClass();
102                        Class<? extends LdapConnectionFactory> connectionFactoryClass =
103                                classCreateLdapConnectionPoolRule.createLdapConnectionPool.connectionFactoryClass();
104                        Class<? extends LdapConnectionValidator> validatorClass =
105                                classCreateLdapConnectionPoolRule.createLdapConnectionPool.validatorClass();
106                        ldapConnectionPool = classCreateLdapConnectionPoolRule
107                                .createLdapConnectionPool( ldapServer, factoryClass, 
108                                    connectionFactoryClass, validatorClass );
109                        ldapConnectionTemplate = new LdapConnectionTemplate( ldapConnectionPool );
110
111                        try
112                        {
113                            base.evaluate();
114                        }
115                        finally
116                        {
117                            LOG.trace( "Reverting to old connection pool" );
118                            ldapConnectionPool = oldLdapConnectionPool;
119                            ldapConnectionTemplate = oldLdapConnectionTemplate;
120                        }
121                    }
122                    else
123                    {
124                        LOG.trace( "no @CreateLdapConnectionPool on: {}", description );
125                        base.evaluate();
126                    }
127                }
128            };
129        }
130        else
131        {
132            return new Statement()
133            {
134                @Override
135                public void evaluate() throws Throwable
136                {
137                    LOG.trace( "Creating ldap connection pool" );
138                    Class<? extends PooledObjectFactory<LdapConnection>> factoryClass =
139                            createLdapConnectionPool.factoryClass();
140                    Class<? extends LdapConnectionFactory> connectionFactoryClass =
141                            createLdapConnectionPool.connectionFactoryClass();
142                    Class<? extends LdapConnectionValidator> validatorClass =
143                            createLdapConnectionPool.validatorClass();
144                    ldapConnectionPool = createLdapConnectionPool( getLdapServer(), factoryClass, 
145                            connectionFactoryClass, validatorClass );
146                    ldapConnectionTemplate = new LdapConnectionTemplate( ldapConnectionPool );
147
148                    try
149                    {
150                        base.evaluate();
151                    }
152                    finally
153                    {
154                        LOG.trace( "Closing ldap connection pool" );
155                        ldapConnectionPool.close();
156                        ldapConnectionTemplate = null;
157                    }
158                }
159            };
160        }
161    }
162
163
164    private LdapConnectionPool createLdapConnectionPool( LdapServer ldapServer, 
165            Class<? extends PooledObjectFactory<LdapConnection>> factoryClass,
166            Class<? extends LdapConnectionFactory> connectionFactoryClass,
167            Class<? extends LdapConnectionValidator> validatorClass )
168    {
169        LdapConnectionConfig config = new LdapConnectionConfig();
170        
171        config.setLdapHost( Network.LOOPBACK_HOSTNAME );
172        
173        config.setLdapPort( ldapServer.getPort() );
174        config.setName( "uid=admin,ou=system" );
175        config.setCredentials( "secret" );
176
177        if ( ( createLdapConnectionPool.additionalBinaryAttributes() != null )
178            && ( createLdapConnectionPool.additionalBinaryAttributes().length > 0 ) )
179        {
180            DefaultConfigurableBinaryAttributeDetector binaryAttributeDetector =
181                new DefaultConfigurableBinaryAttributeDetector();
182            binaryAttributeDetector.addBinaryAttribute(
183                createLdapConnectionPool.additionalBinaryAttributes() );
184            config.setBinaryAttributeDetector( binaryAttributeDetector );
185        }
186
187        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
188        poolConfig.setLifo( createLdapConnectionPool.lifo() );
189        poolConfig.setMaxTotal( createLdapConnectionPool.maxActive() );
190        poolConfig.setMaxIdle( createLdapConnectionPool.maxIdle() );
191        poolConfig.setMaxWaitMillis( createLdapConnectionPool.maxWait() );
192        poolConfig.setMinEvictableIdleTimeMillis( createLdapConnectionPool
193            .minEvictableIdleTimeMillis() );
194        poolConfig.setMinIdle( createLdapConnectionPool.minIdle() );
195        poolConfig.setNumTestsPerEvictionRun( createLdapConnectionPool
196            .numTestsPerEvictionRun() );
197        poolConfig.setSoftMinEvictableIdleTimeMillis( createLdapConnectionPool
198            .softMinEvictableIdleTimeMillis() );
199        poolConfig.setTestOnBorrow( createLdapConnectionPool.testOnBorrow() );
200        poolConfig.setTestOnReturn( createLdapConnectionPool.testOnReturn() );
201        poolConfig.setTestWhileIdle( createLdapConnectionPool.testWhileIdle() );
202        poolConfig.setTimeBetweenEvictionRunsMillis( createLdapConnectionPool
203            .timeBetweenEvictionRunsMillis() );
204        poolConfig.setBlockWhenExhausted( createLdapConnectionPool
205            .whenExhaustedAction() == 1 );
206        
207        try
208        {
209            Constructor<? extends LdapConnectionFactory> constructor = 
210                    connectionFactoryClass.getConstructor( LdapConnectionConfig.class );
211            ldapConnectionFactory = constructor.newInstance( config );
212        }
213        catch ( Exception e )
214        {
215            throw new IllegalArgumentException( "invalid connectionFactoryClass " 
216                    + connectionFactoryClass.getName() + ": " + e.getMessage(), e );
217        }
218        try
219        {
220            Method timeoutSetter = connectionFactoryClass.getMethod( "setTimeOut", Long.TYPE );
221            if ( timeoutSetter != null )
222            {
223                timeoutSetter.invoke( ldapConnectionFactory, createLdapConnectionPool.timeout() );
224            }
225        }
226        catch ( Exception e )
227        {
228            throw new IllegalArgumentException( "invalid connectionFactoryClass "
229                    + connectionFactoryClass.getName() + ", missing setTimeOut(long): " 
230                    + e.getMessage(), e );
231        }
232        
233        try
234        {
235            Constructor<? extends PooledObjectFactory<LdapConnection>> constructor = 
236                    factoryClass.getConstructor( LdapConnectionFactory.class );
237            poolableLdapConnectionFactory = constructor.newInstance( ldapConnectionFactory );
238        }
239        catch ( Exception e )
240        {
241            throw new IllegalArgumentException( "invalid factoryClass " 
242                    + factoryClass.getName() + ": " + e.getMessage(), e );
243        }
244        try
245        {
246            Method setValidator = factoryClass.getMethod( "setValidator", LdapConnectionValidator.class );
247            if ( setValidator != null )
248            {
249                setValidator.invoke( poolableLdapConnectionFactory,
250                    validatorClass.newInstance() );
251            }
252        }
253        catch ( Exception e )
254        {
255            throw new IllegalArgumentException( "invalid connectionFactoryClass "
256                    + connectionFactoryClass.getName() + ", missing setTimeOut(long): " 
257                    + e.getMessage(), e );
258        }
259
260        return new LdapConnectionPool( poolableLdapConnectionFactory, poolConfig );
261    }
262
263
264    public LdapConnectionFactory getLdapConnectionFactory()
265    {
266        return ldapConnectionFactory == null
267            ? ( classCreateLdapConnectionPoolRule == null
268                ? null
269                : classCreateLdapConnectionPoolRule.getLdapConnectionFactory() )
270            : ldapConnectionFactory;
271    }
272
273
274    public LdapConnectionPool getLdapConnectionPool()
275    {
276        return ldapConnectionPool == null
277            ? ( classCreateLdapConnectionPoolRule == null
278                ? null
279                : classCreateLdapConnectionPoolRule.getLdapConnectionPool() )
280            : ldapConnectionPool;
281    }
282
283
284    public LdapConnectionTemplate getLdapConnectionTemplate()
285    {
286        return ldapConnectionTemplate == null
287            ? ( classCreateLdapConnectionPoolRule == null
288                ? null
289                : classCreateLdapConnectionPoolRule.getLdapConnectionTemplate() )
290            : ldapConnectionTemplate;
291    }
292}