diff --git a/core/build.gradle b/core/build.gradle index 006aa060..82f33df7 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -19,7 +19,8 @@ dependencies { "org.springframework:spring-jdbc:$springVersion", "org.springframework:spring-orm:$springVersion", "com.mysema.querydsl:querydsl-apt:$queryDslVersion", - "commons-pool:commons-pool:$commonsPoolVersion" + "commons-pool:commons-pool:$commonsPoolVersion", + "org.apache.commons:commons-pool2:$commonsPool2Version" provided "com.sun:ldapbp:1.0" diff --git a/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java b/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java index 30679215..b61c3900 100644 --- a/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java +++ b/core/src/main/java/org/springframework/ldap/config/ContextSourceParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2013 the original author or authors. + * Copyright 2005-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.pool.PoolExhaustedAction; import org.springframework.ldap.pool.factory.PoolingContextSource; import org.springframework.ldap.pool.validation.DefaultDirContextValidator; +import org.springframework.ldap.pool2.factory.PoolConfig; +import org.springframework.ldap.pool2.factory.PooledContextSource; import org.springframework.ldap.transaction.compensating.manager.TransactionAwareContextSourceProxy; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -72,6 +74,19 @@ public class ContextSourceParser implements BeanDefinitionParser { private static final String ATT_VALIDATION_QUERY_FILTER = "validation-query-filter"; private static final String ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF = "validation-query-search-controls-ref"; private static final String ATT_NON_TRANSIENT_EXCEPTIONS = "non-transient-exceptions"; + private static final String ATT_MAX_IDLE_PER_KEY = "max-idle-per-key"; + private static final String ATT_MIN_IDLE_PER_KEY = "min-idle-per-key"; + private static final String ATT_MAX_TOTAL_PER_KEY = "max-total-per-key"; + private static final String ATT_EVICTION_POLICY_CLASS = "eviction-policy-class"; + private static final String ATT_FAIRNESS = "fairness"; + private static final String ATT_JMX_ENABLE = "jmx-enable"; + private static final String ATT_JMX_NAME_BASE = "jmx-name-base"; + private static final String ATT_JMX_NAME_PREFIX = "jmx-name-prefix"; + private static final String ATT_LIFO = "lifo"; + private static final String ATT_BLOCK_WHEN_EXHAUSTED = "block-when-exhausted"; + private static final String ATT_TEST_ON_CREATE = "test-on-create"; + private static final String ATT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = "soft-min-evictable-idle-time-millis"; + private static final String ATT_USERNAME = "username"; static final String DEFAULT_ID = "contextSource"; @@ -83,6 +98,19 @@ public class ContextSourceParser implements BeanDefinitionParser { private static final int DEFAULT_EVICTION_RUN_MILLIS = -1; private static final int DEFAULT_TESTS_PER_EVICTION_RUN = 3; private static final int DEFAULT_EVICTABLE_MILLIS = 1000 * 60 * 30; + private static final int DEFAULT_MAX_TOTAL_PER_KEY = 8; + private static final int DEFAULT_MAX_IDLE_PER_KEY = 8; + private static final int DEFAULT_MIN_IDLE_PER_KEY = 0; + private static final String DEFAULT_EVICTION_POLICY_CLASS_NAME = + "org.apache.commons.pool2.impl.DefaultEvictionPolicy"; + private static final boolean DEFAULT_FAIRNESS = false; + private static final boolean DEFAULT_JMX_ENABLE = true; + private static final String DEFAULT_JMX_NAME_BASE = null; + private static final String DEFAULT_JMX_NAME_PREFIX = "ldap-pool"; + private static final boolean DEFAULT_LIFO = true; + private static final int DEFAULT_MAX_WAIT_MILLIS = -1; + private static final boolean DEFAULT_BLOCK_WHEN_EXHAUSTED = true; + private static final int DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1; @Override public BeanDefinition parse(Element element, ParserContext parserContext) { @@ -150,7 +178,12 @@ public class ContextSourceParser implements BeanDefinitionParser { boolean nativePooling) { Element poolingElement = DomUtils.getChildElementByTagName(element, Elements.POOLING); - if(poolingElement == null) { + Element pooling2Element = DomUtils.getChildElementByTagName(element, Elements.POOLING2); + + if (pooling2Element != null && poolingElement != null) { + throw new IllegalArgumentException( + String.format("%s cannot be enabled together with %s.", Elements.POOLING2, Elements.POOLING)); + } else if (poolingElement == null && pooling2Element == null) { return targetContextSourceDefinition; } @@ -159,26 +192,44 @@ public class ContextSourceParser implements BeanDefinitionParser { String.format("%s cannot be enabled together with %s", ATT_NATIVE_POOLING, Elements.POOLING)); } - BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PoolingContextSource.class); - builder.addPropertyValue("contextSource", targetContextSourceDefinition); + if (pooling2Element != null) { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PooledContextSource.class); + builder.addPropertyValue("contextSource", targetContextSourceDefinition); - builder.addPropertyValue("maxActive", getInt(poolingElement, ATT_MAX_ACTIVE, DEFAULT_MAX_ACTIVE)); - builder.addPropertyValue("maxTotal", getInt(poolingElement, ATT_MAX_TOTAL, DEFAULT_MAX_TOTAL)); - builder.addPropertyValue("maxIdle", getInt(poolingElement, ATT_MAX_IDLE, DEFAULT_MAX_IDLE)); - builder.addPropertyValue("minIdle", getInt(poolingElement, ATT_MIN_IDLE, DEFAULT_MIN_IDLE)); - builder.addPropertyValue("maxWait", getInt(poolingElement, ATT_MAX_WAIT, DEFAULT_MAX_WAIT)); - String whenExhausted = getString(poolingElement, ATT_WHEN_EXHAUSTED, PoolExhaustedAction.BLOCK.name()); - builder.addPropertyValue("whenExhaustedAction", PoolExhaustedAction.valueOf(whenExhausted).getValue()); + populatePoolConfigProperties(builder, pooling2Element); - boolean testOnBorrow = getBoolean(poolingElement, ATT_TEST_ON_BORROW, false); - boolean testOnReturn = getBoolean(poolingElement, ATT_TEST_ON_RETURN, false); - boolean testWhileIdle = getBoolean(poolingElement, ATT_TEST_WHILE_IDLE, false); + boolean testOnBorrow = getBoolean(pooling2Element, ATT_TEST_ON_BORROW, false); + boolean testOnReturn = getBoolean(pooling2Element, ATT_TEST_ON_RETURN, false); + boolean testWhileIdle = getBoolean(pooling2Element, ATT_TEST_WHILE_IDLE, false); + boolean testOnCreate = getBoolean(pooling2Element, ATT_TEST_ON_CREATE, false); - if(testOnBorrow || testOnReturn || testWhileIdle) { - populatePoolValidationProperties(builder, poolingElement, testOnBorrow, testOnReturn, testWhileIdle); + if (testOnBorrow || testOnCreate || testWhileIdle || testOnReturn) { + populatePoolValidationProperties(builder, pooling2Element); + } + + return builder.getBeanDefinition(); + } else { + BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(PoolingContextSource.class); + builder.addPropertyValue("contextSource", targetContextSourceDefinition); + + builder.addPropertyValue("maxActive", getInt(poolingElement, ATT_MAX_ACTIVE, DEFAULT_MAX_ACTIVE)); + builder.addPropertyValue("maxTotal", getInt(poolingElement, ATT_MAX_TOTAL, DEFAULT_MAX_TOTAL)); + builder.addPropertyValue("maxIdle", getInt(poolingElement, ATT_MAX_IDLE, DEFAULT_MAX_IDLE)); + builder.addPropertyValue("minIdle", getInt(poolingElement, ATT_MIN_IDLE, DEFAULT_MIN_IDLE)); + builder.addPropertyValue("maxWait", getInt(poolingElement, ATT_MAX_WAIT, DEFAULT_MAX_WAIT)); + String whenExhausted = getString(poolingElement, ATT_WHEN_EXHAUSTED, PoolExhaustedAction.BLOCK.name()); + builder.addPropertyValue("whenExhaustedAction", PoolExhaustedAction.valueOf(whenExhausted).getValue()); + + boolean testOnBorrow = getBoolean(poolingElement, ATT_TEST_ON_BORROW, false); + boolean testOnReturn = getBoolean(poolingElement, ATT_TEST_ON_RETURN, false); + boolean testWhileIdle = getBoolean(poolingElement, ATT_TEST_WHILE_IDLE, false); + + if (testOnBorrow || testOnReturn || testWhileIdle) { + populatePoolValidationProperties(builder, poolingElement, testOnBorrow, testOnReturn, testWhileIdle); + } + + return builder.getBeanDefinition(); } - - return builder.getBeanDefinition(); } private void populatePoolValidationProperties(BeanDefinitionBuilder builder, Element element, @@ -216,6 +267,63 @@ public class ContextSourceParser implements BeanDefinitionParser { builder.addPropertyValue("nonTransientExceptions", nonTransientExceptionClasses); } + + private void populatePoolValidationProperties(BeanDefinitionBuilder builder, Element element) { + + BeanDefinitionBuilder validatorBuilder = BeanDefinitionBuilder.rootBeanDefinition( + org.springframework.ldap.pool2.validation.DefaultDirContextValidator.class); + validatorBuilder.addPropertyValue("base", getString(element, ATT_VALIDATION_QUERY_BASE, "")); + validatorBuilder.addPropertyValue("filter", + getString(element, ATT_VALIDATION_QUERY_FILTER, + org.springframework.ldap.pool2.validation.DefaultDirContextValidator.DEFAULT_FILTER)); + String searchControlsRef = element.getAttribute(ATT_VALIDATION_QUERY_SEARCH_CONTROLS_REF); + if(StringUtils.hasText(searchControlsRef)) { + validatorBuilder.addPropertyReference("searchControls", searchControlsRef); + } + builder.addPropertyValue("dirContextValidator", validatorBuilder.getBeanDefinition()); + + String nonTransientExceptions = getString(element, ATT_NON_TRANSIENT_EXCEPTIONS, CommunicationException.class.getName()); + String[] strings = StringUtils.commaDelimitedListToStringArray(nonTransientExceptions); + Set> nonTransientExceptionClasses = new HashSet>(); + for (String className : strings) { + try { + nonTransientExceptionClasses.add(ClassUtils.getDefaultClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format("%s is not a valid class name", className), e); + } + } + + builder.addPropertyValue("nonTransientExceptions", nonTransientExceptionClasses); + } + + private void populatePoolConfigProperties(BeanDefinitionBuilder builder, Element element) { + BeanDefinitionBuilder configBuilder = BeanDefinitionBuilder + .rootBeanDefinition(PoolConfig.class); + + configBuilder.addPropertyValue("maxTotal", getInt(element, ATT_MAX_TOTAL, DEFAULT_MAX_TOTAL)); + configBuilder.addPropertyValue("maxTotalPerKey", getInt(element, ATT_MAX_TOTAL_PER_KEY, DEFAULT_MAX_TOTAL_PER_KEY)); + configBuilder.addPropertyValue("maxIdlePerKey", getInt(element, ATT_MAX_IDLE_PER_KEY, DEFAULT_MAX_IDLE_PER_KEY)); + configBuilder.addPropertyValue("minIdlePerKey", getInt(element, ATT_MIN_IDLE_PER_KEY, DEFAULT_MIN_IDLE_PER_KEY)); + configBuilder.addPropertyValue("evictionPolicyClassName", getString(element, ATT_EVICTION_POLICY_CLASS, DEFAULT_EVICTION_POLICY_CLASS_NAME)); + configBuilder.addPropertyValue("fairness", getBoolean(element, ATT_FAIRNESS, DEFAULT_FAIRNESS)); + configBuilder.addPropertyValue("jmxEnabled", getBoolean(element, ATT_JMX_ENABLE, DEFAULT_JMX_ENABLE)); + configBuilder.addPropertyValue("jmxNameBase", getString(element, ATT_JMX_NAME_BASE, DEFAULT_JMX_NAME_BASE)); + configBuilder.addPropertyValue("jmxNamePrefix", getString(element, ATT_JMX_NAME_PREFIX, DEFAULT_JMX_NAME_PREFIX)); + configBuilder.addPropertyValue("lifo", getBoolean(element, ATT_LIFO, DEFAULT_LIFO)); + configBuilder.addPropertyValue("maxWaitMillis", getInt(element, ATT_MAX_WAIT, DEFAULT_MAX_WAIT_MILLIS)); + configBuilder.addPropertyValue("blockWhenExhausted", getBoolean(element, ATT_BLOCK_WHEN_EXHAUSTED, DEFAULT_BLOCK_WHEN_EXHAUSTED)); + configBuilder.addPropertyValue("testOnBorrow", getBoolean(element, ATT_TEST_ON_BORROW, false)); + configBuilder.addPropertyValue("testOnCreate", getBoolean(element, ATT_TEST_ON_CREATE, false)); + configBuilder.addPropertyValue("testOnReturn", getBoolean(element, ATT_TEST_ON_RETURN, false)); + configBuilder.addPropertyValue("testWhileIdle", getBoolean(element, ATT_TEST_WHILE_IDLE, false)); + configBuilder.addPropertyValue("timeBetweenEvictionRunsMillis", getInt(element, ATT_EVICTION_RUN_MILLIS, DEFAULT_EVICTION_RUN_MILLIS)); + configBuilder.addPropertyValue("numTestsPerEvictionRun", getInt(element, ATT_TESTS_PER_EVICTION_RUN, DEFAULT_TESTS_PER_EVICTION_RUN)); + configBuilder.addPropertyValue("minEvictableIdleTimeMillis", getInt(element, ATT_EVICTABLE_TIME_MILLIS, DEFAULT_EVICTABLE_MILLIS)); + configBuilder.addPropertyValue("softMinEvictableIdleTimeMillis", getInt(element, ATT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS, DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS)); + + builder.addConstructorArgValue(configBuilder.getBeanDefinition()); + } + static class UrlsFactory { public static String[] urls(String value) { return StringUtils.commaDelimitedListToStringArray(value); diff --git a/core/src/main/java/org/springframework/ldap/config/Elements.java b/core/src/main/java/org/springframework/ldap/config/Elements.java index 2c3d8817..8bf87715 100644 --- a/core/src/main/java/org/springframework/ldap/config/Elements.java +++ b/core/src/main/java/org/springframework/ldap/config/Elements.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2013 the original author or authors. + * Copyright 2005-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,12 @@ package org.springframework.ldap.config; /** * @author Mattias Hellborg Arthursson + * @author Anindya Chatterjee */ public abstract class Elements { public static final String CONTEXT_SOURCE = "context-source"; public static final String POOLING = "pooling"; + public static final String POOLING2 = "pooling2"; public static final String LDAP_TEMPLATE = "ldap-template"; public static final String TRANSACTION_MANAGER = "transaction-manager"; public static final String REPOSITORIES = "repositories"; diff --git a/core/src/main/java/org/springframework/ldap/pool2/DelegatingContext.java b/core/src/main/java/org/springframework/ldap/pool2/DelegatingContext.java new file mode 100644 index 00000000..06d0b5e0 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/DelegatingContext.java @@ -0,0 +1,390 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + + +import org.apache.commons.pool2.KeyedObjectPool; +import org.springframework.ldap.pool2.factory.PooledContextSource; +import org.springframework.util.Assert; + +import javax.naming.*; +import java.util.Hashtable; + +/** + * Used by {@link PooledContextSource} to wrap a {@link Context}, delegating most methods + * to the underlying context, retains a reference to the pool the context was checked out + * from and returns itself to the pool when {@link #close()} is called. + * + * @since 2.0 + * @author Eric Dalquist + */ +public class DelegatingContext implements Context { + private KeyedObjectPool keyedObjectPool; + private Context delegateContext; + private final DirContextType dirContextType; + + + /** + * Create a new delegating context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateContext The context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingContext(KeyedObjectPool keyedObjectPool, Context delegateContext, DirContextType dirContextType) { + Assert.notNull(keyedObjectPool, "keyedObjectPool may not be null"); + Assert.notNull(delegateContext, "delegateContext may not be null"); + Assert.notNull(dirContextType, "dirContextType may not be null"); + + this.keyedObjectPool = keyedObjectPool; + this.delegateContext = delegateContext; + this.dirContextType = dirContextType; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this context proxy + */ + public Context getDelegateContext() { + return this.delegateContext; + } + + /** + * Recursivley inspect delegates until a non-delegating context is found. + * + * @return The innermost (real) Context that is being delegated to. + */ + public Context getInnermostDelegateContext() { + final Context delegateContext = this.getDelegateContext(); + + if (delegateContext instanceof DelegatingContext) { + return ((DelegatingContext)delegateContext).getInnermostDelegateContext(); + } + + return delegateContext; + } + + /** + * @throws NamingException If the delegate is null, {@link #close()} has been called. + */ + protected void assertOpen() throws NamingException { + if (this.delegateContext == null) { + throw new NamingException("Context is closed."); + } + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Context)) { + return false; + } + + final Context thisContext = this.getInnermostDelegateContext(); + Context otherContext = (Context)obj; + if (otherContext instanceof DelegatingContext) { + otherContext = ((DelegatingContext)otherContext).getInnermostDelegateContext(); + } + + return thisContext == otherContext || (thisContext != null && thisContext.equals(otherContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final Context context = this.getInnermostDelegateContext(); + return (context != null ? context.toString() : "Context is closed"); + } + + + //***** Context Interface Delegates *****// + + /** + * @see Context#addToEnvironment(String, Object) + */ + public Object addToEnvironment(String propName, Object propVal) throws NamingException { + throw new UnsupportedOperationException("Cannot call addToEnvironment on a pooled context"); + } + + /** + * @see Context#bind(Name, Object) + */ + public void bind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see Context#bind(String, Object) + */ + public void bind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().bind(name, obj); + } + + /** + * @see Context#close() + */ + public void close() throws NamingException { + final Context context = this.getInnermostDelegateContext(); + if (context == null) { + return; + } + + //Get a local reference so the member can be nulled earlier + this.delegateContext = null; + + //Return the object to the Pool and then null the pool reference + try { + boolean valid = true; + + if (context instanceof FailureAwareContext) { + FailureAwareContext failureAwareContext = (FailureAwareContext) context; + if(failureAwareContext.hasFailed()) { + valid = false; + } + } + + if (valid) { + this.keyedObjectPool.returnObject(this.dirContextType, context); + } else { + this.keyedObjectPool.invalidateObject(this.dirContextType, context); + } + } + catch (Exception e) { + final NamingException namingException = new NamingException("Failed to return delegate Context to pool."); + namingException.setRootCause(e); + throw namingException; + } + finally { + this.keyedObjectPool = null; + } + } + + /** + * @see Context#composeName(Name, Name) + */ + public Name composeName(Name name, Name prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see Context#composeName(String, String) + */ + public String composeName(String name, String prefix) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().composeName(name, prefix); + } + + /** + * @see Context#createSubcontext(Name) + */ + public Context createSubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see Context#createSubcontext(String) + */ + public Context createSubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see Context#destroySubcontext(Name) + */ + public void destroySubcontext(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see Context#destroySubcontext(String) + */ + public void destroySubcontext(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call destroySubcontext on a pooled context"); + } + + /** + * @see Context#getEnvironment() + */ + public Hashtable getEnvironment() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getEnvironment(); + } + + /** + * @see Context#getNameInNamespace() + */ + public String getNameInNamespace() throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameInNamespace(); + } + + /** + * @see Context#getNameParser(Name) + */ + public NameParser getNameParser(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see Context#getNameParser(String) + */ + public NameParser getNameParser(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().getNameParser(name); + } + + /** + * @see Context#list(Name) + */ + public NamingEnumeration list(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see Context#list(String) + */ + public NamingEnumeration list(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().list(name); + } + + /** + * @see Context#listBindings(Name) + */ + public NamingEnumeration listBindings(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see Context#listBindings(String) + */ + public NamingEnumeration listBindings(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().listBindings(name); + } + + /** + * @see Context#lookup(Name) + */ + public Object lookup(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see Context#lookup(String) + */ + public Object lookup(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookup(name); + } + + /** + * @see Context#lookupLink(Name) + */ + public Object lookupLink(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see Context#lookupLink(String) + */ + public Object lookupLink(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateContext().lookupLink(name); + } + + /** + * @see Context#rebind(Name, Object) + */ + public void rebind(Name name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see Context#rebind(String, Object) + */ + public void rebind(String name, Object obj) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rebind(name, obj); + } + + /** + * @see Context#removeFromEnvironment(String) + */ + public Object removeFromEnvironment(String propName) throws NamingException { + throw new UnsupportedOperationException("Cannot call removeFromEnvironment on a pooled context"); + } + + /** + * @see Context#rename(Name, Name) + */ + public void rename(Name oldName, Name newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see Context#rename(String, String) + */ + public void rename(String oldName, String newName) throws NamingException { + this.assertOpen(); + this.getDelegateContext().rename(oldName, newName); + } + + /** + * @see Context#unbind(Name) + */ + public void unbind(Name name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } + + /** + * @see Context#unbind(String) + */ + public void unbind(String name) throws NamingException { + this.assertOpen(); + this.getDelegateContext().unbind(name); + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/DelegatingDirContext.java b/core/src/main/java/org/springframework/ldap/pool2/DelegatingDirContext.java new file mode 100644 index 00000000..24da594e --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/DelegatingDirContext.java @@ -0,0 +1,361 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.factory.PooledContextSource; +import org.springframework.util.Assert; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingEnumeration; +import javax.naming.NamingException; +import javax.naming.directory.*; + + +/** + * Used by {@link PooledContextSource} to wrap a {@link DirContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class DelegatingDirContext extends DelegatingContext implements DirContext, DirContextProxy { + private DirContext delegateDirContext; + + /** + * Create a new delegating dir context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateDirContext The dir context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingDirContext(KeyedObjectPool keyedObjectPool, + DirContext delegateDirContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateDirContext, dirContextType); + Assert.notNull(delegateDirContext, "delegateDirContext may not be null"); + + this.delegateDirContext = delegateDirContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this dir context proxy + */ + public DirContext getDelegateDirContext() { + return this.delegateDirContext; + } + + public Context getDelegateContext() { + return this.getDelegateDirContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating dir context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public DirContext getInnermostDelegateDirContext() { + final DirContext delegateDirContext = this.getDelegateDirContext(); + + if (delegateDirContext instanceof DelegatingDirContext) { + return ((DelegatingDirContext)delegateDirContext).getInnermostDelegateDirContext(); + } + + return delegateDirContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateDirContext == null) { + throw new NamingException("DirContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof DirContext)) { + return false; + } + + final DirContext thisDirContext = this.getInnermostDelegateDirContext(); + DirContext otherDirContext = (DirContext)obj; + if (otherDirContext instanceof DelegatingDirContext) { + otherDirContext = ((DelegatingDirContext)otherDirContext).getInnermostDelegateDirContext(); + } + + return thisDirContext == otherDirContext || (thisDirContext != null && thisDirContext.equals(otherDirContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final DirContext context = this.getInnermostDelegateDirContext(); + return (context != null ? context.toString() : "DirContext is closed"); + } + + + //***** DirContextProxy Interface Methods *****// + + /* (non-Javadoc) + * @see org.springframework.ldap.core.DirContextProxy#getTargetContext() + */ + public DirContext getTargetContext() { + return this.getInnermostDelegateDirContext(); + } + + + //***** DirContext Interface Delegates *****// + + /** + * @see DirContext#bind(Name, Object, Attributes) + */ + public void bind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see DirContext#bind(String, Object, Attributes) + */ + public void bind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().bind(name, obj, attrs); + } + + /** + * @see DirContext#createSubcontext(Name, Attributes) + */ + public DirContext createSubcontext(Name name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see DirContext#createSubcontext(String, Attributes) + */ + public DirContext createSubcontext(String name, Attributes attrs) throws NamingException { + throw new UnsupportedOperationException("Cannot call createSubcontext on a pooled context"); + } + + /** + * @see DirContext#getAttributes(Name, String[]) + */ + public Attributes getAttributes(Name name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see DirContext#getAttributes(Name) + */ + public Attributes getAttributes(Name name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see DirContext#getAttributes(String, String[]) + */ + public Attributes getAttributes(String name, String[] attrIds) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name, attrIds); + } + + /** + * @see DirContext#getAttributes(String) + */ + public Attributes getAttributes(String name) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().getAttributes(name); + } + + /** + * @see DirContext#getSchema(Name) + */ + public DirContext getSchema(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see DirContext#getSchema(String) + */ + public DirContext getSchema(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchema on a pooled context"); + } + + /** + * @see DirContext#getSchemaClassDefinition(Name) + */ + public DirContext getSchemaClassDefinition(Name name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see DirContext#getSchemaClassDefinition(String) + */ + public DirContext getSchemaClassDefinition(String name) throws NamingException { + throw new UnsupportedOperationException("Cannot call getSchemaClassDefinition on a pooled context"); + } + + /** + * @see DirContext#modifyAttributes(Name, int, Attributes) + */ + public void modifyAttributes(Name name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see DirContext#modifyAttributes(Name, ModificationItem[]) + */ + public void modifyAttributes(Name name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see DirContext#modifyAttributes(String, int, Attributes) + */ + public void modifyAttributes(String name, int modOp, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, modOp, attrs); + } + + /** + * @see DirContext#modifyAttributes(String, ModificationItem[]) + */ + public void modifyAttributes(String name, ModificationItem[] mods) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().modifyAttributes(name, mods); + } + + /** + * @see DirContext#rebind(Name, Object, Attributes) + */ + public void rebind(Name name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see DirContext#rebind(String, Object, Attributes) + */ + public void rebind(String name, Object obj, Attributes attrs) throws NamingException { + this.assertOpen(); + this.getDelegateDirContext().rebind(name, obj, attrs); + } + + /** + * @see DirContext#search(Name, Attributes, String[]) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see DirContext#search(Name, Attributes) + */ + public NamingEnumeration search(Name name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see DirContext#search(Name, String, Object[], SearchControls) + */ + public NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see DirContext#search(Name, String, SearchControls) + */ + public NamingEnumeration search(Name name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DirContext#search(String, Attributes, String[]) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes, String[] attributesToReturn) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes, attributesToReturn); + } + + /** + * @see DirContext#search(String, Attributes) + */ + public NamingEnumeration search(String name, Attributes matchingAttributes) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, matchingAttributes); + } + + /** + * @see DirContext#search(String, String, Object[], SearchControls) + */ + public NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filterExpr, filterArgs, cons); + } + + /** + * @see DirContext#search(String, String, SearchControls) + */ + public NamingEnumeration search(String name, String filter, SearchControls cons) throws NamingException { + this.assertOpen(); + return this.getDelegateDirContext().search(name, filter, cons); + } + + /** + * @see DelegatingContext#close() + */ + public void close() throws NamingException { + if (this.delegateDirContext == null) { + return; + } + + super.close(); + this.delegateDirContext = null; + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/DelegatingLdapContext.java b/core/src/main/java/org/springframework/ldap/pool2/DelegatingLdapContext.java new file mode 100644 index 00000000..1755a5db --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/DelegatingLdapContext.java @@ -0,0 +1,202 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.factory.PooledContextSource; +import org.springframework.util.Assert; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.Control; +import javax.naming.ldap.ExtendedRequest; +import javax.naming.ldap.ExtendedResponse; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link PooledContextSource} to wrap a {@link LdapContext}, delegating most methods + * to the underlying context. This class extends {@link DelegatingDirContext} which handles returning + * the context to the pool on a call to {@link #close()} + * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class DelegatingLdapContext extends DelegatingDirContext implements LdapContext { + private LdapContext delegateLdapContext; + + /** + * Create a new delegating ldap context for the specified pool, context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public DelegatingLdapContext(KeyedObjectPool keyedObjectPool, + LdapContext delegateLdapContext, DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + Assert.notNull(delegateLdapContext, "delegateLdapContext may not be null"); + + this.delegateLdapContext = delegateLdapContext; + } + + + //***** Helper Methods *****// + + /** + * @return The direct delegate for this ldap context proxy + */ + public LdapContext getDelegateLdapContext() { + return this.delegateLdapContext; + } + + // cannot return subtype in overridden method unless Java5 + public DirContext getDelegateDirContext() { + return this.getDelegateLdapContext(); + } + + /** + * Recursivley inspect delegates until a non-delegating ldap context is found. + * + * @return The innermost (real) DirContext that is being delegated to. + */ + public LdapContext getInnermostDelegateLdapContext() { + final LdapContext delegateLdapContext = this.getDelegateLdapContext(); + + if (delegateLdapContext instanceof DelegatingLdapContext) { + return ((DelegatingLdapContext)delegateLdapContext).getInnermostDelegateLdapContext(); + } + + return delegateLdapContext; + } + + protected void assertOpen() throws NamingException { + if (this.delegateLdapContext == null) { + throw new NamingException("LdapContext is closed."); + } + + super.assertOpen(); + } + + + //***** Object methods *****// + + /** + * @see Object#equals(Object) + */ + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof LdapContext)) { + return false; + } + + final LdapContext thisLdapContext = this.getInnermostDelegateLdapContext(); + LdapContext otherLdapContext = (LdapContext)obj; + if (otherLdapContext instanceof DelegatingLdapContext) { + otherLdapContext = ((DelegatingLdapContext)otherLdapContext).getInnermostDelegateLdapContext(); + } + + return thisLdapContext == otherLdapContext || (thisLdapContext != null && thisLdapContext.equals(otherLdapContext)); + } + + /** + * @see Object#hashCode() + */ + public int hashCode() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.hashCode() : 0); + } + + /** + * @see Object#toString() + */ + public String toString() { + final LdapContext context = this.getInnermostDelegateLdapContext(); + return (context != null ? context.toString() : "LdapContext is closed"); + } + + + //***** LdapContext Interface Delegates *****// + + /** + * @see LdapContext#extendedOperation(ExtendedRequest) + */ + public ExtendedResponse extendedOperation(ExtendedRequest request) throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().extendedOperation(request); + } + + /** + * @see LdapContext#getConnectControls() + */ + public Control[] getConnectControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getConnectControls(); + } + + /** + * @see LdapContext#getRequestControls() + */ + public Control[] getRequestControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getRequestControls(); + } + + /** + * @see LdapContext#getResponseControls() + */ + public Control[] getResponseControls() throws NamingException { + this.assertOpen(); + return this.getDelegateLdapContext().getResponseControls(); + } + + /** + * @see LdapContext#newInstance(Control[]) + */ + public LdapContext newInstance(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call newInstance on a pooled context"); + } + + /** + * @see LdapContext#reconnect(Control[]) + */ + public void reconnect(Control[] connCtls) throws NamingException { + throw new UnsupportedOperationException("Cannot call reconnect on a pooled context"); + } + + /** + * @see LdapContext#setRequestControls(Control[]) + */ + public void setRequestControls(Control[] requestControls) throws NamingException { + throw new UnsupportedOperationException("Cannot call setRequestControls on a pooled context"); + } + + /** + * @see DelegatingDirContext#close() + */ + public void close() throws NamingException { + if (this.delegateLdapContext == null) { + return; + } + + super.close(); + this.delegateLdapContext = null; + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/DirContextType.java b/core/src/main/java/org/springframework/ldap/pool2/DirContextType.java new file mode 100644 index 00000000..4be1a6ee --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/DirContextType.java @@ -0,0 +1,50 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2; + +import org.springframework.ldap.core.ContextSource; + +import javax.naming.directory.DirContext; + + +/** + * An enum representing the two types of {@link DirContext}s that can be returned by a + * {@link ContextSource}. + * + * @author Eric Dalquist + */ +public final class DirContextType { + private String name; + + private DirContextType(String name) { + this.name = name; + } + + public String toString() { + return name; + } + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadOnlyContext()} + */ + public static final DirContextType READ_ONLY = new DirContextType("READ_ONLY"); + + /** + * The type of {@link DirContext} returned by {@link ContextSource#getReadWriteContext()} + */ + public static final DirContextType READ_WRITE = new DirContextType("READ_WRITE"); +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/FailureAwareContext.java b/core/src/main/java/org/springframework/ldap/pool2/FailureAwareContext.java new file mode 100644 index 00000000..0fc626f9 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/FailureAwareContext.java @@ -0,0 +1,24 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2; + +/** + * @author Mattias Hellborg Arthursson + */ +public interface FailureAwareContext { + boolean hasFailed(); +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/MutableDelegatingLdapContext.java b/core/src/main/java/org/springframework/ldap/pool2/MutableDelegatingLdapContext.java new file mode 100644 index 00000000..5e38ee46 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/MutableDelegatingLdapContext.java @@ -0,0 +1,58 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.springframework.ldap.pool2.factory.MutablePooledContextSource; + +import javax.naming.NamingException; +import javax.naming.ldap.Control; +import javax.naming.ldap.LdapContext; + +/** + * Used by {@link MutablePooledContextSource} to wrap a {@link LdapContext}, + * delegating most methods to the underlying context. This class extends + * {@link DelegatingLdapContext}, allowing request controls to be set on the + * wrapped ldap context. This enables the Spring LDAP pooling to be used for + * scenarios such as paged results. + * + * @since 2.0 + * @author Ulrik Sandberg + * @author Anindya Chatterjee + */ +public class MutableDelegatingLdapContext extends DelegatingLdapContext { + + /** + * Create a new mutable delegating ldap context for the specified pool, + * context and context type. + * + * @param keyedObjectPool The pool the delegate context was checked out + * from. + * @param delegateLdapContext The ldap context to delegate operations to. + * @param dirContextType The type of context, used as a key for the pool. + * @throws IllegalArgumentException if any of the arguments are null + */ + public MutableDelegatingLdapContext(KeyedObjectPool keyedObjectPool, LdapContext delegateLdapContext, + DirContextType dirContextType) { + super(keyedObjectPool, delegateLdapContext, dirContextType); + } + + public void setRequestControls(Control[] requestControls) throws NamingException { + assertOpen(); + getDelegateLdapContext().setRequestControls(requestControls); + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java b/core/src/main/java/org/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java new file mode 100644 index 00000000..d59566b2 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/factory/DirContextPoolableObjectFactory.java @@ -0,0 +1,321 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + +import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.DirContextProxy; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.FailureAwareContext; +import org.springframework.ldap.pool2.validation.DirContextValidator; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.util.Assert; + +import javax.naming.CommunicationException; +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Factory that creates {@link DirContext} instances for pooling via a + * configured {@link ContextSource}. The {@link DirContext}s are keyed based + * on if they are read only or read/write. The expected key type is the + * {@link org.springframework.ldap.pool2.DirContextType} enum. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
contextSource The {@link ContextSource} to get {@link DirContext}s from + * for adding to the pool. Yesnull
dirContextValidator The {@link DirContextValidator} to use to validate + * {@link DirContext}s. This is only required if the pool has validation of any + * kind turned on. Nonull
+ * + * @since 2.0 + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + * @author Mattias Hellborg Arthursson + * @author Anindya Chatterjee + */ +class DirContextPooledObjectFactory extends BaseKeyedPooledObjectFactory { + /** + * Logger for this class and subclasses + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private static final Set> DEFAULT_NONTRANSIENT_EXCEPTIONS + = new HashSet>(){{ + add(CommunicationException.class); + }}; + + private ContextSource contextSource; + + private DirContextValidator dirContextValidator; + + private Set> nonTransientExceptions = DEFAULT_NONTRANSIENT_EXCEPTIONS; + + void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.nonTransientExceptions = new HashSet>(nonTransientExceptions); + } + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.contextSource; + } + + /** + * @param contextSource + * the contextSource to set + */ + public void setContextSource(ContextSource contextSource) { + if (contextSource == null) { + throw new IllegalArgumentException("contextSource may not be null"); + } + + this.contextSource = contextSource; + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextValidator; + } + + /** + * @param dirContextValidator + * the dirContextValidator to set + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + if (dirContextValidator == null) { + throw new IllegalArgumentException( + "dirContextValidator may not be null"); + } + + this.dirContextValidator = dirContextValidator; + } + + private Object makeFailureAwareProxy(DirContext readOnlyContext) { + return Proxy.newProxyInstance(DirContextProxy.class + .getClassLoader(), + new Class[]{ + LdapUtils.getActualTargetClass(readOnlyContext), + DirContextProxy.class, + FailureAwareContext.class}, + new FailureAwareContextProxy(readOnlyContext)); + } + + /** + * @see BaseKeyedPooledObjectFactory#validateObject(Object, PooledObject) + * + * */ + @Override + public boolean validateObject(Object key, PooledObject pooledObject) { + Assert.notNull(this.dirContextValidator, + "DirContextValidator may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + Assert.notNull(pooledObject, + "The Object to validate must not be null"); + Assert.isTrue(pooledObject.getObject() instanceof DirContext, + "The Object to validate must be of type '" + DirContext.class + + "'"); + + try { + final DirContextType contextType = (DirContextType) key; + final DirContext dirContext = (DirContext) pooledObject.getObject(); + return this.dirContextValidator.validateDirContext(contextType, + dirContext); + } catch (Exception e) { + this.logger.warn("Failed to validate '" + pooledObject.getObject() + + "' due to an unexpected exception.", e); + return false; + } + } + + /** + * @see BaseKeyedPooledObjectFactory#destroyObject(Object, PooledObject) + * + * */ + @Override + public void destroyObject(Object key, PooledObject pooledObject) throws Exception { + Assert.notNull(pooledObject, + "The Object to destroy must not be null"); + Assert.isTrue(pooledObject.getObject() instanceof DirContext, + "The Object to destroy must be of type '" + DirContext.class + + "'"); + + try { + final DirContext dirContext = (DirContext) pooledObject.getObject(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closing " + key + " DirContext='" + + dirContext + "'"); + } + dirContext.close(); + if (this.logger.isDebugEnabled()) { + this.logger.debug("Closed " + key + " DirContext='" + + dirContext + "'"); + } + } catch (Exception e) { + this.logger.warn( + "An exception occured while closing '" + pooledObject.getObject() + "'", e); + } + } + + /** + * @see BaseKeyedPooledObjectFactory#create(Object) + * + * */ + @Override + public Object create(Object key) throws Exception { + Assert.notNull(this.contextSource, "ContextSource may not be null"); + Assert.isTrue(key instanceof DirContextType, + "key must be a DirContextType"); + + final DirContextType contextType = (DirContextType) key; + if (this.logger.isDebugEnabled()) { + this.logger.debug("Creating a new " + contextType + " DirContext"); + } + + if (contextType == DirContextType.READ_WRITE) { + final DirContext readWriteContext = this.contextSource + .getReadWriteContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_WRITE + + " DirContext='" + readWriteContext + "'"); + } + + return makeFailureAwareProxy(readWriteContext); + } else if (contextType == DirContextType.READ_ONLY) { + + final DirContext readOnlyContext = this.contextSource + .getReadOnlyContext(); + + if (this.logger.isDebugEnabled()) { + this.logger.debug("Created new " + DirContextType.READ_ONLY + + " DirContext='" + readOnlyContext + "'"); + } + + return makeFailureAwareProxy(readOnlyContext); + } else { + throw new IllegalArgumentException("Unrecognized ContextType: " + + contextType); + } + } + + /** + * @see BaseKeyedPooledObjectFactory#wrap(Object) + * + * */ + @Override + public PooledObject wrap(Object value) { + return new DefaultPooledObject(value); + } + + /** + * Invocation handler that checks thrown exceptions against the configured {@link #nonTransientExceptions}, + * marking the Context as invalid on match. + * + * @author Mattias Hellborg Arthursson + * @since 2.0 + */ + private class FailureAwareContextProxy implements + InvocationHandler { + + private DirContext target; + + private boolean hasFailed = false; + + public FailureAwareContextProxy(DirContext target) { + Assert.notNull(target, "Target must not be null"); + this.target = target; + } + + /* + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, + * java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + + String methodName = method.getName(); + if (methodName.equals("getTargetContext")) { + return target; + } else if (methodName.equals("hasFailed")) { + return hasFailed; + } + + try { + return method.invoke(target, args); + } + catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + Class targetExceptionClass = targetException.getClass(); + + boolean nonTransientEncountered = false; + for (Class clazz : nonTransientExceptions) { + if(clazz.isAssignableFrom(targetExceptionClass)) { + logger.info( + String.format("An %s - explicitly configured to be a non-transient exception - encountered; eagerly invalidating the target context.", + targetExceptionClass)); + nonTransientEncountered = true; + break; + } + } + + if(nonTransientEncountered) { + hasFailed = true; + } else { + if (logger.isDebugEnabled()) { + logger.debug(String.format("An %s - not explicitly configured to be a non-transient exception - encountered; ignoring.", + targetExceptionClass)); + } + } + + throw targetException; + } + } + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/factory/MutablePooledContextSource.java b/core/src/main/java/org/springframework/ldap/pool2/factory/MutablePooledContextSource.java new file mode 100644 index 00000000..f76d64b0 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/factory/MutablePooledContextSource.java @@ -0,0 +1,62 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2.factory; + +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.DelegatingDirContext; +import org.springframework.ldap.pool2.MutableDelegatingLdapContext; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +/** + * A {@link PooledContextSource} subclass that creates + * {@link MutableDelegatingLdapContext} instances. This enables the Spring LDAP + * pooling to be used in scenarios that require request controls to be set, such + * as paged results. + * + * @since 2.0 + * @author Anindya Chatterjee + */ +public class MutablePooledContextSource extends PooledContextSource { + /** + * Creates a new pooling context source, setting up the DirContext object + * factory and generic keyed object pool. + * + * @param poolConfig pool configurations to set. + */ + public MutablePooledContextSource(PoolConfig poolConfig) { + super(poolConfig); + } + + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new MutableDelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/factory/PoolConfig.java b/core/src/main/java/org/springframework/ldap/pool2/factory/PoolConfig.java new file mode 100644 index 00000000..15e1e2f2 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/factory/PoolConfig.java @@ -0,0 +1,332 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + + +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; + +/** + * A wrapper class for the pool configuration. It helps to create an instance of + * {@link GenericKeyedObjectPoolConfig}. + * + * @author Anindya Chatterjee + * @since 2.0 + */ +public class PoolConfig { + private int maxIdlePerKey = 8; + private int maxTotal = -1; + private int maxTotalPerKey = 8; + private int minIdlePerKey = 0; + private boolean blockWhenExhausted = true; + private String evictionPolicyClassName = "org.apache.commons.pool2.impl.DefaultEvictionPolicy"; + private boolean fairness = false; + private boolean jmxEnabled = true; + private String jmxNameBase = null; + private String jmxNamePrefix = "ldap-pool"; + private boolean lifo = true; + private long maxWaitMillis = -1L; + private long minEvictableIdleTimeMillis = 1000L * 60L * 30L; + private int numTestsPerEvictionRun = 3; + private long softMinEvictableIdleTimeMillis = -1L; + private boolean testOnBorrow = false; + private boolean testOnCreate = false; + private boolean testOnReturn = false; + private boolean testWhileIdle = false; + private long timeBetweenEvictionRunsMillis = -1L; + + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxIdlePerKey(int) + * + */ + public void setMaxIdlePerKey(int maxIdlePerKey) { + this.maxIdlePerKey = maxIdlePerKey; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxTotal(int) + * + */ + public void setMaxTotal(int maxTotal) { + this.maxTotal = maxTotal; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxTotalPerKey(int) + */ + public void setMaxTotalPerKey(int maxTotalPerKey) { + this.maxTotalPerKey = maxTotalPerKey; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMinIdlePerKey(int) + */ + public void setMinIdlePerKey(int minIdlePerKey) { + this.minIdlePerKey = minIdlePerKey; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setBlockWhenExhausted(boolean) + */ + public void setBlockWhenExhausted(boolean blockWhenExhausted) { + this.blockWhenExhausted = blockWhenExhausted; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setEvictionPolicyClassName(String) + */ + public void setEvictionPolicyClassName(String evictionPolicyClassName) { + this.evictionPolicyClassName = evictionPolicyClassName; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setFairness(boolean) + */ + public void setFairness(boolean fairness) { + this.fairness = fairness; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxEnabled(boolean) + */ + public void setJmxEnabled(boolean jmxEnabled) { + this.jmxEnabled = jmxEnabled; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxNameBase(String) + */ + public void setJmxNameBase(String jmxNameBase) { + this.jmxNameBase = jmxNameBase; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setJmxNamePrefix(String) + */ + public void setJmxNamePrefix(String jmxNamePrefix) { + this.jmxNamePrefix = jmxNamePrefix; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setLifo(boolean) + */ + public void setLifo(boolean lifo) { + this.lifo = lifo; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMaxWaitMillis(long) + */ + public void setMaxWaitMillis(long maxWaitMillis) { + this.maxWaitMillis = maxWaitMillis; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setMinEvictableIdleTimeMillis(long) + */ + public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { + this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setNumTestsPerEvictionRun(int) + */ + public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) { + this.numTestsPerEvictionRun = numTestsPerEvictionRun; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setSoftMinEvictableIdleTimeMillis(long) + */ + public void setSoftMinEvictableIdleTimeMillis(long softMinEvictableIdleTimeMillis) { + this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnBorrow(boolean) + */ + public void setTestOnBorrow(boolean testOnBorrow) { + this.testOnBorrow = testOnBorrow; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnCreate(boolean) + */ + public void setTestOnCreate(boolean testOnCreate) { + this.testOnCreate = testOnCreate; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestOnReturn(boolean) + */ + public void setTestOnReturn(boolean testOnReturn) { + this.testOnReturn = testOnReturn; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTestWhileIdle(boolean) + */ + public void setTestWhileIdle(boolean testWhileIdle) { + this.testWhileIdle = testWhileIdle; + } + + /** + * @see org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig#setTimeBetweenEvictionRunsMillis(long) + */ + public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { + this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxIdlePerKey() + */ + public int getMaxIdlePerKey() { + return maxIdlePerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxTotal() + */ + public int getMaxTotal() { + return maxTotal; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxIdlePerKey() + */ + public int getMaxTotalPerKey() { + return maxTotalPerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMinIdlePerKey() + */ + public int getMinIdlePerKey() { + return minIdlePerKey; + } + + /** + * @see GenericKeyedObjectPoolConfig#getBlockWhenExhausted() + */ + public boolean isBlockWhenExhausted() { + return blockWhenExhausted; + } + + /** + * @see GenericKeyedObjectPoolConfig#getEvictionPolicyClassName() + */ + public String getEvictionPolicyClassName() { + return evictionPolicyClassName; + } + + /** + * @see GenericKeyedObjectPoolConfig#getFairness() + */ + public boolean isFairness() { + return fairness; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxEnabled() + */ + public boolean isJmxEnabled() { + return jmxEnabled; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxNameBase() + */ + public String getJmxNameBase() { + return jmxNameBase; + } + + /** + * @see GenericKeyedObjectPoolConfig#getJmxNamePrefix() + */ + public String getJmxNamePrefix() { + return jmxNamePrefix; + } + + /** + * @see GenericKeyedObjectPoolConfig#getLifo() + */ + public boolean isLifo() { + return lifo; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMaxWaitMillis() + */ + public long getMaxWaitMillis() { + return maxWaitMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getMinEvictableIdleTimeMillis() + */ + public long getMinEvictableIdleTimeMillis() { + return minEvictableIdleTimeMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun() + */ + public int getNumTestsPerEvictionRun() { + return numTestsPerEvictionRun; + } + + /** + * @see GenericKeyedObjectPoolConfig#getSoftMinEvictableIdleTimeMillis() + */ + public long getSoftMinEvictableIdleTimeMillis() { + return softMinEvictableIdleTimeMillis; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnBorrow() + */ + public boolean isTestOnBorrow() { + return testOnBorrow; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnCreate() + */ + public boolean isTestOnCreate() { + return testOnCreate; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestOnReturn() + */ + public boolean isTestOnReturn() { + return testOnReturn; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTestWhileIdle() + */ + public boolean isTestWhileIdle() { + return testWhileIdle; + } + + /** + * @see GenericKeyedObjectPoolConfig#getTimeBetweenEvictionRunsMillis() + */ + public long getTimeBetweenEvictionRunsMillis() { + return timeBetweenEvictionRunsMillis; + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/factory/PooledContextSource.java b/core/src/main/java/org/springframework/ldap/pool2/factory/PooledContextSource.java new file mode 100644 index 00000000..444ae6bf --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/factory/PooledContextSource.java @@ -0,0 +1,310 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2.factory; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.core.support.DelegatingBaseLdapPathContextSourceSupport; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.validation.DirContextValidator; +import org.springframework.ldap.pool2.DelegatingDirContext; +import org.springframework.ldap.pool2.DelegatingLdapContext; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; +import java.util.Collection; + +/** + * A {@link ContextSource} implementation that wraps an object pool and another + * {@link ContextSource}. {@link DirContext}s are retrieved from the pool which + * maintains them. + * + * NOTE: This implementation is based on apache commons-pool2. + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Property Description Required Default
contextSource + * The {@link ContextSource} to get {@link DirContext}s from for adding to the + * pool.Yesnull
dirContextValidator + * The {@link org.springframework.ldap.pool2.validation.DirContextValidator} to use for validating {@link DirContext}s. + * Required if any of the test/validate options are enabled.Nonull
poolConfigThe {@link PoolConfig} to configure the pool.Nonull
+ * + * @since 2.0 + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class PooledContextSource + extends DelegatingBaseLdapPathContextSourceSupport + implements ContextSource, DisposableBean { + /** + * The logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected final GenericKeyedObjectPool keyedObjectPool; + + private final DirContextPooledObjectFactory dirContextPooledObjectFactory; + + private PoolConfig poolConfig; + + /** + * Creates a new pooling context source, setting up the DirContext object + * factory and generic keyed object pool. + */ + public PooledContextSource(PoolConfig poolConfig) { + this.dirContextPooledObjectFactory = new DirContextPooledObjectFactory(); + if (poolConfig != null) { + this.poolConfig = poolConfig; + GenericKeyedObjectPoolConfig objectPoolConfig = getConfig(poolConfig); + this.keyedObjectPool = + new GenericKeyedObjectPool(this.dirContextPooledObjectFactory, objectPoolConfig); + } else { + this.keyedObjectPool = + new GenericKeyedObjectPool(this.dirContextPooledObjectFactory); + } + } + + // ***** Pool Property Configuration *****// + + /** + * @return the poolConfig + * */ + public PoolConfig getPoolConfig() { + return poolConfig; + } + + /** + * @see GenericKeyedObjectPool#getNumIdle() + * */ + public int getNumIdle() { + return this.keyedObjectPool.getNumIdle(); + } + + /** + * @see GenericKeyedObjectPool#getNumIdle(Object) + * */ + public int getNumIdleRead() { + return this.keyedObjectPool.getNumIdle(DirContextType.READ_ONLY); + } + + /** + * @see GenericKeyedObjectPool#getNumIdle(Object) + * */ + public int getNumIdleWrite() { + return this.keyedObjectPool.getNumIdle(DirContextType.READ_WRITE); + } + + /** + * @see GenericKeyedObjectPool#getNumActive() + * */ + public int getNumActive() { + return this.keyedObjectPool.getNumActive(); + } + + /** + * @see GenericKeyedObjectPool#getNumActive(Object) + * */ + public int getNumActiveRead() { + return this.keyedObjectPool.getNumActive(DirContextType.READ_ONLY); + } + + /** + * @see GenericKeyedObjectPool#getNumActive(Object) + * */ + public int getNumActiveWrite() { + return this.keyedObjectPool.getNumActive(DirContextType.READ_WRITE); + } + + /** + * @see GenericKeyedObjectPool#getNumWaiters() + * */ + public int getNumWaiters() { + return this.keyedObjectPool.getNumWaiters(); + } + + // ***** Object Factory Property Configuration *****// + + /** + * @return the contextSource + */ + public ContextSource getContextSource() { + return this.dirContextPooledObjectFactory.getContextSource(); + } + + /** + * @return the dirContextValidator + */ + public DirContextValidator getDirContextValidator() { + return this.dirContextPooledObjectFactory.getDirContextValidator(); + } + + /** + * @param contextSource the contextSource to set + * Required + */ + public void setContextSource(ContextSource contextSource) { + this.dirContextPooledObjectFactory.setContextSource(contextSource); + } + + /** + * @param dirContextValidator the dirContextValidator to set + * Required + */ + public void setDirContextValidator(DirContextValidator dirContextValidator) { + this.dirContextPooledObjectFactory.setDirContextValidator(dirContextValidator); + } + + /** + * Configure the exception classes that are to be interpreted as no-transient with regards to eager + * context invalidation. If one of the configured exceptions (or subclasses of them) + * is thrown by any method on a pooled DirContext, that instance will immediately be marked + * as invalid without any additional testing (i.e. testOnReturn). + * This allows for more efficient management of dead connections. + * Default is {@link javax.naming.CommunicationException}. + * + * @param nonTransientExceptions the exception classes that should be interpreted as non-transient + * with regards to eager invalidation. + * @since 2.0 + */ + public void setNonTransientExceptions(Collection> nonTransientExceptions) { + this.dirContextPooledObjectFactory.setNonTransientExceptions(nonTransientExceptions); + } + + + // ***** DisposableBean interface methods *****// + + /* + * (non-Javadoc) + * + * @see org.springframework.beans.factory.DisposableBean#destroy() + */ + public void destroy() throws Exception { + try { + this.keyedObjectPool.close(); + } + catch (Exception e) { + this.logger.warn("An exception occurred while closing the underlying pool.", e); + } + } + + @Override + protected ContextSource getTarget() { + return getContextSource(); + } + + // ***** ContextSource interface methods *****// + + @Override + public DirContext getReadOnlyContext() { + return this.getContext(DirContextType.READ_ONLY); + } + + @Override + public DirContext getReadWriteContext() { + return this.getContext(DirContextType.READ_WRITE); + } + + /** + * Gets a DirContext of the specified type from the keyed object pool. + * + * @param dirContextType The type of context to return. + * @return A wrapped DirContext of the specified type. + * @throws DataAccessResourceFailureException If retrieving the object from + * the pool throws an exception + */ + protected DirContext getContext(DirContextType dirContextType) { + final DirContext dirContext; + try { + dirContext = (DirContext) this.keyedObjectPool.borrowObject(dirContextType); + } + catch (Exception e) { + throw new DataAccessResourceFailureException("Failed to borrow DirContext from pool.", e); + } + + if (dirContext instanceof LdapContext) { + return new DelegatingLdapContext(this.keyedObjectPool, (LdapContext) dirContext, dirContextType); + } + + return new DelegatingDirContext(this.keyedObjectPool, dirContext, dirContextType); + } + + @Override + public DirContext getContext(String principal, String credentials) { + throw new UnsupportedOperationException("Not supported for this implementation"); + } + + private GenericKeyedObjectPoolConfig getConfig(PoolConfig poolConfig) { + GenericKeyedObjectPoolConfig objectPoolConfig = new GenericKeyedObjectPoolConfig(); + + objectPoolConfig.setMaxTotalPerKey(poolConfig.getMaxTotalPerKey()); + objectPoolConfig.setMaxTotal(poolConfig.getMaxTotal()); + + objectPoolConfig.setMaxIdlePerKey(poolConfig.getMaxIdlePerKey()); + objectPoolConfig.setMinIdlePerKey(poolConfig.getMinIdlePerKey()); + + objectPoolConfig.setTestWhileIdle(poolConfig.isTestWhileIdle()); + objectPoolConfig.setTestOnReturn(poolConfig.isTestOnReturn()); + objectPoolConfig.setTestOnCreate(poolConfig.isTestOnCreate()); + objectPoolConfig.setTestOnBorrow(poolConfig.isTestOnBorrow()); + + objectPoolConfig.setTimeBetweenEvictionRunsMillis(poolConfig.getTimeBetweenEvictionRunsMillis()); + objectPoolConfig.setEvictionPolicyClassName(poolConfig.getEvictionPolicyClassName()); + objectPoolConfig.setMinEvictableIdleTimeMillis(poolConfig.getMinEvictableIdleTimeMillis()); + objectPoolConfig.setNumTestsPerEvictionRun(poolConfig.getNumTestsPerEvictionRun()); + objectPoolConfig.setSoftMinEvictableIdleTimeMillis(poolConfig.getSoftMinEvictableIdleTimeMillis()); + + objectPoolConfig.setJmxEnabled(poolConfig.isJmxEnabled()); + objectPoolConfig.setJmxNameBase(poolConfig.getJmxNameBase()); + objectPoolConfig.setJmxNamePrefix(poolConfig.getJmxNamePrefix()); + + objectPoolConfig.setMaxWaitMillis(poolConfig.getMaxWaitMillis()); + + objectPoolConfig.setFairness(poolConfig.isFairness()); + objectPoolConfig.setBlockWhenExhausted(poolConfig.isBlockWhenExhausted()); + objectPoolConfig.setLifo(poolConfig.isLifo()); + + return objectPoolConfig; + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/factory/package.html b/core/src/main/java/org/springframework/ldap/pool2/factory/package.html new file mode 100644 index 00000000..176375cb --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/factory/package.html @@ -0,0 +1,7 @@ + + + +Core classes for the pooling library based on commons-pool2 library. + + + diff --git a/core/src/main/java/org/springframework/ldap/pool2/package.html b/core/src/main/java/org/springframework/ldap/pool2/package.html new file mode 100644 index 00000000..3231c793 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/package.html @@ -0,0 +1,7 @@ + + + +Base classes for the pooling library based on commons-pool2 library. + + + diff --git a/core/src/main/java/org/springframework/ldap/pool2/validation/DefaultDirContextValidator.java b/core/src/main/java/org/springframework/ldap/pool2/validation/DefaultDirContextValidator.java new file mode 100644 index 00000000..91af7dc5 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/validation/DefaultDirContextValidator.java @@ -0,0 +1,191 @@ +/* + * Copyright 2005-2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2.validation; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.util.Assert; + +import javax.naming.NamingEnumeration; +import javax.naming.directory.DirContext; +import javax.naming.directory.SearchControls; +import javax.naming.directory.SearchResult; + +/** + * Default {@link DirContext} validator that executes {@link DirContext#search(String, String, SearchControls)}. The + * name, filter and {@link SearchControls} are all configurable. There is no special handling for read only versus + * read write {@link DirContext}s. + * + *
+ *
+ * Configuration: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyDescriptionRequiredDefault
base + * The name parameter to the search method. + * No""
filter + * The filter parameter to the search method. + * No"objectclass=*"
searchControls + * The {@link SearchControls} parameter to the search method. + * No + * {@link SearchControls#setCountLimit(long)} = 1
+ * {@link SearchControls#setReturningAttributes(String[])} = new String[] { "objectclass" }
+ * {@link SearchControls#setTimeLimit(int)} = 500 + *
+ * + * @author Eric Dalquist + */ +public class DefaultDirContextValidator implements DirContextValidator { + public static final String DEFAULT_FILTER = "objectclass=*"; + private static final int DEFAULT_TIME_LIMIT = 500; + + /** + * Logger for this class and sub-classes + */ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + private String base; + private String filter; + private SearchControls searchControls; + + /** + * Create the default validator, creates {@link SearchControls} with search scope OBJECT_SCOPE, + * a countLimit of 1, returningAttributes of objectclass and timeLimit of 500. + * The default base is an empty string and the default filter is objectclass=* + */ + public DefaultDirContextValidator() { + this(SearchControls.OBJECT_SCOPE); + } + + /** + * Create a validator with all the defaults of the default constructor, but with the search scope set to the + * referred value. + * + * @param searchScope The searchScope to be set in the default SearchControls + */ + public DefaultDirContextValidator(int searchScope) { + this.searchControls = new SearchControls(); + this.searchControls.setSearchScope(searchScope); + this.searchControls.setCountLimit(1); + this.searchControls.setReturningAttributes(new String[] { "objectclass" }); + this.searchControls.setTimeLimit(DEFAULT_TIME_LIMIT); + + this.base = ""; + + this.filter = DEFAULT_FILTER; + } + + /** + * @return the baseName + */ + public String getBase() { + return this.base; + } + /** + * @param base the baseName to set + */ + public void setBase(String base) { + this.base = base; + } + /** + * @return the filter + */ + public String getFilter() { + return this.filter; + } + /** + * @param filter the filter to set + */ + public void setFilter(String filter) { + if (filter == null) { + throw new IllegalArgumentException("filter may not be null"); + } + + this.filter = filter; + } + /** + * @return the searchControls + */ + public SearchControls getSearchControls() { + return this.searchControls; + } + /** + * @param searchControls the searchControls to set + */ + public void setSearchControls(SearchControls searchControls) { + if (searchControls == null) { + throw new IllegalArgumentException("searchControls may not be null"); + } + + this.searchControls = searchControls; + } + + + /** + * @see DirContextValidator#validateDirContext(DirContextType, DirContext) + */ + public boolean validateDirContext(DirContextType contextType, DirContext dirContext) { + Assert.notNull(contextType, "contextType may not be null"); + Assert.notNull(dirContext, "dirContext may not be null"); + + try { + final NamingEnumeration searchResults = dirContext.search(this.base, this.filter, this.searchControls); + + if (searchResults.hasMore()) { + if (this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' passed validation."); + } + + return true; + } + } + catch (Exception e) { + if(this.logger.isDebugEnabled()) { + this.logger.debug("DirContext '" + dirContext + "' failed validation with an exception.", e); + } + } + + if (this.logger.isInfoEnabled()) { + this.logger.info("DirContext '" + dirContext + "' failed validation."); + } + return false; + } +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/validation/DirContextValidator.java b/core/src/main/java/org/springframework/ldap/pool2/validation/DirContextValidator.java new file mode 100644 index 00000000..1ff0e158 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/validation/DirContextValidator.java @@ -0,0 +1,39 @@ +/* + * Copyright 2005-2010 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ldap.pool2.validation; + +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool2.DirContextType; + +import javax.naming.directory.DirContext; + +/** + * A validator for {@link DirContext}s. + * + * @author Eric Dalquist + */ +public interface DirContextValidator { + /** + * Validates the {@link DirContext}. A valid {@link DirContext} should be able + * to answer queries and if applicable write to the directory. + * + * @param contextType The type of the {@link DirContext}, refers to if {@link ContextSource#getReadOnlyContext()} or {@link ContextSource#getReadWriteContext()} was called to create the {@link DirContext} + * @param dirContext The {@link DirContext} to validate. + * @return true if the {@link DirContext} operated correctly during validation. + */ + boolean validateDirContext(DirContextType contextType, DirContext dirContext); +} diff --git a/core/src/main/java/org/springframework/ldap/pool2/validation/package.html b/core/src/main/java/org/springframework/ldap/pool2/validation/package.html new file mode 100644 index 00000000..45aeeac4 --- /dev/null +++ b/core/src/main/java/org/springframework/ldap/pool2/validation/package.html @@ -0,0 +1,7 @@ + + + +Connection validation support for the pooling library. + + + diff --git a/core/src/main/resources/org/springframework/ldap/config/spring-ldap-2.0.xsd b/core/src/main/resources/org/springframework/ldap/config/spring-ldap-2.0.xsd index d65d7809..492e3559 100644 --- a/core/src/main/resources/org/springframework/ldap/config/spring-ldap-2.0.xsd +++ b/core/src/main/resources/org/springframework/ldap/config/spring-ldap-2.0.xsd @@ -262,6 +262,209 @@ + + + + + The overall maximum number of active connections (for all types) that can be allocated from + this pool at the same time, or non-positive for no limit. Default is -1 (no limit). + + + + + + + The limit on the number of object instances allocated by the pool (checked out or idle), + per key. When the limit is reached, the sub-pool is said to be exhausted. A negative value + indicates no limit. Default is 8. + + + + + + + The maximum number of active connections per type (read-only|read-write) that can remain idle in the pool, + without extra ones being released, or non-positive for no limit. Default is 8. + + + + + + + The minimum number of active connections per type (read-only|read-write) that can remain + idle in the pool, without extra ones being created, or zero to create none. Default is 0. + + + + + + + The maximum number of milliseconds that the pool will wait (when there are no available connections) + for a connection to be returned before throwing an exception, or non-positive to wait indefinitely. + Default is -1. + + + + + + + Sets to wait until a new object is available. If max-wait is positive a NoSuchElementException + is thrown if no new object is available after the maxWait time expires.. + + + + + + + Sets whether objects created for the pool will be validated before borrowing. If the object + fails to validate, then borrowing will fail. Default is false. + + + + + + + The indication of whether objects will be validated before being borrowed from the pool. + If the object fails to validate, it will be dropped from the pool, and an attempt to borrow another will be made. + Default is false. + + + + + + + The indication of whether objects will be validated before being returned to the pool. + Default is false. + + + + + + + The indication of whether objects will be validated by the idle object evictor (if any). + If an object fails to validate, it will be dropped from the pool. + Default is false. + + + + + + + The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, + no idle object evictor thread will be run. Default is -1. + + + + + + + The number of objects to examine during each run of the idle object evictor thread (if any). + Default is 3. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible + for eviction by the idle object evictor (if any). Default is 1000 * 60 * 30. + + + + + + + The minimum amount of time an object may sit idle in the pool before it is eligible for + eviction by the idle object evictor, with the extra condition that at least minimum number + of object instances per key remain in the pool. This settings is overridden by min-evictable-time-millis if + it is set to a positive value. Default is -1. + + + + + + + The name of the eviction policy implementation that is used by this pool. The Pool will + attempt to load the class using the thread context class loader. If that fails, the Pool + will attempt to load the class using the class loader that loaded this class. Default is + org.apache.commons.pool2.impl.DefaultEvictionPolicy. + + + + + + + Sets whether or not the pool serves threads waiting to borrow connections fairly. + True means that waiting threads are served as if waiting in a FIFO queue. Default is false. + + + + + + + Sets whether JMX will be enabled with the platform MBean server for the pool. Default + is true. + + + + + + + The value of the JMX name base that will be used as part of the name assigned + to JMX enabled pools. Default is null. + + + + + + + The value of the JMX name prefix that will be used as part of the name assigned + to JMX enabled pools. Default value is pool. + + + + + + + Sets whether the pool has LIFO (last in, first out) behaviour with + respect to idle objects - always returning the most recently used object + from the pool, or as a FIFO (first in, first out) queue, where the pool + always returns the oldest object in the idle object pool. Default is true. + + + + + + + The base dn to use for validation searches. Default is LdapUtils.emptyPath(). + + + + + + + The filter to use for validation queries. Default is (objectclass=*). + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + + + Id of the SearchControls instance to use for searches. Default is searchScope=OBJECT_SCOPE; + countLimit: 1; timeLimit: 500; returningAttributes: [objectclass]. + + + + + @@ -269,18 +472,32 @@ - - - - - Defines the settings to use for the Spring LDAP connection pooling support. - - - - - - - + + + + + + Defines the settings to use for the Spring LDAP connection pooling support. + + + + + + + + + + + + Defines the settings to use for the Spring LDAP connection pooling support based on commons-pool2 library. + + + + + + + + diff --git a/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java b/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java index 0511148e..2b2901bf 100644 --- a/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java +++ b/core/src/test/java/org/springframework/ldap/config/LdapTemplateNamespaceHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2005-2013 the original author or authors. + * Copyright 2005-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.ldap.config; import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; import org.junit.Test; import org.springframework.beans.BeansException; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -27,6 +28,7 @@ import org.springframework.ldap.core.support.DirContextAuthenticationStrategy; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.ldap.pool.factory.PoolingContextSource; import org.springframework.ldap.pool.validation.DefaultDirContextValidator; +import org.springframework.ldap.pool2.factory.PooledContextSource; import org.springframework.ldap.support.LdapUtils; import org.springframework.ldap.transaction.compensating.TempEntryRenamingStrategy; import org.springframework.ldap.transaction.compensating.manager.ContextSourceAndDataSourceTransactionManager; @@ -36,9 +38,12 @@ import org.springframework.ldap.transaction.compensating.support.DefaultTempEntr import org.springframework.ldap.transaction.compensating.support.DifferentSubtreeTempEntryRenamingStrategy; import org.springframework.transaction.PlatformTransactionManager; +import javax.management.MBeanServer; +import javax.management.ObjectName; import javax.naming.CannotProceedException; import javax.naming.CommunicationException; import javax.naming.directory.SearchControls; +import java.lang.management.ManagementFactory; import java.util.Set; import static org.junit.Assert.assertArrayEquals; @@ -124,6 +129,7 @@ public class LdapTemplateNamespaceHandlerTest { assertSame(authenticationStrategy, getInternalState(contextSource, "authenticationStrategy")); assertEquals(baseEnv, getInternalState(contextSource, "baseEnv")); } + @Test public void verifyParseWithCustomValues() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-values.xml"); @@ -360,4 +366,132 @@ public class LdapTemplateNamespaceHandlerTest { assertNotNull(repository); } + + @Test + public void verifyParsePooling2Defaults() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-pooling2-defaults.xml"); + + ContextSource outerContextSource = ctx.getBean(ContextSource.class); + assertNotNull(outerContextSource); + assertTrue(outerContextSource instanceof TransactionAwareContextSourceProxy); + + ContextSource pooledContextSource = ((TransactionAwareContextSourceProxy) outerContextSource).getTarget(); + assertNotNull(pooledContextSource); + assertTrue(pooledContextSource instanceof PooledContextSource); + assertNotNull(getInternalState(pooledContextSource, "poolConfig")); + + Object objectFactory = getInternalState(pooledContextSource, "dirContextPooledObjectFactory"); + assertNotNull(getInternalState(objectFactory, "contextSource")); + assertNull(getInternalState(objectFactory, "dirContextValidator")); + Set> nonTransientExceptions = + (Set>) getInternalState(objectFactory, "nonTransientExceptions"); + assertEquals(1, nonTransientExceptions.size()); + assertTrue(nonTransientExceptions.contains(CommunicationException.class)); + + org.apache.commons.pool2.impl.GenericKeyedObjectPool objectPool = + (org.apache.commons.pool2.impl.GenericKeyedObjectPool) getInternalState(pooledContextSource, "keyedObjectPool"); + assertEquals(8, objectPool.getMaxIdlePerKey()); + assertEquals(-1, objectPool.getMaxTotal()); + assertEquals(8, objectPool.getMaxTotalPerKey()); + assertEquals(0, objectPool.getMinIdlePerKey()); + assertEquals(true, objectPool.getBlockWhenExhausted()); + assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME, objectPool.getEvictionPolicyClassName()); + assertEquals(false, objectPool.getFairness()); + + // ensures the pool is registered + ObjectName oname = objectPool.getJmxName(); + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + Set result = mbs.queryNames(oname, null); + assertEquals(1, result.size()); + + assertEquals(true, objectPool.getLifo()); + assertEquals(-1L, objectPool.getMaxWaitMillis()); + assertEquals(1000L*60L*30L, objectPool.getMinEvictableIdleTimeMillis()); + assertEquals(3, objectPool.getNumTestsPerEvictionRun()); + assertEquals(-1L, objectPool.getSoftMinEvictableIdleTimeMillis()); + assertEquals(-1L, objectPool.getTimeBetweenEvictionRunsMillis()); + assertEquals(false, objectPool.getTestOnBorrow()); + assertEquals(false, objectPool.getTestOnCreate()); + assertEquals(false, objectPool.getTestOnReturn()); + assertEquals(false, objectPool.getTestWhileIdle()); + } + + @Test + public void verifyParsePool2SizeSet() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-pool2-configured-poolsize.xml"); + + ContextSource outerContextSource = ctx.getBean(ContextSource.class); + assertNotNull(outerContextSource); + + ContextSource pooledContextSource = ((TransactionAwareContextSourceProxy) outerContextSource).getTarget(); + assertNotNull(pooledContextSource); + + org.apache.commons.pool2.impl.GenericKeyedObjectPool objectPool = + (org.apache.commons.pool2.impl.GenericKeyedObjectPool) getInternalState(pooledContextSource, "keyedObjectPool"); + assertEquals(12, objectPool.getMaxTotal()); + assertEquals(20, objectPool.getMaxIdlePerKey()); + assertEquals(10, objectPool.getMaxTotalPerKey()); + assertEquals(13, objectPool.getMaxWaitMillis()); + assertEquals(14, objectPool.getMinIdlePerKey()); + assertEquals(true, objectPool.getBlockWhenExhausted()); + assertEquals("org.springframework.ldap.pool2.DummyEvictionPolicy", objectPool.getEvictionPolicyClassName()); + assertEquals(true, objectPool.getFairness()); + assertEquals(false, objectPool.getLifo()); + + // ensures the pool is registered + ObjectName oname = objectPool.getJmxName(); + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + Set result = mbs.queryNames(oname, null); + assertEquals(1, result.size()); + assertEquals("org.springframework.ldap.pool2:type=ldap-pool,name=test-pool", oname.toString()); + } + + @Test + public void verifyParsePool2ValidationSet() { + ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("/ldap-namespace-config-pool2-test-specified.xml"); + + ContextSource outerContextSource = ctx.getBean(ContextSource.class); + assertNotNull(outerContextSource); + + ContextSource pooledContextSource = ((TransactionAwareContextSourceProxy) outerContextSource).getTarget(); + assertNotNull(pooledContextSource); + + org.apache.commons.pool2.impl.GenericKeyedObjectPool objectPool = + (org.apache.commons.pool2.impl.GenericKeyedObjectPool) getInternalState(pooledContextSource, "keyedObjectPool"); + assertEquals(123, objectPool.getMinEvictableIdleTimeMillis()); + assertEquals(321, objectPool.getTimeBetweenEvictionRunsMillis()); + assertEquals(22, objectPool.getNumTestsPerEvictionRun()); + assertEquals(12, objectPool.getSoftMinEvictableIdleTimeMillis()); + + assertEquals(true, objectPool.getTestOnBorrow()); + assertEquals(true, objectPool.getTestOnReturn()); + assertEquals(true, objectPool.getTestOnCreate()); + assertEquals(true, objectPool.getTestWhileIdle()); + + Object objectFactory = getInternalState(pooledContextSource, "dirContextPooledObjectFactory"); + org.springframework.ldap.pool2.validation.DefaultDirContextValidator validator = + (org.springframework.ldap.pool2.validation.DefaultDirContextValidator) getInternalState(objectFactory, "dirContextValidator"); + assertEquals("ou=test", validator.getBase()); + assertEquals("objectclass=person", validator.getFilter()); + + SearchControls searchControls = ctx.getBean(SearchControls.class); + assertEquals("objectclass=person", validator.getFilter()); + assertSame(searchControls, validator.getSearchControls()); + + Set> nonTransientExceptions = + (Set>) getInternalState(objectFactory, "nonTransientExceptions"); + assertEquals(2, nonTransientExceptions.size()); + assertTrue(nonTransientExceptions.contains(CommunicationException.class)); + assertTrue(nonTransientExceptions.contains(CannotProceedException.class)); + } + + @Test(expected = BeansException.class) + public void verifyParseWithPool2AndNativePoolingWillFail() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-pool2-with-native.xml"); + } + + @Test(expected = BeansException.class) + public void verifyParseWithPool1AndPool2WillFail() { + new ClassPathXmlApplicationContext("/ldap-namespace-config-pool2-with-pool1.xml"); + } } diff --git a/core/src/test/java/org/springframework/ldap/pool2/AbstractPoolTestCase.java b/core/src/test/java/org/springframework/ldap/pool2/AbstractPoolTestCase.java new file mode 100644 index 00000000..f28c9ad9 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/AbstractPoolTestCase.java @@ -0,0 +1,57 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.junit.Before; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool2.validation.DirContextValidator; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import static org.mockito.Mockito.mock; + +/** + * Contains mocks common to many tests for the connection pool. + * + * @author Ulrik Sandberg + */ +public abstract class AbstractPoolTestCase { + + protected Context contextMock; + + protected DirContext dirContextMock; + + protected LdapContext ldapContextMock; + + protected KeyedObjectPool keyedObjectPoolMock; + + protected ContextSource contextSourceMock; + + protected DirContextValidator dirContextValidatorMock; + + @Before + public void setUp() throws Exception { + contextMock = mock(Context.class); + dirContextMock = mock(DirContext.class); + ldapContextMock = mock(LdapContext.class); + keyedObjectPoolMock = mock(KeyedObjectPool.class); + contextSourceMock = mock(ContextSource.class); + dirContextValidatorMock = mock(DirContextValidator.class); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/DelegatingContextTest.java b/core/src/test/java/org/springframework/ldap/pool2/DelegatingContextTest.java new file mode 100644 index 00000000..9588d875 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/DelegatingContextTest.java @@ -0,0 +1,409 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.junit.Test; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingContextTest extends AbstractPoolTestCase { + + @Test + public void testConstructorAssertions() { + try { + new DelegatingContext(null, contextMock, DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingContext(keyedObjectPoolMock, contextMock, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testHelperMethods() throws Exception { + // Wrap the Context once + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + final Context delegateContext = delegatingContext.getDelegateContext(); + assertEquals(contextMock, delegateContext); + + final Context innerDelegateContext = delegatingContext + .getInnermostDelegateContext(); + assertEquals(contextMock, innerDelegateContext); + + delegatingContext.assertOpen(); + + // Wrap the wrapper + KeyedObjectPool secondKeyedObjectPoolMock = mock(KeyedObjectPool.class); + + final DelegatingContext delegatingContext2 = new DelegatingContext( + secondKeyedObjectPoolMock, delegatingContext, + DirContextType.READ_ONLY); + + final Context delegateContext2 = delegatingContext2 + .getDelegateContext(); + assertEquals(delegatingContext, delegateContext2); + + final Context innerDelegateContext2 = delegatingContext2 + .getInnermostDelegateContext(); + assertEquals(contextMock, innerDelegateContext2); + + delegatingContext2.assertOpen(); + + // Close the outer wrapper + delegatingContext2.close(); + + final Context delegateContext2closed = delegatingContext2 + .getDelegateContext(); + assertNull(delegateContext2closed); + + final Context innerDelegateContext2closed = delegatingContext2 + .getInnermostDelegateContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingContext2.assertOpen(); + fail("delegatingContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingContext.close(); + + final Context delegateContextclosed = delegatingContext + .getDelegateContext(); + assertNull(delegateContextclosed); + + final Context innerDelegateContextclosed = delegatingContext + .getInnermostDelegateContext(); + assertNull(innerDelegateContextclosed); + + try { + delegatingContext.assertOpen(); + fail("delegatingContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, contextMock); + verify(secondKeyedObjectPoolMock) + .returnObject(DirContextType.READ_ONLY, contextMock); + } + + @Test + public void testObjectMethods() throws Exception { + // Wrap the Context once + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + assertEquals(contextMock.toString(), delegatingContext.toString()); + delegatingContext.hashCode(); // Run it to make sure it doesn't fail + + assertTrue(delegatingContext.equals(delegatingContext)); + assertFalse(delegatingContext.equals(new Object())); + + final DelegatingContext delegatingContext2 = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + assertTrue(delegatingContext.equals(delegatingContext2)); + assertTrue(delegatingContext2.equals(delegatingContext)); + assertTrue(delegatingContext.equals(contextMock)); + + // Close the contextMock and try again + delegatingContext.close(); + + assertEquals("Context is closed", delegatingContext.toString()); + assertEquals(0, delegatingContext.hashCode()); // Run it to make sure + // it doesn't fail + + assertTrue(delegatingContext.equals(delegatingContext)); + assertFalse(delegatingContext.equals(new Object())); + + assertFalse(delegatingContext.equals(delegatingContext2)); + assertFalse(delegatingContext2.equals(delegatingContext)); + assertFalse(delegatingContext.equals(contextMock)); + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, contextMock); + } + + @Test + public void testUnsupportedMethods() throws Exception { + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + try { + delegatingContext.addToEnvironment(null, null); + fail("DelegatingContext.addToEnvironment Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.createSubcontext((Name) null); + fail("DelegatingContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.createSubcontext((String) null); + fail("DelegatingContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.destroySubcontext((Name) null); + fail("DelegatingContext.destroySubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.destroySubcontext((String) null); + fail("DelegatingContext.destroySubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingContext.removeFromEnvironment(null); + fail("DelegatingContext.removeFromEnvironment Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + } + + @Test + public void testAllMethodsOpened() throws Exception { + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.bind((Name) null, null); + delegatingContext.bind((String) null, null); + delegatingContext.composeName((Name) null, (Name) null); + delegatingContext.composeName((String) null, (String) null); + delegatingContext.getEnvironment(); + delegatingContext.getNameInNamespace(); + delegatingContext.getNameParser((Name) null); + delegatingContext.getNameParser((String) null); + delegatingContext.list((Name) null); + delegatingContext.list((String) null); + delegatingContext.listBindings((Name) null); + delegatingContext.listBindings((String) null); + delegatingContext.lookup((Name) null); + delegatingContext.lookup((String) null); + delegatingContext.lookupLink((Name) null); + delegatingContext.lookupLink((String) null); + delegatingContext.rebind((Name) null, null); + delegatingContext.rebind((String) null, null); + delegatingContext.rename((Name) null, (Name) null); + delegatingContext.rename((String) null, (String) null); + delegatingContext.unbind((Name) null); + delegatingContext.unbind((String) null); + } + + @Test + public void testAllMethodsClosed() throws Exception { + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.close(); + + try { + delegatingContext.bind((Name) null, null); + fail("DelegatingContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.bind((String) null, null); + fail("DelegatingContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.composeName((Name) null, (Name) null); + fail("DelegatingContext.composeName should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.composeName((String) null, (String) null); + fail("DelegatingContext.composeName should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getEnvironment(); + fail("DelegatingContext.getEnvironment should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameInNamespace(); + fail("DelegatingContext.getNameInNamespace should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameParser((Name) null); + fail("DelegatingContext.getNameParser should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.getNameParser((String) null); + fail("DelegatingContext.getNameParser should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.list((Name) null); + fail("DelegatingContext.list should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.list((String) null); + fail("DelegatingContext.list should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.listBindings((Name) null); + fail("DelegatingContext.listBindings should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.listBindings((String) null); + fail("DelegatingContext.listBindings should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookup((Name) null); + fail("DelegatingContext.lookup should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookup((String) null); + fail("DelegatingContext.lookup should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookupLink((Name) null); + fail("DelegatingContext.lookupLink should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.lookupLink((String) null); + fail("DelegatingContext.lookupLink should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rebind((Name) null, null); + fail("DelegatingContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rebind((String) null, null); + fail("DelegatingContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rename((Name) null, (Name) null); + fail("DelegatingContext.rename should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.rename((String) null, (String) null); + fail("DelegatingContext.rename should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.unbind((Name) null); + fail("DelegatingContext.unbind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingContext.unbind((String) null); + fail("DelegatingContext.unbind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, contextMock); + } + + @Test + public void testDoubleClose() throws Exception { + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + delegatingContext.close(); + + // noop close + delegatingContext.close(); + + verify(keyedObjectPoolMock, times(1)).returnObject(DirContextType.READ_ONLY, contextMock); + } + + @Test + public void testPoolExceptionOnClose() throws Exception { + doThrow(new Exception("Fake Pool returnObject Exception")) + .when(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, contextMock); + + final DelegatingContext delegatingContext = new DelegatingContext( + keyedObjectPoolMock, contextMock, DirContextType.READ_ONLY); + + try { + delegatingContext.close(); + fail("DelegatingContext.close should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/DelegatingDirContextTest.java b/core/src/test/java/org/springframework/ldap/pool2/DelegatingDirContextTest.java new file mode 100644 index 00000000..f60a8b2e --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/DelegatingDirContextTest.java @@ -0,0 +1,381 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.junit.Test; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.Attributes; +import javax.naming.directory.DirContext; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingDirContextTest extends AbstractPoolTestCase { + @Test + public void testConstructorAssertions() { + try { + new DelegatingDirContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingDirContext(keyedObjectPoolMock, dirContextMock, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testHelperMethods() throws Exception { + + // Wrap the DirContext once + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + final Context delegateContext = delegatingDirContext + .getDelegateContext(); + assertEquals(dirContextMock, delegateContext); + + final DirContext delegateDirContext = delegatingDirContext + .getDelegateDirContext(); + assertEquals(dirContextMock, delegateDirContext); + + final DirContext innerDelegateDirContext = delegatingDirContext + .getInnermostDelegateDirContext(); + assertEquals(dirContextMock, innerDelegateDirContext); + + delegatingDirContext.assertOpen(); + + // Wrap the wrapper + KeyedObjectPool secondKeyedObjectPoolMock = mock(KeyedObjectPool.class); + + final DelegatingDirContext delegatingDirContext2 = new DelegatingDirContext( + secondKeyedObjectPoolMock, delegatingDirContext, + DirContextType.READ_ONLY); + + final DirContext delegateDirContext2 = delegatingDirContext2 + .getDelegateDirContext(); + assertEquals(delegatingDirContext, delegateDirContext2); + + final DirContext innerDelegateDirContext2 = delegatingDirContext2 + .getInnermostDelegateDirContext(); + assertEquals(dirContextMock, innerDelegateDirContext2); + + delegatingDirContext2.assertOpen(); + + // Close the outer wrapper + delegatingDirContext2.close(); + + final DirContext delegateContext2closed = delegatingDirContext2 + .getDelegateDirContext(); + assertNull(delegateContext2closed); + + final DirContext innerDelegateContext2closed = delegatingDirContext2 + .getInnermostDelegateDirContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingDirContext2.assertOpen(); + fail("delegatingDirContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingDirContext.close(); + + final DirContext delegateDirContextClosed = delegatingDirContext + .getDelegateDirContext(); + assertNull(delegateDirContextClosed); + + final DirContext innerDelegateDirContextClosed = delegatingDirContext + .getInnermostDelegateDirContext(); + assertNull(innerDelegateDirContextClosed); + + try { + delegatingDirContext.assertOpen(); + fail("delegatingDirContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(secondKeyedObjectPoolMock) + .returnObject(DirContextType.READ_ONLY, dirContextMock); + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, dirContextMock); + } + + @Test + public void testObjectMethods() throws Exception { + // Wrap the DirContext once + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + assertEquals(dirContextMock.toString(), delegatingDirContext.toString()); + delegatingDirContext.hashCode(); // Run it to make sure it doesn't + // fail + + assertTrue(delegatingDirContext.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(new Object())); + + final DelegatingDirContext delegatingDirContext2 = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + assertTrue(delegatingDirContext.equals(delegatingDirContext2)); + assertTrue(delegatingDirContext2.equals(delegatingDirContext)); + assertTrue(delegatingDirContext.equals(dirContextMock)); + + // Close the context and try again + delegatingDirContext.close(); + + assertEquals("DirContext is closed", delegatingDirContext.toString()); + assertEquals(0, delegatingDirContext.hashCode()); // Run it to make + // sure it doesn't + // fail + + assertTrue(delegatingDirContext.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(new Object())); + + assertFalse(delegatingDirContext.equals(delegatingDirContext2)); + assertFalse(delegatingDirContext2.equals(delegatingDirContext)); + assertFalse(delegatingDirContext.equals(dirContextMock)); + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, dirContextMock); + } + + @Test + public void testUnsupportedMethods() throws Exception { + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + try { + delegatingDirContext.createSubcontext((Name) null, null); + fail("DelegatingDirContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.createSubcontext((String) null, null); + fail("DelegatingDirContext.createSubcontext Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchema((Name) null); + fail("DelegatingDirContext.getSchema Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchema((String) null); + fail("DelegatingDirContext.getSchema Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchemaClassDefinition((Name) null); + fail("DelegatingDirContext.getSchemaClassDefinition Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingDirContext.getSchemaClassDefinition((String) null); + fail("DelegatingDirContext.getSchemaClassDefinition Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + } + + @Test + public void testAllMethodsOpened() throws Exception { + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.bind((Name) null, null, null); + delegatingDirContext.bind((String) null, null, null); + delegatingDirContext.getAttributes((Name) null, null); + delegatingDirContext.getAttributes((Name) null); + delegatingDirContext.getAttributes((String) null, null); + delegatingDirContext.getAttributes((String) null); + delegatingDirContext.modifyAttributes((Name) null, 0, null); + delegatingDirContext.modifyAttributes((Name) null, null); + delegatingDirContext.modifyAttributes((String) null, 0, null); + delegatingDirContext.modifyAttributes((String) null, null); + delegatingDirContext.rebind((Name) null, null, null); + delegatingDirContext.rebind((String) null, null, null); + delegatingDirContext.search((Name) null, (Attributes) null, null); + delegatingDirContext.search((Name) null, null); + delegatingDirContext.search((Name) null, null, null, null); + delegatingDirContext.search((Name) null, (String) null, null); + delegatingDirContext.search((String) null, (Attributes) null, null); + delegatingDirContext.search((String) null, null); + delegatingDirContext.search((String) null, null, null, null); + delegatingDirContext.search((String) null, (String) null, null); + } + + @Test + public void testAllMethodsClosed() throws Exception { + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.close(); + + try { + delegatingDirContext.bind((Name) null, null, null); + fail("DelegatingDirContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.bind((String) null, null, null); + fail("DelegatingDirContext.bind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((Name) null, null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((Name) null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((String) null, null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.getAttributes((String) null); + fail("DelegatingDirContext.getAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((Name) null, 0, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((Name) null, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((String) null, 0, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.modifyAttributes((String) null, null); + fail("DelegatingDirContext.modifyAttributes should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.rebind((Name) null, null, null); + fail("DelegatingDirContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.rebind((String) null, null, null); + fail("DelegatingDirContext.rebind should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, (Attributes) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, null, null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((Name) null, (String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, (Attributes) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, null, null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingDirContext.search((String) null, (String) null, null); + fail("DelegatingDirContext.search should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, dirContextMock); + } + + @Test + public void testDoubleClose() throws Exception { + final DelegatingDirContext delegatingDirContext = new DelegatingDirContext( + keyedObjectPoolMock, dirContextMock, DirContextType.READ_ONLY); + + delegatingDirContext.close(); + + // noop close + delegatingDirContext.close(); + + verify(keyedObjectPoolMock, times(1)).returnObject(DirContextType.READ_ONLY, dirContextMock); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/springframework/ldap/pool2/DelegatingLdapContextTest.java b/core/src/test/java/org/springframework/ldap/pool2/DelegatingLdapContextTest.java new file mode 100644 index 00000000..f80828c0 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/DelegatingLdapContextTest.java @@ -0,0 +1,250 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.KeyedObjectPool; +import org.junit.Test; + +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Eric Dalquist eric.dalquist@doit.wisc.edu + */ +public class DelegatingLdapContextTest extends AbstractPoolTestCase { + @Test + public void testConstructorAssertions() { + try { + new DelegatingLdapContext(keyedObjectPoolMock, null, + DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + new DelegatingLdapContext(keyedObjectPoolMock, ldapContextMock, + null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testHelperMethods() throws Exception { + // Wrap the LdapContext once + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + final DirContext delegateDirContext = delegatingLdapContext + .getDelegateDirContext(); + assertEquals(ldapContextMock, delegateDirContext); + + final LdapContext delegateLdapContext = delegatingLdapContext + .getDelegateLdapContext(); + assertEquals(ldapContextMock, delegateLdapContext); + + final LdapContext innerDelegateLdapContext = delegatingLdapContext + .getInnermostDelegateLdapContext(); + assertEquals(ldapContextMock, innerDelegateLdapContext); + + delegatingLdapContext.assertOpen(); + + // Wrap the wrapper + KeyedObjectPool secondKeyedObjectPoolMock = mock(KeyedObjectPool.class); + + final DelegatingLdapContext delegatingLdapContext2 = new DelegatingLdapContext( + secondKeyedObjectPoolMock, delegatingLdapContext, + DirContextType.READ_ONLY); + + final LdapContext delegateLdapContext2 = delegatingLdapContext2 + .getDelegateLdapContext(); + assertEquals(delegatingLdapContext, delegateLdapContext2); + + final LdapContext innerDelegateLdapContext2 = delegatingLdapContext2 + .getInnermostDelegateLdapContext(); + assertEquals(ldapContextMock, innerDelegateLdapContext2); + + delegatingLdapContext2.assertOpen(); + + // Close the outer wrapper + delegatingLdapContext2.close(); + + final LdapContext delegateContext2closed = delegatingLdapContext2 + .getDelegateLdapContext(); + assertNull(delegateContext2closed); + + final LdapContext innerDelegateContext2closed = delegatingLdapContext2 + .getInnermostDelegateLdapContext(); + assertNull(innerDelegateContext2closed); + + try { + delegatingLdapContext2.assertOpen(); + fail("delegatingLdapContext2.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + // Close the outer wrapper + delegatingLdapContext.close(); + + final LdapContext delegateLdapContextClosed = delegatingLdapContext + .getDelegateLdapContext(); + assertNull(delegateLdapContextClosed); + + final LdapContext innerDelegateLdapContextClosed = delegatingLdapContext + .getInnermostDelegateLdapContext(); + assertNull(innerDelegateLdapContextClosed); + + try { + delegatingLdapContext.assertOpen(); + fail("delegatingLdapContext.assertOpen() should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(secondKeyedObjectPoolMock) + .returnObject(DirContextType.READ_ONLY, ldapContextMock); + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, ldapContextMock); + } + + @Test + public void testObjectMethods() throws Exception { + // Wrap the LdapContext once + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + assertEquals(ldapContextMock.toString(), + delegatingLdapContext.toString()); + delegatingLdapContext.hashCode(); // Run it to make sure it doesn't fail + + assertTrue(delegatingLdapContext.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(new Object())); + + final DelegatingLdapContext delegatingLdapContext2 = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + assertTrue(delegatingLdapContext.equals(delegatingLdapContext2)); + assertTrue(delegatingLdapContext2.equals(delegatingLdapContext)); + assertTrue(delegatingLdapContext.equals(ldapContextMock)); + + // Close the context and try again + delegatingLdapContext.close(); + + assertEquals("LdapContext is closed", delegatingLdapContext.toString()); + assertEquals(0, delegatingLdapContext.hashCode()); // Run it to make + // sure it doesn't + // fail + + assertTrue(delegatingLdapContext.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(new Object())); + + assertFalse(delegatingLdapContext.equals(delegatingLdapContext2)); + assertFalse(delegatingLdapContext2.equals(delegatingLdapContext)); + assertFalse(delegatingLdapContext.equals(ldapContextMock)); + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, ldapContextMock); + } + + @Test + public void testUnsupportedMethods() throws Exception { + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + try { + delegatingLdapContext.newInstance(null); + fail("DelegatingLdapContext.newInstance Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingLdapContext.reconnect(null); + fail("DelegatingLdapContext.reconnect Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + try { + delegatingLdapContext.setRequestControls(null); + fail("DelegatingLdapContext.setRequestControls Should have thrown an UnsupportedOperationException"); + } catch (UnsupportedOperationException uoe) { + // Expected + } + } + + // nice + @Test + public void testAllMethodsOpened() throws Exception { + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.extendedOperation(null); + delegatingLdapContext.getConnectControls(); + delegatingLdapContext.getRequestControls(); + delegatingLdapContext.getResponseControls(); + } + + @Test + public void testAllMethodsClosed() throws Exception { + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.close(); + + try { + delegatingLdapContext.extendedOperation(null); + fail("DelegatingLdapContext.extendedOperation should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getConnectControls(); + fail("DelegatingLdapContext.getConnectControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getRequestControls(); + fail("DelegatingLdapContext.getRequestControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + try { + delegatingLdapContext.getResponseControls(); + fail("DelegatingLdapContext.getResponseControls should have thrown a NamingException"); + } catch (NamingException ne) { + // Expected + } + + verify(keyedObjectPoolMock).returnObject(DirContextType.READ_ONLY, ldapContextMock); + } + + @Test + public void testDoubleClose() throws Exception { + final DelegatingLdapContext delegatingLdapContext = new DelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.close(); + + // noop close + delegatingLdapContext.close(); + + verify(keyedObjectPoolMock, times(1)).returnObject(DirContextType.READ_ONLY, ldapContextMock); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/DummyEvictionPolicy.java b/core/src/test/java/org/springframework/ldap/pool2/DummyEvictionPolicy.java new file mode 100644 index 00000000..da506f7d --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/DummyEvictionPolicy.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.EvictionConfig; +import org.apache.commons.pool2.impl.EvictionPolicy; + +/** + * A dummy {@link EvictionPolicy} implementation to test pool2 config. + * + * @author Anindya Chatterjee + * */ +public class DummyEvictionPolicy implements EvictionPolicy { + + /** + * @see EvictionPolicy#evict(EvictionConfig, PooledObject, int) + * + * */ + @Override + public boolean evict(EvictionConfig config, PooledObject underTest, int idleCount) { + return false; + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/MutableDelegatingLdapContextTest.java b/core/src/test/java/org/springframework/ldap/pool2/MutableDelegatingLdapContextTest.java new file mode 100644 index 00000000..5debcca2 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/MutableDelegatingLdapContextTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2; + +import org.junit.Test; + +import static org.mockito.Mockito.verify; + +/** + * Unit tests for the MutableDelegatingLdapContext class. + * + * @author Ulrik Sandberg + */ +public class MutableDelegatingLdapContextTest extends AbstractPoolTestCase { + @Test + public void testSupportedMethodsAllowedToCall() throws Exception { + final MutableDelegatingLdapContext delegatingLdapContext = new MutableDelegatingLdapContext( + keyedObjectPoolMock, ldapContextMock, DirContextType.READ_ONLY); + + delegatingLdapContext.setRequestControls(null); + + verify(ldapContextMock).setRequestControls(null); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/factory/DirContextPooledObjectFactoryTest.java b/core/src/test/java/org/springframework/ldap/pool2/factory/DirContextPooledObjectFactoryTest.java new file mode 100644 index 00000000..0a8c2da0 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/factory/DirContextPooledObjectFactoryTest.java @@ -0,0 +1,227 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.internal.util.reflection.Whitebox; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool2.DirContextType; +import org.springframework.ldap.pool2.validation.DirContextValidator; +import org.springframework.ldap.pool2.AbstractPoolTestCase; + +import javax.naming.directory.DirContext; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class DirContextPooledObjectFactoryTest extends AbstractPoolTestCase { + + @Test + public void testProperties() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + try { + objectFactory.setContextSource(null); + fail("DirContextPooledObjectFactory.setContextSource should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + + objectFactory.setContextSource(contextSourceMock); + final ContextSource contextSource2 = objectFactory.getContextSource(); + assertEquals(contextSourceMock, contextSource2); + + + try { + objectFactory.setDirContextValidator(null); + fail("DirContextPooledObjectFactory.setDirContextValidator should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + + objectFactory.setDirContextValidator(dirContextValidatorMock); + final DirContextValidator dirContextValidator2 = objectFactory.getDirContextValidator(); + assertEquals(dirContextValidatorMock, dirContextValidator2); + } + + @Test + public void testMakeObjectAssertions() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + try { + objectFactory.makeObject(DirContextType.READ_ONLY); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + objectFactory.setContextSource(contextSourceMock); + + try { + objectFactory.makeObject(null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testMakeObjectReadOnly() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + DirContext readOnlyContextMock = mock(DirContext.class); + + when(contextSourceMock.getReadOnlyContext()).thenReturn(readOnlyContextMock); + objectFactory.setContextSource(contextSourceMock); + + final PooledObject createdDirContext = objectFactory.makeObject(DirContextType.READ_ONLY); + InvocationHandler invocationHandler = Proxy.getInvocationHandler(createdDirContext.getObject()); + assertEquals(readOnlyContextMock, Whitebox.getInternalState(invocationHandler, "target")); + } + + @Test + public void testMakeObjectReadWrite() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + DirContext readWriteContextMock = mock(DirContext.class); + + when(contextSourceMock.getReadWriteContext()).thenReturn(readWriteContextMock); + objectFactory.setContextSource(contextSourceMock); + + final PooledObject createdDirContext = objectFactory.makeObject(DirContextType.READ_WRITE); + + InvocationHandler invocationHandler = Proxy.getInvocationHandler(createdDirContext.getObject()); + assertEquals(readWriteContextMock, Whitebox.getInternalState(invocationHandler, "target")); + } + + @Test + public void testValidateObjectAssertions() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + try { + PooledObject pooledObject = new DefaultPooledObject(dirContextMock); + objectFactory.validateObject(DirContextType.READ_ONLY, pooledObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + objectFactory.setDirContextValidator(dirContextValidatorMock); + + try { + PooledObject pooledObject = new DefaultPooledObject(dirContextMock); + objectFactory.validateObject(null, pooledObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + PooledObject pooledObject = new DefaultPooledObject(dirContextMock); + objectFactory.validateObject(new Object(), pooledObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + objectFactory.validateObject(DirContextType.READ_ONLY, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + PooledObject pooledObject = new DefaultPooledObject(new Object()); + objectFactory.validateObject(DirContextType.READ_ONLY, pooledObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testValidateObject() throws Exception { + when(dirContextValidatorMock + .validateDirContext(DirContextType.READ_ONLY, dirContextMock)) + .thenReturn(true); + + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + objectFactory.setDirContextValidator(dirContextValidatorMock); + + PooledObject pooledObject = new DefaultPooledObject(dirContextMock); + final boolean valid = objectFactory.validateObject(DirContextType.READ_ONLY, pooledObject); + assertTrue(valid); + + //Check exception in validator + DirContextValidator secondDirContextValidatorMock = mock(DirContextValidator.class); + + when(secondDirContextValidatorMock.validateDirContext(DirContextType.READ_ONLY, dirContextMock)) + .thenThrow(new RuntimeException("Failed to validate")); + objectFactory.setDirContextValidator(secondDirContextValidatorMock); + + final boolean valid2 = objectFactory.validateObject(DirContextType.READ_ONLY, pooledObject); + assertFalse(valid2); + } + + @Test + public void testDestroyObjectAssertions() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + try { + objectFactory.destroyObject(DirContextType.READ_ONLY, null); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + try { + PooledObject pooledObject = new DefaultPooledObject(new Object()); + objectFactory.validateObject(DirContextType.READ_ONLY, pooledObject); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + } + + @Test + public void testDestroyObject() throws Exception { + final DirContextPooledObjectFactory objectFactory = new DirContextPooledObjectFactory(); + + PooledObject pooledObject = new DefaultPooledObject(dirContextMock); + objectFactory.destroyObject(DirContextType.READ_ONLY, pooledObject); + + DirContext throwingDirContextMock = Mockito.mock(DirContext.class); + + doThrow(new RuntimeException("Failed to close")) + .when(throwingDirContextMock).close(); + + pooledObject = new DefaultPooledObject(throwingDirContextMock); + objectFactory.destroyObject(DirContextType.READ_ONLY, pooledObject); + verify(dirContextMock).close(); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/factory/MutablePooledContextSourceTest.java b/core/src/test/java/org/springframework/ldap/pool2/factory/MutablePooledContextSourceTest.java new file mode 100644 index 00000000..8a45618e --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/factory/MutablePooledContextSourceTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + +import org.junit.Test; +import org.springframework.ldap.pool2.AbstractPoolTestCase; +import org.springframework.ldap.pool2.MutableDelegatingLdapContext; + +import javax.naming.directory.DirContext; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +/** + * Unit tests for the MutablePoolingContextSource class. + * + * @author Ulrik Sandberg + * @author Anindya Chatterjee + */ +public class MutablePooledContextSourceTest extends AbstractPoolTestCase { + + @Test + public void testGetReadOnlyLdapContext() throws Exception { + + when(contextSourceMock.getReadOnlyContext()).thenReturn(ldapContextMock); + + final MutablePooledContextSource poolingContextSource = new MutablePooledContextSource(null); + poolingContextSource.setContextSource(contextSourceMock); + + // Get a context + final DirContext result = poolingContextSource.getReadOnlyContext(); + + assertEquals(MutableDelegatingLdapContext.class, result.getClass()); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/factory/PoolConfigTest.java b/core/src/test/java/org/springframework/ldap/pool2/factory/PoolConfigTest.java new file mode 100644 index 00000000..c6c2aacb --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/factory/PoolConfigTest.java @@ -0,0 +1,113 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import org.junit.Test; +import org.springframework.ldap.pool2.AbstractPoolTestCase; + +import static org.junit.Assert.assertEquals; + +/** + * @author Anindya Chatterjee + * */ +public class PoolConfigTest extends AbstractPoolTestCase { + + @Test + public void testProperties() { + final PoolConfig poolConfig = new PoolConfig(); + + poolConfig.setMaxTotalPerKey(5); + final int maxTotalPerKey = poolConfig.getMaxTotalPerKey(); + assertEquals(5, maxTotalPerKey); + + poolConfig.setMaxIdlePerKey(500); + final int maxIdle = poolConfig.getMaxIdlePerKey(); + assertEquals(500, maxIdle); + + poolConfig.setMaxTotal(5000); + final int maxTotal = poolConfig.getMaxTotal(); + assertEquals(5000, maxTotal); + + poolConfig.setMaxWaitMillis(2000L); + final long maxWait = poolConfig.getMaxWaitMillis(); + assertEquals(2000L, maxWait); + + poolConfig.setMinEvictableIdleTimeMillis(60000L); + final long minEvictableIdleTimeMillis = poolConfig.getMinEvictableIdleTimeMillis(); + assertEquals(60000L, minEvictableIdleTimeMillis); + + poolConfig.setMinIdlePerKey(100); + final int minIdle = poolConfig.getMinIdlePerKey(); + assertEquals(100, minIdle); + + poolConfig.setNumTestsPerEvictionRun(5); + final int numTestsPerEvictionRun = poolConfig.getNumTestsPerEvictionRun(); + assertEquals(5, numTestsPerEvictionRun); + + poolConfig.setTestOnBorrow(true); + final boolean testOnBorrow = poolConfig.isTestOnBorrow(); + assertEquals(true, testOnBorrow); + + poolConfig.setTestOnReturn(true); + final boolean testOnReturn = poolConfig.isTestOnReturn(); + assertEquals(true, testOnReturn); + + poolConfig.setTestWhileIdle(true); + final boolean testWhileIdle = poolConfig.isTestWhileIdle(); + assertEquals(true, testWhileIdle); + + poolConfig.setTestOnCreate(true); + final boolean testOnCreate = poolConfig.isTestOnCreate(); + assertEquals(true, testOnCreate); + + poolConfig.setTimeBetweenEvictionRunsMillis(120000L); + final long timeBetweenEvictionRunsMillis = poolConfig.getTimeBetweenEvictionRunsMillis(); + assertEquals(120000L, timeBetweenEvictionRunsMillis); + + poolConfig.setSoftMinEvictableIdleTimeMillis(120000L); + final long softMinEvictableIdleTimeMillis = poolConfig.getSoftMinEvictableIdleTimeMillis(); + assertEquals(120000L, softMinEvictableIdleTimeMillis); + + poolConfig.setBlockWhenExhausted(true); + final boolean whenExhaustedAction = poolConfig.isBlockWhenExhausted(); + assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED, whenExhaustedAction); + + poolConfig.setEvictionPolicyClassName(GenericKeyedObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME); + final String evictionPolicyClassName = poolConfig.getEvictionPolicyClassName(); + assertEquals(GenericKeyedObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME, evictionPolicyClassName); + + poolConfig.setFairness(true); + final boolean fairness = poolConfig.isFairness(); + assertEquals(true, fairness); + + poolConfig.setJmxEnabled(true); + final boolean jmxEnabled = poolConfig.isJmxEnabled(); + assertEquals(true, jmxEnabled); + + poolConfig.setJmxNameBase("test"); + final String jmxBaseName = poolConfig.getJmxNameBase(); + assertEquals("test", jmxBaseName); + + poolConfig.setJmxNamePrefix("pool"); + final String prefix = poolConfig.getJmxNamePrefix(); + assertEquals("pool", prefix); + + poolConfig.setLifo(true); + final boolean lifo = poolConfig.isLifo(); + assertEquals(true, lifo); + } +} diff --git a/core/src/test/java/org/springframework/ldap/pool2/factory/PooledContextSourceTest.java b/core/src/test/java/org/springframework/ldap/pool2/factory/PooledContextSourceTest.java new file mode 100644 index 00000000..b2af3231 --- /dev/null +++ b/core/src/test/java/org/springframework/ldap/pool2/factory/PooledContextSourceTest.java @@ -0,0 +1,237 @@ +/* + * Copyright 2005-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ldap.pool2.factory; + +import org.junit.Test; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.ldap.core.ContextSource; +import org.springframework.ldap.pool2.validation.DirContextValidator; +import org.springframework.ldap.pool2.AbstractPoolTestCase; + +import javax.naming.directory.DirContext; +import javax.naming.ldap.LdapContext; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Eric Dalquist + * @author Anindya Chatterjee + */ +public class PooledContextSourceTest extends AbstractPoolTestCase { + + @Test + public void testProperties() throws Exception { + final PoolConfig poolConfig = new PoolConfig(); + poolConfig.setMaxIdlePerKey(500); + poolConfig.setMinIdlePerKey(100); + poolConfig.setMaxTotal(5000); + poolConfig.setMaxTotalPerKey(5); + poolConfig.setMaxWaitMillis(2000L); + poolConfig.setMinEvictableIdleTimeMillis(60000L); + poolConfig.setNumTestsPerEvictionRun(5); + poolConfig.setTestOnBorrow(true); + poolConfig.setTestOnReturn(true); + poolConfig.setTestWhileIdle(true); + poolConfig.setTestOnCreate(true); + poolConfig.setTimeBetweenEvictionRunsMillis(120000L); + poolConfig.setSoftMinEvictableIdleTimeMillis(120000L); + poolConfig.setBlockWhenExhausted(true); + poolConfig.setFairness(true); + poolConfig.setJmxEnabled(true); + poolConfig.setJmxNameBase("test"); + poolConfig.setJmxNamePrefix("pool"); + poolConfig.setLifo(true); + + final PooledContextSource PooledContextSource = new PooledContextSource(poolConfig); + + try { + PooledContextSource.setContextSource(null); + fail("PooledContextSource.setBaseName should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + PooledContextSource.setContextSource(contextSourceMock); + final ContextSource contextSource2 = PooledContextSource.getContextSource(); + assertEquals(contextSourceMock, contextSource2); + + try { + PooledContextSource.setDirContextValidator(null); + fail("PooledContextSource.setDirContextValidator should have thrown an IllegalArgumentException"); + } + catch (IllegalArgumentException iae) { + // Expected + } + PooledContextSource.setDirContextValidator(dirContextValidatorMock); + final DirContextValidator dirContextValidator2 = PooledContextSource.getDirContextValidator(); + assertEquals(dirContextValidatorMock, dirContextValidator2); + + final int numActive = PooledContextSource.getNumActive(); + assertEquals(0, numActive); + + final int numIdle = PooledContextSource.getNumIdle(); + assertEquals(0, numIdle); + } + + @Test + public void testGetReadOnlyContextPool() throws Exception { + DirContext secondDirContextMock = mock(DirContext.class); + + when(contextSourceMock.getReadOnlyContext()).thenReturn(dirContextMock, secondDirContextMock); + + final PooledContextSource PooledContextSource = new PooledContextSource(null); + PooledContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = PooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext1, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, PooledContextSource.getNumActive()); + assertEquals(1, PooledContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = PooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext2, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = PooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext3, secondDirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(1, PooledContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, PooledContextSource.getNumActive()); + assertEquals(2, PooledContextSource.getNumIdle()); + } + + @Test + public void testGetReadWriteContextPool() throws Exception { + DirContext secondDirContextMock = mock(DirContext.class); + + when(contextSourceMock.getReadWriteContext()).thenReturn(dirContextMock, secondDirContextMock); + + final PooledContextSource PooledContextSource = new PooledContextSource(null); + PooledContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = PooledContextSource.getReadWriteContext(); + assertEquals(readOnlyContext1, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, PooledContextSource.getNumActive()); + assertEquals(1, PooledContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = PooledContextSource.getReadWriteContext(); + assertEquals(readOnlyContext2, dirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = PooledContextSource.getReadWriteContext(); + assertEquals(readOnlyContext3, secondDirContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, PooledContextSource.getNumActive()); + assertEquals(0, PooledContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, PooledContextSource.getNumActive()); + assertEquals(1, PooledContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, PooledContextSource.getNumActive()); + assertEquals(2, PooledContextSource.getNumIdle()); + } + + @Test + public void testGetContextException() throws Exception { + when(contextSourceMock.getReadWriteContext()) + .thenThrow(new RuntimeException("Problem getting context")); + + final PooledContextSource PooledContextSource = new PooledContextSource(null); + PooledContextSource.setContextSource(contextSourceMock); + + try { + PooledContextSource.getReadWriteContext(); + fail("PooledContextSource.getReadWriteContext should have thrown DataAccessResourceFailureException"); + } + catch (DataAccessResourceFailureException darfe) { + // Expected + } + } + + @Test + public void testGetReadOnlyLdapContext() throws Exception { + LdapContext secondLdapContextMock = mock(LdapContext.class); + + when(contextSourceMock.getReadOnlyContext()).thenReturn(ldapContextMock, secondLdapContextMock); + + final PooledContextSource pooledContextSource = new PooledContextSource(null); + pooledContextSource.setContextSource(contextSourceMock); + + //Get a context + final DirContext readOnlyContext1 = pooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext1, ldapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, pooledContextSource.getNumActive()); + assertEquals(0, pooledContextSource.getNumIdle()); + + //Close the context + readOnlyContext1.close(); + assertEquals(0, pooledContextSource.getNumActive()); + assertEquals(1, pooledContextSource.getNumIdle()); + + //Get the context again + final DirContext readOnlyContext2 = pooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext2, ldapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(1, pooledContextSource.getNumActive()); + assertEquals(0, pooledContextSource.getNumIdle()); + + //Get a new context + final DirContext readOnlyContext3 = pooledContextSource.getReadOnlyContext(); + assertEquals(readOnlyContext3, secondLdapContextMock); //Order reversed because the 'wrapper' has the needed equals logic + assertEquals(2, pooledContextSource.getNumActive()); + assertEquals(0, pooledContextSource.getNumIdle()); + + //Close context + readOnlyContext2.close(); + assertEquals(1, pooledContextSource.getNumActive()); + assertEquals(1, pooledContextSource.getNumIdle()); + + //Close context + readOnlyContext3.close(); + assertEquals(0, pooledContextSource.getNumActive()); + assertEquals(2, pooledContextSource.getNumIdle()); + } +} diff --git a/core/src/test/resources/ldap-namespace-config-pool2-configured-poolsize.xml b/core/src/test/resources/ldap-namespace-config-pool2-configured-poolsize.xml new file mode 100644 index 00000000..f025bb5e --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pool2-configured-poolsize.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-pool2-test-specified.xml b/core/src/test/resources/ldap-namespace-config-pool2-test-specified.xml new file mode 100644 index 00000000..5ae7edba --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pool2-test-specified.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-pool2-with-native.xml b/core/src/test/resources/ldap-namespace-config-pool2-with-native.xml new file mode 100644 index 00000000..c9f59c14 --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pool2-with-native.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-pool2-with-pool1.xml b/core/src/test/resources/ldap-namespace-config-pool2-with-pool1.xml new file mode 100644 index 00000000..ed3f5b76 --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pool2-with-pool1.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/resources/ldap-namespace-config-pooling2-defaults.xml b/core/src/test/resources/ldap-namespace-config-pooling2-defaults.xml new file mode 100644 index 00000000..d2b0dcbd --- /dev/null +++ b/core/src/test/resources/ldap-namespace-config-pooling2-defaults.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/gradle/java.gradle b/gradle/java.gradle index d5a6341e..f7c15e7a 100644 --- a/gradle/java.gradle +++ b/gradle/java.gradle @@ -20,6 +20,7 @@ ext.mockitoVersion = '1.10.19' ext.queryDslVersion = '3.6.3' ext.slf4jVersion = '1.7.12' ext.powerMockVersion = '1.6.2' +ext.commonsPool2Version = '2.4.2' ext.powerMockDependencies = [ "org.powermock:powermock-core:$powerMockVersion",